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:
@@ -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
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user