diff --git a/fusion_plating/fusion_plating/__manifest__.py b/fusion_plating/fusion_plating/__manifest__.py index b4ed0b2b..fddcfaff 100644 --- a/fusion_plating/fusion_plating/__manifest__.py +++ b/fusion_plating/fusion_plating/__manifest__.py @@ -5,7 +5,7 @@ { 'name': 'Fusion Plating', - 'version': '19.0.18.15.3', + 'version': '19.0.18.15.4', 'category': 'Manufacturing/Plating', 'summary': 'Core plating / metal finishing ERP: facilities, processes, tanks, baths, jobs, operators.', 'description': """ diff --git a/fusion_plating/fusion_plating/models/fp_job_step.py b/fusion_plating/fusion_plating/models/fp_job_step.py index ce8fd16a..fcc4ceea 100644 --- a/fusion_plating/fusion_plating/models/fp_job_step.py +++ b/fusion_plating/fusion_plating/models/fp_job_step.py @@ -393,12 +393,24 @@ class FpJobStep(models.Model): # a downstream step to move them into. Last runnable step # is exempt — parts finishing there complete in place # (qty_done reconciliation at job close is the catch-net). + # + # Seed-only exemption: the first-step seed in + # _compute_qty_at_step gives the earliest non-terminal step + # a notional qty = job.qty. That's a UI hint, not a real + # parked batch — no incoming move record backs it. Paperwork + # steps (Contract Review, Inspection, etc.) sit on that seed. + # If the step has no REAL incoming moves, skip the gate. if not skip_qty_gate and step.qty_at_step > 0: has_downstream = step.job_id.step_ids.filtered( lambda s: s.sequence > step.sequence and s.state in ('pending', 'ready') ) - if has_downstream: + has_real_incoming = bool( + step.incoming_move_ids.filtered( + lambda m: m.from_step_id != step + ) + ) + if has_downstream and has_real_incoming: raise UserError(_( "Step '%(name)s' still has %(n)d part(s) " "parked — move them to the next step before " diff --git a/fusion_plating/fusion_plating_jobs/__manifest__.py b/fusion_plating/fusion_plating_jobs/__manifest__.py index fed43300..9119b765 100644 --- a/fusion_plating/fusion_plating_jobs/__manifest__.py +++ b/fusion_plating/fusion_plating_jobs/__manifest__.py @@ -3,7 +3,7 @@ # License OPL-1 (Odoo Proprietary License v1.0) { 'name': 'Fusion Plating — Native Jobs', - 'version': '19.0.8.20.5', + 'version': '19.0.8.20.6', 'category': 'Manufacturing/Plating', 'summary': 'Native plating job model — replaces mrp.production / mrp.workorder bridge.', 'author': 'Nexa Systems Inc.', diff --git a/fusion_plating/fusion_plating_jobs/models/fp_job_step.py b/fusion_plating/fusion_plating_jobs/models/fp_job_step.py index e5b212f3..d60cbc57 100644 --- a/fusion_plating/fusion_plating_jobs/models/fp_job_step.py +++ b/fusion_plating/fusion_plating_jobs/models/fp_job_step.py @@ -1112,16 +1112,18 @@ class FpJobStep(models.Model): def _fp_record_one_piece_auto_move(self): - """Decide whether to silently record a move(qty=1) before - the step finishes. Five cases: + """Decide whether to silently record a move before the step + finishes. Six cases: - qty_at_step == 0: nothing to do (parts already moved). - last runnable step: parts complete in place; no move. - - qty_at_step == 1 + downstream: record move(1). - - qty_at_step > 1 + downstream: raise. - - qty_at_step > 1 + last step: allow (parts complete in - place; qty_done auto-tick is Phase 2). - Called from action_finish_and_advance just before - button_finish. + - SEED-ONLY qty + downstream: bulk-move all parts to next + step in one move. Paperwork / first steps don't physically + hold parts per-piece. + - real qty == 1 + downstream: record move(1). + - real qty > 1 + downstream: raise — operator must use + Complete 1 → Next (streaming) or Move… (batched). + - real qty > 1 + last step: allow (qty_done auto-tick Phase 2). + Called from action_finish_and_advance just before button_finish. """ self.ensure_one() qty = self.qty_at_step @@ -1133,6 +1135,23 @@ class FpJobStep(models.Model): ).sorted('sequence')[:1] if not next_step: return False + # Seed-only qty: no real incoming moves backing it. Paperwork + # step or first-step seed — bulk-move all parts in one shot. + has_real_incoming = bool( + self.incoming_move_ids.filtered( + lambda m: m.from_step_id != self + ) + ) + if not has_real_incoming: + self.env['fp.job.step.move'].create({ + 'job_id': self.job_id.id, + 'from_step_id': self.id, + 'to_step_id': next_step.id, + 'transfer_type': 'step', + 'qty_moved': qty, + 'moved_by_user_id': self.env.user.id, + }) + return True if qty > 1: raise UserError(_( "Step '%s' still has %d parts here — use the row's " @@ -1140,6 +1159,7 @@ class FpJobStep(models.Model): "or the 'Move…' wizard (for batched flow) to drain " "the step before finishing." ) % (self.name, qty)) + # qty == 1 + real incoming → record single move. self.env['fp.job.step.move'].create({ 'job_id': self.job_id.id, 'from_step_id': self.id,