Skip to main content
Inventory lives in apps/inventory and runs locally through Portless at https://inventory.tuturuuu.localhost. Production is intended for https://inventory.tuturuuu.com.

Native Inventory presentation

The Flutter mini-app keeps Overview, Products, Sales, Manage and Storefront destinations in the established floating navigation. Overview retains Sell and Create product as visible workflows; it does not repeat the dock destinations. Manage keeps every setup collection and its permitted add action visible without a second counter dashboard. Season selection, creation, editing and archiving remain visible on Sales. Product summaries show metadata once and keep each quantity and price beside its warehouse and unit. Quantities across different units are not summed. A null quantity means unlimited stock and must never display as zero or a low-stock warning. An absent stock row is shown as unconfigured. The Products low-stock count covers loaded search results, not the entire catalog. Native presentation does not change server permissions, mutation authorization, or canonical workspace membership. Existing pending-sync indicators remain attached to their owning rows. The product editor preserves unlimited stock when an unrelated field is edited. Leave quantity empty to represent unlimited stock; finite zero remains zero. Manage clears previous rows and add controls when the authenticated account or selected workspace changes. An open setup sheet cannot submit into a different account/workspace scope. These UI safeguards supplement server authorization. Native Inventory writes accept pending setup selections. The dependency queue creates referenced owners, manufacturers, categories, units, warehouses and Finance categories before product writes, then applies their authoritative IDs. The earlier sync-first guard is replaced by automatic prerequisite handling. Repository regressions in inventory_setup_dependencies_test.dart cover both product writes, all six pending references, nullable optional fields and resource-scoped mappings. Legacy already-completed setup creates whose old API returned no ID cannot retroactively recover local-ID provenance; that historical limitation remains tracked in issue #5737. This compact presentation does not establish parity for web commerce checkout, payment providers, stock ledgers, scoped analytics, costing or effective season pricing. Those require their existing authorized API contracts and separate verification; today’s catalog price must not replace historical sold prices.

Automatic offline dependency sync implementation

Native period-sale creation omits optional notes when none were entered; it does not send JSON null to the non-nullable optional server field. Empty entered notes remain an empty string. A confirmed invoice remains a successful sale if local cache invalidation fails; the client attempts scoped resource-cache eviction without repeating the financial write. Server validation and authorization failures still propagate. The native HTTP regressions in inventory_api_mutation_contract_test.dart cover omitted, empty and populated notes, durable receipts and post-acknowledgment cache failure. Read contracts are covered by inventory_api_read_contract_test.dart. These client safeguards do not change server permissions, season pricing or stock accounting. Native create and supported Inventory mutations persist their operation identity before any HTTP request, including successful online creates. Online callers receive the authoritative acknowledged resource; offline or temporarily blocked callers keep their optimistic representation. This source behavior becomes available after the matching mobile/API/database release has passed its gates. A queued product write must wait for referenced Inventory owners, manufacturers, categories, units, warehouses and Finance categories to be created, regardless of enqueue order. Dependencies and mappings belong to the authenticated account, workspace and resource type. Related mutations retain their per-entity order; independent ready writes can progress. Missing prerequisites and dependency cycles remain visible without deleting queued work. Account/workspace purges serialize with durable pending writes. Malformed encrypted pending records remain visible as conflicts while independent valid records can continue; corrupt reference metadata cannot abort the entire replay pass. Online sale-period clearing preserves the nullable server response. These boundaries are covered by offline_inventory_review_regression_test.dart and the foreground verification regressions in offline_inventory_verification_test.dart. Product create permits at most 500 stock rows. Stock quantity and minimum quantity must be integers, matching their database columns; unit prices preserve fractional major currency amounts. offline-create-schema.test.ts and inventory-offline-create.sql cover these limits for the offline create contract. The dedicated setup/product/period create contract returns authoritative IDs and deduplicates the original operation identity before the client retries an uncertain create. The client never falls back to a legacy create route when this contract is missing. After acknowledgment, the queue persists the mapping and rewrites dependent paths/payload references. An acknowledged create must not be sent again because mapping storage or cache refresh failed. Automatic sync must preserve user data across account switches, restart, retry and temporary network loss. A missing endpoint or RPC retains the mutation and retries with bounded backoff, connectivity, authentication and app-resume wakeups. Permission/domain failures, canceled/deleted prerequisites, malformed payloads, ambiguous creators and cycles remain stored with localized diagnostics; they cannot block unrelated ready work. Confirmed create and write acknowledgments survive restart before mapping/dequeue. Successful deletions persist resource-scoped tombstones, preventing stale mappings from restoring deleted resources. Automatic replay never invents a replacement for a canceled/deleted prerequisite. Typed references never rewrite free text, workspace path segments or query parameters. The dedicated online scheduled-sale journal keeps its existing receipt recovery contract. Legacy invoices without an inventory period do not gain retry safety; an uncertain legacy invoice remains retained for review, rather than duplicating its financial effects. Wallets retain their existing stable client-ID contract. Regression evidence is in offline_inventory_replay_test.dart, offline_dependency_graph_test.dart, offline_inventory_mutation_test.dart, finance_repository_inventory_dependency_test.dart and offline_changes_sheet_test.dart; these synthetic tests do not establish native airplane-mode or authenticated production acceptance. The app is a registered Tuturuuu satellite app. It uses Tuturuuu app-session auth for targetApp: 'inventory', exposes local /verify-token and /api/auth/verify-app-token handoff routes, and forwards fallback /api/* traffic to centralized apps/web APIs. Keep protected inventory, payment, Stripe Connect, checkout, Square Terminal, and audit mutations behind apps/web so provider secrets, bot protection, request logging, and Observability stay centralized. Workspace-scoped Inventory APIs in apps/web must authorize through authorizeInventoryWorkspace, which accepts the Inventory app-session cookie and then performs workspace membership and permission checks. Authenticated dashboard and workspace API access is permission-driven; do not hide these routes behind the ENABLE_INVENTORY workspace config. Keep public storefront delivery separate, since published storefront behavior may still apply storefront-specific rollout gates. Do not use resolveAuthenticatedSessionUser, supabase.auth.getUser(), or request-scoped Supabase clients as the primary auth gate for these routes, because the satellite clears local Supabase cookies and forwards app-session cookies instead. The operator console uses the same collapsible Tuturuuu satellite workspace structure as the Tasks, Finance, Calendar, and CMS apps: dashboard layouts should keep server work to auth/workspace gating, then render client-side views backed by TanStack Query and @tuturuuu/internal-api. Do not reintroduce a separate Inventory-only app shell or direct client Supabase reads for protected workspace data. The local token verifier must call the central Web verifier with verificationBaseUrl: WEB_APP_URL; Inventory protected routes require both the host-only Inventory app-session cookie and the Web-issued app-session cookie used by rewritten apps/web API requests. Local-only token validation will create a redirect loop back to platform login. Current-user bootstrap APIs such as /api/v1/users/me/default-workspace and /api/v1/users/me/profile must keep inventory in their app-session audience allowlist, or /dashboard will accept the local handoff cookies and then bounce back to Web auth after the bootstrap request returns 401. Public storefront routes live at /store/[storeSlug] inside apps/inventory and are intentionally exempt from the operator auth proxy. They should only read published storefront data through apps/web public APIs and should create checkout reservations through the central apps/web RPC wrapper. Do not add storefront CRUD, reservation writes, invoice finalization, or settlement writes directly to apps/inventory.

Local Development

Use:
The package dev script runs Portless. For direct port debugging, run apps/inventory with bun dev:app, which falls back to port 7815.

Product Boundary

Inventory starts with workspace-scoped surfaces for:
  • product catalog categorization by type, owner, manufacturer, talent, supplier, channel, and fulfillment policy
  • stock movement and reservation ledgers
  • bundle and promotion availability controls
  • checkout fee visibility for processing fees, Tuturuuu platform fees, conversion fees, settlement estimates, and net payout
  • payment and inventory audit streams
  • Stripe Connect readiness for B2B2C sellers who link their own Stripe account
The commerce storefront extension adds:
  • operator routes for overview, catalog, stock, bundles, storefronts, checkouts, sales, setup readiness, and audits
  • public routes for /store/[storeSlug], /store/[storeSlug]/products/[listingId], cart, checkout, and order status
  • private-schema tables for storefronts, listings, bundles, checkout sessions, checkout lines, reservations, Square connection state, terminal checkout identifiers, and settlement ledger entries
  • commerce money (listing/bundle prices, checkout amounts, settlement, and costing) stored in integer minor units of the row currency (cents for USD, whole units for JPY/VND); convert with @tuturuuu/utils/money and enter with the shared MoneyInput. See the Polar storefront and Square Terminal integration runbooks.
  • Square Terminal settings for workspace app credentials, OAuth/manual tokens, location selection, device pairing, webhook verification, and commerce actions that send, cancel, and reconcile terminal payments after local stock is reserved. The same settings surface provides guarded Square catalog and stock import, additive publish, and conflict-aware two-way sync. Provider deletion is deliberately unsupported.
  • service-role-only RPCs for creating reservations, releasing or expiring reservations, materializing checkout TTL expiry, and linking a completed checkout to a finance_invoice
  • private sales periods that group both invoice-backed and completed checkout sales into seasons, conventions, campaigns, or other operating windows. A sale has at most one period assignment, while archived periods keep their historical assignments and remain available for reporting.
  • durable Finance source entries for every completed real-provider checkout, refund, Square chargeback hold/release, and audited provider-unavailable adjustment. Simulated checkouts are excluded. Manual Inventory sales continue using their existing Finance invoice path.
The Inventory Sales list shows whether a provider checkout is linked, pending, refunded, or disputed and links to the corresponding Finance transaction or reconciliation entry. Unlinked entries are operationally visible but do not affect Finance ledger totals. New commerce tables belong in the private schema, not public. Public and satellite clients must go through Inventory-owned apps/inventory APIs plus @tuturuuu/internal-api; direct Supabase client access to these tables is not part of the contract. Protected workspace routes must authenticate and normalize the workspace with the request-scoped client before any private-table read or write, then use server-only database access or service-role RPCs behind apps/inventory. Convention selling uses an explicit sales period. The sale dialog defaults to an in-range configured period, otherwise the sole current scheduled period. An intentionally configured current legacy default is honored; legacy-only implicit selection requires a choice. Overlapping or absent current periods require a choice. Operators can explicitly choose the legacy unassigned workflow; it is never an automatic fallback. New periods default to effective season pricing with explicit dates and an IANA timezone. Prices are major currency units, scoped to product, unit, warehouse and period. Price intervals start at local midnight; end dates are inclusive. A new later interval closes an earlier interval without replacing its amount. Price history does not alter stock quantity, legacy catalog prices or completed invoice snapshots. Existing periods remain in legacy pricing mode until deliberately changed; the migration performs no price backfill. Priced period dates, timezone and pricing mode are immutable. Use another period to change those boundaries. Scheduled selling filters eligible active products and requires a current price in the workspace currency. Gaps and missing prices prevent checkout; another season’s prices are never borrowed. Price controls require catalog-management permissions. Managers can preview upcoming selected-season prices in the price history, labeled with effective dates and the season timezone. Viewing a future price does not make it eligible for checkout at the current sale time. Offline/stale carts require refresh and review, with no silently changed total. Period-linked invoice creation validates the quote and eligibility and captures line amounts, stock changes and period assignment in one private transaction after the existing Finance authorization checks. Historical invoices, reports and completed commerce orders continue using their saved amounts. Price receipts survive invoice deletion as durable idempotency tombstones: deleted requests cannot recreate a canceled sale, and restored invoices retain the same receipt and captured prices. Legacy manual periods keep six-decimal major-unit precision, existing currency fallback and existing finite overselling behavior. Provider listings/promotions and their minor-unit prices are intentionally unchanged. Scheduled manual sales keep the existing custom-price workflow’s no-promotion semantics and require a matching wallet currency. The additive migration must be installed before scheduled controls can be used. Missing tables/RPCs fail closed; never apply production migrations manually. Sales-period clients use the Inventory-owned routes below. Keep these paths in the Flutter API mapping check whenever the mobile catalog changes:
  • GET/POST /api/v1/workspaces/:wsId/inventory/sales-periods
  • PATCH/DELETE /api/v1/workspaces/:wsId/inventory/sales-periods/:periodId
  • GET/POST /api/v1/workspaces/:wsId/inventory/sales-periods/:periodId/prices
  • PUT /api/v1/workspaces/:wsId/inventory/sales/:saleId/period
  • GET /api/v1/workspaces/:wsId/inventory/sales?period_id=:periodId
Core stock and setup data lives in private inventory tables: private.inventory_products, private.inventory_units, private.inventory_warehouses, private.inventory_suppliers, private.inventory_batches, private.inventory_batch_products, private.inventory_owners, private.inventory_audit_logs, and private.inventory_manufacturers. Dashboard pages and APIs should access them through server-owned apps/web routes. Prefer private-schema RPCs for product catalog, low-stock, and other repeated join-heavy reads; call them with createAdminClient().schema('private').rpc(...) from server code. Manufacturers are a normalized workspace setup entity matching the supplier-style management model. Products store only workspace_products.manufacturer_id; API responses may still include manufacturer as a display name for older clients. Legacy imports that send package/product manufacturer text should upsert the trimmed name into private.inventory_manufacturers, assign the resulting manufacturer_id, and avoid writing manufacturer text back to workspace_products. Bundle components are a workspace-scoped stock contract. When creating or updating a bundle, every component’s product, unit, warehouse, and stock row must belong to the same workspace as the bundle. The database trigger on private.inventory_bundle_components enforces that invariant for direct writes, and the checkout reservation RPC re-checks the same workspace before locking stock. Do not trust stored bundle component UUIDs as already authorized input. Do not hardcode Stripe fee schedules as durable truth. The checkout estimator is for quoting and operator review; production reconciliation should persist the estimate shown to the seller and then reconcile it against actual Stripe balance transaction fee rows after settlement.

Deployment

Inventory has dedicated Vercel workflows:
  • .github/workflows/vercel-preview-inventory.yaml
  • .github/workflows/vercel-production-inventory.yaml
These workflows use VERCEL_INVENTORY_PROJECT_ID. Add that secret before enabling hosted deployments for inventory.tuturuuu.com. Price history references durable product/unit/warehouse identities instead of the replaceable stock tuple. Combined product edits and dedicated stock edits for priced products commit metadata, tuple changes and movement history together; a failed edit leaves no partial metadata or phantom stock movement. Scheduled period scope/rule replacement takes the same period lock as checkout in a single transaction. Search filters never invalidate cart quotes; selecting the current period is a no-op. Price authoring forwards the scoped catalog pagination so products after the first page can be selected without preloading another dialog. Atomic pricing-aware edit routing also applies before a product has its first authored price and while a legacy period is being converted. The database transaction resolves current state under its locks; routing never relies on an earlier mode or price-existence read. Checkout, stock editing and price authoring acquire stock tuples before the product row to avoid mixed legacy/scheduled lock inversion. If price support is installed but its edit transaction is unavailable, edits fail closed instead of falling back to separate writes. On native mobile, an active user Save may open the existing verification dialog for that record only. Background replay never opens verification dialogs or persists verification tokens. Canceling that dialog leaves the already-requested Save queued: the editor closes and the item keeps its existing Waiting to sync indicator, rather than claiming server completion or creating another operation on a repeated Save. The original operation identity is reused on later replay. Explicitly discarding the queued change cancels unsent work; canceling verification does not discard it. Account changes and real membership or permission denials retain their original error behavior. Regression coverage lives in offline_inventory_verification_test.dart and the mounted product editor tests.

Transactional offline create validation

Automatic dependency sync uses a separate deduplicated create contract. Its private receipt and all resource, stock, history and audit effects commit together. Reusing an operation identity with different input is rejected. The service authorizes the captured account, workspace and resource permission before invoking this contract; customers cannot read receipts or forge actors. .github/workflows/inventory-offline-contract.yaml validates the exact PR head by applying the complete schema in a disposable hosted database, running strict inventory-offline-create.sql pgTAP assertions, and generating packages/types/src/supabase.ts as an artifact. A queued workflow is not schema or type parity evidence. The native scheduler persists operation identities before sending, orders prerequisites, and rewrites acknowledged references automatically. An unavailable server contract retains queued work and retries automatically; it never falls back to an unsafe legacy create. Release readiness still requires the exact-source database, generated-type, native, and owning-app CI gates. The new /api/v1/workspaces/:wsId/inventory/offline-mutations POST route accepts an operation identity, an explicit create kind and a strictly validated payload. Current workspace membership and the owning kind permission are checked on every retry, even when a prior receipt exists; a revoked grant denies the request. Inventory stock initialization additionally requires stock permission. Finance categories use create_transactions and accept Inventory or Finance app sessions. Receipt identities have no TTL or automatic pruning. Recognized responses carry X-Tuturuuu-Offline-Contract. Missing RPC/schema returns OFFLINE_CONTRACT_UNAVAILABLE with 503; an unknown route on an older deployment has no contract header. These rollout gaps must retain the operation and retry automatically, while a marked domain 404 or permission 403 is permanent. The native implementation must never fall back to an undeduplicated create POST. Offline catalog create receipts intentionally have no time-based expiry. Their private storage lasts for the owning actor/workspace lifetime and is removed by those owners’ deletion cascades. The mobile outbox has no maximum retry age, so pruning a receipt could replay an old successful create as a new resource. Changing this default requires a coordinated client/server operation expiry and server rejection of expired operation IDs before receipt deletion is enabled. This rule covers inventory catalog/setup create receipts, including stored request/response data; it does not introduce a financial transaction retention policy. The private table is inaccessible to ordinary client roles. Receipt replay and payload-identity checks in inventory-offline-create.sql guard the current behavior; lifecycle pruning remains intentionally disabled. An offline create RPC that completes with a malformed receipt or a different resource returns sanitized HTTP 500 OFFLINE_CONTRACT_RESPONSE_MISMATCH. No row IDs are exposed, and the same operation remains retained for operator review. A missing RPC function/schema remains HTTP 503 OFFLINE_CONTRACT_UNAVAILABLE. These distinguish a server receipt defect from contract availability; the client must not blindly resend a new create identity. offline-create-route.test.ts covers malformed receipts and resource mismatches.