diff --git a/fusion_plating/docs/superpowers/tests/2026-04-22-sub4-smoke.py b/fusion_plating/docs/superpowers/tests/2026-04-22-sub4-smoke.py index 9c0d0933..f10be09f 100644 --- a/fusion_plating/docs/superpowers/tests/2026-04-22-sub4-smoke.py +++ b/fusion_plating/docs/superpowers/tests/2026-04-22-sub4-smoke.py @@ -135,6 +135,45 @@ if demo_user: else: 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 ------------------------------------------------------- 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]) diff --git a/fusion_plating/fusion_plating_quality/__manifest__.py b/fusion_plating/fusion_plating_quality/__manifest__.py index 9bb854ed..42d1484b 100644 --- a/fusion_plating/fusion_plating_quality/__manifest__.py +++ b/fusion_plating/fusion_plating_quality/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating — Quality (QMS)', - 'version': '19.0.2.0.0', + 'version': '19.0.2.1.0', 'category': 'Manufacturing/Plating', 'summary': 'Native QMS for plating shops: NCR, CAPA, calibration, AVL, FAIR, ' 'internal audits, customer specs, document control. CE + EE compatible.', diff --git a/fusion_plating/fusion_plating_quality/models/fp_contract_review.py b/fusion_plating/fusion_plating_quality/models/fp_contract_review.py index 83a86ffe..541427d2 100644 --- a/fusion_plating/fusion_plating_quality/models/fp_contract_review.py +++ b/fusion_plating/fusion_plating_quality/models/fp_contract_review.py @@ -258,6 +258,62 @@ class FpContractReview(models.Model): ) % self.env.user.name) 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): """Clear all sign-off data and revert to draft. Manager only.""" self.ensure_one() diff --git a/fusion_plating/fusion_plating_quality/reports/fp_contract_review_template.xml b/fusion_plating/fusion_plating_quality/reports/fp_contract_review_template.xml index af3e906c..37b8bcd1 100644 --- a/fusion_plating/fusion_plating_quality/reports/fp_contract_review_template.xml +++ b/fusion_plating/fusion_plating_quality/reports/fp_contract_review_template.xml @@ -46,7 +46,7 @@