feat(configurator): blanket/block-partial flags + WO group + per-line missing indicator

Phase B partial landing (B1, B2, B3, B5):

- B1/B2: x_fc_is_blanket_order and x_fc_block_partial_shipments on
  sale.order; matching booleans on the wizard header.
- B3: x_fc_wo_group_tag Char on sale.order.line and wo_group_tag on
  wizard line. Free-text tag; bridge_mrp will batch lines sharing a
  tag into one MO in a follow-up.
- B5: is_missing_info computed Boolean on fp.direct.order.line;
  tree uses decoration-warning to highlight incomplete rows in amber.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-19 20:45:27 -04:00
parent f0c3661277
commit f55022c3d6
6 changed files with 61 additions and 1 deletions

View File

@@ -70,6 +70,18 @@ class SaleOrder(models.Model):
x_fc_internal_deadline = fields.Date(
string='Internal Deadline', tracking=True,
)
x_fc_is_blanket_order = fields.Boolean(
string='Is Blanket Sales Order',
help='Blanket orders release parts in quantities over time, '
'often with a negotiated price and a fixed expiry.',
tracking=True,
)
x_fc_block_partial_shipments = fields.Boolean(
string='Block Partial Shipments',
help='If set, the order must ship all-or-nothing. '
'Partial pickings are blocked.',
tracking=True,
)
@api.onchange('upload_rfq_file')
def _onchange_upload_rfq_file(self):

View File

@@ -20,3 +20,8 @@ class SaleOrderLine(models.Model):
)
x_fc_part_deadline = fields.Date(string='Part Deadline')
x_fc_rush_order = fields.Boolean(string='Rush')
x_fc_wo_group_tag = fields.Char(
string='Work Order Group',
help='Lines sharing a tag (e.g. "WO#1") will be batched into one '
'manufacturing order when bridge_mrp generates MOs.',
)

View File

@@ -89,6 +89,8 @@
<field name="x_fc_planned_start_date"/>
<field name="x_fc_internal_deadline"/>
<field name="commitment_date" string="Customer Deadline"/>
<field name="x_fc_is_blanket_order"/>
<field name="x_fc_block_partial_shipments"/>
</group>
</group>
</page>
@@ -98,6 +100,7 @@
<field name="x_fc_coating_config_id" optional="show"/>
<field name="x_fc_treatment_ids" widget="many2many_tags" optional="hide"/>
<field name="x_fc_part_deadline" optional="hide"/>
<field name="x_fc_wo_group_tag" optional="hide"/>
<field name="x_fc_rush_order" optional="hide"/>
</xpath>
</field>

View File

@@ -79,6 +79,11 @@ class FpDirectOrderLine(models.TransientModel):
help='Per-line deadline. Defaults to SO customer deadline if blank.',
)
rush_order = fields.Boolean(string='Rush')
wo_group_tag = fields.Char(
string='WO Group',
help='Free-text tag. Lines sharing a tag (e.g. "WO#1", "WO#2") '
'will be batched into one manufacturing order.',
)
# ---- Description ----
description_template_id = fields.Many2one(
@@ -91,12 +96,28 @@ class FpDirectOrderLine(models.TransientModel):
'Edit freely — your changes override the template.',
)
# ---- Missing info per line ----
is_missing_info = fields.Boolean(
string='Missing Info',
compute='_compute_is_missing_info',
)
# ---- Computes ----
@api.depends('quantity', 'unit_price')
def _compute_line_subtotal(self):
for rec in self:
rec.line_subtotal = (rec.quantity or 0) * (rec.unit_price or 0.0)
@api.depends('part_catalog_id', 'coating_config_id', 'unit_price', 'quantity')
def _compute_is_missing_info(self):
for rec in self:
rec.is_missing_info = not (
rec.part_catalog_id
and rec.coating_config_id
and rec.unit_price
and rec.quantity
)
# ---- Onchange ----
@api.onchange('coating_config_id', 'quantity', 'part_catalog_id')
def _onchange_lookup_price(self):

View File

@@ -45,6 +45,16 @@ class FpDirectOrderWizard(models.TransientModel):
internal_deadline = fields.Date(string='Internal Deadline')
customer_deadline = fields.Date(string='Customer Deadline')
# ---- Order flags (Phase B) ----
is_blanket_order = fields.Boolean(
string='Blanket Sales Order',
help='Blanket orders release parts in quantities over time.',
)
block_partial_shipments = fields.Boolean(
string='Block Partial Shipments',
help='Ship all-or-nothing; partial pickings are blocked.',
)
# ---- PO (required — that's what makes this a "direct" order) ----
po_number = fields.Char(string='Customer PO #', required=True)
po_attachment_file = fields.Binary(string='PO Document', required=True)
@@ -184,6 +194,8 @@ class FpDirectOrderWizard(models.TransientModel):
'x_fc_deposit_percent': self.deposit_percent,
'x_fc_progress_initial_percent': self.progress_initial_percent,
'x_fc_delivery_method': self.delivery_method,
'x_fc_is_blanket_order': self.is_blanket_order,
'x_fc_block_partial_shipments': self.block_partial_shipments,
'origin': 'Direct Order',
'note': self.notes or False,
'order_line': [],
@@ -213,6 +225,7 @@ class FpDirectOrderWizard(models.TransientModel):
'x_fc_treatment_ids': [(6, 0, line.treatment_ids.ids)],
'x_fc_part_deadline': line.part_deadline,
'x_fc_rush_order': line.rush_order,
'x_fc_wo_group_tag': line.wo_group_tag or False,
}))
# 5. Create + confirm

View File

@@ -45,6 +45,8 @@
<field name="planned_start_date"/>
<field name="internal_deadline"/>
<field name="customer_deadline"/>
<field name="is_blanket_order"/>
<field name="block_partial_shipments"/>
</group>
<group string="Fulfilment &amp; Invoicing">
<field name="delivery_method"/>
@@ -69,7 +71,9 @@
<notebook>
<page string="Lines" name="lines">
<field name="line_ids">
<list editable="bottom">
<list editable="bottom"
decoration-warning="is_missing_info">
<field name="is_missing_info" column_invisible="1"/>
<field name="sequence" widget="handle"/>
<field name="part_catalog_id"
context="{'default_partner_id': parent.partner_id}"
@@ -88,6 +92,7 @@
options="{'currency_field': 'currency_id'}"
sum="Total"/>
<field name="part_deadline"/>
<field name="wo_group_tag" optional="show"/>
<field name="rush_order" optional="hide"/>
<field name="currency_id" column_invisible="1"/>
</list>
@@ -115,6 +120,7 @@
options="{'currency_field': 'currency_id'}"/>
<field name="part_deadline"/>
<field name="rush_order"/>
<field name="wo_group_tag"/>
<field name="currency_id" invisible="1"/>
</group>
</group>