feat(configurator): Part cell rows 2-3 now editable — type to save

Customer feedback: rows 2 (description) and 3 (serials) in the Part
cell rendered as read-only spans. User wanted to edit directly.

New writable computed fields on fp.direct.order.line:
- part_name_editable: compute reads part_catalog_id.name, inverse
  writes back to part.name on the linked catalog record
- serials_text: compute joins serial_ids names with commas; inverse
  parses the typed string and find-or-creates fp.serial records,
  updates the line's serial_ids M2M

Removed the redundant rev separator (display_name already includes
'(Rev X)' so showing it twice was clutter). Rev edits happen by
editing the part record directly via the OPEN button.

OWL widget templates updated:
- Row 2: <input> bound to part_name_editable, t-on-change saves
- Row 3: <input> bound to serials_text, t-on-change parses + saves

SCSS:
- Row 2 input: italic, transparent border, focus tints background yellow
- Row 3 input: small grey text, comma-separated friendly placeholder
- Both disabled-look when no part is picked

Both inputs trigger the inverse method on blur. The G4 sync chain
takes over from there to push line.line_description etc. back to
the part as before — so editing in the line keeps the part defaults
fresh for future orders.
This commit is contained in:
gsinghpal
2026-05-26 23:26:58 -04:00
parent 48c2a4bfe1
commit 9c7b7c54e5
5 changed files with 171 additions and 70 deletions

View File

@@ -478,6 +478,66 @@ class FpDirectOrderLine(models.Model):
string='Part Name (display)',
readonly=True,
)
# Writable bridges so the Part cell widget's row-2 description input
# and row-3 serials input save back to the underlying records
# (part.name and the line's serial_ids M2M) on blur.
part_name_editable = fields.Char(
string='Part Name (editable)',
compute='_compute_part_name_editable',
inverse='_inverse_part_name_editable',
store=False,
)
serials_text = fields.Char(
string='Serials (text)',
compute='_compute_serials_text',
inverse='_inverse_serials_text',
store=False,
help='Comma-separated list of serial numbers — typing here parses, '
'creates new fp.serial records as needed, and updates the M2M.',
)
@api.depends('part_catalog_id', 'part_catalog_id.name')
def _compute_part_name_editable(self):
for rec in self:
rec.part_name_editable = rec.part_catalog_id.name or ''
def _inverse_part_name_editable(self):
for rec in self:
if rec.part_catalog_id:
new_name = (rec.part_name_editable or '').strip()
if new_name and new_name != rec.part_catalog_id.name:
rec.part_catalog_id.sudo().write({'name': new_name})
@api.depends('serial_ids', 'serial_ids.name')
def _compute_serials_text(self):
for rec in self:
rec.serials_text = ', '.join(rec.serial_ids.mapped('name'))
def _inverse_serials_text(self):
Serial = self.env['fp.serial']
company_id = self.env.company.id
for rec in self:
raw = (rec.serials_text or '').strip()
if not raw:
rec.serial_ids = [(5, 0)]
continue
names = [n.strip() for n in raw.replace(';', ',').split(',') if n.strip()]
if not names:
rec.serial_ids = [(5, 0)]
continue
existing = Serial.search([
('name', 'in', names),
('company_id', '=', company_id),
])
existing_by_name = {s.name: s for s in existing}
ids = []
for name in names:
if name in existing_by_name:
ids.append(existing_by_name[name].id)
else:
new = Serial.sudo().create({'name': name})
ids.append(new.id)
rec.serial_ids = [(6, 0, ids)]
# Anchor field for the FpExpressActionBtns widget — renders the
# stacked DWG / OPEN buttons in one list column. The widget reads
# part_catalog_id from the line; this field's value is unused.