feat(plating): Sub 4 — Check All / Clear All buttons + fix QA-005 PDF logo render
- New bulk-toggle actions on fp.contract.review flip all 10 checklist items in Section 2.0 (and all 11 in Section 3.0) in one click. Rendered as "Check All" / "Clear All" buttons above each checklist. User can still tick boxes individually. Buttons hide once the section is signed (locked). - Fix QA-005 PDF: replaced `to_text(...)` (not in QWeb context) with `image_data_uri(...)` for the company logo embed. PDF now renders with the full colour ENTECH logo (render size 103 KB). - Smoke test extended: 5 new assertions covering bulk-toggle on/off and locked-section guard. 17/17 pass on entech. fusion_plating_quality → 19.0.2.1.0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -135,6 +135,45 @@ if demo_user:
|
|||||||
else:
|
else:
|
||||||
print('[SKIP] No demo user for non-roster check')
|
print('[SKIP] No demo user for non-roster check')
|
||||||
|
|
||||||
|
# ---- Bulk-toggle (Check All / Clear All) buttons ----------------------
|
||||||
|
part4 = Part.create({
|
||||||
|
'partner_id': cust.id,
|
||||||
|
'part_number': 'SUB4-SMOKE-004',
|
||||||
|
'revision': 'A',
|
||||||
|
})
|
||||||
|
part4.action_start_contract_review()
|
||||||
|
part4.invalidate_recordset()
|
||||||
|
rev4 = part4.x_fc_contract_review_id
|
||||||
|
|
||||||
|
rev4.action_check_all_section_20()
|
||||||
|
for f in rev4._SECTION_20_CHECKLIST:
|
||||||
|
assert rev4[f] is True, f'{f} should be True after Check All'
|
||||||
|
print('[OK] Section 2.0 Check All ticks all 10 boxes')
|
||||||
|
|
||||||
|
rev4.action_clear_all_section_20()
|
||||||
|
for f in rev4._SECTION_20_CHECKLIST:
|
||||||
|
assert rev4[f] is False, f'{f} should be False after Clear All'
|
||||||
|
print('[OK] Section 2.0 Clear All clears all 10 boxes')
|
||||||
|
|
||||||
|
rev4.action_check_all_section_30()
|
||||||
|
for f in rev4._SECTION_30_CHECKLIST:
|
||||||
|
assert rev4[f] is True, f'{f} should be True after Check All'
|
||||||
|
print('[OK] Section 3.0 Check All ticks all 11 boxes')
|
||||||
|
|
||||||
|
rev4.action_clear_all_section_30()
|
||||||
|
for f in rev4._SECTION_30_CHECKLIST:
|
||||||
|
assert rev4[f] is False, f'{f} should be False after Clear All'
|
||||||
|
print('[OK] Section 3.0 Clear All clears all 11 boxes')
|
||||||
|
|
||||||
|
# Lock section 2.0, bulk toggle should refuse
|
||||||
|
rev4.with_user(admin).action_sign_section_20()
|
||||||
|
try:
|
||||||
|
rev4.action_check_all_section_20()
|
||||||
|
assert False, 'bulk toggle should fail on locked section'
|
||||||
|
except Exception as e:
|
||||||
|
assert 'locked' in str(e).lower() or 'signed' in str(e).lower()
|
||||||
|
print('[OK] Bulk toggle blocked on locked section')
|
||||||
|
|
||||||
# ---- QWeb render -------------------------------------------------------
|
# ---- QWeb render -------------------------------------------------------
|
||||||
report = env.ref('fusion_plating_quality.action_report_contract_review')
|
report = env.ref('fusion_plating_quality.action_report_contract_review')
|
||||||
pdf_bytes, mime = report._render_qweb_pdf('fusion_plating_quality.report_contract_review_qa005', [review.id])
|
pdf_bytes, mime = report._render_qweb_pdf('fusion_plating_quality.report_contract_review_qa005', [review.id])
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Fusion Plating — Quality (QMS)',
|
'name': 'Fusion Plating — Quality (QMS)',
|
||||||
'version': '19.0.2.0.0',
|
'version': '19.0.2.1.0',
|
||||||
'category': 'Manufacturing/Plating',
|
'category': 'Manufacturing/Plating',
|
||||||
'summary': 'Native QMS for plating shops: NCR, CAPA, calibration, AVL, FAIR, '
|
'summary': 'Native QMS for plating shops: NCR, CAPA, calibration, AVL, FAIR, '
|
||||||
'internal audits, customer specs, document control. CE + EE compatible.',
|
'internal audits, customer specs, document control. CE + EE compatible.',
|
||||||
|
|||||||
@@ -258,6 +258,62 @@ class FpContractReview(models.Model):
|
|||||||
) % self.env.user.name)
|
) % self.env.user.name)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# Checklist fields per section, for the "Check All" / "Clear All"
|
||||||
|
# bulk-toggle buttons. Only the checklist boxes are flipped —
|
||||||
|
# outcome fields (Accepted, Evaluate Risk, Risk Level / Matrix,
|
||||||
|
# Mitigation Plan Required) remain under the user's explicit
|
||||||
|
# decision so they don't get accidentally ticked.
|
||||||
|
_SECTION_20_CHECKLIST = (
|
||||||
|
's20_acceptable_lead_time',
|
||||||
|
's20_capacity_to_process',
|
||||||
|
's20_skills_to_process',
|
||||||
|
's20_fixtures_required',
|
||||||
|
's20_prime_approvals',
|
||||||
|
's20_pricing',
|
||||||
|
's20_approved_technique',
|
||||||
|
's20_drawings_available',
|
||||||
|
's20_process_type_class_grade',
|
||||||
|
's20_pre_post_processing_steps',
|
||||||
|
)
|
||||||
|
_SECTION_30_CHECKLIST = (
|
||||||
|
's30_source_control_docs',
|
||||||
|
's30_quality_clauses_supplied',
|
||||||
|
's30_quality_clauses_attainable',
|
||||||
|
's30_critical_tolerance',
|
||||||
|
's30_measuring_tooling',
|
||||||
|
's30_quality_tests_verified',
|
||||||
|
's30_specification_revisions',
|
||||||
|
's30_certifications_requirements',
|
||||||
|
's30_psd_rfd_reviewed',
|
||||||
|
's30_specification_deviations',
|
||||||
|
's30_design_authority',
|
||||||
|
)
|
||||||
|
|
||||||
|
def _bulk_toggle_checklist(self, fields_tuple, value, locked_field):
|
||||||
|
self.ensure_one()
|
||||||
|
if self[locked_field]:
|
||||||
|
raise UserError(_(
|
||||||
|
'Section is already signed — checklist is locked.'
|
||||||
|
))
|
||||||
|
self.write({f: value for f in fields_tuple})
|
||||||
|
return True
|
||||||
|
|
||||||
|
def action_check_all_section_20(self):
|
||||||
|
return self._bulk_toggle_checklist(
|
||||||
|
self._SECTION_20_CHECKLIST, True, 's20_locked')
|
||||||
|
|
||||||
|
def action_clear_all_section_20(self):
|
||||||
|
return self._bulk_toggle_checklist(
|
||||||
|
self._SECTION_20_CHECKLIST, False, 's20_locked')
|
||||||
|
|
||||||
|
def action_check_all_section_30(self):
|
||||||
|
return self._bulk_toggle_checklist(
|
||||||
|
self._SECTION_30_CHECKLIST, True, 's30_locked')
|
||||||
|
|
||||||
|
def action_clear_all_section_30(self):
|
||||||
|
return self._bulk_toggle_checklist(
|
||||||
|
self._SECTION_30_CHECKLIST, False, 's30_locked')
|
||||||
|
|
||||||
def action_reopen(self):
|
def action_reopen(self):
|
||||||
"""Clear all sign-off data and revert to draft. Manager only."""
|
"""Clear all sign-off data and revert to draft. Manager only."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<div class="qa005-title-row">
|
<div class="qa005-title-row">
|
||||||
<div class="qa005-title-logo">
|
<div class="qa005-title-logo">
|
||||||
<img t-if="doc.company_id.logo"
|
<img t-if="doc.company_id.logo"
|
||||||
t-att-src="'data:image/png;base64,%s' % to_text(doc.company_id.logo)"
|
t-att-src="image_data_uri(doc.company_id.logo)"
|
||||||
style="max-height: 45pt; max-width: 100%;"/>
|
style="max-height: 45pt; max-width: 100%;"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="qa005-title-center">
|
<div class="qa005-title-center">
|
||||||
|
|||||||
@@ -69,6 +69,21 @@
|
|||||||
<group>
|
<group>
|
||||||
<field name="s20_locked" invisible="1"/>
|
<field name="s20_locked" invisible="1"/>
|
||||||
</group>
|
</group>
|
||||||
|
<div class="mb-2" invisible="s20_locked">
|
||||||
|
<button name="action_check_all_section_20"
|
||||||
|
type="object"
|
||||||
|
string="Check All"
|
||||||
|
class="btn btn-sm btn-secondary me-2"
|
||||||
|
icon="fa-check-square-o"/>
|
||||||
|
<button name="action_clear_all_section_20"
|
||||||
|
type="object"
|
||||||
|
string="Clear All"
|
||||||
|
class="btn btn-sm btn-link"
|
||||||
|
icon="fa-square-o"/>
|
||||||
|
<span class="text-muted ms-2">
|
||||||
|
Bulk-toggle the 10 checklist items, or tick them individually below.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<group col="4" string="Checklist">
|
<group col="4" string="Checklist">
|
||||||
<field name="s20_acceptable_lead_time" readonly="s20_locked"/>
|
<field name="s20_acceptable_lead_time" readonly="s20_locked"/>
|
||||||
<field name="s20_capacity_to_process" readonly="s20_locked"/>
|
<field name="s20_capacity_to_process" readonly="s20_locked"/>
|
||||||
@@ -101,6 +116,21 @@
|
|||||||
<group>
|
<group>
|
||||||
<field name="s30_locked" invisible="1"/>
|
<field name="s30_locked" invisible="1"/>
|
||||||
</group>
|
</group>
|
||||||
|
<div class="mb-2" invisible="s30_locked">
|
||||||
|
<button name="action_check_all_section_30"
|
||||||
|
type="object"
|
||||||
|
string="Check All"
|
||||||
|
class="btn btn-sm btn-secondary me-2"
|
||||||
|
icon="fa-check-square-o"/>
|
||||||
|
<button name="action_clear_all_section_30"
|
||||||
|
type="object"
|
||||||
|
string="Clear All"
|
||||||
|
class="btn btn-sm btn-link"
|
||||||
|
icon="fa-square-o"/>
|
||||||
|
<span class="text-muted ms-2">
|
||||||
|
Bulk-toggle the 11 checklist items, or tick them individually below.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<group col="4" string="Checklist">
|
<group col="4" string="Checklist">
|
||||||
<field name="s30_source_control_docs" readonly="s30_locked"/>
|
<field name="s30_source_control_docs" readonly="s30_locked"/>
|
||||||
<field name="s30_quality_clauses_supplied" readonly="s30_locked"/>
|
<field name="s30_quality_clauses_supplied" readonly="s30_locked"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user