feat(configurator): B4+B8 - Express onchange auto-fill cascade + DWG/OPEN on direct-order line
This commit is contained in:
@@ -494,34 +494,115 @@ class FpDirectOrderLine(models.Model):
|
||||
|
||||
@api.onchange('part_catalog_id')
|
||||
def _onchange_part_default_thickness(self):
|
||||
"""Auto-fill thickness range — same chain as the SO line.
|
||||
"""Auto-fill thickness range + Express defaults — same chain as the SO line.
|
||||
|
||||
For each cell, the chain is:
|
||||
1. Operator already typed → keep
|
||||
2. Most recent SO line for (part, customer) with a thickness → copy
|
||||
3. Part's x_fc_default_thickness_range → copy
|
||||
2. Most recent SO line for (part, customer) with a value → copy
|
||||
3. Part's default field → copy
|
||||
4. Blank
|
||||
"""
|
||||
for rec in self:
|
||||
if rec.thickness_range:
|
||||
continue
|
||||
if not rec.part_catalog_id:
|
||||
continue
|
||||
part = rec.part_catalog_id
|
||||
partner = rec.wizard_id.partner_id
|
||||
if partner:
|
||||
recent = self.env['sale.order.line'].search([
|
||||
('x_fc_part_catalog_id', '=', rec.part_catalog_id.id),
|
||||
('order_id.partner_id', '=', partner.id),
|
||||
('x_fc_thickness_range', '!=', False),
|
||||
('x_fc_thickness_range', '!=', ''),
|
||||
], order='create_date desc', limit=1)
|
||||
if recent:
|
||||
rec.thickness_range = recent.x_fc_thickness_range
|
||||
continue
|
||||
part_default = getattr(
|
||||
rec.part_catalog_id, 'x_fc_default_thickness_range', None,
|
||||
)
|
||||
if part_default:
|
||||
rec.thickness_range = part_default
|
||||
|
||||
# ---- Thickness range (existing) ----
|
||||
if not rec.thickness_range:
|
||||
done = False
|
||||
if partner:
|
||||
recent = self.env['sale.order.line'].search([
|
||||
('x_fc_part_catalog_id', '=', part.id),
|
||||
('order_id.partner_id', '=', partner.id),
|
||||
('x_fc_thickness_range', '!=', False),
|
||||
('x_fc_thickness_range', '!=', ''),
|
||||
], order='create_date desc', limit=1)
|
||||
if recent:
|
||||
rec.thickness_range = recent.x_fc_thickness_range
|
||||
done = True
|
||||
if not done:
|
||||
part_default = getattr(part, 'x_fc_default_thickness_range', None)
|
||||
if part_default:
|
||||
rec.thickness_range = part_default
|
||||
|
||||
# ---- Express bake_instructions (2026-05-26) ----
|
||||
if not rec.bake_instructions:
|
||||
done = False
|
||||
if partner:
|
||||
recent = self.env['sale.order.line'].search([
|
||||
('x_fc_part_catalog_id', '=', part.id),
|
||||
('order_id.partner_id', '=', partner.id),
|
||||
('x_fc_bake_instructions', '!=', False),
|
||||
('x_fc_bake_instructions', '!=', ''),
|
||||
], order='create_date desc', limit=1)
|
||||
if recent:
|
||||
rec.bake_instructions = recent.x_fc_bake_instructions
|
||||
done = True
|
||||
if not done:
|
||||
bake_default = getattr(part, 'default_bake_instructions', None)
|
||||
if bake_default:
|
||||
rec.bake_instructions = bake_default
|
||||
|
||||
# ---- Express line_description (Specification) ----
|
||||
# Only seed if currently empty (don't clobber operator-typed text).
|
||||
if not rec.line_description:
|
||||
spec_default = getattr(part, 'default_specification_text', None)
|
||||
if spec_default:
|
||||
rec.line_description = spec_default
|
||||
|
||||
# ---- Express masking_enabled (always re-seed from part default) ----
|
||||
# Boolean has no "empty" state; explicit reseed on every part-change
|
||||
# matches the spec ("part default wins unless operator flips it after").
|
||||
mask_default = getattr(part, 'default_masking_enabled', True)
|
||||
rec.masking_enabled = mask_default
|
||||
|
||||
def action_open_part(self):
|
||||
"""Open the linked fp.part.catalog form in a modal."""
|
||||
self.ensure_one()
|
||||
if not self.part_catalog_id:
|
||||
return False
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': self.part_catalog_id.display_name,
|
||||
'res_model': 'fp.part.catalog',
|
||||
'view_mode': 'form',
|
||||
'res_id': self.part_catalog_id.id,
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
def action_upload_drawing(self):
|
||||
"""Attach a file (via context) to the line's part as a drawing.
|
||||
|
||||
Mirrors sale.order.line.action_upload_drawing — same behaviour,
|
||||
same context keys (fp_drawing_file + fp_drawing_filename).
|
||||
"""
|
||||
self.ensure_one()
|
||||
from odoo.exceptions import UserError
|
||||
if not self.part_catalog_id:
|
||||
raise UserError(_('Pick or create a part on this line first.'))
|
||||
file_data = self.env.context.get('fp_drawing_file')
|
||||
filename = self.env.context.get('fp_drawing_filename', 'drawing.pdf')
|
||||
if not file_data:
|
||||
raise UserError(_('No file data received.'))
|
||||
att = self.env['ir.attachment'].sudo().create({
|
||||
'name': filename,
|
||||
'datas': file_data,
|
||||
'res_model': 'fp.part.catalog',
|
||||
'res_id': self.part_catalog_id.id,
|
||||
})
|
||||
self.part_catalog_id.sudo().write({
|
||||
'drawing_attachment_ids': [(4, att.id)],
|
||||
})
|
||||
self.part_catalog_id.sudo().message_post(body=_(
|
||||
'Drawing "%(name)s" uploaded by %(user)s from line %(seq)s on draft %(draft)s.'
|
||||
) % {
|
||||
'name': filename,
|
||||
'user': self.env.user.display_name,
|
||||
'seq': self.sequence or self.id,
|
||||
'draft': self.wizard_id.name,
|
||||
})
|
||||
return {'type': 'ir.actions.client', 'tag': 'reload'}
|
||||
|
||||
def action_generate_serial(self):
|
||||
"""Generate one auto-sequenced fp.serial and append to the M2M.
|
||||
|
||||
Reference in New Issue
Block a user