Lewati ke konten utama

Purchasing + Sales Refactor Plan

Status: Approved — all open questions answered (see §9).

Status (2026-06-23): shipped, then superseded. Migrations 038–041 live under merchant_database/db_kesles_merchant/migrations/legacy/. The purchasing/sales source-of-truth has since moved out of db_kesles_merchant to inventory_service (db_kesles_merchant_inventory) and order_service (db_kesles_merchant_order); the merchant.* purchasing/sales tables were dropped (migrations v1/089, v1/093, v1/094). This plan is retained as the original design record. Owner: Dashboard team. Related tables: merchant.registration_device_offers, merchant.payment_device_models, merchant.payment_terminal_inventory, merchant.merchants, merchant.merchant_registration_requests. Note (2026-05-26): payment_device_models dan payment_terminal_inventory akan di-rename/restruktur ke tabel generik products + product_inventory_items via product-catalog-restructure-plan.md. Kolom device_model_id di purchase_order_items, goods_receipt_items, sales_quotations, sales_order_items akan di-rename ke product_id di Phase 5 plan tersebut. Terminology plan ini (device_model_id) tetap valid hingga Phase 5 selesai.

1. Context & Problem

The current Device Offer form (merchant.registration_device_offers) mixes two concerns in one row:

  • Procurement cost (HPP)purchase_price_amount, customs_fee_amount, vat_amount, inbound_shipping_cost_amount, total_capital_amount, margin_percent, sales_vat_amount.
  • Selling catalogdevice_price_amount, shipping_fee_amount, promo_amount, stock_quantity.

Effects:

  1. Operators get confused separating "what we pay the vendor" vs "what we sell to the merchant".
  2. There is no traceability to the vendor — Kesles doesn't record who the supplier is, the PO number, or when goods were received.
  3. There is no Purchase Order → Goods Receipt → Invoice → Payment workflow — every procurement step is implicit.
  4. The Sales side (Order, Shipping) is still a frontend mock (apps/merchant_dashboard/lib/dashboard/features/home/presentation/state/order_shipping_mock_store.dart). History is lost on refresh.

The refactor introduces a Group Purchasing module (Vendors, Purchase Order, Goods Receipt, Purchase Invoice, Vendor Payment, Purchasing Report) and tidies up the Group Sales module (Quotation, Sales Order, Shipping Order, Customer, Inventory).

Critical constraint: the snapshot pattern in merchants.device_offer_code / device_price_amount / shipping_fee_amount / promo_amount and merchant_registration_requests.* (migration 025) MUST NOT be touched — per-merchant price history must stay intact.


2. Terminology Mapping (user → DB)

User termDB artefactNotes
Quotationmerchant.sales_quotations (rename of registration_device_offers)Selling-only; HPP fields removed
Sales Ordermerchant.sales_orders + sales_order_items (new)Replace OrderMockRecord
Shipping Ordermerchant.shipping_orders + shipping_order_units (new)Replace ShippingMockRecord
Customermerchant.merchantsLabel only, schema unchanged
Inventorymerchant.payment_terminal_inventoryLabel only, schema unchanged
Vendormerchant.vendors (new)
Purchase Ordermerchant.purchase_orders + purchase_order_items (new)
Goods Receiptmerchant.goods_receipts + goods_receipt_items (new)On post → create rows in payment_terminal_inventory
Purchase Invoice / Billsmerchant.purchase_invoices + purchase_invoice_items (new)3-way match against PO + GR
Vendor Paymentmerchant.vendor_payments + vendor_payment_applications (new)AP ledger
Purchasing Reportcomputed (handler + view), no dedicated table
Cost history for marginmerchant.device_purchase_cost_history (new)Append-only, seeded once from old HPP

3. Database Changes (Migrations 038–041)

Run order: 038 → 039 → 040 → 041. Convention: every amount is bigint (IDR), soft delete via deleted_at, timestamp timestamptz not null default now().

3.1 Migration 038 — Purchasing + Sales tables

New tables in the merchant schema:

  • vendors — supplier master. Columns: vendor_code (UNIQUE ci), legal_name, display_name, npwp, contact_person, email, phone, address_line, city, province, postal_code, country, payment_term_days, bank_name, bank_account_number, bank_account_holder, is_active, notes, metadata jsonb, timestamps.
  • purchase_orders — PO header. po_number (UNIQUE, sequential PO-YYYY-NNNN), vendor_id fk, order_date, expected_delivery_date, incoterm, delivery_address, amount fields, status CHECK in ('draft','approved','sent','partial_received','received','closed','cancelled'), audit timestamps + user FKs.
  • purchase_order_items — PO line. purchase_order_id fk on delete cascade, line_no, device_model_id fk nullable, item_name, quantity_ordered, unit_price_amount, line_discount_amount, line_tax_amount, line_total_amount, quantity_received (default 0), quantity_invoiced (default 0). UNIQUE (purchase_order_id, line_no). CHECK quantity_received BETWEEN 0 AND quantity_ordered.
  • goods_receipts — GR header. gr_number UNIQUE, purchase_order_id fk, vendor_id (denormalised), receipt_date, warehouse_location, status CHECK in ('draft','posted','cancelled'), received_by_user_id, posted_at.
  • goods_receipt_items — GR line. goods_receipt_id fk on delete cascade, purchase_order_item_id fk, device_model_id fk, quantity_received, unit_cost_amount (copy from PO), landed_cost_allocated_amount, serial_numbers text[]. Validation: array_length(serial_numbers) = quantity_received on GR post (when device_model_id is not null).
  • purchase_invoices — vendor invoice header. invoice_number (vendor's), vendor_id fk, purchase_order_id fk nullable, invoice_date, due_date, amount fields, amount_paid (default 0), amount_outstanding generated stored, status CHECK in ('draft','awaiting_approval','approved','partially_paid','paid','void'), file_url (MinIO), metadata jsonb. UNIQUE (vendor_id, invoice_number) where deleted_at is null.
  • purchase_invoice_items — invoice line. Linked to goods_receipt_item_id (for 3-way match) + purchase_order_item_id.
  • vendor_payments — vendor payment. payment_number (sequential PAY-YYYY-NNNN), vendor_id fk, payment_date, payment_method, bank_reference, amount, status CHECK in ('pending','posted','failed','void'), proof_file_url.
  • vendor_payment_applications — M:N payment vs invoice. vendor_payment_id fk, purchase_invoice_id fk, amount_applied. The /post handler updates purchase_invoices.amount_paid.

The Sales-side tables also live in this migration:

  • sales_ordersorder_number (SO-YYYY-NNNN), merchant_id fk, status CHECK in ('pending','accepted','payment_received','processing','completed','cancelled'), total_amount, payment_proof_url, cancellation_reason.
  • sales_order_itemssales_order_id fk on delete cascade, line_no, sales_quotation_id fk, device_model_id fk, quantity, unit_price_amount (snapshot), shipping_fee_amount (snapshot), promo_amount (snapshot), line_total_amount.
  • shipping_ordersshipping_number (SHP-YYYY-NNNN), sales_order_id fk, status CHECK in ('not_processed','packed','in_transit','delivered','returned'), courier, tracking_number.
  • shipping_order_unitsshipping_order_id fk, payment_terminal_inventory_id fk UNIQUE. On delivery → update the linked unit's status allocateddelivered + stamp merchant_id.

All tables get partial indexes where deleted_at is null + status/FK+date indexes following the existing pattern.

3.2 Migration 039 — device_purchase_cost_history

Append-only fact for WAC (weighted-average cost) per device_model_id:

create table merchant.device_purchase_cost_history (
id uuid primary key default gen_random_uuid(),
device_model_id uuid not null references merchant.payment_device_models(id),
goods_receipt_id uuid references merchant.goods_receipts(id),
goods_receipt_item_id uuid references merchant.goods_receipt_items(id),
unit_cost_amount bigint not null,
landed_cost_amount bigint not null default 0,
total_unit_cost_amount bigint generated always as (unit_cost_amount + landed_cost_amount) stored,
quantity int not null default 1,
cost_date date not null default current_date,
currency char(3) not null default 'IDR',
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now()
);
create index idx_dpch_model_date
on merchant.device_purchase_cost_history (device_model_id, cost_date desc);

View: merchant.v_sales_quotations_with_cost — LEFT JOIN every (future) sales_quotations row to the latest cost history, expose current_capital_amount + current_margin_percent. The Quotation panel reads this view, so the margin is always live without writing back to the quotation row.

3.3 Migration 040 — Seed cost history from device_offers

One row per current registration_device_offers:

  • unit_cost_amount = purchase_price_amount
  • landed_cost_amount = customs_fee_amount + vat_amount + inbound_shipping_cost_amount
  • quantity = greatest(stock_quantity, 1)
  • cost_date = current_date
  • goods_receipt_id = null

Preserves HPP history → the margin view is not NULL after 041 drops the columns.

3.4 Migration 041 — Split → sales_quotations

alter table merchant.registration_device_offers rename to sales_quotations;
-- rename indexes: registration_device_offers_* → sales_quotations_*

alter table merchant.sales_quotations
drop column purchase_price_amount,
drop column customs_fee_amount,
drop column vat_amount,
drop column inbound_shipping_cost_amount,
drop column total_capital_amount,
drop column margin_percent,
drop column sales_vat_amount;

alter table merchant.sales_quotations
add column effective_from date default current_date,
add column effective_to date; -- nullable = currently active

offer_code is NOT renamedmerchants.device_offer_code and merchant_registration_requests.device_offer_code are snapshot columns pointing here. Renaming would force a multi-table rewrite with no user-facing benefit.

3.5 GR-post transactional side-effects

The handler POST /api/dashboard/purchasing/goods-receipts/{id}/post opens a single tx:

  1. Lock the GR row; validate status = draft.
  2. Per device line: validate array_length(serial_numbers) = quantity_received, reject duplicates (PG 23505 → 409).
  3. Insert one payment_terminal_inventory row per serial with merchant_id=null, status='stock', received_at=now(), auto-generated terminal_code (reuse generateTerminalCode()).
  4. Compute landed-cost allocation: shipping_cost + customs + vat + other_cost from the PO header, pro-rata per line_total_amount. Save to goods_receipt_items.landed_cost_allocated_amount.
  5. Insert one row per GR line into device_purchase_cost_history (unit_cost_amount from PO, landed_cost_amount = allocated / quantity).
  6. Bump purchase_order_items.quantity_received. Flip the PO header: all lines received → received; partial → partial_received.
  7. Append payment_terminal_status_logs per unit (source_type='system', event_title='Received via GR {gr_number}').
  8. Commit. Flip GR status → posted, stamp posted_at.

Any failure → full rollback (no partial units).


4. Backend (Go)

4.1 File layout (one file per domain, existing pattern)

In services/dashboard_api/internal/app/:

  • dashboard_vendors.go
  • dashboard_purchase_orders.go
  • dashboard_goods_receipts.go ⚡ transactional GR-post
  • dashboard_purchase_invoices.go
  • dashboard_vendor_payments.go
  • dashboard_purchasing_reports.go
  • dashboard_sales_orders.go
  • dashboard_shipping_orders.go
  • Rename dashboard_device_offers.godashboard_sales_quotations.go (the legacy /master-data/device-offers route alias stays for 1 release)

New shared helper: rbac.go with requireRoles(profile, roles...) — replace the per-handler profile.hasAnyRole(...) slice pattern.

4.2 Route map

Purchasing:
GET/POST /api/dashboard/purchasing/vendors
GET/PATCH/DELETE /api/dashboard/purchasing/vendors/{id}

GET/POST /api/dashboard/purchasing/purchase-orders
GET/PATCH /api/dashboard/purchasing/purchase-orders/{id} (PATCH only when status='draft')
POST /api/dashboard/purchasing/purchase-orders/{id}/approve (draft→approved)
POST /api/dashboard/purchasing/purchase-orders/{id}/send (approved→sent)
POST /api/dashboard/purchasing/purchase-orders/{id}/cancel
POST /api/dashboard/purchasing/purchase-orders/{id}/close

GET/POST /api/dashboard/purchasing/goods-receipts
GET/PATCH /api/dashboard/purchasing/goods-receipts/{id}
POST /api/dashboard/purchasing/goods-receipts/{id}/post ⚡ tx
POST /api/dashboard/purchasing/goods-receipts/{id}/cancel

GET/POST /api/dashboard/purchasing/purchase-invoices
GET/PATCH /api/dashboard/purchasing/purchase-invoices/{id}
POST /api/dashboard/purchasing/purchase-invoices/{id}/submit
POST /api/dashboard/purchasing/purchase-invoices/{id}/approve (3-way match)
POST /api/dashboard/purchasing/purchase-invoices/{id}/void
POST /api/dashboard/purchasing/purchase-invoices/{id}/upload-file

GET/POST /api/dashboard/purchasing/vendor-payments
POST /api/dashboard/purchasing/vendor-payments/{id}/post
POST /api/dashboard/purchasing/vendor-payments/{id}/void

GET /api/dashboard/purchasing/reports/spending-by-vendor?from&to
GET /api/dashboard/purchasing/reports/open-pos
GET /api/dashboard/purchasing/reports/gr-variance
GET /api/dashboard/purchasing/reports/invoice-aging
GET /api/dashboard/purchasing/reports/cost-trend?device_model_id

Sales:
GET/POST /api/dashboard/sales/quotations (legacy alias: /master-data/device-offers)
GET/PATCH/DELETE /api/dashboard/sales/quotations/{id}

GET/POST /api/dashboard/sales/orders
GET/PATCH /api/dashboard/sales/orders/{id}
POST /api/dashboard/sales/orders/{id}/{accept|payment-received|processing|complete|cancel}

GET/POST /api/dashboard/sales/shipping-orders
POST /api/dashboard/sales/shipping-orders/{id}/{pack|in-transit|deliver|return}

4.3 3-way Match (on Invoice approve)

For each purchase_invoice_items with a goods_receipt_item_id:

  • quantity ≤ gr_item.quantity_received − already_invoiced_qty
  • unit_price ∈ [po_line.unit_price × (1−tolerance), po_line.unit_price × (1+tolerance)] (default tolerance 0.05, env-configurable via INVOICE_PRICE_TOLERANCE)

Out-of-tolerance → HTTP 422 with an array {line_no, reason, expected, actual}. A super_admin may override via ?force=true (audit-logged).


5. Frontend (Flutter / apps/merchant_dashboard)

5.1 Menu enum (restructure)

MerchantDashboardMenu:
overview
sales → submenus: quotation, salesOrder, shippingOrder, customers, inventory
purchasing → submenus: vendors, purchaseOrder, goodsReceipt, purchaseInvoice, vendorPayments, purchasingReport
masterData → submenus: partners, paymentDevices, mobileUser (trimmed)
transactions (unchanged)
reports (unchanged)
usersAccess (unchanged)
settings (unchanged)

The merchants enum is removed — its children redistribute into sales.

5.2 RBAC additions (in dashboard_rbac.dart)

New permissions: purchaseRead, purchaseCreate, purchaseApprove, purchaseReceive, purchasePay, salesRead, salesCreate, salesUpdate, salesFulfill, quotationRead, quotationManage, vendorRead, vendorManage, inventoryAdjust.

Role matrix highlights:

  • super_admin, admin → everything
  • finance → full Purchasing (approve + pay) + Sales read/update + Quotation manage + Vendor manage + all Reports
  • operationspurchaseReceive + inventoryAdjust + Sales fulfill + read everything Sales/Purchasing
  • partner_admin → Sales read (scoped to own merchants) + Quotation read + Reports read
  • support, viewer → read-only

5.3 New panels / dialogs

In apps/merchant_dashboard/lib/dashboard/features/home/presentation/widgets/panels/:

  • vendors_panel.dart + forms/vendor_form_dialog.dart
  • purchase_orders_panel.dart + forms/purchase_order_form_dialog.dart + purchase_order_detail_dialog.dart (state transition buttons)
  • goods_receipts_panel.dart + forms/goods_receipt_form_dialog.dart + goods_receipt_post_dialog.dart (serial-number grid)
  • purchase_invoices_panel.dart + forms/purchase_invoice_form_dialog.dart
  • vendor_payments_panel.dart + forms/vendor_payment_form_dialog.dart
  • purchasing_report_panel.dart (reuse widgets/charts/dashboard_charts.dart)

Rename + simplify:

  • device_offers_panel.dartsales_quotations_panel.dart (drop the HPP section; only Identity + Selling + Availability + Effective From/To remain)
  • forms/device_offer_form_dialog.dartforms/sales_quotation_form_dialog.dart
  • order_panel.dartsales_order_panel.dart (real API after Ship-7)
  • shipping_panel.dartshipping_order_panel.dart (real API after Ship-7)

Keep-but-relabel:

  • merchants_panel.dart — displayed as "Customers" via the menu label
  • terminal_stock_panel.dart — displayed as "Inventory" via the menu label

The margin display in the Quotation form → a read-only strip below that reads from the v_sales_quotations_with_cost view.

i18n: every new label goes into core/i18n/dashboard_language.dart with EN/ID.


6. Mock → Real API Migration (Phase 4)

Files to retire: apps/merchant_dashboard/lib/dashboard/features/home/presentation/state/order_shipping_mock_store.dart

  1. New datasources: sales_order_remote_data_source.dart, shipping_order_remote_data_source.dart, sales_quotation_remote_data_source.dart (pattern from home_remote_data_source.dart).
  2. New entities: domain/entities/sales_order.dart, shipping_order.dart, sales_quotation.dart — replacing OrderMockRecord, ShippingMockRecord, kDeviceCatalog.
  3. Repository contract + impl.
  4. New state stores: sales_order_store.dart, shipping_order_store.dart, sales_quotation_store.dart (same ChangeNotifier surface). Use home_dashboard_async_state.dart.
  5. Rewire order_panel, order_detail_dialog, shipping_panel, shipping_detail_dialog, dashboard_sections.dart to the new stores.
  6. kDeviceCatalog is replaced by SalesQuotationStore which prefetches the active quotations on dashboard load → the existing sync unitPrice getter still works in the row list.
  7. Delete order_shipping_mock_store.dart. Grep verify: kDeviceCatalog, OrderMockRecord, ShippingMockRecord, OrderShippingMockStore — zero references.

7. Rollout Order (7 Ships, lock-step, never combine DB rename + FE release)

  1. Ship-1 — Migrations 038 + 039 + 040. No UI visible. Smoke-test: merchant registration + transactions + terminal_stock bulk form still work.
  2. Ship-2 — Read-only Purchasing UI: Vendors CRUD + PO CRUD (draft only) + menu group placeholder. Finance seeds master data.
  3. Ship-3 — Purchasing write path: PO approve/send/cancel, GR draft + post (inventory creation), Purchase Invoice CRUD + approve + 3-way match. The bulk-stock form is flipped to "super_admin legacy seed only".
  4. Ship-4 — Flutter Quotation panel update (drop HPP inputs). Backend still accepts but ignores HPP payload. Ships before 041 so the frontend build doesn't break.
  5. Ship-5 — Migration 041 (drop HPP columns + rename to sales_quotations). Backend rename handler, the legacy alias stays for 1 release.
  6. Ship-6 — Flutter menu restructure: enum change, Sales group, Quotation under Sales, trimmed Master Data.
  7. Ship-7 — De-mock: Phase 4 migration. Plus Vendor Payments + Purchasing Reports + cost trend chart.

Risk mitigations

  • Merchant snapshot columns (merchants.device_offer_code, device_price_amount, shipping_fee_amount, promo_amount) — NOT TOUCHED. Add a regression test: register a merchant end-to-end after every migration.
  • Dual write path to payment_terminal_inventory: bulk form (legacy) + GR posting (canonical). Document; restrict the bulk form to super_admin in Ship-3.
  • Serial uniqueness: uk_payment_terminal_inventory_serial_number already exists. The GR post handler translates PG 23505 → a readable 409 with the offending serial.
  • Multi-currency (IDR + USD) — keep currency + exchange_rate on purchase_orders, purchase_invoices, vendor_payments. vendors.default_currency sets the default when creating a new PO. Realised/unrealised FX gain is not handled in v1 — the invoice posted-value = the value until settlement.

8. Effort Estimate

PhasePerson-days
Phase 1 — Migrations (038–041) + view4–5
Phase 2 — Backend handlers (9 files + RBAC helper + GR-post tx + 3-way match)10–14
Phase 3 — Frontend menu restructure + 6 new panels + forms + i18n8–12
Phase 4 — Mock → real migration3–5
Phase 5 — Rollout (staging verification, 7 lock-step ships, docs)2–3
Total27–39 person-days (≈ 5.5–8 weeks for 1 full-stack)

9. Decisions (answered)

#QuestionDecisionNotes
1HPP strategyWAC via view (Option A)merchant.v_sales_quotations_with_cost LEFT JOINs device_purchase_cost_history + a SQL weighted-avg over the last N GRs. Margin is always live. If perf becomes an issue, upgrade to a MATERIALIZED VIEW with a scheduled refresh. Cost history becomes the single source of truth — no duplication in sales_quotations.
2Serial number UXManual grid + CSV uploadgoods_receipt_post_dialog.dart provides 2 modes: (a) row-per-serial grid input for small qty (<20); (b) "Upload CSV" button → parse file (1 serial_number column per row), validate, preview before post. Duplicate detection runs both frontend + backend.
3CurrencyMulti-currency: IDR (default) + USDThe currency char(3) and exchange_rate numeric(18,6) columns are KEPT on purchase_orders, purchase_invoices, vendor_payments. The vendor record has default_currency. The dashboard reports in IDR (convert via the rate when the PO/Invoice is created). Realised/unrealised FX gain is not handled in v1 — the invoice posted value = the value used until paid.
43-way match tolerance5% default + super_admin ?force=true overrideEnv var INVOICE_PRICE_TOLERANCE=0.05. The override is always audit-logged (source_type=manual_override, actor + reason in metadata).
5Operations GR approvalOperations approve themselves (v1)No separate Warehouse role. A second-approver can be added later via the GR_REQUIRE_SECOND_APPROVAL feature flag without a migration.
6Landed cost allocationPer-GR only (v1)Cross-GR allocation is deferred. Per-GR = pro-rata shipping+customs+VAT from the PO header, distributed by line_total_amount.
7ERP integrationDashboard = source of truth (Option A)No external API integration in v1. The accounting team manually inputs from the dashboard CSV export when needed. Upgrade path: Ship-N+1 add CSV export templates for Accurate/Jurnal, Ship-N+2 API sync if volume grows.
8offer_code renameKeep offer_code (table renamed, column not)Table registration_device_offerssales_quotations. The offer_code column STAYS. In the UI it shows as "Quotation Code" (label only). merchants.device_offer_code + merchant_registration_requests.device_offer_code are not touched (snapshot pattern stays safe).

10. Verification / Smoke Test per Ship

Run on staging after every Ship:

  1. Merchant registration still works — register a merchant via the mobile app, verify the merchants.device_offer_code / device_price_amount / shipping_fee_amount / promo_amount snapshot is populated. Run BEFORE AND AFTER migration 041.
  2. Transaction flow still works — seed a test transaction via core-api, verify the merchant.transactions row + Dashboard Overview chart reflects it.
  3. The existing terminal_stock bulk form still creates inventory (until Ship-3 flips it to legacy-only).
  4. Purchase flow (Ship-3+): create Vendor → PO draft → approve → send → GR draft with serials → post GR → verify:
    • payment_terminal_inventory gains 1 row per serial, status='stock'
    • device_purchase_cost_history gains 1 row per GR line
    • purchase_order_items.quantity_received is bumped
    • PO header status = partial_received / received
  5. Invoice flow (Ship-3): create an Invoice linked to the GR → submit → approve → verify the 3-way match passes; create a mismatched invoice → verify the 422 detail.
  6. Vendor payment (Ship-7): create a payment → apply to an invoice → post → verify purchase_invoices.amount_paid updates, status paid if fully settled.
  7. Sales Order flow (Ship-7): create an SO (quotation + qty) → accept → payment-received → processing → complete → verify the shipping_order is auto-created → pack → in-transit → deliver → verify payment_terminal_inventory.status='delivered' + merchant_id is stamped.
  8. Reports (Ship-7): open /purchasing/reports/spending-by-vendor + /cost-trend — verify non-zero rows.
  9. RBAC probe: log in per role (finance, operations, partner_admin, support, viewer) — verify menu visibility + 403 on forbidden endpoints.

Automated tests:

  • Go integration test in services/dashboard_api/... for transactional GR-post rollback on duplicate serial.
  • Flutter widget test on the Quotation form — verify the HPP section is gone after Ship-4.
  • Go unit test for the 3-way match tolerance: exact bound, off-by-one, override flag.

11. Critical Files Reference

Migrations (new)

  • merchant_database/db_kesles_merchant/migrations/legacy/038_create_purchasing_and_sales_tables.sql
  • merchant_database/db_kesles_merchant/migrations/legacy/039_create_device_purchase_cost_history.sql
  • merchant_database/db_kesles_merchant/migrations/legacy/040_seed_cost_history_from_device_offers.sql
  • merchant_database/db_kesles_merchant/migrations/legacy/041_rename_device_offers_to_sales_quotations.sql

Backend (new + rename)

  • services/dashboard_api/internal/app/server.go (route registration)
  • services/dashboard_api/internal/app/rbac.go (new helper)
  • services/dashboard_api/internal/app/dashboard_vendors.go
  • services/dashboard_api/internal/app/dashboard_purchase_orders.go
  • services/dashboard_api/internal/app/dashboard_goods_receipts.go
  • services/dashboard_api/internal/app/dashboard_purchase_invoices.go
  • services/dashboard_api/internal/app/dashboard_vendor_payments.go
  • services/dashboard_api/internal/app/dashboard_purchasing_reports.go
  • services/dashboard_api/internal/app/dashboard_sales_orders.go
  • services/dashboard_api/internal/app/dashboard_shipping_orders.go
  • services/dashboard_api/internal/app/dashboard_device_offers.go (serves the canonical /api/dashboard/sales/quotations route; the planned rename to dashboard_sales_quotations.go was not applied)

Frontend (restructure)

  • apps/merchant_dashboard/lib/dashboard/features/home/presentation/models/dashboard_menu.dart
  • apps/merchant_dashboard/lib/dashboard/features/home/presentation/models/dashboard_rbac.dart
  • apps/merchant_dashboard/lib/dashboard/features/home/presentation/widgets/menu/dashboard_sidebar.dart
  • apps/merchant_dashboard/lib/dashboard/features/home/presentation/widgets/sections/dashboard_sections.dart
  • apps/merchant_dashboard/lib/dashboard/features/home/presentation/state/home_dashboard_ui_state.dart
  • (new panels per §5.3)

Retired

  • apps/merchant_dashboard/lib/dashboard/features/home/presentation/state/order_shipping_mock_store.dart (Phase 4, Ship-7)