feat(configurator): complete all deferred Phase D/E/F tasks

Ships the remaining items from the Sales UX Uplift plan:

D2 BOM Items kanban
  New view_sale_order_line_bom_kanban grouped by x_fc_part_catalog_id.
  Smart button 'BOM Items' on SO form opens it.

D5 Archive line
  x_fc_archived Boolean on sale.order.line plus action_archive_line /
  action_unarchive_line. Acknowledgement report filters out archived
  lines.

D6 Add Quoted Lines sub-wizard
  New fp.add.from.quote.wizard parallel to fp.add.from.so.wizard. Pick
  quotes for this customer and clone them into direct-order lines
  carrying part, coating, qty, unit price (from calculated or
  override), and notes. Button '+ Add From Quotes' on wizard Lines tab.

D7 SO Acknowledgement PDF
  New ir.actions.report + QWeb template in configurator/report/.
  Header shows customer / contact / PO / Customer Job #, Bill-To,
  Ship-To, planned start + customer deadline + ship-via. Line table
  skips archived lines. Includes external notes, blanket-order
  callout, and customer-signature + vendor-signature blocks.
  Binding added to sale.order so it shows up under Print menu.

D9 Quick-nav chip bar
  New smart buttons on SO form: Invoices / Pickings / NCRs / Files
  with counts and icons. Each opens a filtered list. NCR button
  appears only when fusion_plating_quality is installed.

D10 SO/WO perspective toggle
  view_sale_order_line_wo_kanban grouped by x_fc_wo_group_tag. Smart
  button 'By WO' on SO form.

D11 Assemblies minimal model
  fp.sale.assembly + fp.sale.assembly.line with name, ship_to, count,
  procured_count, completed_at. UX (forms / kanbans / integration
  into receiving) deferred — model only for now.

D14 Uploaded Files
  Files smart button on SO form opens ir.attachment kanban filtered
  to this SO. Count appears in the chip bar.

F4 Signed tracking
  x_fc_signed_at / x_fc_signed_by / x_fc_is_signed on sale.order +
  action_mark_signed helper. Signed column on quotes list view.

F10 New Quote
  Kept on existing action_fp_quotations (already surfaces the
  default New button).

E5/F9 Action icons per row
  Deferred — requires a custom widget; the native PDF action via the
  Print menu covers 80% of the use case.

Bumped to 19.0.8.0.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-19 21:45:17 -04:00
parent b85e208856
commit b834ae3117
13 changed files with 636 additions and 1 deletions

View File

@@ -5,4 +5,5 @@
from . import fp_direct_order_wizard
from . import fp_direct_order_line
from . import fp_add_from_so_wizard
from . import fp_add_from_quote_wizard
from . import fp_part_catalog_import_wizard

View File

@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family.
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class FpAddFromQuoteWizard(models.TransientModel):
"""Pick fp.quote.configurator rows and clone them onto the direct-order wizard.
Parallels fp.add.from.so.wizard but sources from the quote library
instead of prior sale orders. Each selected quote becomes one
fp.direct.order.line with part, coating, qty and unit price
carried over.
"""
_name = 'fp.add.from.quote.wizard'
_description = 'Fusion Plating - Add Lines From Quotes'
direct_order_wizard_id = fields.Many2one(
'fp.direct.order.wizard',
required=True,
ondelete='cascade',
)
partner_id = fields.Many2one(
related='direct_order_wizard_id.partner_id', readonly=True,
)
quote_ids = fields.Many2many(
'fp.quote.configurator',
string='Quotes to Copy',
domain="[('partner_id', '=', partner_id), ('state', 'in', ['sent', 'accepted', 'won'])]",
help='Select one or more quotes for this customer. Each quote '
'becomes a new line on the direct order.',
)
def action_copy_quotes(self):
self.ensure_one()
if not self.quote_ids:
raise UserError(_('Pick at least one quote to copy.'))
Line = self.env['fp.direct.order.line']
wizard = self.direct_order_wizard_id
copied = 0
for q in self.quote_ids:
if not q.part_catalog_id or not q.coating_config_id:
continue
final = q.estimator_override_price or q.calculated_price
unit = (final / q.quantity) if (final and q.quantity) else 0.0
Line.create({
'wizard_id': wizard.id,
'part_catalog_id': q.part_catalog_id.id,
'coating_config_id': q.coating_config_id.id,
'quantity': int(q.quantity) or 1,
'unit_price': unit,
'quote_id': q.id,
'line_description': q.notes or False,
})
copied += 1
if not copied:
raise UserError(_(
'The selected quotes do not have both part and coating set, '
'so nothing could be copied.'
))
return {
'type': 'ir.actions.act_window',
'res_model': 'fp.direct.order.wizard',
'res_id': wizard.id,
'view_mode': 'form',
'target': 'new',
}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_fp_add_from_quote_wizard_form" model="ir.ui.view">
<field name="name">fp.add.from.quote.wizard.form</field>
<field name="model">fp.add.from.quote.wizard</field>
<field name="arch" type="xml">
<form string="Add Lines From Quotes">
<sheet>
<div class="oe_title">
<h1>Copy Lines From Quotes</h1>
<p class="text-muted">
Select quotes for this customer. Each becomes a
new line on the direct order with part, coating,
quantity and unit price pre-filled.
</p>
</div>
<group>
<field name="direct_order_wizard_id" invisible="1"/>
<field name="partner_id" readonly="1"/>
</group>
<field name="quote_ids">
<list>
<field name="name"/>
<field name="part_catalog_id"/>
<field name="coating_config_id"/>
<field name="quantity"/>
<field name="calculated_price" widget="monetary"/>
<field name="estimator_override_price" widget="monetary"/>
<field name="currency_id" column_invisible="1"/>
<field name="state"/>
</list>
</field>
</sheet>
<footer>
<button name="action_copy_quotes"
type="object"
string="Copy Selected Quotes"
class="btn-primary"/>
<button string="Cancel" special="cancel" class="btn-secondary"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -162,6 +162,23 @@ class FpDirectOrderWizard(models.TransientModel):
'target': 'new',
}
def action_add_from_quotes(self):
"""Open a sub-wizard to copy lines from prior quotes."""
self.ensure_one()
if not self.partner_id:
raise UserError(_('Pick a customer first.'))
sub = self.env['fp.add.from.quote.wizard'].create({
'direct_order_wizard_id': self.id,
})
return {
'type': 'ir.actions.act_window',
'name': _('Add Lines From Quotes'),
'res_model': 'fp.add.from.quote.wizard',
'res_id': sub.id,
'view_mode': 'form',
'target': 'new',
}
def action_create_order(self):
"""Create and confirm the sale order with one SO line per wizard line."""
self.ensure_one()

View File

@@ -76,6 +76,11 @@
string="+ Add From Prior SO"
class="btn-secondary"
invisible="not partner_id"/>
<button name="action_add_from_quotes"
type="object"
string="+ Add From Quotes"
class="btn-secondary"
invisible="not partner_id"/>
</div>
<field name="line_ids">
<list editable="bottom"