feat(plating-migration): dry-run + Owner-approval workflow
Phase H of permissions overhaul (LAST subagent phase). New models: - fp.migration.preview (state: pending/approved/cancelled/rolled_back) - fp.migration.preview.line (one per active internal user) On -u, post_init_hook creates a preview in 'pending' state, walks all active non-share users through the 12-rule mapping predicate chain (first match wins, highest precedence first), and schedules a mail.activity on every Owner. Mapping table (per spec Section 5): uid 1/2 / Administrator -> owner CGP DO (existing) -> owner + res.company DO field set CGP Officer -> quality_manager Manager / Shop Mgr (old) -> manager Accounting -> manager Estimator-without-Manager -> sales_rep (flagged: loses confirm) Supervisor / Receiving -> shop_manager Operator -> technician catchall -> 'no' Owner clicks 'Approve & Run' on the preview form -> sudo write removes old plating groups, adds new role's group, posts Markup chatter audit. Optionally sets res.company.x_fc_cgp_designated_official_id for the DO. 30-day rollback window via JSON snapshot of groups_id per line. Daily cron (Fusion Plating: Purge Expired Role Migrations) clears snapshots + unlinks old [DEPRECATED] groups after 30 days. ACL: fp.migration.preview + .line both Owner-only (CRUD). Menu: Plating > Configuration > Role Migrations (Owner-only). Tests cover: only-Owner-can-approve, approve advances state, cancel blocks after approval, rollback restores groups_id, Estimator warning flagged, uid 2 maps to owner, rollback blocked after 30 days. Per CLAUDE.md: ir.cron uses only Odoo-19-valid fields (no numbercall, no doall). Post-init hook is idempotent — won't double-create previews or re-fire if all users already migrated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,8 @@ def post_init_hook(env):
|
||||
3. Sub 12a — seed fp.step.template with starter library entries
|
||||
derived from ENP-ALUM-BASIC if the library is currently empty.
|
||||
4. Sub 12b — seed 4 starter rack tags if the registry is empty.
|
||||
5. Phase H — create a pending fp.migration.preview if any user
|
||||
still holds an old plating-role group + notify Owners.
|
||||
"""
|
||||
_seed_default_timezone(env)
|
||||
_backfill_node_input_kind(env)
|
||||
@@ -31,6 +33,40 @@ def post_init_hook(env):
|
||||
_seed_rack_tags_if_empty(env)
|
||||
_migrate_legacy_uom_columns(env)
|
||||
_seed_starter_recipes_once(env)
|
||||
_fp_post_init_role_migration(env)
|
||||
|
||||
|
||||
def _fp_post_init_role_migration(env):
|
||||
"""Idempotent: creates a fp.migration.preview if none is pending or applied.
|
||||
|
||||
Called automatically on `-u fusion_plating`. The preview enters 'pending'
|
||||
state and schedules a mail.activity on every Owner. Owner must explicitly
|
||||
click 'Approve & Run' to actually apply the migration.
|
||||
"""
|
||||
Preview = env['fp.migration.preview']
|
||||
if Preview.search_count([('state', '=', 'pending')]):
|
||||
return
|
||||
if Preview.search_count([('state', '=', 'approved')]):
|
||||
# Already migrated previously; only re-fire if any unmigrated user remains
|
||||
# An unmigrated user is one who still holds an OLD plating group directly
|
||||
# AND does NOT hold any NEW role group. The compute on res.users.x_fc_plating_role
|
||||
# returns 'no' for users without any new group regardless of their old groups.
|
||||
# Heuristic: if any active user still holds an old group, re-fire.
|
||||
from .models.fp_role_constants import _FP_OLD_GROUP_XMLIDS
|
||||
any_unmigrated = False
|
||||
for xmlid in _FP_OLD_GROUP_XMLIDS:
|
||||
old_grp = env.ref(xmlid, raise_if_not_found=False)
|
||||
if not old_grp:
|
||||
continue
|
||||
if old_grp.users.filtered(lambda u: u.active and not u.share):
|
||||
# Found at least one user still on an old group → re-fire
|
||||
any_unmigrated = True
|
||||
break
|
||||
if not any_unmigrated:
|
||||
return # All users migrated; nothing to do
|
||||
preview = Preview.create({})
|
||||
preview._fp_build_lines()
|
||||
preview._fp_notify_owners()
|
||||
|
||||
|
||||
def _seed_starter_recipes_once(env):
|
||||
|
||||
Reference in New Issue
Block a user