Commit Graph

598 Commits

Author SHA1 Message Date
gsinghpal
392359d2c4 docs(receiving): Sub 8 design spec — split receiving vs inspection + box parity
Receiving simplifies to box-count-only (draft → counted → staged →
closed). New fp.racking.inspection + line models capture the per-part
inspection the racking crew does when they open the boxes, linked to
MO not to receiving. Soft gate on first WO start when the inspection
is still pending (manager override). fp.delivery gains box_count_out;
action_mark_delivered posts a non-blocking chatter warning when
boxes out ≠ boxes in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 00:21:02 -04:00
gsinghpal
0342535b9f feat(notifications): Sub 6 — contact profiles + communication routing
Five new boolean flags on res.partner applied to CHILD contacts:
x_fc_receives_certs, _qc, _quotes_so, _invoices, and _is_global_contact.
Single resolver helper res.partner._fp_resolve_notification_recipients
(stream, delivery_location=None) walks location contacts first then
company contacts, returning emails for contacts that opted into the
stream (or flagged themselves global). Falls back to partner.email
when no contact opts in so existing customers keep their exact
pre-Sub-6 routing.

fp.notification.template._dispatch now maps each trigger event to a
stream (so_confirmed→quotes_so, invoice_posted→invoices, shipped→
certs, etc.) and overrides the mail_template's email_to with the
resolved list. fp.delivery passes its delivery_address_id so the
shipped/CoC email routes through location-scoped contacts when they
exist.

Partner form gets a new "Communication Routing" tab on child contact
forms with the 5 flags (hides the per-stream checkboxes when
Global Contact is on, since it overrides them).

fusion_plating_certificates → 19.0.4.0.0 (adds the flag fields)
fusion_plating_notifications → 19.0.5.0.0 (+depends certificates)

Smoke on entech: 11/11 assertions pass including per-stream routing,
delivery-location scoping, zero-flag fallback, email-less skip,
unknown-stream + global behaviour, and case-insensitive dedup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 00:01:15 -04:00
gsinghpal
f9e1b62409 docs(notifications): Sub 6 design spec — contact profiles + routing
Five per-contact boolean flags (certs / qc / quotes-so / invoices /
global), native Odoo delivery-location child contacts reused for
per-location routing, and a single resolver on res.partner that the
dispatcher + all mail-send sites call. Fallback to self.email keeps
existing customers bit-identical when no flags are set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 23:54:59 -04:00
gsinghpal
a7fd39d6f3 feat(iot): Sub 7 — per-sensor polling interval + rate-limit + entech seed
Per-sensor override on fp.tank.sensor.poll_interval_minutes with a
company-wide default (res.company.x_fc_default_poll_interval_minutes,
default 30) exposed in Settings → Fusion Plating → IoT. Single
lookup helper _fp_effective_poll_interval_minutes keeps downstream
call sites simple. Read-only poll_interval_display Char ("30 min
(default)" / "15 min (override)") keeps units unambiguous per user
request.

Ingest endpoint /fp/iot/ingest drops readings that arrive inside a
sensor's effective interval, returning {accepted, skipped} so the Pi
agent can log it. Pi-side interval stays its own concern.

Post-init hook seeds 5 small tanks + 20 big tanks (10 active, 10
inactive) with 1 temperature + 1 pH sensor each → 25 tanks, 50
sensors. Idempotent (keyed by tank.code, with_context(active_test=
False)). Opt-in via ir.config_parameter
fusion_plating_iot.seed_entech_tanks = '1' so a fresh install
elsewhere doesn't auto-seed. Flag set on entech today; 27 tanks / 52
sensors now live (2 pilot + 25 seeded).

Smoke on entech: 14/14 assertions pass including idempotency and
rate-limit conditions.

fusion_plating_iot → 19.0.2.0.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 23:29:08 -04:00
gsinghpal
def9c801fa docs(iot): Sub 7 design spec — per-sensor polling interval + entech seed
Per-sensor override on fp.tank.sensor.poll_interval_minutes, company-
wide default on res.company, ingest controller rate-limits readings
that arrive inside the effective interval. Seeds entech with 25
tanks (5 small, 20 big — 10 active/10 inactive) and 50 sensors
(temp + pH per tank) as noupdate=1 data so admin edits survive
upgrades.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 23:20:29 -04:00
gsinghpal
25c3f6f8d1 feat(plating): Sub 5 — order-line fields (serial, job#, thickness, revision)
Four new fields on every sale.order.line, propagated through to MO,
Delivery, and Invoice for end-to-end traceability:

- fp.serial registry (new model in configurator) with smart-button
  traceability to Sale Order, MO, Delivery, Invoice, Part. M2O on SO
  line; optional; user types a customer serial or clicks Generate
  Serial for a sequence-backed one. Reverse O2M links split across
  configurator (invoice) / bridge_mrp (MO) / logistics (delivery) so
  module load order is respected.
- x_fc_job_number on SO line, auto-sequenced FP-JOB-NNNNN on SO
  confirm. Editable — shops can override for customer/legacy schemes.
- fp.coating.thickness (new child of fp.coating.config) with per-
  config discrete thickness options; x_fc_thickness_id on SO line
  domain-filtered to the line's coating. Auto-clears when coating
  changes.
- x_fc_revision_snapshot Char on SO line, frozen from
  x_fc_part_catalog_id.revision at save. Protects historical SOs from
  later catalog edits. Secondary "Revision" picker on the tree view
  lets users switch between prior revisions of the same part number;
  the Part M2O still surfaces only is_latest_revision rows.

Reports (CoC, packing slip, invoice, BoL) pick up all four via the
Sub 2 customer_line_header macro — one macro edit, four reports.

Smoke on entech: 11 assertions pass including revision snapshot,
generate-serial button, typed-serial create-on-fly, coating→thickness
domain reset, SO confirm auto job#, and MO traceability carry.

Module version bumps:
  fusion_plating_configurator  → 19.0.12.0.0
  fusion_plating_bridge_mrp    → 19.0.11.0.0
  fusion_plating_logistics     → 19.0.2.0.0 (+depends configurator)
  fusion_plating_reports       → 19.0.5.1.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 23:04:44 -04:00
gsinghpal
bb9bcf45f8 docs(plating): Sub 5 design spec — order-line fields (serial, job#, thickness, revision)
fp.serial registry with smart-button traceability (SO, MO, Delivery,
Invoice, Part), fp.coating.thickness child table per coating config,
four new fields on sale.order.line propagating through to MO /
Delivery / Invoice via existing hooks. Revision picker with latest-
only default + switcher + snapshot Char. Reports pick up all four
via Sub 2's customer-line-header macro.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:48:14 -04:00
gsinghpal
1393c9e6ac fix(plating): Sub 4 — tighten Contract Review checklist columns
The Checklist group used col=\"4\" which stretched each label+field
pair across a quarter of the sheet, producing a big empty gap
between the two columns. Replaced with the idiomatic Odoo two-
nested-group pattern so each column hugs its labels, removing the
dead space. Same fix applied to Section 3.0 and to the Outcome
blocks on both sections.

fusion_plating_quality → 19.0.2.3.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 21:56:27 -04:00
gsinghpal
af199bda9f feat(plating): Sub 4 — smart button on part form for Contract Review
Adds a "Contract Review" stat button to fp.part.catalog's button box.
Shows a coloured state badge (green=complete, blue=manager_review,
yellow=assistant_review, muted=dismissed, em-dash when none). Click
routes through action_start_contract_review so it opens the existing
review or lazy-creates one — same behaviour as the banner / tab.

fusion_plating_quality → 19.0.2.2.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 21:54:47 -04:00
gsinghpal
5d9c78f8ce feat(plating): Sub 4 — Check All / Clear All buttons + fix QA-005 PDF logo render
- New bulk-toggle actions on fp.contract.review flip all 10 checklist
  items in Section 2.0 (and all 11 in Section 3.0) in one click.
  Rendered as "Check All" / "Clear All" buttons above each checklist.
  User can still tick boxes individually. Buttons hide once the
  section is signed (locked).
- Fix QA-005 PDF: replaced `to_text(...)` (not in QWeb context) with
  `image_data_uri(...)` for the company logo embed. PDF now renders
  with the full colour ENTECH logo (render size 103 KB).
- Smoke test extended: 5 new assertions covering bulk-toggle on/off
  and locked-section guard. 17/17 pass on entech.

fusion_plating_quality → 19.0.2.1.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 21:50:18 -04:00
gsinghpal
21da526aa7 feat(plating): Sub 4 — Contract Review (optional, QA-005 1:1 PDF)
Per-part contract review record (fp.contract.review) gated by a
customer-level toggle, signed in two sections (QA Assistant → QA
Manager), settings-based signer rosters (no new res.groups), banner on
the part form that auto-dismisses once the first MO for the part hits
confirmed. QA-005 Rev. 0 paper form reproduced 1:1 in a QWeb PDF.

Never blocks MO/SO/WO — review is purely an audit artefact.

Smoke test run on entech: 12 assertions pass including the 25-cell
risk matrix parity with the paper form and 22 KB PDF render.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 21:43:06 -04:00
gsinghpal
98a8bc234b docs(plating): Sub 4 design spec — Contract Review (optional, QA-005 1:1)
Dedicated fp.contract.review model in fusion_plating_quality, triggered by
a per-customer toggle, two-section QA sign-off (QA Assistant + QA Manager),
settings-based roster (no new res.groups), printable 1:1 QA-005 PDF. Never
blocks MO/SO/WO. Banner auto-dismisses once first MO for the part reaches
confirmed state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 21:31:05 -04:00
gsinghpal
d3b4eadbec test(sub3): Sub 3 smoke + verify SQL; mark shipped in CLAUDE.md roadmap
Sub 3 (Default Process + Composer per Part) complete:
 - Phase A: schema additions (part_catalog_id, cloned_from_id,
   treatment_uom on process_node; default_process_id on part_catalog),
   opt_in_out label rename, General Processing seed flipped to
   noupdate=1
 - Phase B: part-scoped Process Composer client action
   (fp_part_process_composer) with 3 RPC endpoints + OWL wrapper +
   Process tab on part form with Compose button
 - Phase C: tree node MO-state palette (green=completed, blue=active,
   red=error-only)

All 8 Sub 3 smoke checks green. Phase 1-3 QC smoke + E2E still green.
Sub 2 features untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 09:06:41 -04:00
gsinghpal
f0593487a7 feat(plating): Sub 3 Phase C \u2014 tree node MO-state palette (green=done, blue=active, red=error-only) 2026-04-22 09:04:38 -04:00
gsinghpal
3de37ea735 feat(configurator): Sub 3 Phase B — part-scoped Process Composer client action + part form Compose button
- Add fp_part_composer_controller with 3 JSON-RPC endpoints:
  /fp/part/composer/state, /fp/part/composer/templates,
  /fp/part/composer/load_template (deep-clones a shared template
  into a part-owned tree inside a cr.savepoint, sets
  fp.part.catalog.default_process_id atomically)
- _clone_subtree copies name/sequence/opt_in_out/treatment_uom plus
  description/notes/icon/color/timing/behaviour/work_center/process_type
  and stamps part_catalog_id + cloned_from_id on every node
- Add fp_part_process_composer OWL client action (JS + XML + SCSS):
  picks template from dropdown, clones, hands off to existing
  fp_recipe_tree_editor via context={recipe_id, part_id}
- Add Process tab on part form with readonly default_process_id
  field and Compose button calling action_open_part_composer
- Register new assets in web.assets_backend, bump configurator
  version to 19.0.11.0.0
2026-04-22 09:02:03 -04:00
gsinghpal
7d5c826f3e fix(plating): Sub 3 \u2014 move part_catalog_id/cloned_from_id/treatment_uom to configurator inherit (core can't reference fp.part.catalog) 2026-04-22 08:52:10 -04:00
gsinghpal
b1a8849e70 chore(plating): Sub 3 version bumps + flip General Processing seed to noupdate=1 (Tasks 4+5) 2026-04-22 08:50:19 -04:00
gsinghpal
4beffe9dc1 feat(bridge_mrp): Sub 3 — _resolve_mo_process_tree helper; walker prefers part-cloned tree (Task 3) 2026-04-22 08:47:41 -04:00
gsinghpal
dd7c408df3 feat(configurator): Sub 3 — fp.part.catalog gains default_process_id + action_open_part_composer (Task 2) 2026-04-22 08:47:01 -04:00
gsinghpal
a1ebe9000f feat(fusion_plating): Sub 3 — process_node gets part_catalog_id, cloned_from_id, treatment_uom; opt_in_out label rename (Task 1) 2026-04-22 08:46:32 -04:00
gsinghpal
817fdb4948 docs(plating): Sub 3 design spec \u2014 Default Process + Composer per Part 2026-04-22 08:44:06 -04:00
gsinghpal
733236f987 feat(configurator): Sub 1 — direct-order wizard stops auto-confirm + auto-email
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>
2026-04-21 23:43:01 -04:00
gsinghpal
afd8bae514 fix(configurator): programmatic SO-line create fallback for x_fc_internal_description
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>
2026-04-21 23:34:32 -04:00
gsinghpal
733998dc95 chore(bridge_mrp): bump to 19.0.9.0.0 after cert-resolver refactor (Sub 2 Task 28) 2026-04-21 23:27:56 -04:00
gsinghpal
1eac630d87 feat(configurator): drop legacy description column after dual-field migration (Sub 2 Task 27)
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>
2026-04-21 23:27:22 -04:00
gsinghpal
e3dcc1c381 feat(configurator): flip sale.order.line.x_fc_internal_description to required (Sub 2 Task 26) 2026-04-21 23:22:02 -04:00
gsinghpal
6bbba93963 feat(configurator): flip dual-descriptions to required on template (Sub 2 Task 25) 2026-04-21 23:21:53 -04:00
gsinghpal
a66794084f feat(configurator): flip part_number + revision to required, name optional (Sub 2 Task 24) 2026-04-21 23:21:39 -04:00
gsinghpal
20d547bb4f feat(reports): traveler PDF surfaces part_number + internal description (Sub 2 Task 23) 2026-04-21 23:19:33 -04:00
gsinghpal
84c0745ab5 feat(reports): WO PDF surfaces part_number + internal description (Sub 2 Task 22) 2026-04-21 23:18:10 -04:00
gsinghpal
79d9e6b3b0 feat(reports): BoL PDF uses customer_line_header macro (Sub 2 Task 21)
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>
2026-04-21 23:06:07 -04:00
gsinghpal
ac824c2cfb feat(reports): packing slip PDF uses customer_line_header macro (Sub 2 Task 20)
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>
2026-04-21 23:05:58 -04:00
gsinghpal
ea2cfc37c3 feat(reports+configurator): invoice PDF uses macro; x_fc_part_catalog_id on account.move.line (Sub 2 Task 19)
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.
2026-04-21 22:59:40 -04:00
gsinghpal
6cbea9d2f3 feat(reports): SO PDF uses customer_line_header macro (Sub 2 Task 18)
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.
2026-04-21 22:57:33 -04:00
gsinghpal
d959775648 feat(reports): customer_line_header QWeb macro + version bump (Sub 2 Task 17) 2026-04-21 22:54:17 -04:00
gsinghpal
dcd5d2a1ec feat(configurator): direct-order wizard dual-description inputs + onchange (Sub 2 Task 16)
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>
2026-04-21 22:51:53 -04:00
gsinghpal
a659eba8f5 feat(configurator): SO-line template picker + dual descriptions + onchange (Sub 2 Task 15)
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>
2026-04-21 22:49:49 -04:00
gsinghpal
d1c855698a feat(configurator): two-column dual-description repeater on part form (Sub 2 Task 14) 2026-04-21 22:47:05 -04:00
gsinghpal
afee8d5ee8 feat(configurator): cert requirement + SKU relabel on part form (Sub 2 Task 13) 2026-04-21 22:46:10 -04:00
gsinghpal
8cea92cca4 test(sub2): cert-resolver edge cases (Task 11) 2026-04-21 20:23:32 -04:00
gsinghpal
332cdc8baa refactor(bridge_mrp): route button_mark_done cert cascade through resolver (Sub 2 Task 10) 2026-04-21 20:22:20 -04:00
gsinghpal
334a10dcb7 feat(bridge_mrp): _fp_resolve_cert_requirement single-source resolver (Sub 2 Task 9) 2026-04-21 20:21:40 -04:00
gsinghpal
cf6faf4cdf fix(configurator): strip legacy 'Rev ' prefix in display_name to avoid 'Rev Rev N' 2026-04-21 20:19:14 -04:00
gsinghpal
b89737f3c9 feat(configurator): display_name compute for fp.part.catalog (Sub 2 Task 8) 2026-04-21 20:18:33 -04:00
gsinghpal
fb2f360e7b test(sub2): migration verification SQL (Task 6) 2026-04-21 20:15:42 -04:00
gsinghpal
b3405a21eb feat(configurator): Sub 2 data migration — backfill part_number/revision, split descriptions (Task 5) 2026-04-21 20:15:12 -04:00
gsinghpal
93b2b2e0cf chore(configurator): bump version to 19.0.9.0.0 for Sub 2 (Task 4) 2026-04-21 20:15:10 -04:00
gsinghpal
77d5b91327 feat(configurator): add dual descriptions to sale.order.line (Sub 2 Task 3) 2026-04-21 20:13:17 -04:00
gsinghpal
c574689664 feat(configurator): add internal + customer-facing description fields (Sub 2 Task 2) 2026-04-21 20:13:05 -04:00
gsinghpal
868b418333 feat(configurator): add certificate_requirement field to fp.part.catalog (Sub 2 Task 1) 2026-04-21 20:12:52 -04:00