fteg-pos SKU pos P0 documented P1 foundation next first tenant 9981yuzu
FTEG-owned in-person sales product. First client is 9981Yuzu (Yuzu Marketing Sdn Bhd), replacing Loyverse for expo and counter checkout. A working prototype is milestone P2. Production readiness and Loyverse migration are demonstrated separately. Do not switch live operations without explicit authorization.
pkg-pos engine + bo-starter BO, same pattern as Reservation
and Social Content. 9981’s live Loyverse sync stays authoritative until a
documented cutover.
| Field | Value |
|---|---|
| Product | FTEG POS |
| SKU | pos (fteg-store later; not sold yet) |
| Owner | FTEG Technology Sdn Bhd — repository, hosting, DB, service accounts |
| First tenant | 9981Yuzu — brand of Yuzu Marketing Sdn Bhd |
| Replaces | Loyverse POS at expo / booth / counter (~3 years of use) |
| Does not replace | 9981 web shop, member app, YMSB ERP, marketplace channels |
| Currency default | MYR — configurable per company; never hard-coded as the only currency |
| Timezone default | Asia/Kuala_Lumpur — configurable; business-day reports use this clock |
| Tax | Configurable. Do not infer SST, service charge, or e-Invoice duty from this plan |
| Progress log | /srv/www/fteg-products/pos/docs/PROGRESS.md |
POS is a transaction system (receipts, cash, shifts), not a CRM. Expo walk-ups stay anonymous unless they already have a 9981 member or a wholesale account. See Yuzu sales channels.
| Layer | Path | Role |
|---|---|---|
| Product | /srv/www/fteg-products/pos |
Multi-tenant host — register UI, merchant BO, APIs. First tenant: 9981Yuzu demo outlet. |
| Engine | /srv/www/pkg-pos |
Composer library — catalog, cart, tender, receipts, shifts, inventory ledger, migration, permissions. |
| Commercial | /srv/www/hubs/fteg-erp + fteg-store |
Later: catalog SKU, subscribe, provision tenant. Not in P0–P2. |
fteg-pos (product app) ├── Tenant: 9981yuzu ← first client (YMSB brand) ├── Tenant: demo ← fictional catalog for development └── Future tenants ← other FTEG POS subscribers Consumers / siblings (do not merge codebases) ├── 9981beelongz ← live ecommerce + current Loyverse sync (source for migration) ├── ymsb Business OS ← bottle stock book of record; POS may later POST channel sales └── fteg-commerce ← web shop future; not the till
Studied from 9981beelongz (14 Sep 2026).
n8n boards: fteg-pos-map.html.
Screen mocks: fteg-pos-flows.html.
| Today | What FTEG POS must do |
|---|---|
| Loyverse iPad is the live till. 9981 only syncs after the sale (15 min receipts). | Own the till. Do not dual-run with Loyverse. |
No expo-vs-store mode in code. Event List + cashier-day binding sets event_id. |
Choose event / sale type on open shift. |
| Bottles → inventory logs; cocktails/shots/OTR pours → cocktail inventory; Gachapon RM99 allocated later in BO. | Same split. Gachapon prize is a BO screen, not checkout. |
| Cash, non-integrated card, DuitNow/QR/OCBC — recorded, not captured. | Keep that model in P2. Label recorded vs captured. |
| Walk-in default. Loyverse points not used as 9981 loyalty. Lucky draw only if a member is attached. | Walk-in default. Find by phone or name; attach only with phone / email / member id. No loyalty replay on import. |
| Cancelled / REFUND receipts stored then mostly excluded. No clean refund pipeline. | Native linked refunds (P4). Imported sales read-only until D12. |
| Loyverse stock unused. Sheets / gg_inventory after the event. Thermal receipt is a PDF. | POS inventory ledger for native sales. Print hardware is P5. |
9981 admin Loyverse POS menu is audit/export, not a till. Event P&L stays on Event List during P2.
9981 already runs a Loyverse integration. FTEG POS must reuse knowledge and data, not rewrite it in place.
| Existing asset | Keep as |
|---|---|
9981beelongz loyverse_* tables, API sync, raw JSON |
Immutable source / archive until cutover. Do not stop the live sync. |
LoyversePosOrderImportService → 9981 orders |
Operational bridge for today’s expo P&L and gg_orders. POS import is a separate pipeline. |
config/loyverse_sku_mapping.php + F&B SKU seeds |
Catalog mapping input for migration. Do not invent missing SKUs. |
| Cashier ↔ event bindings, receipt-prefix mapping | Event attribution model. POS must support sale-type / outlet / event the same way. |
| Payment-type map (cash, non-integrated card, DuitNow, bank transfer) | Proves 9981 records externally processed card/QR — it does not charge cards. |
| Member ↔ Loyverse customer sync | Evaluate in P4. Do not award loyalty twice on historical import. |
YMSB ERP POST /api/v1/channel/sales |
Bottle stock issue after POS is live. Glass/cocktail remains POS revenue only. |
| Choice | Why |
|---|---|
| Laravel 12 + PHP 8.2 + bo-starter | Every other FTEG product. laravel-ops deploy, permissions, commits. |
pkg-pos engine | Shared validation and money math. Host stays thin. |
| PostgreSQL preferred; MySQL acceptable if the first host already uses it | Exact numeric types; migrations via Laravel. |
| Blade + Livewire (or a small JS island) for P1–P2 till UI | Ship a touch-friendly register without a second runtime. Revisit a dedicated SPA only if till latency requires it. |
| Modular monolith | No microservices. POS, BO, and migration share one app and one DB per tenant host. |
| Not Next.js / not a new Node POS | Would fork ops, auth, and MyInvois from the rest of FTEG. |
Data is scoped company → brand → outlet → register → staff membership. Server-side checks on every write. A 9981 cashier cannot see another tenant. A cashier assigned to Outlet A cannot close Outlet B’s shift.
Malaysian e-Invoice is out of P1–P3. When required, integrate via the existing
myinvois-cert-mgr / pkg-myinvois
path already used by 9981 and FTEG ERP. Verify current LHDN rules before coding.
Do not hard-code tax treatment in this product.
company
└── brand
└── outlet
└── register
staff_membership (company/outlet + role: owner | manager | cashier)
category / product / variant / modifier / barcode
outlet_product (availability, price override)
customer (optional; do not match on name alone)
loyalty_ledger (auditable; import = no re-award)
sale (idempotency_key, client_txn_id, source: native|imported, status)
└── sale_line (description/price/tax/discount/modifiers SNAPSHOT)
└── payment (method, amount, external_ref, recorded vs captured)
└── refund → original sale (linked, never mutate finalized sale)
shift (open float, pay-in/out, expected, counted, discrepancy)
inventory_movement (ledger: sale, refund, receipt, transfer, count, waste, adjust)
└── never applied for historical imported sales
migration_batch / migration_row (source file checksum, source id, errors)
audit_log (refunds, discounts, permissions, stock, config)
Sale states: open → tendering → completed |
voided (pre-finalize, permissioned) |
completed → refunded / partially_refunded via linked refunds.
Finalized rows are immutable. Corrections are new linked records.
Idempotency: unique (company_id, idempotency_key) and
unique (company_id, client_txn_id). Double-tap and retry return the
original sale. Network failure must not create a second receipt.
| Rule | Decision |
|---|---|
| Storage | Integer sen (MYR minor units). Display as 2 decimal places. |
| Arithmetic | Integer only in PHP. Percentage discounts: compute in sen, define half-up vs banker’s rounding in config before P2 ships. |
| Line snapshot | Each sale line stores name, SKU, unit price, qty, tax, discount, modifiers at sale time. Later catalog edits do not rewrite history. |
| Weighted items | Qty stored as decimal mass; money still integer sen. Enable only when the catalog has weighted products. |
| Tax / SST / service charge / rounding | Configurable per company. Unset until discovery answers. Tests will lock the chosen rule. |
| Inventory | Movement ledger. Sale deducts; refund restocks only when the refund reason says so. Historical imports never deduct current stock. |
| Opening stock | Verified count at cutover. Not reconstructed from three years of receipts. |
| Recipes / purchasing / transfers | Out of scope until 9981 confirms they need them. Expo cocktails today are POS SKUs, not a recipe engine. |
Demo / sample products stay visibly fictional until real 9981 catalog data is supplied or migrated.
| Constraint | Implication |
|---|---|
| Browser tab / PWA storage | Cleared by OS, full disk, or another staff member on a shared iPad. Unsynced sales can vanish. Show unsynced count; do not claim crash-safe durability. |
| Printers / cash drawers | Browsers cannot talk to most ESC/POS printers or RJ11 drawers reliably. Options: (a) cloud print / vendor SDK, (b) local device service on the till, (c) native wrapper (Capacitor — FTEG already has app-wrapper). Decision deferred to P5 after we see the hardware list. |
| Scanners | USB/HID wedge keyboards work in a web till today. Camera scan is fallback only. |
| Multiple offline registers | Can oversell the same SKU. Documented risk; stock is eventually consistent. Managers accept or we require online for stock-tracked items. |
| Offline payments / refunds | Cash only unless a provider supports offline. Refunds against unsynced sales are forbidden. |
| Clock / price / permission conflicts | Server clock and server price book win on sync. Offline permission cache expires. Receipt numbers allocated in a per-register range or repaired on sync — choose in P5. |
Do not assume three years of receipts are API-reachable until the 9981 Loyverse
subscription is confirmed. Official Loyverse help:
Exporting data.
API: https://api.loyverse.com/v1.0 (Bearer token; cursor pages; limit ≤ 250;
~300 req / 300 s). Receipt line items live on the receipt object — there is no
separate line-item endpoint.
| Source | Official capability | Class |
|---|---|---|
| Item list CSV | Full catalog; no images | 1 — operational catalog (images are a separate requirement) |
| Customers CSV / API | Customers + points if Loyalty is used | 1 — if used; match on Loyverse customer id / phone / email, never name alone |
| Receipts CSV + Receipts by item CSV | Back Office export for a selected period | 2 — read-only historical sales when detail is complete |
API GET /receipts |
Default window is about 31 days unless Unlimited Sales History is subscribed; older requests can return HTTP 402 | 2 — only the window we can actually pull. 9981 already stores pulled receipts + raw_data |
| Sales summary / by item / by payment / taxes / shifts reports | Aggregates | 3 — reconciliation only. Never reconstruct receipts from totals |
| Advanced Inventory docs (PO, transfer, count, production) | Per-document CSV/PDF if the add-on is used | 1 or 3 — unknown until we confirm the 9981 subscription |
| Item images | Not in CSV; no official bulk image export | 4 — re-upload or skip |
9981 loyverse_receipts / items / customers |
Already synced locally with source ids and raw JSON | 2 — primary historical extract for periods already pulled; still reconcile to Loyverse |
Class key: 1 operational · 2 read-only historical · 3 archive / recon only · 4 unavailable or manual.
Compare source vs destination: receipt/refund counts, gross/net (definitions written
on the report), discounts, taxes, payment totals, stock and loyalty if in scope.
Explain every difference. Do not mark migration complete with unexplained gaps.
Synthetic fixtures are labelled FIXTURE. Never fabricate missing years.
.env only. TLS in any deployed environment.vendor/bin/laravel-ops-deploy --full. Never scp/rsync app code. Never manual chown on storage/.vendor/bin/laravel-ops-commit when the user asks. Author fung unless told otherwise.Touch-first till: large targets, readable totals, obvious payment and sync status. Destructive money actions need a confirm step and a role check. Keyboard and HID scanner support. Contrast must stay readable on a booth iPad in daylight. Brand colours and logo are provisional until 9981 supplies assets. Sample items in the demo outlet stay fictional (e.g. “Demo Yuzu Soda (FIXTURE)”).
Provisional defaults (MYR, KL timezone, online-only, cash + recorded external pay) are enough to build P1–P2. These answers change schema or hardware:
| # | Question | Why it blocks |
|---|---|---|
| D1 | Business type and actual checkout steps at expo vs any standing counter | Sale types, held orders, modifiers |
| D2 | Number of outlets, tills, concurrent cashiers | Tenancy, shift model, offline risk |
| D3 | Devices and OS (iPad / Android / Windows) | PWA vs Capacitor vs local print service |
| D4 | Receipt printers, kitchen printers, cash drawers, scanners (make/model) | P5 hardware path |
| D5 | Loyverse plan: Unlimited Sales History, Loyalty, Advanced Inventory, current stores/devices | How much history is importable |
| D6 | Variants, modifiers, bundles, recipes, stock-tracked vs open-bottle | Catalog + inventory scope |
| D7 | Cash / QR / card workflow today (which app, which bank) | Recorded vs integrated pay |
| D8 | Loyalty, refund, discount, tax, rounding, service charge rules | Money tests; statutory risk |
| D9 | Required offline duration | Whether P5 is in the first pilot |
| D10 | Accounting + MyInvois need for POS receipts | Do not guess LHDN obligations |
| D11 | Hosting, budget, who owns the Loyverse token after cutover | Ops and data retention |
| D12 | How to handle returns on imported Loyverse receipts | Must be written before enabling those refunds |
pkg-pos skeleton, schema migrations, auth roles,
demo company/brand/outlet/register, fictional catalog, local setup README,
.env.example. Automated tests for money helpers and tenancy.
loyverse_* extract.
Synthetic fixtures labelled. Gaps explained, not invented.
| Item | Status |
|---|---|
Product exists on the FTEG platform map as fteg-pos | verified |
| Written architecture, migration classes, Track P | verified |
| 9981 Loyverse usage study + till/BO wireframes | documented — mocks are not a working till |
| Runnable POS application | not built |
| Cash checkout / receipts / shifts | not built |
| QR / card payment capture | not integrated — will record external pay only until a provider is wired |
| Offline checkout | out of scope until P5 |
| Printer / drawer | unverified — needs device list |
| Loyverse 3-year history complete | unproven — API window + export coverage TBD (D5) |
| SST / e-Invoice on POS | not inferred |
| Live cutover | forbidden without authorization |
Tests that must exist by the milestone that introduces the behaviour:
Handoff pack: working app, setup + .env.example, migrations + demo seed,
migration guide + recon report, hardware findings, deploy/backup/restore/cutover
notes, and this verified/blocker list kept current in
fteg-products/pos/docs/PROGRESS.md.
/srv/www/fteg-products/pos/srv/www/pkg-pos/srv/www/fteg-products/pos/docs/PROGRESS.md/srv/www/9981beelongz — docs/roadmaps/LOYVERSE_SYNC_STRATEGY.md, docs/EVENT_SALES_SETUP_GUIDE.md