Skip to content

Sale Schema

The sale schema contains 2 models that manage the sale order lifecycle. Orders progress through a defined state machine from draft to completion or cancellation.

Source: packages/core/src/models/schemas/sale/

Models Overview

ModelTable NameDescription
SaleOrdersale.SaleOrderSale order with status lifecycle and pricing totals
SaleOrderItemsale.SaleOrderItemLine items within a sale order

SaleOrder

Status Lifecycle

Status Values (from SaleOrderStatuses):

StatusValueTerminalCan Modify ItemsCan CheckoutCan Cancel
DRAFT001_DRAFTNoYesYesYes
PROCESSING203_PROCESSINGNoNoNoYes
PARTIAL300_PARTIALNoNoNoYes
COMPLETED303_COMPLETEDYesNoNoNo
CANCELLED505_CANCELLEDYesNoNoNo

Helper methods on SaleOrderStatuses:

  • isValid(status) -- checks if status is in the valid set
  • isTerminal(status) -- returns true for COMPLETED or CANCELLED
  • canModifyItems(status) -- returns true only for DRAFT
  • canCheckout(status) -- returns true only for DRAFT
  • canRevertToCart(status) -- returns true only for PROCESSING
  • canCancel(status) -- returns true for any active status (DRAFT, PROCESSING, PARTIAL)

Columns

ColumnTypeConstraintsDescription
order_numbertextNOT NULL, UNIQUEAuto-generated unique order number
nametextOrder name
slugtextUNIQUEURL-friendly identifier
validityjsonb{ from: string, to: string } validity period
statustextNOT NULL, DEFAULT 001_DRAFT, indexedOrder status
draft_attimestamptzTimestamp when order entered DRAFT
processing_attimestamptzTimestamp when order entered PROCESSING
partial_attimestamptzTimestamp when order entered PARTIAL
completed_attimestamptzTimestamp when order COMPLETED
cancelled_attimestamptzTimestamp when order CANCELLED
cancellation_reasontextReason for cancellation
merchant_idtextNOT NULL, indexedFK to Merchant
sale_channel_idtextNOT NULL, indexedFK to SaleChannel
currencytextNOT NULL, DEFAULT 'VND'Currency code
exchange_ratedecimal(12,6)DEFAULT '1'Exchange rate
subtotaldecimal(15,4)NOT NULL, DEFAULT '0'Subtotal before tax/discount
taxdecimal(15,4)NOT NULL, DEFAULT '0'Total tax amount
discountdecimal(15,4)NOT NULL, DEFAULT '0'Total discount amount
totaldecimal(15,4)NOT NULL, DEFAULT '0'Final total
counterjsonbPayment counter { paid: number, paidItemIds: string[], total: number }
metadatajsonbTyped as TSaleOrderMetadata
created_bytextCreator user ID
modified_bytextLast modifier user ID
+ common columnsid, createdAt, modifiedAt, deletedAt

TSaleOrderMetadata

typescript
type TSaleOrderMetadata = {
  note?: string;
  merchantId: string;
  finance:
    | { use: false }
    | {
        use: true;
        walletId: string;
        categoryId: string;
      };
};

The finance field controls whether a finance transaction is automatically created when the order completes:

  • { use: false } -- no finance integration
  • { use: true, walletId, categoryId } -- automatically create an income transaction in the specified wallet and category

Relations

RelationTypeTargetDescription
creatorOneUserCreated by user
modifierOneUserLast modified by user
saleChannelOneSaleChannelAssociated sale channel
itemsManySaleOrderItemLine items in this order

SaleOrderItem

Item Modes

ModeValueDescription
PRODUCT000_PRODUCTStandard product item linked to a ProductVariant
CUSTOM100_CUSTOMCustom/manual item not linked to a product

Columns

ColumnTypeConstraintsDescription
sale_order_idtextNOT NULL, indexed, FKFK to SaleOrder
modetextNOT NULL, DEFAULT 000_PRODUCTItem mode (PRODUCT or CUSTOM)
lead_item_idtextLead item for grouped/addon items
item_typetextPolymorphic entity type (default: ProductVariant)
item_idtextPolymorphic entity ID
currencytextNOT NULL, DEFAULT 'VND'Currency code
base_pricedecimal(15,4)NOT NULL, DEFAULT '0'Original base price
unit_pricedecimal(15,4)NOT NULL, DEFAULT '0'Price per unit (after adjustments)
discountdecimal(15,4)NOT NULL, DEFAULT '0'Discount amount
quantitydecimal(15,4)NOT NULL, DEFAULT '1'Quantity
taxdecimal(15,4)NOT NULL, DEFAULT '0'Tax amount
totaldecimal(15,4)NOT NULL, GENERATEDComputed: (unit_price * quantity + tax)
fare_idtextReference to the Fare used for pricing
fare_providertextProvider that supplied the fare
price_metadatajsonbTPriceMetadata with fare source details
metadatajsonbTProductMetadata with product snapshot
created_bytextCreator user ID
modified_bytextLast modifier user ID
+ common columnsid, createdAt, modifiedAt, deletedAt

Indexes: (saleOrderId), (itemType, itemId)

Foreign key: saleOrderId references SaleOrder.id

TProductMetadata

Product snapshot captured at time of sale:

typescript
type TProductMetadata = {
  name: { default: string; vi?: string; en?: string };
  description?: string;
  sku?: string;
  barcode?: string;
  imageUrl?: string;
  category?: string;
  subcategory?: string;
  brand?: string;
  attributes?: Record<string, string | number | boolean>;
  taxRate?: number;
  externalId?: string;
  externalSource?: string;
  customData?: Record<string, unknown>;
};

TFareSource (Discriminated Union)

Tracks how the price was determined:

typescript
// Manual pricing (operator-entered)
type TFareSourceManual = {
  type: 'MANUAL';
  unitPrice: number;
  basePrice: number;
  tax: { mode: 'AMOUNT'; value: number } | { mode: 'PERCENTAGE'; value: number };
};

// System pricing (from fare engine)
type TFareSourceSystem = {
  type: 'SYSTEM';
  fareId: string;
  unitPrice: number;
  basePrice: number;
  originalPrice?: number;
  provider?: string;
  appliedRules?: Array<{ fareRuleId: string; code: string }>;
  validUntil?: string;
  metadata?: Record<string, unknown>;
};

type TFareSource = TFareSourceManual | TFareSourceSystem;

Entity Relationships

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.