changes
This commit is contained in:
@@ -116,11 +116,61 @@ class FpDirectOrderLine(models.Model):
|
||||
|
||||
@api.onchange('part_catalog_id')
|
||||
def _onchange_part_clears_variant(self):
|
||||
"""Clear variant pick when the part changes (variants are part-scoped)."""
|
||||
"""Clear variant pick when the part changes (variants are part-scoped).
|
||||
|
||||
Pre-fill coating + treatments from the part's saved defaults so
|
||||
Sarah doesn't re-pick the same coating every repeat customer.
|
||||
Defaults only apply when the line currently has no coating set
|
||||
— editing an existing line with a chosen coating doesn't get
|
||||
clobbered.
|
||||
|
||||
For BRAND-NEW parts (no defaults saved yet) auto-tick
|
||||
`push_to_defaults` so Sarah's first coating pick gets persisted
|
||||
back to the part. Without this Sarah has to remember to tick the
|
||||
toggle herself, and the second order doesn't pre-fill.
|
||||
Returns a warning popup explaining what's happening.
|
||||
"""
|
||||
warning = None
|
||||
for rec in self:
|
||||
# Variant clear (original behaviour).
|
||||
if (rec.process_variant_id
|
||||
and rec.process_variant_id.part_catalog_id != rec.part_catalog_id):
|
||||
rec.process_variant_id = False
|
||||
if not rec.part_catalog_id:
|
||||
continue
|
||||
part = rec.part_catalog_id
|
||||
has_default_coating = bool(getattr(
|
||||
part, 'x_fc_default_coating_config_id', False))
|
||||
has_default_treatments = bool(getattr(
|
||||
part, 'x_fc_default_treatment_ids', False))
|
||||
# Pre-fill default coating if the line is empty.
|
||||
if not rec.coating_config_id and has_default_coating:
|
||||
rec.coating_config_id = part.x_fc_default_coating_config_id
|
||||
# Pre-fill default treatments if any are configured.
|
||||
if not rec.treatment_ids and has_default_treatments:
|
||||
rec.treatment_ids = [(6, 0, part.x_fc_default_treatment_ids.ids)]
|
||||
# New-part auto-suggest: if neither default exists, this is
|
||||
# likely a first-time use of the part. Auto-tick the
|
||||
# push_to_defaults toggle so whatever Sarah picks becomes
|
||||
# the saved default — surface a warning popup so she knows.
|
||||
# `is_one_off` always wins (operator opted out of catalog
|
||||
# persistence), so don't auto-tick in that case.
|
||||
if (not has_default_coating
|
||||
and not has_default_treatments
|
||||
and not rec.is_one_off
|
||||
and not rec.push_to_defaults):
|
||||
rec.push_to_defaults = True
|
||||
warning = {
|
||||
'title': _('First-Time Part — Defaults Will Be Saved'),
|
||||
'message': _(
|
||||
'%(part)s has no saved coating / treatments. '
|
||||
'The coating + treatments you pick on this line '
|
||||
'will be saved as the part\'s defaults so the '
|
||||
'next order auto-fills them. Untick "Save as '
|
||||
'Default" on the line if you don\'t want this.'
|
||||
) % {'part': part.display_name or part.part_number or '(part)'},
|
||||
}
|
||||
return {'warning': warning} if warning else None
|
||||
|
||||
# ---- Qty / price ----
|
||||
quantity = fields.Integer(string='Qty', default=1, required=True)
|
||||
|
||||
@@ -209,37 +209,63 @@ class FpDirectOrderWizard(models.Model):
|
||||
# ---- Onchange ----
|
||||
@api.onchange('partner_id')
|
||||
def _onchange_partner_id(self):
|
||||
"""Seed invoice defaults + addresses + payment terms when customer changes."""
|
||||
if self.partner_id and 'x_fc_default_invoice_strategy' in self.partner_id._fields:
|
||||
self.invoice_strategy = self.partner_id.x_fc_default_invoice_strategy or False
|
||||
self.deposit_percent = self.partner_id.x_fc_default_deposit_percent or 0.0
|
||||
if self.partner_id:
|
||||
addrs = self.partner_id.address_get(['invoice', 'delivery'])
|
||||
self.partner_invoice_id = addrs.get('invoice') or self.partner_id.id
|
||||
self.partner_shipping_id = addrs.get('delivery') or self.partner_id.id
|
||||
# Seed payment terms: customer's invoice-strategy default wins;
|
||||
# fallback to partner.property_payment_term_id.
|
||||
term = False
|
||||
isd = self.env['fp.invoice.strategy.default'].search(
|
||||
[('partner_id', '=', self.partner_id.id)], limit=1,
|
||||
)
|
||||
if isd and isd.payment_term_id:
|
||||
term = isd.payment_term_id
|
||||
# Also seed strategy from the same record if not already set.
|
||||
if not self.invoice_strategy:
|
||||
self.invoice_strategy = isd.default_strategy
|
||||
if not self.deposit_percent:
|
||||
self.deposit_percent = isd.default_deposit_percent or 0.0
|
||||
if not term and self.partner_id.property_payment_term_id:
|
||||
term = self.partner_id.property_payment_term_id
|
||||
self.payment_term_id = term or False
|
||||
else:
|
||||
"""Seed invoice defaults + addresses + payment terms when customer
|
||||
changes. Also surface an account-hold warning so Sarah doesn't
|
||||
build a full quote for a customer she can't ship to.
|
||||
"""
|
||||
if not self.partner_id:
|
||||
self.partner_invoice_id = False
|
||||
self.partner_shipping_id = False
|
||||
self.payment_term_id = False
|
||||
self._apply_strategy_payment_term()
|
||||
return
|
||||
|
||||
# Legacy partner-field defaults (pre-Sub-5).
|
||||
if 'x_fc_default_invoice_strategy' in self.partner_id._fields:
|
||||
self.invoice_strategy = self.partner_id.x_fc_default_invoice_strategy or False
|
||||
self.deposit_percent = self.partner_id.x_fc_default_deposit_percent or 0.0
|
||||
|
||||
# Addresses.
|
||||
addrs = self.partner_id.address_get(['invoice', 'delivery'])
|
||||
self.partner_invoice_id = addrs.get('invoice') or self.partner_id.id
|
||||
self.partner_shipping_id = addrs.get('delivery') or self.partner_id.id
|
||||
|
||||
# Per-customer invoice strategy default (fp.invoice.strategy.default).
|
||||
# Pull strategy + deposit even when payment_term_id is empty — the
|
||||
# previous condition `if isd and isd.payment_term_id` silently
|
||||
# skipped the strategy fill for net-terms customers without
|
||||
# explicit terms configured.
|
||||
isd = self.env['fp.invoice.strategy.default'].search(
|
||||
[('partner_id', '=', self.partner_id.id)], limit=1,
|
||||
)
|
||||
term = False
|
||||
if isd:
|
||||
if not self.invoice_strategy:
|
||||
self.invoice_strategy = isd.default_strategy
|
||||
if not self.deposit_percent:
|
||||
self.deposit_percent = isd.default_deposit_percent or 0.0
|
||||
term = isd.payment_term_id
|
||||
if not term and self.partner_id.property_payment_term_id:
|
||||
term = self.partner_id.property_payment_term_id
|
||||
self.payment_term_id = term or False
|
||||
|
||||
# Re-apply strategy → terms mapping after partner switch.
|
||||
self._apply_strategy_payment_term()
|
||||
|
||||
# Account-hold early warning. Hard block lives in action_confirm
|
||||
# but Sarah deserves to know NOW before she builds 5 lines.
|
||||
if getattr(self.partner_id, 'x_fc_account_hold', False):
|
||||
return {
|
||||
'warning': {
|
||||
'title': _('Customer on Account Hold'),
|
||||
'message': _(
|
||||
'%s is currently on account hold. You can still '
|
||||
'build the quotation, but it cannot be confirmed '
|
||||
'until the hold is cleared by accounting.'
|
||||
) % self.partner_id.display_name,
|
||||
}
|
||||
}
|
||||
|
||||
@api.onchange('invoice_strategy')
|
||||
def _onchange_invoice_strategy(self):
|
||||
"""Map the strategy onto sensible payment terms."""
|
||||
@@ -247,12 +273,15 @@ class FpDirectOrderWizard(models.Model):
|
||||
|
||||
def _apply_strategy_payment_term(self):
|
||||
"""Mapping rule:
|
||||
- cod_prepay → Immediate Payment (Odoo's stock term)
|
||||
- deposit / progress / net_terms → keep what the partner default
|
||||
already gave us; if blank, leave it blank so the user can pick.
|
||||
Never overwrites an explicit user choice for non-COD strategies —
|
||||
only fills in when payment_term_id is empty.
|
||||
- cod_prepay → Immediate Payment
|
||||
- net_terms / deposit / progress → fall back to a 30-day
|
||||
term when nothing is set. Without ANY payment term Odoo
|
||||
blocks invoice posting, which silently strands SOs at the
|
||||
invoicing step. Better to default to net-30 and let the
|
||||
estimator override if the customer's terms are different.
|
||||
Never overwrites an explicit user choice — only fills the gap.
|
||||
"""
|
||||
Pt = self.env['account.payment.term']
|
||||
for rec in self:
|
||||
if rec.invoice_strategy == 'cod_prepay':
|
||||
immediate = rec.env.ref(
|
||||
@@ -261,6 +290,20 @@ class FpDirectOrderWizard(models.Model):
|
||||
)
|
||||
if immediate:
|
||||
rec.payment_term_id = immediate.id
|
||||
elif rec.invoice_strategy in ('net_terms', 'deposit', 'progress') \
|
||||
and not rec.payment_term_id:
|
||||
# Try canonical Net-30, then any term named "30 Days",
|
||||
# then any term at all as last-ditch.
|
||||
term = rec.env.ref(
|
||||
'account.account_payment_term_30days',
|
||||
raise_if_not_found=False,
|
||||
)
|
||||
if not term:
|
||||
term = Pt.search([('name', 'ilike', '30 Days')], limit=1)
|
||||
if not term:
|
||||
term = Pt.search([], limit=1)
|
||||
if term:
|
||||
rec.payment_term_id = term.id
|
||||
|
||||
# ---- Actions ----
|
||||
@api.model
|
||||
@@ -351,6 +394,17 @@ class FpDirectOrderWizard(models.Model):
|
||||
raise UserError(_('Pick a customer before confirming.'))
|
||||
if not self.line_ids:
|
||||
raise UserError(_('Add at least one part line before confirming.'))
|
||||
# Account-hold hard block — same policy as sale.order.action_confirm
|
||||
# but enforced earlier so the wizard doesn't waste Sarah's time.
|
||||
# Manager override allowed via context key fp_skip_account_hold=True.
|
||||
if (getattr(self.partner_id, 'x_fc_account_hold', False)
|
||||
and not self.env.context.get('fp_skip_account_hold')
|
||||
and not self.env.user.has_group(
|
||||
'fusion_plating.group_fusion_plating_manager')):
|
||||
raise UserError(_(
|
||||
'Customer %s is on account hold. Have a manager clear the '
|
||||
'hold (or override) before creating the order.'
|
||||
) % self.partner_id.display_name)
|
||||
|
||||
# Accept EITHER a PO (document + number) OR the PO Pending
|
||||
# flag. Customers who haven't sent paperwork yet use Pending;
|
||||
|
||||
@@ -141,9 +141,9 @@
|
||||
<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}"
|
||||
context="{'default_partner_id': parent.partner_id, 'default_revision': 'A'}"
|
||||
domain="[('partner_id', '=', parent.partner_id), ('is_latest_revision', '=', True)]"
|
||||
options="{'no_create_edit': True}"/>
|
||||
options="{'no_quick_create': True}"/>
|
||||
<field name="description_template_id"
|
||||
domain="[('part_catalog_id', '=', part_catalog_id)]"
|
||||
context="{'default_part_catalog_id': part_catalog_id}"
|
||||
|
||||
Reference in New Issue
Block a user