feat(jobs): Record Inputs OWL Dialog (v4) — replaces list-as-cards hack
Scrapped the v2/v3 form-view + list-as-cards CSS approach after
extensive failure to make Odoo's editable list look like cards.
Built a proper OWL Dialog component instead, mirroring the pattern
used by fusion_plating_shopfloor's move_parts_dialog.js.
What changed
============
* New OWL Dialog: fp_record_inputs_dialog.js
- Loads step + prompt definitions via /fp/record_inputs/load
- Renders each prompt as a semantic <div class="o_fp_ri_card">
- Per-row widget chosen by input_type:
numeric/temperature/thickness/time_seconds/ph -> number input
boolean/pass_fail -> custom CSS toggle (clearer than Bootstrap)
date -> datetime-local input
photo -> file picker w/ preview + clear
multi_point_thickness -> 5-cell grid + live average
bath_chemistry_panel -> pH/Conc/Temp/Bath grid
selection -> dropdown sourced from selection_options
text/signature/... -> text input
- Live in-range hint for numeric prompts
("in range" / "below target" / "above target")
- Save validates ad-hoc rows have a Prompt label
- Save dispatches the next_action returned by the wizard model
(e.g. action_finish_and_advance for the Finish & Next flow)
* New XML template: fp_record_inputs_dialog.xml
Full DOM control. No fighting Odoo's list view, no class-stripping
bugs from canUseFormatter, no read-mode-vs-edit-mode CSS dance.
* New SCSS: fp_record_inputs_dialog.scss
- Dark mode aware (compile-time @if $o-webclient-color-scheme==dark)
- Pure semantic selectors (.o_fp_ri_card, .o_fp_ri_input, etc.)
- 14 surface tokens with light/dark hex pairs
- Tablet polish via @media (max-width: 768px)
- Custom toggle widget (no <input type="checkbox"> hidden trick)
* New controller: controllers/record_inputs.py
- /fp/record_inputs/load: returns step + prompts payload
- /fp/record_inputs/commit: creates a wizard, populates lines,
calls action_commit (reuses existing audit-trail / synthetic
move semantics — no commit logic duplicated)
* fp_job_step.py wired to dispatch the new action
- _fp_open_input_wizard returns
{ type: 'ir.actions.client', tag: 'fp_record_inputs_dialog' }
- action_open_input_wizard same
- Contract-review redirect gate preserved (Sub 4 work intact)
* Manifest registers JS/XML/SCSS in BOTH backend + dark bundles
per the dark-mode pattern in CLAUDE.md.
What was kept
=============
* fp.job.step.input.wizard TransientModel — UNCHANGED. The new
controller's commit endpoint creates a wizard record and calls
action_commit() on it, so all the audit-trail / synthetic-move
/ chatter logic stays in Python where it belongs.
* v2 + v3 form views still exist in the XML file. If the OWL
dialog ever fails, switch action_open_input_wizard back to
ir.actions.act_window with view_id=v2 or v3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,514 @@
|
||||
// =============================================================================
|
||||
// Record Inputs Dialog — Sub 12e v4 (proper OWL component)
|
||||
// Copyright 2026 Nexa Systems Inc.
|
||||
//
|
||||
// Pure semantic HTML inside a Dialog. No list view to fight, no
|
||||
// table-cell unwinding, no class-stripping bugs. Just cards.
|
||||
//
|
||||
// Dark mode: branched at compile time on $o-webclient-color-scheme,
|
||||
// per fusion-plating/CLAUDE.md. Registered in BOTH backend + dark
|
||||
// asset bundles.
|
||||
// =============================================================================
|
||||
|
||||
$o-webclient-color-scheme: bright !default;
|
||||
|
||||
// ---------- Surface tokens ---------------------------------------------------
|
||||
|
||||
$_fp-rid-card-hex : #ffffff;
|
||||
$_fp-rid-card-hover-hex: #f8f9fa;
|
||||
$_fp-rid-page-hex : #f3f4f6;
|
||||
$_fp-rid-input-hex : #ffffff;
|
||||
$_fp-rid-pill-hex : #f1f3f5;
|
||||
$_fp-rid-border-hex : #d8dadd;
|
||||
$_fp-rid-border-strong-hex: #b6babf;
|
||||
$_fp-rid-border-focus-hex : #714B67;
|
||||
$_fp-rid-ink-hex : #1f2937;
|
||||
$_fp-rid-ink-soft-hex : #4b5563;
|
||||
$_fp-rid-ink-mute-hex : #6b7280;
|
||||
$_fp-rid-ink-faint-hex : #9ca3af;
|
||||
$_fp-rid-required-hex : #dc3545;
|
||||
$_fp-rid-ok-hex : #198754;
|
||||
$_fp-rid-warn-hex : #b18307;
|
||||
|
||||
@if $o-webclient-color-scheme == dark {
|
||||
$_fp-rid-card-hex : #2a2f37 !global;
|
||||
$_fp-rid-card-hover-hex: #323843 !global;
|
||||
$_fp-rid-page-hex : #1a1d21 !global;
|
||||
$_fp-rid-input-hex : #1a1d21 !global;
|
||||
$_fp-rid-pill-hex : #353a44 !global;
|
||||
$_fp-rid-border-hex : #3f4651 !global;
|
||||
$_fp-rid-border-strong-hex: #5a606b !global;
|
||||
$_fp-rid-border-focus-hex : #a78bca !global;
|
||||
$_fp-rid-ink-hex : #e5e7eb !global;
|
||||
$_fp-rid-ink-soft-hex : #c8ccd2 !global;
|
||||
$_fp-rid-ink-mute-hex : #8a909a !global;
|
||||
$_fp-rid-ink-faint-hex : #6a707b !global;
|
||||
$_fp-rid-required-hex : #ea868f !global;
|
||||
$_fp-rid-ok-hex : #75b798 !global;
|
||||
$_fp-rid-warn-hex : #ffd866 !global;
|
||||
}
|
||||
|
||||
$rid-card : var(--fp-rid-card-bg, #{$_fp-rid-card-hex});
|
||||
$rid-card-hover : var(--fp-rid-card-hover-bg, #{$_fp-rid-card-hover-hex});
|
||||
$rid-page : var(--fp-rid-page-bg, #{$_fp-rid-page-hex});
|
||||
$rid-input : var(--fp-rid-input-bg, #{$_fp-rid-input-hex});
|
||||
$rid-pill : var(--fp-rid-pill-bg, #{$_fp-rid-pill-hex});
|
||||
$rid-border : var(--fp-rid-border, #{$_fp-rid-border-hex});
|
||||
$rid-border-strong: var(--fp-rid-border-strong, #{$_fp-rid-border-strong-hex});
|
||||
$rid-border-focus: var(--fp-rid-border-focus, #{$_fp-rid-border-focus-hex});
|
||||
$rid-ink : var(--fp-rid-ink, #{$_fp-rid-ink-hex});
|
||||
$rid-ink-soft : var(--fp-rid-ink-soft, #{$_fp-rid-ink-soft-hex});
|
||||
$rid-ink-mute : var(--fp-rid-ink-mute, #{$_fp-rid-ink-mute-hex});
|
||||
$rid-ink-faint : var(--fp-rid-ink-faint, #{$_fp-rid-ink-faint-hex});
|
||||
$rid-required : var(--fp-rid-required, #{$_fp-rid-required-hex});
|
||||
$rid-ok : var(--fp-rid-ok, #{$_fp-rid-ok-hex});
|
||||
$rid-warn : var(--fp-rid-warn, #{$_fp-rid-warn-hex});
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// Dialog frame — generous body, scrollable card stack
|
||||
// =============================================================================
|
||||
|
||||
.o_fp_ri_dialog_content {
|
||||
.modal-body {
|
||||
padding: 16px 20px;
|
||||
background-color: $rid-page;
|
||||
}
|
||||
}
|
||||
|
||||
.o_fp_ri_header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
.o_fp_ri_step_name {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: $rid-ink;
|
||||
}
|
||||
.o_fp_ri_job_label {
|
||||
font-size: 0.875rem;
|
||||
color: $rid-ink-mute;
|
||||
}
|
||||
|
||||
.o_fp_ri_loading,
|
||||
.o_fp_ri_empty {
|
||||
padding: 48px 24px;
|
||||
text-align: center;
|
||||
color: $rid-ink-mute;
|
||||
background-color: $rid-card;
|
||||
border: 1px dashed $rid-border;
|
||||
border-radius: 12px;
|
||||
|
||||
p { margin-bottom: 12px; }
|
||||
i.fa-spinner { color: $rid-border-focus; font-size: 1.5rem; }
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// Card stack
|
||||
// =============================================================================
|
||||
|
||||
.o_fp_ri_cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.o_fp_ri_card {
|
||||
padding: 16px 20px;
|
||||
background-color: $rid-card;
|
||||
border: 1px solid $rid-border;
|
||||
border-radius: 12px;
|
||||
transition: border-color 120ms ease, background-color 120ms ease;
|
||||
|
||||
&:hover {
|
||||
border-color: $rid-border-strong;
|
||||
}
|
||||
&:focus-within {
|
||||
border-color: $rid-border-focus;
|
||||
box-shadow: 0 0 0 3px
|
||||
color-mix(in srgb, #{$rid-border-focus} 18%, transparent);
|
||||
}
|
||||
|
||||
&.o_fp_ri_card_required {
|
||||
border-left: 3px solid $rid-border-focus;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------- Card header — prompt + meta + remove ----------------------------
|
||||
|
||||
.o_fp_ri_card_head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.o_fp_ri_prompt {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.o_fp_ri_prompt_label {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: $rid-ink;
|
||||
}
|
||||
.o_fp_ri_required_mark {
|
||||
color: $rid-required;
|
||||
font-weight: 700;
|
||||
}
|
||||
.o_fp_ri_prompt_input {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
background: $rid-input;
|
||||
color: $rid-ink;
|
||||
border: 1px solid $rid-border;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
|
||||
&:focus {
|
||||
border-color: $rid-border-focus;
|
||||
outline: none;
|
||||
}
|
||||
&::placeholder { color: $rid-ink-faint; }
|
||||
}
|
||||
|
||||
.o_fp_ri_meta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.o_fp_ri_pill {
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
background-color: $rid-pill;
|
||||
color: $rid-ink-soft;
|
||||
border: 1px solid $rid-border;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.2;
|
||||
text-transform: lowercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.o_fp_ri_pill_unit {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
color: $rid-ink-mute;
|
||||
font-weight: 600;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.o_fp_ri_remove {
|
||||
flex-shrink: 0;
|
||||
color: $rid-ink-faint;
|
||||
padding: 4px 8px;
|
||||
opacity: 0.5;
|
||||
transition: opacity 120ms ease, color 120ms ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: $rid-required;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------- Target / hint helpers ------------------------------------------
|
||||
|
||||
.o_fp_ri_target {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 0.8125rem;
|
||||
color: $rid-ink-mute;
|
||||
}
|
||||
.o_fp_ri_hint {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 0.8125rem;
|
||||
color: $rid-ink-faint;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// Card body — inputs per type
|
||||
// =============================================================================
|
||||
|
||||
.o_fp_ri_card_body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
|
||||
// ---------- Common input chrome --------------------------------------------
|
||||
|
||||
.o_fp_ri_input {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
padding: 10px 14px;
|
||||
min-height: 48px;
|
||||
background-color: $rid-input;
|
||||
color: $rid-ink;
|
||||
border: 1px solid $rid-border-strong;
|
||||
border-radius: 8px;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
transition: border-color 120ms ease, box-shadow 120ms ease;
|
||||
|
||||
&::placeholder { color: $rid-ink-faint; font-weight: 400; }
|
||||
|
||||
&:hover {
|
||||
border-color: $rid-ink-mute;
|
||||
}
|
||||
&:focus {
|
||||
border-color: $rid-border-focus;
|
||||
box-shadow: 0 0 0 3px
|
||||
color-mix(in srgb, #{$rid-border-focus} 25%, transparent);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.o_fp_ri_input_select {
|
||||
appearance: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
.o_fp_ri_input_numeric {
|
||||
text-align: left;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
|
||||
// ---------- Numeric — input + range hint -----------------------------------
|
||||
|
||||
.o_fp_ri_numeric {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.o_fp_ri_range_hint {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
|
||||
&.o_fp_ri_range_ok {
|
||||
background-color: color-mix(in srgb, #{$rid-ok} 15%, transparent);
|
||||
color: $rid-ok;
|
||||
}
|
||||
&.o_fp_ri_range_low,
|
||||
&.o_fp_ri_range_high {
|
||||
background-color: color-mix(in srgb, #{$rid-warn} 18%, transparent);
|
||||
color: $rid-warn;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------- Boolean toggle (custom — bigger + clearer than Bootstrap) ------
|
||||
|
||||
.o_fp_ri_toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
> input { position: absolute; opacity: 0; pointer-events: none; }
|
||||
}
|
||||
.o_fp_ri_toggle_track {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 56px;
|
||||
height: 32px;
|
||||
background-color: $rid-pill;
|
||||
border: 1px solid $rid-border-strong;
|
||||
border-radius: 999px;
|
||||
transition: background-color 150ms ease, border-color 150ms ease;
|
||||
}
|
||||
.o_fp_ri_toggle_thumb {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-color: $rid-ink-mute;
|
||||
border-radius: 50%;
|
||||
transition: transform 150ms ease, background-color 150ms ease;
|
||||
}
|
||||
.o_fp_ri_toggle > input:checked ~ .o_fp_ri_toggle_track {
|
||||
background-color: $rid-border-focus;
|
||||
border-color: $rid-border-focus;
|
||||
|
||||
.o_fp_ri_toggle_thumb {
|
||||
transform: translateX(24px);
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
.o_fp_ri_toggle_label {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: $rid-ink-soft;
|
||||
}
|
||||
|
||||
|
||||
// ---------- Photo upload — modest size, semantic ---------------------------
|
||||
|
||||
.o_fp_ri_photo {
|
||||
display: inline-block;
|
||||
}
|
||||
.o_fp_ri_photo_placeholder .btn {
|
||||
background-color: $rid-pill;
|
||||
color: $rid-ink-soft;
|
||||
border: 1px dashed $rid-border-strong;
|
||||
padding: 12px 18px;
|
||||
|
||||
&:hover {
|
||||
border-color: $rid-border-focus;
|
||||
color: $rid-border-focus;
|
||||
}
|
||||
}
|
||||
.o_fp_ri_photo_preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
img {
|
||||
max-width: 200px;
|
||||
max-height: 150px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid $rid-border;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------- Multi-point thickness ------------------------------------------
|
||||
|
||||
.o_fp_ri_multi_grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(70px, 1fr));
|
||||
gap: 8px;
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 0.75rem;
|
||||
color: $rid-ink-mute;
|
||||
font-weight: 600;
|
||||
|
||||
input {
|
||||
margin-top: 4px;
|
||||
padding: 8px 10px;
|
||||
background: $rid-input;
|
||||
color: $rid-ink;
|
||||
border: 1px solid $rid-border-strong;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
&:focus {
|
||||
border-color: $rid-border-focus;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_fp_ri_multi_avg {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
padding-bottom: 4px;
|
||||
|
||||
strong {
|
||||
font-size: 1.125rem;
|
||||
color: $rid-ink;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------- Bath chemistry panel — same shape as multi ---------------------
|
||||
|
||||
.o_fp_ri_panel {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(120px, 1fr));
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 0.75rem;
|
||||
color: $rid-ink-mute;
|
||||
font-weight: 600;
|
||||
|
||||
input {
|
||||
margin-top: 4px;
|
||||
padding: 8px 10px;
|
||||
background: $rid-input;
|
||||
color: $rid-ink;
|
||||
border: 1px solid $rid-border-strong;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
|
||||
&:focus {
|
||||
border-color: $rid-border-focus;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------- Add-row CTA -----------------------------------------------------
|
||||
|
||||
.o_fp_ri_add_btn {
|
||||
align-self: flex-start;
|
||||
padding: 10px 18px;
|
||||
color: $rid-ink-soft;
|
||||
background-color: $rid-card;
|
||||
border: 1px dashed $rid-border-strong;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: $rid-border-focus;
|
||||
border-color: $rid-border-focus;
|
||||
background-color: $rid-card-hover;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// Tablet polish — bigger inputs on narrow screens
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.o_fp_ri_card_head {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.o_fp_ri_meta {
|
||||
order: 3;
|
||||
width: 100%;
|
||||
}
|
||||
.o_fp_ri_input {
|
||||
max-width: 100%;
|
||||
min-height: 56px;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.o_fp_ri_multi_grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
.o_fp_ri_panel {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user