This commit is contained in:
gsinghpal
2026-05-10 10:25:12 -04:00
parent 6c6a59ceef
commit 6b7b44264a
59 changed files with 2461 additions and 324 deletions

View File

@@ -37,6 +37,25 @@ _INPUT_SNAPSHOT_FIELDS = [
]
def _copy_snapshot_fields(source, fields):
"""Copy ``fields`` from ``source`` record into a write-ready dict.
Many2one values must be unwrapped to their integer id — passing a
recordset to ``create`` triggers psycopg2 ``can't adapt type X``
because the SQL adapter doesn't know how to serialize a recordset.
Scalar fields pass through untouched.
"""
out = {}
for f in fields:
field = source._fields[f]
val = source[f]
if field.type == 'many2one':
out[f] = val.id if val else False
else:
out[f] = val
return out
class SimpleRecipeController(http.Controller):
# ------------------------------------------------------------------ load
@@ -115,6 +134,18 @@ class SimpleRecipeController(http.Controller):
),
'measurements_badge_text': badge_text,
'measurements_badge_class': badge_class,
# Reference images attached to the step. Operators see
# these in the Record Inputs dialog and the step quick-look
# modal — recipe authors upload via the inline edit panel.
'instruction_images': [
{
'id': att.id,
'name': att.name or '',
'mimetype': att.mimetype or '',
'url': '/web/image/%s' % att.id,
}
for att in step.instruction_attachment_ids
],
'inputs': [
{
'id': i.id,
@@ -457,8 +488,7 @@ class SimpleRecipeController(http.Controller):
tpl = False
if template_id:
tpl = request.env['fp.step.template'].browse(template_id)
for f in _SNAPSHOT_FIELDS:
new_vals[f] = tpl[f]
new_vals.update(_copy_snapshot_fields(tpl, _SNAPSHOT_FIELDS))
if tpl.process_type_id:
new_vals['process_type_id'] = tpl.process_type_id.id
if tpl.tank_ids:
@@ -598,8 +628,7 @@ class SimpleRecipeController(http.Controller):
'sequence': src_node.sequence,
'source_template_id': src_node.source_template_id.id or False,
}
for f in _SNAPSHOT_FIELDS:
new_vals[f] = src_node[f]
new_vals.update(_copy_snapshot_fields(src_node, _SNAPSHOT_FIELDS))
if src_node.process_type_id:
new_vals['process_type_id'] = src_node.process_type_id.id
if src_node.tank_ids:
@@ -690,6 +719,69 @@ class SimpleRecipeController(http.Controller):
rec.unlink()
return {'ok': True}
# ============================================================
# Step instruction images — recipe authors attach reference photos
# / screenshots / diagrams to a step from the Simple Editor's inline
# edit panel. Operators see them on the Record Inputs dialog and
# the step quick-look modal at runtime.
# ============================================================
@http.route('/fp/simple_recipe/step/image/add', type='jsonrpc', auth='user')
def step_image_add(self, node_id, filename, datas, mimetype=None):
"""Upload a new instruction image to a recipe step.
Args:
node_id: recipe node (fusion.plating.process.node) id
filename: display name (with extension) for the attachment
datas: base64-encoded image payload (no data: URL prefix)
mimetype: optional override; falls back to image/png
Returns the new attachment metadata so the JS can append it to
the step's gallery without a full reload.
"""
node = request.env['fusion.plating.process.node'].browse(int(node_id))
node.check_access('write')
att = request.env['ir.attachment'].create({
'name': filename or 'image.png',
'datas': datas,
'res_model': 'fusion.plating.process.node',
'res_id': node.id,
'mimetype': mimetype or 'image/png',
})
node.instruction_attachment_ids = [(4, att.id)]
return {
'ok': True,
'image': {
'id': att.id,
'name': att.name,
'mimetype': att.mimetype or '',
'url': '/web/image/%s' % att.id,
},
}
@http.route('/fp/simple_recipe/step/image/remove', type='jsonrpc', auth='user')
def step_image_remove(self, node_id, attachment_id):
"""Unlink an instruction image from a recipe step.
Soft-removes from the M2M; the underlying ir.attachment is
deleted only if it isn't referenced by any other recipe node.
"""
node = request.env['fusion.plating.process.node'].browse(int(node_id))
node.check_access('write')
Att = request.env['ir.attachment']
att = Att.browse(int(attachment_id))
if not att.exists():
return {'ok': False, 'error': 'not_found'}
node.instruction_attachment_ids = [(3, att.id)]
# Drop the attachment file too if no other node still links to it.
Node = request.env['fusion.plating.process.node']
still_used = Node.search_count([
('instruction_attachment_ids', '=', att.id),
])
if not still_used:
att.sudo().unlink()
return {'ok': True}
@http.route('/fp/simple_recipe/step/reset_to_library', type='jsonrpc', auth='user')
def step_reset_to_library(self, node_id):
"""Re-sync the recipe step's input_ids + description from the linked