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')
|
@api.onchange('part_catalog_id')
|
||||||
def _onchange_part_default_thickness(self):
|
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
|
1. Operator already typed → keep
|
||||||
2. Most recent SO line for (part, customer) with a thickness → copy
|
2. Most recent SO line for (part, customer) with a value → copy
|
||||||
3. Part's x_fc_default_thickness_range → copy
|
3. Part's default field → copy
|
||||||
4. Blank
|
4. Blank
|
||||||
"""
|
"""
|
||||||
for rec in self:
|
for rec in self:
|
||||||
if rec.thickness_range:
|
|
||||||
continue
|
|
||||||
if not rec.part_catalog_id:
|
if not rec.part_catalog_id:
|
||||||
continue
|
continue
|
||||||
|
part = rec.part_catalog_id
|
||||||
partner = rec.wizard_id.partner_id
|
partner = rec.wizard_id.partner_id
|
||||||
if partner:
|
|
||||||
recent = self.env['sale.order.line'].search([
|
# ---- Thickness range (existing) ----
|
||||||
('x_fc_part_catalog_id', '=', rec.part_catalog_id.id),
|
if not rec.thickness_range:
|
||||||
('order_id.partner_id', '=', partner.id),
|
done = False
|
||||||
('x_fc_thickness_range', '!=', False),
|
if partner:
|
||||||
('x_fc_thickness_range', '!=', ''),
|
recent = self.env['sale.order.line'].search([
|
||||||
], order='create_date desc', limit=1)
|
('x_fc_part_catalog_id', '=', part.id),
|
||||||
if recent:
|
('order_id.partner_id', '=', partner.id),
|
||||||
rec.thickness_range = recent.x_fc_thickness_range
|
('x_fc_thickness_range', '!=', False),
|
||||||
continue
|
('x_fc_thickness_range', '!=', ''),
|
||||||
part_default = getattr(
|
], order='create_date desc', limit=1)
|
||||||
rec.part_catalog_id, 'x_fc_default_thickness_range', None,
|
if recent:
|
||||||
)
|
rec.thickness_range = recent.x_fc_thickness_range
|
||||||
if part_default:
|
done = True
|
||||||
rec.thickness_range = part_default
|
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):
|
def action_generate_serial(self):
|
||||||
"""Generate one auto-sequenced fp.serial and append to the M2M.
|
"""Generate one auto-sequenced fp.serial and append to the M2M.
|
||||||
|
|||||||
Reference in New Issue
Block a user