diff --git a/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_line.py b/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_line.py index a4d85f5e..437b3a28 100644 --- a/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_line.py +++ b/fusion_plating/fusion_plating_configurator/wizard/fp_direct_order_line.py @@ -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.