feat(sub12a): post_init_hook — backfill kind + seed step library
Extends the existing post_init_hook to also do (idempotent) Sub 12a work on first install / upgrade: 1. Backfill kind='step_input' on existing fusion_plating_process_node_input rows where kind IS NULL. 2. Seed fp.step.template from the ENP-ALUM-BASIC recipe's child nodes if the library is currently empty. Uses _STARTER_KIND_BY_NAME to map recipe-step names to default_kind values, then calls _seed_default_inputs() to populate the per-kind input rows. 3. Falls back to a 15-entry hard-coded minimal seed list if ENP-ALUM-BASIC doesn't exist on the target DB. All three operations no-op when the relevant state already exists. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,14 +12,23 @@ _logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def post_init_hook(env):
|
def post_init_hook(env):
|
||||||
"""Auto-detect a sensible default timezone on first install.
|
"""Run on first install / module upgrade. Idempotent.
|
||||||
|
|
||||||
Sets ``res.company.x_fc_default_tz`` to the admin user's timezone
|
Does several things, each guarded by an "is this already done?"
|
||||||
(Odoo populates that from the browser on first login), falling back
|
check so re-running the hook doesn't clobber state:
|
||||||
to the host server's timezone, then to ``America/Toronto`` as a
|
1. Auto-detect a sensible default timezone (original behavior).
|
||||||
last resort. Only writes when the field is still empty so re-installs
|
2. Sub 12a — backfill `kind='step_input'` on existing
|
||||||
never clobber a user's choice.
|
fusion.plating.process.node.input rows that pre-date the
|
||||||
|
`kind` field.
|
||||||
|
3. Sub 12a — seed fp.step.template with starter library entries
|
||||||
|
derived from ENP-ALUM-BASIC if the library is currently empty.
|
||||||
"""
|
"""
|
||||||
|
_seed_default_timezone(env)
|
||||||
|
_backfill_node_input_kind(env)
|
||||||
|
_seed_step_library_if_empty(env)
|
||||||
|
|
||||||
|
|
||||||
|
def _seed_default_timezone(env):
|
||||||
from .models.fp_tz import detect_default_tz
|
from .models.fp_tz import detect_default_tz
|
||||||
|
|
||||||
detected = detect_default_tz(env)
|
detected = detect_default_tz(env)
|
||||||
@@ -30,3 +39,149 @@ def post_init_hook(env):
|
|||||||
'Fusion Plating: set default timezone for company %s -> %s',
|
'Fusion Plating: set default timezone for company %s -> %s',
|
||||||
company.name, detected,
|
company.name, detected,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _backfill_node_input_kind(env):
|
||||||
|
"""Sub 12a — set kind='step_input' on rows that have NULL kind."""
|
||||||
|
cr = env.cr
|
||||||
|
cr.execute(
|
||||||
|
"UPDATE fusion_plating_process_node_input "
|
||||||
|
"SET kind = 'step_input' WHERE kind IS NULL"
|
||||||
|
)
|
||||||
|
if cr.rowcount:
|
||||||
|
_logger.info(
|
||||||
|
"Fusion Plating: backfilled kind='step_input' on %s "
|
||||||
|
"fusion.plating.process.node.input rows", cr.rowcount,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Mapping of recipe-step name → default_kind. Drives sane-default
|
||||||
|
# input seeding on the starter library entries.
|
||||||
|
_STARTER_KIND_BY_NAME = {
|
||||||
|
'soak clean': 'cleaning',
|
||||||
|
'electroclean': 'cleaning',
|
||||||
|
'solvent clean': 'cleaning',
|
||||||
|
'rinse': 'rinse',
|
||||||
|
'primary rinse': 'rinse',
|
||||||
|
'secondary rinse': 'rinse',
|
||||||
|
'hot rinse': 'rinse',
|
||||||
|
'final rinse': 'rinse',
|
||||||
|
'etch': 'etch',
|
||||||
|
'desmut': 'etch',
|
||||||
|
'zincate': 'etch',
|
||||||
|
'strip zincate': 'etch',
|
||||||
|
'acid dip': 'etch',
|
||||||
|
'water break test': 'wbf_test',
|
||||||
|
'issue panels': 'mask',
|
||||||
|
'racking': 'racking',
|
||||||
|
'rack': 'racking',
|
||||||
|
'e-nickel plate': 'plate',
|
||||||
|
'electroless nickel plate': 'plate',
|
||||||
|
'electroless nickel plating': 'plate',
|
||||||
|
'drying': 'dry',
|
||||||
|
'dry': 'dry',
|
||||||
|
'de-rack': 'derack',
|
||||||
|
'de-racking': 'derack',
|
||||||
|
'inspection': 'inspect',
|
||||||
|
'final inspection': 'final_inspect',
|
||||||
|
'shipping': 'ship',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _seed_step_library_if_empty(env):
|
||||||
|
"""Sub 12a — seed fp.step.template starter library.
|
||||||
|
|
||||||
|
Source priority:
|
||||||
|
1. ENP-ALUM-BASIC recipe's child nodes (best — reuses the
|
||||||
|
author-curated step set).
|
||||||
|
2. Hard-coded minimal list (fallback for fresh DBs).
|
||||||
|
"""
|
||||||
|
Tpl = env['fp.step.template']
|
||||||
|
if Tpl.search_count([]):
|
||||||
|
_logger.info(
|
||||||
|
'Fusion Plating: step library already populated, skip seed',
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
Node = env['fusion.plating.process.node']
|
||||||
|
src = Node.search([
|
||||||
|
('node_type', '=', 'recipe'),
|
||||||
|
'|', ('code', '=', 'ENP-ALUM-BASIC'),
|
||||||
|
('name', 'ilike', 'ENP-ALUM-BASIC'),
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
if not src:
|
||||||
|
_seed_minimal_library(env)
|
||||||
|
return
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
for child in src.child_ids:
|
||||||
|
if child.node_type == 'step':
|
||||||
|
_create_template_from_node(env, child, seen)
|
||||||
|
else:
|
||||||
|
for grandchild in child.child_ids:
|
||||||
|
_create_template_from_node(env, grandchild, seen)
|
||||||
|
|
||||||
|
_logger.info(
|
||||||
|
"Fusion Plating: seeded step library with %s entries from %s",
|
||||||
|
len(seen), src.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _create_template_from_node(env, node, seen):
|
||||||
|
if not node.name or node.name.lower() in seen:
|
||||||
|
return
|
||||||
|
seen.add(node.name.lower())
|
||||||
|
|
||||||
|
kind = _STARTER_KIND_BY_NAME.get(node.name.lower())
|
||||||
|
vals = {
|
||||||
|
'name': node.name,
|
||||||
|
'description': node.description or False,
|
||||||
|
'icon': node.icon or 'fa-cog',
|
||||||
|
'process_type_id': node.process_type_id.id,
|
||||||
|
'requires_signoff': node.requires_signoff,
|
||||||
|
'requires_predecessor_done': node.requires_predecessor_done,
|
||||||
|
'default_kind': kind,
|
||||||
|
}
|
||||||
|
# Snapshot tank_ids if the node has them (added by Sub 12a;
|
||||||
|
# existing nodes may not).
|
||||||
|
if 'tank_ids' in node._fields and node.tank_ids:
|
||||||
|
vals['tank_ids'] = [(6, 0, node.tank_ids.ids)]
|
||||||
|
# Snapshot any time/temp targets the node may already carry.
|
||||||
|
for f in ('time_min_target', 'time_max_target', 'time_unit',
|
||||||
|
'temp_min_target', 'temp_max_target', 'temp_unit'):
|
||||||
|
if f in node._fields:
|
||||||
|
vals[f] = node[f] or vals.get(f)
|
||||||
|
|
||||||
|
tpl = env['fp.step.template'].create(vals)
|
||||||
|
if kind:
|
||||||
|
tpl._seed_default_inputs()
|
||||||
|
|
||||||
|
|
||||||
|
def _seed_minimal_library(env):
|
||||||
|
"""Hard-coded minimal seed when ENP-ALUM-BASIC isn't on the target DB."""
|
||||||
|
Tpl = env['fp.step.template']
|
||||||
|
minimal = [
|
||||||
|
('Soak Clean', 'cleaning'),
|
||||||
|
('Electroclean', 'cleaning'),
|
||||||
|
('Rinse', 'rinse'),
|
||||||
|
('Etch', 'etch'),
|
||||||
|
('Desmut', 'etch'),
|
||||||
|
('Zincate', 'etch'),
|
||||||
|
('Acid Dip', 'etch'),
|
||||||
|
('Water Break Test', 'wbf_test'),
|
||||||
|
('Racking', 'racking'),
|
||||||
|
('De-Racking', 'derack'),
|
||||||
|
('E-Nickel Plate', 'plate'),
|
||||||
|
('Drying', 'dry'),
|
||||||
|
('Inspection', 'inspect'),
|
||||||
|
('Final Inspection', 'final_inspect'),
|
||||||
|
('Shipping', 'ship'),
|
||||||
|
]
|
||||||
|
for name, kind in minimal:
|
||||||
|
tpl = Tpl.create({'name': name, 'default_kind': kind})
|
||||||
|
tpl._seed_default_inputs()
|
||||||
|
_logger.info(
|
||||||
|
'Fusion Plating: seeded minimal step library (%s entries)',
|
||||||
|
len(minimal),
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user