feat(fusion_plating_shopfloor): fpRpc wrapper + env_for_tablet_tech helper (P6.3.1)
Client-side fpRpc() is a drop-in for rpc() that automatically injects tablet_tech_id from the tech_store into every action call. Read-only endpoints can keep using plain rpc(). Server-side env_for_tablet_tech(env, tablet_tech_id) returns an env scoped via with_user() when the id is a valid active user; otherwise returns the original env unchanged. Controllers call this at the top of action methods so all subsequent writes carry the right uid. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -166,6 +166,19 @@ These modules have **source code in this repo** but are **intentionally NOT inst
|
||||
| `fusion_plating_culture` | `state=uninstalled`, dir removed from entech disk | Soft people-ops feature (peer kudos / "Fundamental of the Week"); zero data entered; not a client priority. Top-level "Culture" menu confused operators. | Ask the client whether they want it before reinstalling. If yes: re-sync folder + `-i fusion_plating_culture` + seed a value set. |
|
||||
| `fusion_plating_sensors` | deleted entirely (not in repo anymore) | Duplicated `fusion_plating_iot`'s scope but with no working alerting logic. Its valuables (sensor_type taxonomy, dashboard, location flexibility) were ported into `fusion_iot/fusion_plating_iot/`. | N/A — gone. Any new sensor work goes in `fusion_iot/fusion_plating_iot/`. |
|
||||
|
||||
## Shop-floor action endpoints — credit the correct tech via `tablet_tech_id`
|
||||
The tablet sits on a long-lived "shopfloor service" Odoo session shared by many techs. The actual tech-of-record is established via the PIN unlock (Phase 6); their id lives in the OWL `fp_shopfloor_tech_store` service and is sent as `tablet_tech_id` on every action RPC.
|
||||
|
||||
When writing a NEW shop-floor controller endpoint that **writes** (creates a record, calls a `button_*` method, posts to chatter):
|
||||
1. Add `tablet_tech_id=None` as a kwarg on the route handler.
|
||||
2. At the top, call: `env = env_for_tablet_tech(request.env, tablet_tech_id)` (from `fusion_plating_shopfloor/controllers/_tablet_audit.py`).
|
||||
3. Use `env` (not `request.env`) for all subsequent writes. `env.with_user(...)` is applied internally so `create_uid` / `write_uid` / chatter authorship carry the right uid.
|
||||
4. Read-only endpoints (load / kanban / funnel / overview) don't need this — leave them as `request.env`.
|
||||
|
||||
On the client side: use `fpRpc()` from `services/fp_rpc.js` (drop-in for `rpc()`) for action calls. It auto-injects `tablet_tech_id`. Read calls can keep using plain `rpc()`.
|
||||
|
||||
If `tablet_tech_id` is missing or invalid, `env_for_tablet_tech` falls back to the session uid — old callers and pre-Phase-6.3 endpoints continue working.
|
||||
|
||||
## Removing menus/records — Odoo does NOT auto-delete orphans
|
||||
Deleting a `<menuitem>` (or any `<record>`) from a data XML file does NOT remove the corresponding database row. The XML loader only updates records it sees; orphans persist in `ir.ui.menu` / `ir.model.data` until you delete them explicitly. Symptom: the menu still appears in the UI after `-u`. Fix — add a `<delete>` directive in a data file with `noupdate="0"`:
|
||||
```xml
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
"""Helper for audit-credit propagation (Phase 6.3 tablet redesign).
|
||||
|
||||
Controllers that accept an optional `tablet_tech_id` kwarg use this
|
||||
helper to switch their `env` to the tech-of-record before performing
|
||||
writes. The result: chatter posts + create_uid/write_uid carry the
|
||||
unlocked tech's identity, not the tablet's persistent session user.
|
||||
"""
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def env_for_tablet_tech(env, tablet_tech_id):
|
||||
"""Return an env scoped to `tablet_tech_id` if it's a valid user;
|
||||
otherwise return the original env unchanged.
|
||||
|
||||
Validation: the user must exist and be active. We deliberately do
|
||||
NOT cross-check that they actually unlocked recently — the OWL
|
||||
component is the source of truth for "who's at the tablet right
|
||||
now", and the only path that produces a tablet_tech_id is a
|
||||
successful /fp/tablet/unlock followed by an active session in the
|
||||
OWL tech_store.
|
||||
"""
|
||||
if not tablet_tech_id:
|
||||
return env
|
||||
try:
|
||||
tech_id = int(tablet_tech_id)
|
||||
except (TypeError, ValueError):
|
||||
return env
|
||||
User = env['res.users'].sudo()
|
||||
tech = User.browse(tech_id)
|
||||
if not tech.exists() or not tech.active:
|
||||
_logger.warning(
|
||||
"tablet_tech_id %s invalid (not found or inactive); "
|
||||
"falling back to session uid %s",
|
||||
tablet_tech_id, env.uid,
|
||||
)
|
||||
return env
|
||||
return env(user=tech_id)
|
||||
@@ -0,0 +1,42 @@
|
||||
/** @odoo-module **/
|
||||
// =============================================================================
|
||||
// Fusion Plating — fpRpc() wrapper
|
||||
//
|
||||
// Drop-in replacement for the standard `rpc()` import. Automatically
|
||||
// injects the current tablet_tech_id from the tech_store into every
|
||||
// call, so server-side endpoints can attribute the action to the right
|
||||
// user via env.with_user() (see env_for_tablet_tech in
|
||||
// controllers/_tablet_audit.py).
|
||||
//
|
||||
// USE for any RPC that WRITES (start step, finish step, hold create,
|
||||
// sign-off, milestone advance). For read-only loads (kanban, workspace
|
||||
// load, manager funnel), plain rpc() is fine.
|
||||
//
|
||||
// Example:
|
||||
// import { fpRpc } from "../services/fp_rpc";
|
||||
// await fpRpc("/fp/shopfloor/start_wo", { workorder_id: stepId });
|
||||
//
|
||||
// =============================================================================
|
||||
|
||||
import { rpc as baseRpc } from "@web/core/network/rpc";
|
||||
|
||||
function _getTechStore() {
|
||||
// Lazy-resolve via the global debug API — avoids circular service init
|
||||
try {
|
||||
const env = odoo.__WOWL_DEBUG__?.root?.env;
|
||||
if (env && env.services && env.services.fp_shopfloor_tech_store) {
|
||||
return env.services.fp_shopfloor_tech_store;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function fpRpc(url, params = {}) {
|
||||
const techStore = _getTechStore();
|
||||
if (techStore && techStore.currentTechId) {
|
||||
params = { ...params, tablet_tech_id: techStore.currentTechId };
|
||||
}
|
||||
return baseRpc(url, params);
|
||||
}
|
||||
Reference in New Issue
Block a user