The wizard was calling so.action_confirm() immediately after creating the
sale order, which flipped it from draft to sale state and triggered the
fusion_plating_notifications hook that auto-emails the customer.
Client wants a review step: keep the SO in quotation (draft) so the
user can adjust before the customer sees anything. They manually click
Send (to email the quotation) or Confirm (to convert to sale order,
which intentionally fires the confirmation email).
Changes:
- Remove so.action_confirm() call in action_create_order
- Update docstring + inline comment to reflect manual-confirm flow
- Update the chatter message on the created SO
CLAUDE.md updated to mark Sub 1 + Sub 2 as Shipped.
Verified:
- Static check: wizard.action_create_order contains no action_confirm
- Dynamic check: SO created programmatically stays in draft
- Manual action_confirm() flow still works as designed
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When Sub 2 Task 26 flipped x_fc_internal_description to required=True,
any programmatic sale.order.line creation that doesn't set the field
fails at the Postgres NOT NULL constraint. Callers include:
- sale_mrp stock-move line creation (doesn't set name either)
- demo seeders
- external integrations
- test scripts
The UI-side onchange populates the field when the user picks a
description template; this hook mirrors that for programmatic callers.
Fallback chain: explicit vals['x_fc_internal_description'] → vals['name']
→ product_id.display_name → '—'. Matches the migration's backfill rule.
Also adds Sub 2 end-to-end smoke test (6 cases, all green):
1. Required-field rejection on part creation
2. Required-field rejection on template creation
3. Template picker populates both SO-line descriptions
4. Cert resolver: part-level override wins over partner
5. display_name renders part_number + revision + name
6. certificate_requirement defaults to 'inherit'
QC Phase 1-3 regression suite remains green after the fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the `description` field from `fp.sale.description.template` now
that all readers (reports, wizard, sale line) consume the new
`internal_description` + `customer_facing_description` pair.
- Model: drop `description = fields.Text(...)` declaration
- Migration 19.0.9.0.0 Step 6: `ALTER TABLE ... DROP COLUMN IF EXISTS description`
- Template form/search views: swap `description` for the two new fields
- Seed data: write new fields instead of legacy column (dupes old text into both)
- Direct-order wizard: remove `tpl.description` fallback in both onchange handlers
Entech column dropped via Odoo's auto-schema-sync during module upgrade
(migration step is for fresh installs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewired portrait + landscape variants of report_fp_bol. The BoL had no
line collection of its own (fusion.plating.delivery only has a soft
`job_ref` Char), so the previous cargo-description block was a single
hardcoded row. Restructured to look up the job's mrp.production via
`job_ref`, iterate its `move_finished_ids` (excluding cancelled), and
render each finished-goods move through the shared
customer_line_header macro using the `move.sale_line_id or move`
adapter pattern.
When no MO is found or there are no finished moves, the template falls
back to the previous single-row "Plated parts — Job X" behavior so
legacy records without a backing MO still print correctly. Per-row QTY
now reflects the individual move's `product_uom_qty` instead of the
MO's aggregate `product_qty`.
Both variants render successfully on entech against a delivery whose
job_ref matches a real MO with one finished move.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewired portrait + landscape variants of report_fp_packing_slip to use the
shared customer_line_header QWeb macro. The packing slip iterates
stock.move records (doc.move_ids_without_package); the adapter
`<t t-set="line" t-value="move.sale_line_id or move"/>` bridges the macro's
`line.x_fc_part_catalog_id` lookup to the sale line when the move is tied
to a sale (preferred path), falling back to rendering the stock.move's
product_id for stray moves with no sale line.
SKU + PRODUCT columns collapsed into a single PART column (width
adjusted to absorb the removed SKU column). Both variants render
successfully on entech with a real picking whose move has a sale_line_id.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Invoice PDF (portrait + landscape) now collapses SKU + Description into
a single Part column rendered via fusion_plating_reports.customer_line_header,
so customer-facing invoices print the customer's part number (with
revision) instead of the internal service SKU.
To feed the macro on invoice lines, add x_fc_part_catalog_id to
account.move.line and override sale.order.line._prepare_invoice_line so
the part reference propagates automatically when an SO is invoiced.
Collapse the SKU and Description columns in both the portrait and
landscape sale-order PDFs into a single Part column rendered through
the shared customer_line_header macro, so customer-facing quotes and
confirmed orders print the customer's part number (with revision)
instead of the internal service SKU.
Updates column widths, section/note colspans, and the conditional
col_count used for the landscape template's optional discount column
to reflect the collapsed header.
Adds an `internal_description` text field to the direct-order wizard
line so the shop-floor copy is captured at order entry alongside the
customer-facing text. Picking a template now fires both sides of the
onchange: `line_description` gets `customer_facing_description` (with
fallback to the legacy `description` field for backward compat) and
`internal_description` gets the template's internal text. The
auto-suggest onchange was refactored around a tiny `_apply` helper so
all three fallback paths populate both fields consistently.
The template picker is surfaced as an optional column on the wizard
list (hidden until a part is chosen, domain-scoped to that part) and
as a dedicated labeled row in the per-line form. The internal text
field is also surfaced in the form under "Line Description" so the
estimator can review / edit it before confirm. On create_order, both
`x_fc_description_template_id` and `x_fc_internal_description` are
written through to the generated sale.order.line so the audit trail
and WO printout stay linked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaces the per-part description template on the SO line list alongside
a hidden-by-default internal description column. Picking a template
fires an onchange that copies `customer_facing_description` into Odoo's
standard `name` (customer-visible) and `internal_description` into
x_fc_internal_description (shop-floor / WO only). Estimator can edit
either field after the template is applied.
The template picker's domain filters by the line's part, and the field
stays hidden until a part is chosen — avoids showing every global
template when the line is blank.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Full bite-sized plan matching the approved spec. Each task has file
paths, complete code, syntax-check commands, upgrade commands, expected
outputs, and commit messages.
Phase A (Tasks 1-12): additive schema + migration + cert-resolver.
System runnable throughout.
Phase B (Tasks 13-23): UI + QWeb macro + report rewiring. Users see new
fields. Old fields still exist.
Phase C (Tasks 24-30): flip required=True, drop legacy column, regression,
deploy to entech.
Self-review pass: every spec section mapped to a task; no TBD/TODO/placeholder.
Type signatures (_fp_resolve_cert_requirement, display_name, macro
params) consistent across tasks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the current state of the system-wide fine-tuning initiative so a
fresh Claude Code session can resume without context loss.
CLAUDE.md additions (fusion_plating/CLAUDE.md):
* Sub-project roadmap (Sub 1 through 8 + two deferred items)
* Sub 2 locked decisions (Q1–Q6 answers)
* Sub 2 defensive measures that prevent rework when later subs land
* Sub 6 / 7 / 8 previews from the client transcript
* Client-confirmed operational thresholds (tank polling, active tanks)
* How to resume in a fresh session
Sub 2 design spec (docs/superpowers/specs/):
* Part Data Model Overhaul — covers gaps 2b, 2c, 2d, 4
* 12 sections: scope, data model, migration, UI, cert resolution,
reports, testing, defensive measures, files touched, rollout,
success criteria, open questions
* All clarifying questions answered; zero placeholders
* Ready for writing-plans skill to generate implementation plan
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a QC uploaded the XDAL 600 report, the CoC PDF render pipeline
now appends the Fischerscope PDF directly after the cert pages. This
matches what aerospace / Nadcap auditors expect (and how Steelhead
ships certs today) — a single PDF file carrying both the certificate
declaration and the raw equipment report.
Flow:
* _fp_generate_cert_pdf renders the CoC via QWeb as before
* _fp_merge_thickness_into_cert resolves the QC for the MO (preferring
the passed one) and extracts its thickness_report_pdf_id bytes
* PyPDF2.PdfMerger concatenates CoC then Fischerscope into a single PDF
* Merged bytes replace pdf_content before the ir.attachment is written
* Falls back to CoC-only (and logs a warning) if PyPDF2 is missing or
either PDF fails to parse — never blocks MO completion
Smoke test: synthetic Fischerscope + real QWeb CoC → 2-page merged PDF
with page 1 CoC text and page 2 Fischerscope text, verified via
PyPDF2 extract_text.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Future sessions shouldn't silently re-install retired modules during
install/upgrade sweeps. Added an explicit "Retired / Do-Not-Install
Modules" section with guardrails:
- Don't include in -i / -u sequences
- Don't add as a depends target
- Don't re-sync to entech /mnt/extra-addons/custom/
- Don't recommend installing without user confirmation
Covers the two modules currently in this state:
- fusion_plating_culture (code in repo, uninstalled on entech)
- fusion_plating_sensors (fully removed, absorbed into fusion_iot)
Also struck-through the "| 80 | Culture | ..." menu row and added a
retired tag to the module-list tree so at-a-glance scans don't
suggest it's a live part of the menu hierarchy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Culture/values/recognitions framework was shipping zero data and zero
workflow integration for this client. It's a people-ops concern (peer
kudos, "Fundamental of the Week" rotations) with no overlap with the
technical plating pipeline — no interaction with process recipes,
quality holds, sensors, or compliance.
Verified zero data on entech before uninstalling:
fusion.plating.value 0 records
fusion.plating.value.set 0
fusion.plating.value.recognition 0
fusion.plating.value.rotation 0
Clean uninstall on entech, module dir removed from disk. The Culture
top-level menu disappears. If a future client wants it back, the
module is easy to re-author — nothing we built on top of it depends
on it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fusion_plating_sensors had broader scope (sensor_type taxonomy,
dashboards, location flexibility) but its core logic was ALL
scaffolding — alert rules stored thresholds with zero side effects,
measurement create just filled a name sequence, the HTTP endpoint
required user-auth session cookies. Meanwhile fusion_plating_iot had
the actual working alerting: in-spec checks, quality-hold auto-raise
with excursion dedupe, setpoint + deviation, token-auth ingest for
headless hardware. Plus 563 real readings from the pilot Pi.
Right unification: keep fusion_plating_iot (working) as the base,
port the valuable structural bits from fusion_plating_sensors, demolish
the latter entirely.
**Ported to fusion_plating_iot:**
- `fp.sensor.type` — taxonomy model with 8 seeded types (Temperature,
pH, Conductivity, Level, Pressure, Flow, Concentration, Switch).
Richer than the device_kind Selection; hardware-independent (one
"Temperature" type covers DS18B20 / PT100 / thermocouple).
- `fp.sensor.dashboard` — named grouping of sensors with
out-of-spec count. Simple but useful ("ENP Line 1 — all tanks")
without the broken alert-rule complexity.
- Extended `fp.tank.sensor`:
* `uuid` (stable logical ID, survives hardware swaps)
* `sensor_type_id` (link to the taxonomy above)
* `work_center_id`, `facility_id`, `location_name` — alternatives to
tank_id so probes can live on ovens, ambient air, effluent pipes
without faking a "tank"
* `effective_location` computed — picks the first non-empty of the
four location fields for display
**Post-install hook** backfills UUID + default sensor_type on existing
live sensors. Verified on the 2 pilot sensors: both got UUIDs, both
auto-assigned the Temperature type via device_kind=ds18b20 mapping.
**Deleted** (all of fusion_plating_sensors, 1205 LOC):
- fp.sensor (replaced by fp.tank.sensor with added fields)
- fp.sensor.measurement (replaced by fp.tank.reading)
- fp.sensor.alert.rule (replaced by inline alert_min/max + working hold)
- /fp/sensor/measure controller (replaced by /fp/iot/ingest)
- fp.sensor.measure.wizard (not needed — Odoo's normal create form works)
- The "Sensors" submenu hierarchy (Dashboards/All Sensors/Measurements/
Sensor Types) that created the dup menus the user reported
**Menu now**: Plating → Operations → Sensors
- Dashboards (fp.sensor.dashboard)
- Sensors (fp.tank.sensor — renamed from "Tank Sensors" since
it supports non-tank locations now)
- Readings (fp.tank.reading)
- Sensor Types (fp.sensor.type)
No data loss: all 591 Pi readings preserved (up from 563 earlier as
the live poller kept running throughout the refactor). Brief 503 on
the Pi during the Odoo module-update restart; poller auto-retried on
the next 30s tick.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sensors previously only tracked alarm thresholds (alert_min/alert_max).
Missing the third piece of standard process control: the SETPOINT —
what the heater/chiller controls toward and what dashboards compare
against. Without it an operator can't tell whether 89°C is "on target"
or "barely still in spec".
Schema changes:
**fusion.plating.bath.parameter** (shop-wide default)
- New `target_value` field — the default setpoint for this parameter
across the shop (e.g. 87°C for ENP bath). Parallel to existing
target_min / target_max.
**fp.tank.sensor** (per-sensor override)
- New `target_value_override` — per-sensor override, zero = inherit
from parameter. Matches the existing override pattern for alert
thresholds so users can fine-tune per-tank without touching the
shop-wide spec.
- New `effective_target` / `effective_target_unit` computed — resolves
override → parameter default, converts to company-preferred unit.
- New `_get_setpoint()` helper for internal use.
**fp.tank.reading**
- New `deviation_from_target` — signed Δ from the sensor's effective
setpoint, in the company's preferred unit. Positive = above, negative
= below, zero if no setpoint defined.
- New `deviation_band` (selection: on/near/far/out/none) — coarse band
for fast visual scanning. `on` = within ±1° of target, `near` = ±3°,
`far` = beyond, `out` = actually out of the alarm band.
**Views**
- Sensor form: split the alerting panel into two groups — "Target
(setpoint)" on the left, "Alarm band" on the right. Makes the
distinction between "where we want to be" and "where we'd panic"
visually obvious.
- Reading list: new Δ + band columns, with decoration classes
(success/info/warning/danger) so the list reads at a glance.
- Tank form Sensors tab: inline setpoint + unit column.
Seeded: parameter "Bath Temperature (Hot Process)" now carries
target_value=87°C as a realistic ENP shop default. Sensors inherit
unless they set their own override.
Design decisions kept simple:
- Did NOT add a warning band (warn_min/warn_max). Two-tier model
(setpoint + alarm band) is enough for the pilot. Can add soft
warnings later as a separate commit if ops wants them.
- Did NOT auto-control heaters. Setpoint is stored as metadata only;
actual heater actuation via IoT is a future phase C project.
Verified: setpoint 87°C stored → displays 188.60°F on the live pilot
sensor (company pref = F). Each incoming reading correctly computes
signed deviation; bands colour the reading list appropriately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sensor readings list always showed raw °C regardless of the
Plating Settings Temperature Unit preference (res.company.x_fc_default_temp_uom).
On a Fahrenheit-preferred shop, a 40°C reading should render as 105°F.
Fix: add display-aware computed fields alongside the canonical ones.
**fp.tank.reading**
- `value` / `unit` renamed with "(raw)" labels — these are the stored
canonical values (always °C for temperature, because every
temperature chip reports in Celsius natively)
- `display_value` + `display_unit` computed from company pref — only
flips C→F when parameter_type='temperature' AND company pref='F';
pH/conductivity/etc pass through untouched
- `display_name` now uses display_value so it reads naturally
("Sensor — 105.58 °F @ ...") regardless of region
**fp.tank.sensor**
- Mirrored the same pattern on the cached last-reading fields
- `last_reading_display` + `last_reading_display_unit` for lists
- `last_reading_value` hidden behind group_no_one (debug-only)
**Views**
- fp.tank.reading list: show display_value/display_unit, raw value
hidden by default (toggle from column picker if needed)
- fp.tank.sensor list + form + tank inline: same pattern
- Raw value kept visible as an optional column so data engineers
can still audit canonical storage
Why store canonical: spec thresholds (alert_min/max) live on the
sensor in °C. If the same Odoo serves a multi-region company
(Canada in C, US affiliate in F), switching a single preference
flips every UI without touching data. Alert logic keeps comparing
canonical values, so out-of-spec holds fire correctly regardless
of display unit.
Verified: 40.88°C raw → 105.58°F display on the live pilot probe
with company pref='F'. All 5 recent readings tested, display
fields updated correctly on every poll.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pi is at our office today but moves to the client's shop in the next
few days. The client accesses Odoo at https://erp.enplating.ca (not a
LAN/Tailscale path — it's the same HTTPS URL any browser uses). By
pointing the poller at the public URL instead of the internal
10.200.1.26 LAN IP, the Pi works IDENTICALLY wherever it's plugged
in — no reconfiguration when it physically relocates.
- Updated poller's docstring + example config to use
https://erp.enplating.ca
- Updated fusion_iot/CLAUDE.md with the portable-deployment notes and
the failed-Tailscale-on-entech side-story (LXC can't create tun,
apt state broken from a pre-existing python3-lxml-html-clean
conflict — skipped because public URL is simpler anyway).
Verified live: poller restarted against https://erp.enplating.ca,
HTTP 200, TLS valid, 121ms RTT, two consecutive readings accepted
(46.25°C, 45.94°C — probe still cooling from the out-of-spec test).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fp-iot-01 is now on Tailscale at 100.108.41.97. SSH config on the
Mac aliases `ssh fp-iot-01` to the Tailscale IP with key-based auth
(no more sshpass + password flying around in shell history).
Also noted the Pi-side folder structure (pi/ + scripts/) and the
live deployment facts (probe serial, systemd unit, config path)
so future sessions can pick up from zero without re-investigating.
Verified end-to-end with real hardware:
- Physical probe heated to 79.94°C → auto-raised HOLD-0015
- 30 subsequent out-of-spec readings → no duplicate holds (as designed)
- hold_id correctly linked back to the triggering reading
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase B kickoff — Pi hardware is wired up and posting readings to
Odoo via /fp/iot/ingest every 30 seconds. No more simulations;
this is real tank-temperature data.
New files:
- `pi/fp_iot_poller.py` — tiny systemd daemon. Reads every DS18B20
under /sys/bus/w1/devices/28-* (kernel CRC-validated) and POSTs
a batch to /fp/iot/ingest with the shared-secret token. Handles
transient network failures by logging + retrying on the next
30-second tick. Config in /etc/fp-iot/poller.conf (ODOO_URL,
INGEST_TOKEN, INTERVAL_SECONDS).
- `pi/fp-iot-poller.service` — systemd unit with hardened sandbox
(NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp,
ReadOnlyPaths=/sys/bus/w1 /etc/fp-iot). Auto-starts on boot,
restarts on failure.
- `scripts/fp_iot_setup_live_sensor.py` — one-shot entech
initialiser: rotates the ingest token to a real random secret,
picks a test tank + temperature parameter, creates the
fp.tank.sensor record for serial 28-000000b276e4 with 15-35°C
alert thresholds sized for bench testing.
Verified end-to-end: Pi reads probe → posts to Odoo → reading lands
in fp.tank.reading within 1s. 5 consecutive readings at 30s
cadence show smooth temperature trend (probe cooling from 27.25°C
to 26.06°C after being handled). in_spec flag correct, sensor
cache (last_reading_value / _at / _in_spec) updates on every
reading.
Not yet done — Phase B continued:
- Repackaged iot_drivers path (full Odoo IoT integration vs this
simple HTTP path) — this poller is the minimal viable pilot.
- Multi-probe (scalable to N probes per Pi; code already supports,
just need more hardware).
- Graduate to the proper iot.device + iot.box Odoo registration
flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two user-reported bugs on S00056 (and visible on any SO/invoice):
Bug 1: Cancelling the Send dialog still sends the email.
action_quotation_send is a button handler that returns a
compose-dialog action synchronously. Our override was calling
_dispatch('quote_sent', ...) AFTER super(), which immediately
sends the email via template.send_mail() before the user ever
sees the dialog. Clicking Cancel at that point only dismisses
the already-sent email's compose window.
Fix: removed the _dispatch call entirely from action_quotation_send.
The Send button IS the manual-send path; Odoo's compose dialog
(pre-populated with our FP: Quotation Sent template thanks to
_find_mail_template) handles the send-or-cancel choice correctly.
If an auto-quote-sent notification is ever wanted, it should be
wired from a cron that scans SOs that just transitioned
draft -> sent, not from the button handler. Noted in a code
comment.
Bug 2: Two copies of the same PDF attached to every email.
The mail.template records now carry report_template_ids (set by
the post_init hook from the previous refactor) which Odoo uses to
auto-attach PDFs on send_mail(). But the fp.notification.template
records ALSO had attach_quotation / attach_sale_order / attach_invoice
flags set, which cause _collect_attachments() to render the same
PDF a second time and pass it in email_values.
Fix: turned those three flags off in the XML data file. Other
flags (attach_coc, attach_bol, attach_receipt, attach_thickness_report,
attach_packing_list, attach_pod) stay on — those are genuinely
different documents, not dupes.
Because the records are noupdate="1", updated the post_init_hook
to also backfill the flag changes onto existing DB rows so
production instances get cleaned up on -u, not just fresh installs.
Smoke:
attach_quotation=False attach_sale_order=False attach_invoice=False on all three records
1 report per template (no dupes)
action_quotation_send no longer contains _dispatch("quote_sent", ...)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback: 'i like the odoo enterprise style reports, I hate our style.'
Replaces our custom 'o_fusion_reports' visual with a faithful adaptation
of Enterprise account_reports. Same .account_report root class, same
table semantics (line_name + line_cell + line_level_N), same border
treatment (1px gray-300 borders, 0.25rem radius, sticky thead), same
button hover behavior (gray-300 -> enterprise-action-color), same dense
0.8rem font-size + padded cells.
SCSS layout:
- reports.scss in web.assets_backend bundle (eager light)
- reports.dark.scss in web.assets_web_dark bundle (lazy dark mode)
- _variables.scss reduced to spacing/typography only -- colors use
Odoo's \$o-* SCSS vars so dark mode flips automatically via the
separate dark bundle
- old dark_mode.scss removed (was using non-Odoo [data-color-scheme]
selector that never matched anything)
QWeb templates rewritten to mirror Enterprise's structure:
- report_viewer.xml roots at .account_report with scroll container
- report_table.xml uses Enterprise's td.line_name + td.line_cell with
.wrapper > .content nesting; partner-grouped reports now actually
render their aging buckets (previously showed nothing)
- period_filter.xml is now a clean Bootstrap-styled inline filter bar
Kept Fusion-only components but restyled to fit:
- anomaly_strip uses Bootstrap alert-{danger,warning,info} colors
- ai_commentary_panel is a plain bordered panel, no gradients/emojis
- drill_down_dialog unchanged (already a Bootstrap modal)
Made-with: Cursor
Earlier I built report_fp_so_acknowledgement.xml as a separate
customer-facing document. On review there was no good reason — our
existing report_fp_sale.xml already flips its title between
"Quotation" and "Sales Order" based on state, and carried ~90% of
the same content. Two documents would have meant the shop had to
remember which to send when, and the customer would get two
near-identical PDFs in their inbox.
Consolidation:
1. Merged the four unique blocks from the acknowledgement into
report_fp_sale.xml (both portrait AND landscape variants):
- CUSTOMER JOB # / PLANNED START / CUSTOMER DEADLINE / SHIP VIA
info row (shown only when any of those fields is populated)
- Blanket / block-partial highlight-box callout (shown only
when the flags are set)
- External notes (x_fc_external_note) block above Terms and
Conditions
2. Deleted fusion_plating_reports/report/report_fp_so_acknowledgement.xml
and removed it from the module manifest. Also purged the orphan
ir.actions.report and ir.ui.view DB rows + the stale
ir.model.data entries.
3. Re-pointed the fp_mail_template_so_confirmed mail template's
report_template_ids from the now-gone acknowledgement report to
action_report_fp_sale_portrait. Updated hooks.py accordingly; the
hook now uses "set" semantics (replace all) instead of "add" so
re-running it cleans up stale attachments from prior refactors.
4. UAT on S00071: the Send button pre-selects the FP: Order
Confirmation template with SalesOrder_S00071.pdf attached. The
PDF renders with the new plating rows populated — Customer Job #
AMPH-2026-0420-01, Customer Deadline 05/14/2026 08:00:00 PM,
"Partial shipments blocked" callout, all lines + totals.
One PDF, one Send button behaviour, matching what Odoo and most
ERP systems do.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User reported that after Enterprise uninstall, clicking 'Reports' opened
PDF statements instead of the dynamic Fusion report viewer. Root cause:
the OWL ReportViewer (registered as view_type='fusion_reports') was only
reachable via the period-picker WIZARD; no menu items used the OWL view
directly. Plus the JS service ignored report_code, so even within the
viewer, all PnL-typed reports rendered the canonical P&L line_specs.
Changes:
JS layer
- reports_service.js: runReport now accepts and forwards reportCode;
state tracks currentReportCode so re-runs after period/comparison
changes preserve the variant.
- report_viewer.js: reads default_report_code (and default_comparison)
from the action context.
- period_filter.js: passes the cached reportCode on date changes;
clears it when the user picks a different report_type.
Backend
- New fusion_accounting_reports/views/report_actions.xml with 11
dedicated ir.actions.act_window records, one per built-in report
(P&L, Balance Sheet, Trial Balance, GL, Cash Flow, Executive Summary,
Annual Statements, Aged Receivable, Aged Payable, Partner Ledger,
Tax Summary). Each opens view_mode='fusion_reports' with the
appropriate default_report_type + default_report_code context.
- views/menu_views.xml: each report now gets its own menu item
directly under Accounting > Reporting (sequence 10-40), matching
Enterprise's flat structure. Custom Period wizard, XLSX export and
Anomaly browser collected under a 'Tools' sub-group at the bottom.
- fusion_accounting_l10n_ca: adds menu items for 'Profit and Loss
(Canada)' and 'Balance Sheet (Canada)' as siblings, plus a 'Tax
Returns (CA)' configuration menu.
Verified live on westin-v19:
- pnl rendering 3 rows, cash_flow 9, executive_summary 7,
annual_statements 5, ca_profit_loss 9 \u2014 each report now renders
its own line_specs correctly.
- Reporting menu shows 14 Fusion report entries + Tools group.
- 136/136 reports + l10n_ca tests pass.
Version bumps: reports 19.0.1.1.1, l10n_ca 19.0.1.1.0.
Made-with: Cursor