{"entities":[{"name":"Actor","class":"Core","subsystem":"CONFIG","area":"Configuration","desc":"A platform identity that can own or consume resources, hold configurations, and appear in audit trails. Actors are split into two kinds: Standard actors (human-tied — associated to a User entity) and Service actors (system, service, or application identities with no User association). Actor is the canonical principal across the platform — services, audit logs, and tool-level RBAC reference Actor rather than User directly so that human and non-human principals can be authorized and attributed uniformly. Resolves to a User only when the Actor is Standard.","status":"draft","properties":[{"n":"actorId","r":true,"t":"string"},{"n":"actorType","r":true,"t":"enumeration","v":["Standard","Service"],"info":"Discriminates the kind of identity this Actor represents. Standard = human-tied; the Actor MUST be associated to a User via the user property. Service = system, service, or application identity; the Actor MUST NOT have a user value set. Service actors are how non-human principals (background workers, integration accounts, external system credentials) participate in audit trails and RBAC without polluting the User entity."},{"n":"name","r":true,"t":"string"},{"n":"user","t":"entityDetail","re":"User","info":"The human User this Actor represents. Populated only when actorType = Standard; must be null/absent when actorType = Service. Modeled as entityDetail rather than entityRef because both Actor and User are Company-scoped — the reference must respect the Company isolation boundary. Conditional-required relationship enforced via business validation rather than the property being unconditionally required."}],"service":"APR Config","shopify":"API client credentials / app installations","related":["Config","Secret","Environment","User"],"bv":{"rules":[{"rule":"user must be set when actorType is Standard — a Standard Actor represents a human and must resolve to a User entity","when":"always","field":"user","severity":"error"},{"rule":"user must be null/absent when actorType is Service — a Service Actor represents a non-human identity and must not be associated to a User","when":"always","field":"user","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Advanced Shipping Notice","class":"Operational","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"Advance Shipment Notice from a Vendor confirming goods in transit, broken into Cartons.","status":"stub","properties":[{"n":"asnId","r":true,"t":"string"},{"n":"cartons","r":true,"t":"array","re":"Advanced Shipping Notice Carton"},{"n":"eta","t":"datetime"},{"n":"purchaseOrder","r":true,"t":"entityRef","re":"Purchase Order"},{"n":"vendor","r":true,"t":"entityRef","re":"Vendor"}],"ext":"OperationalDocument","shopify":"EDI 856","related":["Purchase Order","Vendor","Advanced Shipping Notice Carton","Goods Receipt"],"bv":{"rules":[],"lifecycle":{"states":["Pending","Received","PartiallyReceived","Discrepant"],"transitions":[{"to":"Received","from":"Pending","conditions":["All items matched to receipt"]},{"to":"PartiallyReceived","from":"Pending","conditions":["Some items received"]},{"to":"Discrepant","from":"Pending","conditions":["Received quantities do not match ASN"]}],"initialState":"Pending"},"calculations":[],"crossEntityConstraints":[{"rule":"Should reference a valid Purchase Order for reconciliation","entity":"Purchase Order"}]}},{"name":"Advanced Shipping Notice Carton","class":"Operational","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"Individual carton within an ASN shipment, tracking packed items at the box level.","status":"stub","properties":[{"n":"asn","r":true,"t":"entityRef","re":"Advanced Shipping Notice"},{"n":"cartonId","r":true,"t":"string"},{"n":"cartonNo","t":"string"},{"n":"lines","r":true,"t":"array","info":"Array of AdvancedShippingNoticeLine sub-documents within this ASN carton."},{"n":"trackingNo","t":"string"},{"n":"weight","t":"decimal"}],"ext":"OperationalSubDocument","related":["Advanced Shipping Notice","Goods Receipt"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Must belong to a valid ASN","entity":"Advanced Shipping Notice"}]}},{"name":"Announcement","class":"Operational","subsystem":"CONNECT","area":"Messaging","desc":"A tenant-scoped, push-style communication published to a targeted audience within a Company. Unlike Chat Message (interactive, channel-bound) or Email Log (delivery audit), an Announcement is a first-class authored content document with its own draft/scheduled/published/expired lifecycle, multi-surface delivery (portal banner, in-app feed, email, mobile push), franchise-group and location-level audience targeting, and optional mandatory acknowledgement. Authored in Fulcrum X / Compass by Franchisor or Company admins; surfaces the same content consistently across every channel a Company is entitled to. Companion entity Announcement Acknowledgement (Ledger) records per-user view/ack/dismiss events for audit and rollup.","status":"draft","properties":[{"c":true,"n":"acknowledgementCount","r":true,"t":"integer","info":"Calculated rollup. COUNT(Announcement Acknowledgement WHERE announcement = this AND acknowledgement = 'acknowledged'). Query-time."},{"n":"announcementNo","r":true,"t":"string","u":true,"info":"Human-readable identifier, unique within Company. Per entity-no-property convention."},{"n":"attachments","r":true,"t":"array","info":"File attachments rendered alongside the announcement body. Mirrors the Chat Message attachments shape. Empty array default."},{"n":"audience","r":true,"t":"valueType","info":"AnnouncementAudience value type. Targeting filter that, combined with inherited franchiseGroups and targetLocations/targetRoles, resolves the final delivery set."},{"n":"author","r":true,"t":"entityRef","re":"User","info":"The User who authored the announcement. Only the author or a Company admin may edit prior to publish."},{"n":"bannerImageUrl","t":"string","info":"Optional header image rendered in portal and email surfaces."},{"n":"body","r":true,"t":"string","info":"Announcement content. Supports markdown formatting."},{"n":"category","r":true,"t":"enumeration","v":["operational","policy","training","marketing","system","compliance"],"info":"Classifies the announcement purpose. Drives surface treatment, filtering, and (for compliance/policy) default isAcknowledgementRequired."},{"n":"deliveryChannels","r":true,"t":"array","info":"Set of surfaces to fan out to. Allowed values: 'portal', 'inAppBanner', 'email', 'mobilePush'. When 'email' is included, a record is written to Email Log on send."},{"n":"expireAt","t":"datetime","info":"When the announcement falls out of active feeds and transitions to status='expired'. UTC. Null = no auto-expiry."},{"n":"isAcknowledgementRequired","r":true,"t":"boolean","info":"When true, targeted users must record an 'acknowledged' Announcement Acknowledgement before the announcement can be dismissed. Per boolean-must-be-required."},{"n":"isPinned","r":true,"t":"boolean","info":"When true, the announcement sticks to the top of the feed for its audience until expireAt or unpinned."},{"n":"priority","r":true,"t":"enumeration","v":["info","important","urgent"],"info":"Drives surface treatment — info renders inline, important shows a banner, urgent triggers a modal/toast and push notification regardless of deliveryChannels."},{"n":"publishAt","t":"datetime","info":"Scheduled publish time in UTC. Null = publish immediately on save. Must be >= now() when set."},{"n":"publishedAt","t":"datetime","info":"Actual publish timestamp set by the system when status transitions to 'published'. Immutable thereafter."},{"n":"status","r":true,"t":"enumeration","v":["draft","scheduled","published","expired","archived"],"info":"Lifecycle state. Transitions: draft -> scheduled -> published -> expired/archived; draft -> published is also valid. No transitions back from published except archived."},{"n":"summary","t":"string","info":"Short preview/teaser used in feed rows and mobile push notification body. Falls back to truncated body when null."},{"n":"targetLocations","r":true,"t":"array","re":"Location","info":"EntityDetail references narrowing the audience to specific Locations within the inherited franchiseGroups. Empty array = no Location narrowing. Per location-ref-uses-entity-detail and array-must-be-required."},{"n":"targetRoles","r":true,"t":"array","re":"Scope","info":"EntityDetail references narrowing the audience to specific Scopes/roles. Empty array = no role narrowing."},{"n":"title","r":true,"t":"string","info":"Display title shown in feeds, banners, push notifications, and email subject (when no Email Template override)."},{"c":true,"n":"viewCount","r":true,"t":"integer","info":"Calculated rollup. COUNT(DISTINCT user) from Announcement Acknowledgement WHERE announcement = this AND acknowledgement IN ('viewed','acknowledged','dismissed'). Query-time."}],"ext":"OperationalDocument","notes":"PROPOSED — not yet reviewed. Sits in CONNECT > Messaging alongside Chat Channel/Chat Message/Calendar Event/Email Log/Email Template. Inherits company, franchiseGroups, idmpKey, identifiers, audit, customData, tags, notes, recentActions from OperationalDocument. Open questions: (1) cross-Company franchisor broadcasts — fan-out at publish or parentAnnouncement self-ref; (2) Announcement Template (mirror of Email Template) for recurring patterns; (3) versioning post-publish — void+re-publish vs version-in-place; (4) coupling to Email Template when 'email' is in deliveryChannels.","related":["User","Location","Scope","Email Template","Announcement Acknowledgement"],"bv":{"rules":[{"rule":"Must be >= now() on create or transition to 'scheduled'","when":"create","field":"PublishAt","severity":"error"},{"rule":"If set, must be > publishAt","when":"always","field":"ExpireAt","severity":"error"},{"rule":"Immutable once status = 'published'","when":"update","field":"PublishedAt","severity":"error"},{"rule":"When audience.scope = 'all', targetLocations and targetRoles must be empty","when":"always","field":"Audience","severity":"error"},{"rule":"All targetLocations must belong to the same Company as this Announcement","when":"always","field":"TargetLocations","severity":"error"},{"rule":"Must contain at least one value when status moves to 'scheduled' or 'published'","when":"publish","field":"DeliveryChannels","severity":"error"},{"rule":"Allowed values are 'portal', 'inAppBanner', 'email', 'mobilePush'","when":"always","field":"DeliveryChannels","severity":"error"}],"lifecycle":{"states":["Draft","Scheduled","Published","Expired","Archived"],"transitions":[{"to":"Scheduled","from":"Draft","conditions":["publishAt is set and >= now()"]},{"to":"Published","from":"Draft","conditions":["Author publishes immediately"]},{"to":"Published","from":"Scheduled","conditions":["publishAt reached; system publishes"]},{"to":"Draft","from":"Scheduled","conditions":["Author cancels scheduled publish"]},{"to":"Expired","from":"Published","conditions":["expireAt reached"]},{"to":"Archived","from":"Published","conditions":["Admin manually archives"]},{"to":"Archived","from":"Expired","conditions":["Admin manually archives"]}],"initialState":"Draft"},"calculations":[{"name":"acknowledgementCount","formula":"COUNT(Announcement Acknowledgement WHERE announcement = this AND acknowledgement = 'acknowledged')","trigger":"query-time"},{"name":"viewCount","formula":"COUNT(DISTINCT user) FROM Announcement Acknowledgement WHERE announcement = this","trigger":"query-time"},{"name":"audienceSize","formula":"resolved size of audience filter (franchiseGroups ∩ targetLocations ∩ targetRoles + includeUsers - excludeUsers)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Author and any User in audience.includeUsers/excludeUsers must belong to the same Company","entity":"User"},{"rule":"All targetLocations must belong to the same Company as this Announcement","entity":"Location"},{"rule":"When 'email' is in deliveryChannels, an Email Log entry is written per recipient at send time","entity":"Email Log"},{"rule":"When 'email' is in deliveryChannels, an Email Template may be referenced for subject/body override (open question — not currently a property)","entity":"Email Template"}]}},{"name":"Announcement Acknowledgement","class":"Ledger","subsystem":"CONNECT","area":"Messaging","desc":"Immutable, append-only record of a User's interaction with an Announcement — viewed, acknowledged (when required), or dismissed. One row per User per state-change. Companion ledger to Announcement; the source of truth for viewCount/acknowledgementCount rollups and the audit trail for compliance-category announcements. Mirrors the Email Log pattern: ledger-class, no modifiedBy/modifiedDate (per ledger-entry-immutability), corrections via new compensating entries.","status":"draft","properties":[{"n":"acknowledgement","r":true,"t":"enumeration","v":["viewed","acknowledged","dismissed","expired_unacknowledged"],"info":"The state recorded. 'viewed' = surfaced to user; 'acknowledged' = explicit confirmation when isAcknowledgementRequired; 'dismissed' = user dismissed without ack (only when isAcknowledgementRequired = false); 'expired_unacknowledged' = system-written at expireAt for required announcements that were never acknowledged."},{"n":"acknowledgementNo","r":true,"t":"string","u":true,"info":"Human-readable identifier, unique within Company. Per entity-no-property convention."},{"n":"announcement","r":true,"t":"entityRef","re":"Announcement","info":"The Announcement this acknowledgement is for. Drives the rollup on Announcement.acknowledgementCount/viewCount."},{"n":"clientContext","t":"valueType","info":"Captures the surface and device on which the state change occurred (e.g. surface='portal'|'mobile'|'email', deviceType, appVersion). Optional — system-recorded entries may omit."},{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"Tenant isolation boundary. Inherited from the parent Announcement's Company at write time. Per company-scoping-required."},{"n":"user","r":true,"t":"entityRef","re":"User","info":"The User whose interaction is recorded. Null is not permitted — for 'expired_unacknowledged' rows, the system writes one row per targeted user who failed to ack."}],"ext":"LedgerEntry","notes":"Open question: should the ledger carry a separate idempotency key beyond the inherited LedgerEntry shape, to deduplicate client-side 'viewed' events fired multiple times by the same surface? Companion to Announcement.","related":["Announcement","User","Company"],"bv":{"rules":[{"rule":"Must reference a published or expired Announcement (not draft/scheduled)","when":"create","field":"Announcement","severity":"error"},{"rule":"Must belong to the same Company as the referenced Announcement","when":"create","field":"Company","severity":"error"},{"rule":"Must be a User in the same Company as the Announcement","when":"create","field":"User","severity":"error"},{"rule":"'acknowledged' is only valid when the parent Announcement.isAcknowledgementRequired = true","when":"create","field":"Acknowledgement","severity":"error"},{"rule":"'expired_unacknowledged' may only be written by the system, not by users","when":"create","field":"Acknowledgement","severity":"error"},{"rule":"Append-only; no modifications or deletions permitted (per ledger-entry-immutability)","when":"always","field":"*","severity":"error"}],"crossEntityConstraints":[{"rule":"Each Announcement Acknowledgement row points to one Announcement and one User; the (announcement, user, acknowledgement) tuple is the unique audit fact","entity":"Announcement"},{"rule":"User must be within the resolved audience of the Announcement at the time of write","entity":"User"}]}},{"name":"Application","class":"Core","subsystem":"ALLPOINT","area":"Organization","desc":"A registered application that interacts with the All Point platform. This covers both APR platform products (CONNECT, Fulcrum X, APR Config) and APR-developed apps for external platforms (e.g., APR's Shopify App, APR's NetSuite Integration App). Defines the product's capabilities and supported features. Applications exist in a global registry, independent of any Client or Company. They are deployed to Companies as Application Installations.","status":"draft","properties":[{"n":"applicationNo","r":true,"t":"string","u":true,"info":"Human-readable business identifier"},{"n":"description","t":"string"},{"n":"developer","r":true,"t":"enumeration","v":["all-point","3rd-party"],"info":"Who built the application. all-point (developed by APR) or 3rd-party (developed by a third party)"},{"n":"distribution","r":true,"t":"enumeration","v":["public","all-point","custom"],"info":"How the application is made available. public (publicly listed), all-point (internal to APR), custom (built for a specific company)"},{"k":true,"n":"Id","r":true,"t":"string","info":"Primary identifier (surrogate key)"},{"n":"name","r":true,"t":"string","u":true},{"n":"platform","r":true,"t":"enumeration","v":["all-point","shopify"],"info":"Where the application runs. all-point (runs on APR's platform) or shopify (runs on Shopify)"},{"n":"slug","r":true,"t":"string","u":true,"info":"Human-readable key (e.g., franchise-portal, shopify-app, netsuite-app). Globally unique"},{"n":"supportedFeatures","t":"array","info":"Feature capabilities this Application supports (e.g. 'inventory', 'orders', 'reporting'). Used to validate Application Configuration feature flags."},{"n":"type","r":true,"t":"enumeration","v":["web-application","service","embedded"],"info":"What the application is technically. web-application (Web/React app), service (microservice), embedded (embedded app)"}],"notes":"Canonical subsystem is PLATFORM. Currently registered under ALLPOINT pending subsystem reassignment — needs delete-and-recreate to move to PLATFORM.","related":["Application Installation","Connector"],"bv":{"rules":[{"rule":"Must be globally unique","when":"create","field":"Code","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Deactivating an Application must revoke all associated Client tokens","entity":"Client"}]}},{"name":"Application Installation","class":"Core","subsystem":"CONNECT","area":"Organization","desc":"A Company-scoped installation of an Application. This is the entity created when an Application is actually deployed or installed for a specific Company — whether that's deploying Fulcrum X for a franchise group, installing APR's Shopify app on a merchant's Shopify store, or setting up API credentials for NetSuite. Application Installation holds the authenticated connection state: OAuth tokens, API keys, external account identifiers, and health status. It is the credentials and identity boundary — a single installation can be shared across multiple Connections.","status":"draft","properties":[{"k":true,"n":"Id","r":true,"t":"string","info":"Primary identifier (surrogate key)"},{"n":"installationNo","r":true,"t":"string","u":true,"info":"Human-readable business identifier"},{"n":"application","r":true,"t":"entityRef","re":"Application","info":"The Application this is an installation of"},{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"The Company tenant this installation belongs to. Defines the hard isolation boundary for queries, permissions, replication, and export."},{"n":"displayName","r":true,"t":"string","info":"Operator-facing label (e.g., Acme Shopify Store, Acme Fulcrum X)"},{"n":"status","r":true,"t":"enumeration","v":["pending_install","pending_auth","connected","error","disconnected","suspended"],"info":"Installation lifecycle status"},{"n":"credentials","r":true,"t":"encrypted","info":"OAuth tokens, API keys, secrets (encrypted at rest)"},{"n":"externalAccountId","t":"string","info":"Account/store/tenant ID on the external system (e.g., Shopify store ID, NetSuite account ID)"},{"n":"externalAccountUrl","t":"string","info":"URL to the account on the external system (e.g., acme.myshopify.com)"},{"n":"configuration","t":"schema","info":"Application-specific config (API version, sync preferences, feature toggles)"},{"n":"healthStatus","t":"enumeration","v":["healthy","degraded","failing","unknown"],"info":"Current health of the installation"},{"n":"lastHealthCheckAt","t":"datetime","info":"Last time health was verified"},{"n":"installedBy","t":"entityRef","re":"User","info":"User who set up the installation"},{"n":"installedAt","t":"datetime","info":"When this installation was created"}],"related":["Application","Company","Connection"]},{"name":"Area","class":"Dictionary","subsystem":"ACCESS","area":"Security & Permissions","desc":"A logical grouping of related Permissions — roughly equivalent to a resource domain (e.g. 'Inventory', 'Orders').","status":"draft","properties":[{"n":"areaId","r":true,"t":"string"},{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key. Unique within scope."},{"n":"description","t":"string"},{"n":"name","r":true,"t":"string","u":true},{"n":"permissions","r":true,"t":"array","re":"Permission"}],"service":"APR Access","related":["Permission","Scope"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Attribute","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"Defines a product dimension (e.g. Color, Material). A combination of attribute values uniquely identifies an Item.","status":"draft","properties":[{"n":"alias","t":"string"},{"n":"code","r":true,"t":"string"},{"n":"name","r":true,"t":"string","u":true},{"n":"attributeValues","r":true,"t":"schema","info":"Array of AttributeValue inline schemas. Each value extends LookupEntityValue — inherits id, identifiers, code, name, aliases, isActive, isDeleted, isDefault, sequence, audit fields, and customData."}],"ext":"LookupEntity","shopify":"ProductOption resource","related":["Product","Item"],"bv":{"rules":[{"rule":"Must be unique within the Attribute type","when":"create","field":"Code","severity":"error"}],"lifecycle":null,"calculations":[{"name":"productCount","formula":"COUNT(Product WHERE attributes CONTAINS this)","trigger":"query-time"}],"crossEntityConstraints":[]},"inlineSchemas":[{"name":"AttributeValue","desc":"A single value within an Attribute's value set. Extends LookupEntityValue — inherits id, identifiers, code, name, aliases, isActive, isDeleted, isDefault, sequence, audit fields, and customData.","extends":"LookupEntityValue","properties":[]}]},{"name":"Bill","class":"Transactional","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"Internal payable record generated from a matched Vendor Invoice (type 'invoice') or a matched Vendor Credit (type 'credit'). Tracks line-item costs, discounts, payment terms, and due dates against a Purchase Order. Credit-type Bills reduce the outstanding AP balance.","status":"stub","properties":[{"n":"billNo","r":true,"t":"string"},{"n":"type","r":true,"t":"string","info":"Discriminator: 'invoice' for standard payables (from Vendor Invoice), 'credit' for credit memos (from Vendor Credit). Determines sign of the AP impact."},{"n":"discount","t":"decimal"},{"n":"dueDate","t":"datetime"},{"n":"lines","r":true,"t":"array","info":"Array of BillLine sub-documents. Each line represents a charge on the bill, which may be non-inventory (services, freight, etc.)."},{"n":"location","t":"entityDetail","re":"Location"},{"n":"paymentMethod","t":"string"},{"n":"purchaseOrder","r":true,"t":"entityRef","re":"Purchase Order"},{"n":"vendorCredit","t":"entityRef","re":"Vendor Credit","info":"Source Vendor Credit when type is 'credit'. Null for invoice-type Bills."},{"n":"vendorInvoice","t":"entityRef","re":"Vendor Invoice","info":"Source Vendor Invoice when type is 'invoice'. Null for credit-type Bills."},{"n":"status","r":true,"t":"string"},{"n":"termsCode","t":"string"},{"n":"totalAmount","r":true,"t":"decimal"},{"n":"vendor","t":"entityRef","re":"Vendor"}],"ext":"TransactionalDocument","related":["Vendor","Vendor Invoice","Vendor Credit","Purchase Order","Location"]},{"name":"Brand","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"Brand or manufacturer label associated with a product.","status":"stub","properties":[{"n":"brandId","r":true,"t":"string"},{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key. Unique within scope."},{"n":"description","t":"string"},{"n":"name","r":true,"t":"string","u":true}],"ext":"LookupEntity","related":["Product"],"bv":{"rules":[],"lifecycle":null,"calculations":[{"name":"productCount","formula":"COUNT(Product WHERE brand = this)","trigger":"query-time"}],"crossEntityConstraints":[]}},{"name":"Business Entity","class":"Core","subsystem":"ALLPOINT","area":"Organization","desc":"Legal business entity within a Company — the registered legal/compliance unit for invoicing, tax, and regulatory purposes.","status":"stub","properties":[{"n":"address","t":"valueType","vt":"Address"},{"n":"businessEntityNo","r":true,"t":"string","u":true,"info":"Human-readable business identifier"},{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"The Company tenant this business entity belongs to. Defines the hard isolation boundary for queries, permissions, replication, and export."},{"n":"legalName","r":true,"t":"string"},{"n":"locations","r":true,"t":"array","re":"Location","info":"Locations operating under this legal entity for tax, invoicing, and regulatory purposes."},{"n":"registrationNumber","t":"string"},{"n":"taxId","t":"string"}],"notes":"A Company can have multiple Business Entities. Each Business Entity is scoped to a Company and optionally associated with specific Locations for tax and invoicing purposes.","related":["Company","Location"],"bv":{"rules":[{"rule":"Must be unique within the Company","when":"always","field":"LegalName","severity":"error"},{"rule":"Must conform to jurisdiction-specific tax ID format","when":"always","field":"TaxId","severity":"warning"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Must reference a valid active Company","entity":"Company"}]},"inheritance":{"extends":"CoreEntity"}},{"name":"Calendar Event","class":"Operational","subsystem":"CONNECT","area":"Messaging","desc":"A platform-native calendar event scoped to the Company tenant. Supports attendees with RSVP status, recurrence rules (RFC 5545 RRULE), and linking to source entities (e.g. a PO review meeting linked to a Purchase Order, a delivery window linked to a Ship Order). Franchise-group scoping controls visibility — users only see events tagged with their assigned franchise groups.","status":"draft","properties":[{"n":"attendees","r":true,"t":"array","info":"Invited users with individual response status."},{"n":"description","t":"string","info":"Event description. Supports markdown formatting."},{"n":"endAt","r":true,"t":"datetime","info":"Event end time in UTC."},{"n":"eventType","r":true,"t":"enumeration","v":["meeting","deadline","delivery","stockTake","shift","custom"],"info":"Classifies the event purpose. Used for filtering and calendar view grouping."},{"n":"isAllDay","r":true,"t":"boolean","info":"Whether this is an all-day event. When true, startAt and endAt represent date boundaries."},{"n":"isCancelled","r":true,"t":"boolean","info":"Soft-cancel flag. Cancelled events remain visible (with strikethrough) but are excluded from reminders and feeds."},{"n":"location","t":"string","info":"Free-text event location. May reference a platform Location name or an external address."},{"n":"organizer","r":true,"t":"entityRef","re":"User","info":"The User who created and owns this event. Only the organizer can edit or cancel."},{"n":"recurrenceRule","t":"string","info":"RFC 5545 RRULE string defining the recurrence pattern (e.g. FREQ=WEEKLY;BYDAY=MO;COUNT=10). Null for one-off events."},{"n":"reminderMinutes","t":"integer","info":"Minutes before the event to send a reminder notification. Null means no reminder."},{"n":"sourceEntity","t":"string","info":"Entity type this event is linked to (e.g. 'Purchase Order', 'Stock Take'). Enables contextual navigation from the calendar to the source document."},{"n":"sourceEntityId","t":"string","info":"ID of the specific entity instance this event is linked to."},{"n":"startAt","r":true,"t":"datetime","info":"Event start time in UTC."},{"n":"title","r":true,"t":"string","info":"Event title displayed in calendar views and notifications."}],"ext":"OperationalDocument","related":["User"],"bv":{"rules":[{"rule":"Must be after startAt","when":"always","field":"EndAt","severity":"error"},{"rule":"All attendees must be active Users within the same Company","when":"always","field":"Attendees","severity":"error"},{"rule":"If set, must be a valid RFC 5545 RRULE string","when":"always","field":"RecurrenceRule","severity":"error"}],"lifecycle":{"states":["Scheduled","Completed","Cancelled"],"transitions":[{"to":"Completed","from":"Scheduled","conditions":["Event endAt has passed or organizer marks complete"]},{"to":"Cancelled","from":"Scheduled","conditions":["Organizer cancels the event"]}],"initialState":"Scheduled"},"calculations":[{"name":"attendeeCount","formula":"COUNT(attendees)","trigger":"query-time"},{"name":"acceptedCount","formula":"COUNT(attendees WHERE status = 'accepted')","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Organizer and all attendees must be active Users within the same Company","entity":"User"},{"rule":"Reminder notifications may use an Email Template for delivery","entity":"Email Template"}]}},{"name":"Catalog","class":"Operational","subsystem":"CONNECT","area":"Products & Pricing","desc":"A curated subset of Products made available to specific markets or customer segments.","status":"stub","properties":[{"n":"catalogId","r":true,"t":"string"},{"n":"markets","r":true,"t":"array","re":"Market"},{"n":"name","r":true,"t":"string"},{"n":"products","r":true,"t":"array","re":"Product"}],"ext":"OperationalDocument","shopify":"Catalog resource","related":["Product","Market"],"bv":{"rules":[],"lifecycle":null,"calculations":[{"name":"productCount","formula":"COUNT(products)","trigger":"query-time"},{"name":"marketCount","formula":"COUNT(markets)","trigger":"query-time"}],"crossEntityConstraints":[]}},{"name":"Chat Channel","class":"Operational","subsystem":"CONNECT","area":"Messaging","desc":"A conversation container scoped to the Company tenant. Channels are segmented by franchiseGroups — users only see channels whose franchise groups overlap with their own assignments. Supports direct (1:1), group, and broadcast channel types.","status":"draft","properties":[{"n":"channelNo","r":true,"t":"integer","info":"Auto-incrementing channel number within the Company."},{"n":"isArchived","r":true,"t":"boolean","info":"Soft-archive flag. Archived channels are hidden from default views but data is retained."},{"n":"name","r":true,"t":"string","info":"Display name for the channel. For direct channels, typically auto-generated from participant names."},{"n":"participants","r":true,"t":"array","re":"User","info":"Users who can view and post in this channel. For broadcast channels, only admins can post."},{"n":"pinnedMessages","r":true,"t":"array","re":"Chat Message","info":"Messages pinned for quick reference. Ordered by pin date descending."},{"n":"topic","t":"string","info":"Channel topic or purpose description. Displayed in channel header."},{"n":"type","r":true,"t":"enumeration","v":["direct","group","broadcast"],"info":"Channel type. direct = 1:1 conversation; group = multi-party; broadcast = admin-post-only announcements."}],"ext":"OperationalDocument","related":["User","Chat Message"],"bv":{"rules":[{"rule":"Direct channels must have exactly two participants","when":"create","field":"Participants","severity":"error"},{"rule":"Group channels must have at least two participants","when":"create","field":"Participants","severity":"error"},{"rule":"Must be unique within the Company for group and broadcast channels","when":"always","field":"Name","severity":"error"}],"lifecycle":{"states":["Active","Archived"],"transitions":[{"to":"Archived","from":"Active","conditions":["Manual archive by channel admin or system policy"]},{"to":"Active","from":"Archived","conditions":["Manual unarchive by channel admin"]}],"initialState":"Active"},"calculations":[{"name":"participantCount","formula":"COUNT(participants)","trigger":"query-time"},{"name":"messageCount","formula":"COUNT(Chat Message WHERE channelId = this)","trigger":"query-time"},{"name":"pinnedMessageCount","formula":"COUNT(pinnedMessages)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"All participants must be active Users within the same Company","entity":"User"},{"rule":"Messages inherit tenant scope from their parent channel","entity":"Chat Message"}]}},{"name":"Chat Message","class":"Transactional","subsystem":"CONNECT","area":"Messaging","desc":"An individual message within a Chat Channel. Supports threaded replies, @mentions, emoji reactions, file attachments, and pinning. Inherits tenant isolation from its parent channel — no franchiseGroups or idmpKey on the message itself.","status":"draft","properties":[{"n":"attachments","r":true,"t":"array","info":"File attachments on this message."},{"n":"body","r":true,"t":"string","info":"Message content. Supports markdown formatting."},{"n":"channelId","r":true,"t":"entityRef","re":"Chat Channel","info":"The channel this message belongs to. Drives tenant isolation — the channel's companyId and franchiseGroups scope access."},{"n":"editedAt","t":"datetime","info":"Timestamp of last edit. Null if never edited."},{"n":"isEdited","r":true,"t":"boolean","info":"Whether the message body has been modified after sending."},{"n":"isPinned","r":true,"t":"boolean","info":"Whether this message is pinned in the channel for quick reference."},{"n":"mentions","r":true,"t":"array","re":"User","info":"Users @mentioned in this message. Drives notification delivery."},{"n":"messageType","r":true,"t":"enumeration","v":["text","system","attachment"],"info":"Message classification. text = user-authored; system = auto-generated (join/leave/archive); attachment = file-only message."},{"n":"reactions","r":true,"t":"array","info":"Emoji reactions aggregated by emoji. Each entry contains the emoji code and the list of Users who reacted."},{"n":"sender","r":true,"t":"entityRef","re":"User","info":"The User who sent this message."},{"n":"sentAt","r":true,"t":"datetime","info":"When the message was sent. Distinct from createdDate to support offline/queued message delivery."},{"n":"threadParentId","t":"entityRef","re":"Chat Message","info":"Reference to the parent message for threaded replies. Null for top-level messages."}],"ext":"OperationalSubDocument","related":["Chat Channel","User"],"bv":{"rules":[{"rule":"Must not be empty for text and system message types","when":"create","field":"Body","severity":"error"},{"rule":"Must have at least one attachment when messageType is 'attachment'","when":"create","field":"Attachments","severity":"error"},{"rule":"If set, must reference a message in the same channel","when":"always","field":"ThreadParentId","severity":"error"},{"rule":"Must be an active participant of the channel","when":"create","field":"Sender","severity":"error"}],"lifecycle":{"states":["Active","Edited","Deleted"],"transitions":[{"to":"Edited","from":"Active","conditions":["Sender modifies the message body"]},{"to":"Edited","from":"Edited","conditions":["Sender modifies the message body again"]},{"to":"Deleted","from":"Active","conditions":["Sender or channel admin deletes the message"]},{"to":"Deleted","from":"Edited","conditions":["Sender or channel admin deletes the message"]}],"initialState":"Active"},"calculations":[{"name":"reactionCount","formula":"SUM(reactions[].users.length)","trigger":"query-time"},{"name":"replyCount","formula":"COUNT(Chat Message WHERE threadParentId = this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Message must belong to a valid, non-deleted channel","entity":"Chat Channel"},{"rule":"Sender must be a participant of the channel. Mentioned users must be in the same Company.","entity":"User"}]}},{"name":"Classification","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"Merchandise hierarchy: Department → Class → Subclass. Carries default tax code and weeks of supply.","status":"draft","properties":[{"n":"class","t":"entityDetail","re":"Classification Class"},{"n":"code","r":true,"t":"string"},{"n":"defaultTaxClass","t":"entityDetail","re":"Tax Class"},{"n":"defaultWeeksOfSupply","t":"integer"},{"n":"department","t":"entityDetail","re":"Classification Department"},{"n":"fullName","t":"string","info":"Calculated field. Concatenation of the department name, class name, and each subclass name (e.g. 'Apparel > Men > Outerwear')."},{"n":"name","r":true,"t":"string","u":true},{"n":"subclasses","r":true,"t":"array","re":"Classification Subclass","info":"All subclasses belonging to this classification node. Each entry is a denormalized entityDetail snapshot."}],"ext":"LookupEntity","related":["Product","Tax Class","Classification Department","Classification Class","Classification Subclass"],"bv":{"rules":[{"rule":"Must be unique within the classification level/type","when":"create","field":"Code","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Cannot delete a Classification that is referenced by Products","entity":"Product"}]}},{"name":"Classification Class","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"Mid level of the merchandise hierarchy, one step below Department (e.g. Mens Shirts, Televisions). Groups related Classification Subclasses for assortment planning.","status":"stub","properties":[],"ext":"TaxonomyEntityNode","related":["Classification","Classification Department"]},{"name":"Classification Department","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"Top level of the merchandise hierarchy (e.g. Apparel, Electronics, Home). Groups related Classification Classes under a single department for reporting and planning.","status":"stub","properties":[],"ext":"TaxonomyEntityNode","related":["Classification"]},{"name":"Classification Subclass","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"Finest level of the merchandise hierarchy, below Class (e.g. Dress Shirts, OLED TVs). Products are assigned at the subclass level for precise categorization.","status":"stub","properties":[],"ext":"TaxonomyEntityNode","related":["Classification","Classification Class"]},{"name":"Client","class":"Core","subsystem":"ACCESS","area":"Infrastructure","desc":"A deployed platform instance — the environment boundary. The Client IS the environment. \"Acme Production\" and \"Acme Sandbox\" are two separate Clients. A Client hosts one or many Companies (tenants). Some Clients are shared infrastructure (multiple tenants), others are dedicated to a single Organization (single tenant). Each Client carries its own OAuth2/OIDC credential sets and scope grants. Auth and permissions are scoped to the Client/Company boundary — a token issued for one Client/Company cannot access resources belonging to another.","status":"draft","properties":[{"n":"clientNo","r":true,"t":"string","u":true,"info":"Human-readable business identifier"},{"n":"companies","r":true,"t":"array","re":"Company","info":"Tenants hosted within this Client"},{"n":"deploymentType","r":true,"t":"enumeration","v":["shared","dedicated"],"info":"shared = multi-tenant infrastructure, dedicated = single Organization"},{"n":"environment","r":true,"t":"entityRef","re":"Environment","info":"The Environment this Client is deployed to (e.g. dev, qa, uat, prod)."},{"k":true,"n":"Id","r":true,"t":"string","info":"Primary identifier (surrogate key)"},{"n":"name","r":true,"t":"string","info":"Display name (e.g., \"APR Cloud US-East Production\")"},{"n":"status","r":true,"t":"enumeration","v":["active","provisioning","maintenance","decommissioned"]}],"service":"APR Access","shopify":"App/Client credentials model","notes":"Canonical subsystem is PLATFORM (Infrastructure). Currently registered under ACCESS pending subsystem reassignment — needs delete-and-recreate to move to PLATFORM.","related":["Company","Environment","Scope"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Company","class":"Core","subsystem":"ALLPOINT","area":"Organization","desc":"The tenant boundary in the platform's multi-tenant architecture. All data — products, inventory, sales, locations, pricing, users, and access control — is isolated within a Company. Each Company belongs to an Organization and scopes commerce operations end-to-end. Each Company lives within a Client (the deployed instance). The Company's deploymentModel (Shared / Dedicated) determines whether the Company shares its Client with other tenants or receives an isolated Client at an increased licensing tier — tenancy is enforced at the Company level, not at the Organization level.","status":"draft","properties":[{"c":true,"n":"applicationCount","t":"integer","info":"Query-time count of Application entities belonging to this Company. Formula: COUNT(Application WHERE company = this). Computed; not stored."},{"n":"client","r":true,"t":"entityRef","re":"Client","info":"The Client (deployed instance) this Company lives within"},{"n":"companyNo","r":true,"t":"string","u":true,"info":"Human-readable business identifier"},{"n":"configurationId","r":true,"t":"entityRef","re":"Config","info":"Reference to this Company's Config record in the CONFIG subsystem (Config Service). The referenced Config entity holds the tenant-scoped settings for this Company — commerce rules, feature flags, integration defaults, currency/timezone/locale, and any other configuration that applies to all data within the Company boundary. Replaces the previous inline `settings` schema and `configuration` valueType: settings now live in the Config Service rather than on the Company entity itself."},{"n":"currency","r":true,"t":"entityDetail","re":"Currency","info":"Tenant-default currency for this Company. All monetary values within the Company boundary (pricing, costs, ledger entries) resolve to this currency unless explicitly overridden by Market, Sales Channel, or transaction-level rules. Modeled as entityDetail (denormalized code/name/alias/sequence) for Company isolation."},{"n":"defaultCostingMethod","r":true,"t":"enumeration","v":["FIFO","LIFO","WAC"],"info":"Tenant-default inventory costing method. FIFO is the platform default per the Stock Ledger Service PRD. Individual item × location pairs may override this via Item Stock.costingMethod; if not overridden, they inherit this value. Changing this after go-live only affects new item-locations; existing positions retain whatever method they were last configured with until an offline replay job is run."},{"n":"deploymentModel","r":true,"t":"enumeration","v":["Shared","Dedicated"],"info":"Shared: this Company runs on multi-tenant infrastructure (a Client hosting multiple tenants). Dedicated: this Company receives an isolated environment (compute, databases, cache) — a Client dedicated to a single tenant — provisioned at an increased licensing tier. Tenancy is enforced at the Company level (the tenant boundary), so deploymentModel is a Company property, not an Organization property. Infrastructure details are managed as Config Service configurations, not as entity properties."},{"n":"locale","r":true,"t":"entityDetail","re":"Locale","info":"Tenant-default locale (BCP 47 language-region tag, e.g. en-US, fr-CA) controlling number/date/currency formatting and the default content language for storefront and back-office UI. Modeled as entityDetail (denormalized code/name/alias/sequence) for Company isolation. May be overridden at Market or Sales Channel level."},{"n":"name","r":true,"t":"string"},{"n":"organization","r":true,"t":"entityRef","re":"Organization","info":"Many-to-one. Each Company belongs to exactly one Organization."},{"n":"status","r":true,"t":"schema","info":"CompanyStatus inline schema capturing current status with audit trail."},{"n":"tier","t":"enumeration","v":["starter","growth","professional","enterprise"]},{"n":"timezone","r":true,"t":"string","info":"IANA timezone identifier (e.g. 'America/New_York', 'Europe/London') used as the Company's default for fiscal-date calculations, scheduled jobs, business-hours logic, and any datetime presentation that doesn't carry its own location-level timezone override. Locations may set their own timezone for store-level operations."},{"c":true,"n":"userCount","t":"integer","info":"Query-time count of User entities belonging to this Company. Formula: COUNT(User WHERE company = this). Computed; not stored."}],"related":["Organization","Client","Location","Market","Business Entity","Application Installation","Connection"],"bv":{"rules":[{"rule":"Must be unique across the platform","when":"create","field":"Name","severity":"error"},{"rule":"Must reference a valid Currency dictionary entry","when":"create","field":"currency","severity":"error"},{"rule":"Must be a valid IANA timezone identifier (e.g. 'America/New_York')","when":"create","field":"timezone","severity":"error"},{"rule":"Must reference a valid Locale dictionary entry (BCP 47 tag, e.g. en-US)","when":"create","field":"locale","severity":"error"}],"lifecycle":null,"calculations":[{"name":"userCount","formula":"COUNT(User WHERE company = this)","trigger":"query-time"},{"name":"applicationCount","formula":"COUNT(Application WHERE company = this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Cannot delete a Company that has active Locations","entity":"Location"},{"rule":"At least one Market must exist before Commerce operations can begin","entity":"Market"}]},"inlineSchemas":[{"name":"CompanyStatus","properties":[{"n":"status","r":true,"t":"enumeration","v":["Draft","Active","Archived"],"info":"Current status of the company."},{"n":"changedBy","t":"string","info":"User who last changed the status."},{"n":"changedDate","t":"datetime","info":"Timestamp of the last status change."}]}],"inheritance":{"extends":"CoreEntity"}},{"name":"Config","class":"Core","subsystem":"CONFIG","area":"Configuration","desc":"A resolved configuration instance for a given Actor and Environment. Derived from a Config Template with overrides applied.","status":"stub","properties":[{"n":"actor","r":true,"t":"entityRef","re":"Actor"},{"n":"configId","r":true,"t":"string"},{"n":"environment","r":true,"t":"entityDetail","re":"Environment"},{"n":"template","r":true,"t":"entityRef","re":"Config Template"},{"n":"values","r":true,"t":"schema"},{"c":true,"n":"version","r":true,"t":"integer"}],"service":"APR Config","shopify":"Shop resource settings / metafield-based config","related":["Config Template","Actor","Environment"],"bv":{"rules":[{"rule":"Must validate against the associated Config Template schema","when":"always","field":"Value","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Must reference a valid Config Template","entity":"Config Template"},{"rule":"Values may be overridden per Environment","entity":"Environment"}]}},{"name":"Config Template","class":"Core","subsystem":"CONFIG","area":"Configuration","desc":"A reusable schema defining the shape and defaults of a Config. Services register expected config structure via templates.","status":"draft","properties":[{"n":"actor","t":"entityRef","re":"Actor"},{"n":"defaultValues","t":"schema"},{"n":"documentType","t":"entityDetail","re":"Document Type"},{"n":"name","r":true,"t":"string"},{"n":"schema","r":true,"t":"schema"},{"n":"templateId","r":true,"t":"string"}],"service":"APR Config","related":["Config","Actor","Document Type"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Templates define the schema that Config instances must conform to","entity":"Config"}]}},{"name":"Connection","class":"Core","subsystem":"CONNECT","area":"Integration","desc":"A Company-scoped configuration that pairs a Source and Destination — each defined by a Connector (what data flows) and an Application Installation (authenticated access) — plus the transformation rules and workflow logic governing how data moves between them. The Connection is the central orchestration entity in CONNECT. Connections reference Application Installations for credentials rather than holding credentials directly. Multiple Connections can share the same Application Installation.","status":"draft","properties":[{"k":true,"n":"Id","r":true,"t":"string","info":"Primary identifier (surrogate key)"},{"n":"connectionNo","r":true,"t":"string","u":true,"info":"Human-readable business identifier"},{"n":"name","r":true,"t":"string","info":"Operator-facing label (e.g., Fulcrum X → Shopify Product Sync)"},{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"The Company tenant this connection belongs to. Defines the hard isolation boundary for queries, permissions, replication, and export."},{"n":"sourceConnector","r":true,"t":"entityRef","re":"Connector","info":"Source integration blueprint (from global registry)"},{"n":"destinationConnector","r":true,"t":"entityRef","re":"Connector","info":"Destination integration blueprint (from global registry)"},{"n":"sourceInstallation","r":true,"t":"entityRef","re":"Application Installation","info":"Authenticated source (credentials, external account)"},{"n":"destinationInstallation","r":true,"t":"entityRef","re":"Application Installation","info":"Authenticated destination (credentials, external account)"},{"n":"entityScope","r":true,"t":"array","info":"Canonical schema entities this Connection covers"},{"n":"direction","r":true,"t":"enumeration","v":["unidirectional","bidirectional"]},{"n":"status","r":true,"t":"enumeration","v":["draft","active","paused","error","archived"]},{"n":"syncMode","r":true,"t":"enumeration","v":["realtime","scheduled","manual"]},{"n":"syncSchedule","t":"string","info":"Cron expression (if scheduled)"},{"n":"workflowRules","t":"schema","info":"Transformation rules, field mappings, filters, conflict resolution"},{"n":"errorPolicy","t":"schema","info":"Retry config, dead-letter behavior, alert thresholds"},{"n":"lastSyncAt","t":"datetime","info":"Last successful sync timestamp"},{"n":"lastSyncStatus","t":"enumeration","v":["success","partial","failed"]},{"n":"createdBy","t":"entityRef","re":"User","info":"User who configured the Connection"},{"n":"createdAt","t":"datetime"}],"related":["Company","Connector","Application Installation"]},{"name":"Connector","class":"Core","subsystem":"CONNECT","area":"Organization","desc":"A source or destination integration blueprint that defines the data exchange capabilities for a system. Each Connector describes what canonical schema entities it supports, what directions it can operate in, and what sync capabilities it has. Connectors exist once in a global registry. Connectors are linked to Applications — the Shopify Connector is linked to the APR Shopify App Application, the Fulcrum X Connector is linked to the Fulcrum X Application. The Connector defines what data can flow; the Application Installation (via Connection) provides the authenticated access.","status":"draft","properties":[{"n":"applicationId","t":"entityRef","re":"Application","info":"FK to Application (the APR app that provides auth and data exchange for this connector)"},{"n":"configuration","r":true,"t":"valueType","info":"Reference to this Connector's configuration record in the CONFIG subsystem. Uses the ConfigurationRef value type (config: entityDetail → Config, templateCode: string). Holds connector-level defaults for supported entities, sync cadence, and capability toggles — distinct from per-Connection runtime settings."},{"n":"connectorNo","r":true,"t":"string","u":true,"info":"Human-readable business identifier"},{"n":"description","t":"string","info":"What this Connector integrates with"},{"k":true,"n":"Id","r":true,"t":"string","info":"Primary identifier (surrogate key)"},{"n":"name","r":true,"t":"string","u":true,"info":"Display name (e.g., Shopify, Fulcrum X). Globally unique"},{"n":"slug","r":true,"t":"string","u":true,"info":"Human-readable key (e.g., shopify, fulcrum-x, netsuite). Globally unique"},{"n":"supportedDirections","r":true,"t":"array","info":"source, destination, or both"},{"n":"supportedEntities","r":true,"t":"array","info":"Canonical schema entities this Connector can source/sink (e.g., Product, Order, Customer)"},{"n":"type","r":true,"t":"enumeration","v":["first_party","commerce_platform","erp","accounting","marketing","loyalty","wms","3pl","shipping","marketplace"],"info":"Integration category"}],"related":["Application","Connection"]},{"name":"Cost Adjustment","class":"Transactional","subsystem":"CONNECT","area":"Products & Pricing","desc":"Transactional document recording a cost change applied to items for a specific vendor. Writes entries to the Cost Ledger. Standard in retail merchandising systems for managing vendor cost lifecycle events. Distinct from option-level vendorCostModifier deltas on OptionValues which are relative and non-transactional.","status":"stub","properties":[{"n":"adjustmentId","r":true,"t":"string"},{"n":"costLevel","t":"entityDetail","re":"Cost Level"},{"n":"currency","r":true,"t":"enumeration","v":["USD","CAD","EUR","GBP","AUD","NZD","MXN","JPY","CNY","INR","BRL","CHF","SEK","NOK","DKK","SGD","HKD","KRW","ZAR","AED"],"info":"ISO 4217 currency code for this cost adjustment."},{"n":"item","r":true,"t":"entityRef","re":"Item"},{"n":"newCost","r":true,"t":"decimal"},{"n":"oldCost","r":true,"t":"decimal"},{"n":"reason","t":"string"},{"n":"vendor","r":true,"t":"entityRef","re":"Vendor"}],"ext":"TransactionalDocument","related":["Product","Item","Vendor","Cost Level","Cost Ledger"],"bv":{"rules":[{"rule":"Must be >= 0","when":"always","field":"NewCost","severity":"error"},{"rule":"Must differ from OldCost","when":"submit","field":"NewCost","severity":"error"},{"rule":"Must not be in the past unless backdating is enabled in Company settings","when":"create","field":"EffectiveDate","severity":"warning"}],"lifecycle":{"states":["Draft","Approved","Posted","Cancelled"],"transitions":[{"to":"Approved","from":"Draft","conditions":["At least one item line exists","Vendor is valid and active","NewCost differs from OldCost"]},{"to":"Posted","from":"Approved","conditions":["Cost Ledger entries written","Item cost updated at the specified Cost Level"]},{"to":"Cancelled","from":"Draft","conditions":[]},{"to":"Cancelled","from":"Approved","conditions":["Not yet posted"]}],"initialState":"Draft"},"calculations":[{"name":"CostDelta","formula":"NewCost - OldCost","trigger":"On cost value change"},{"name":"CostDeltaPct","formula":"((NewCost - OldCost) / OldCost) × 100","trigger":"On cost value change"}],"crossEntityConstraints":[{"rule":"Must reference a valid active Vendor","entity":"Vendor"},{"rule":"Must reference a valid active Item","entity":"Item"},{"rule":"Must reference a valid Cost Level if provided","entity":"Cost Level"},{"rule":"Posting writes an immutable Cost Ledger entry recording the old→new cost change","entity":"Cost Ledger"}]}},{"name":"Cost Ledger","class":"Ledger","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"Immutable chronological audit log of vendor unit cost changes. Records entries for both Product/Vendor and Item/Vendor relationships at each cost level. Functions identically to the Price Ledger but for vendor purchase costs rather than retail prices.","status":"stub","properties":[{"n":"costLevel","t":"entityDetail","re":"Cost Level","info":"Cost level this entry applies to (parallels PriceLevel on Price Ledger)"},{"n":"item","t":"entityRef","re":"Item","info":"Item-level cost entry (for Item/Vendor granularity); mutually exclusive with Product"},{"c":true,"n":"ledgerLine","r":true,"t":"integer"},{"n":"product","t":"entityRef","re":"Product","info":"Product-level cost entry (for Product/Vendor granularity); mutually exclusive with Item"},{"n":"unitCost","r":true,"t":"decimal","info":"The new vendor unit cost for this item"},{"n":"vendor","r":true,"t":"entityRef","re":"Vendor","info":"The vendor whose unit cost is being recorded"}],"ext":"LedgerEntry","notes":"Mirrors the Price Ledger pattern. Each entry records a vendor unit cost change at either the Product/Vendor or Item/Vendor level for a given cost level.","related":["Vendor","Product","Item","Cost Level"],"bv":{"rules":[{"rule":"Append-only — existing entries must not be modified or deleted","when":"always","field":"LedgerLine","severity":"error"},{"rule":"Must be non-negative","when":"create","field":"UnitCost","severity":"error"},{"rule":"Exactly one of Product or Item must be set per entry","when":"create","field":"Product|Item","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Product-level entries track vendor cost per Product × Vendor × CostLevel","entity":"Product"},{"rule":"Item-level entries track vendor cost per Item × Vendor × CostLevel","entity":"Item"},{"rule":"Must reference a valid Cost Level","entity":"Cost Level"}]}},{"name":"Cost Level","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"Tier or level at which product costs are defined (e.g. by vendor, by region, by channel). Extends LookupEntity — no additional fields beyond the base.","status":"stub","properties":[],"ext":"LookupEntity","related":["Market","Vendor","Item"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Country","class":"Dictionary","subsystem":"CONNECT","area":"Organization","desc":"ISO 3166 country reference. Used for country of origin on products, address countries, tax jurisdictions, and regulatory compliance. Pure LookupEntity — no additional fields beyond the base.","status":"stub","properties":[],"ext":"LookupEntity","related":["Product"]},{"name":"Coupon","class":"Operational","subsystem":"CONNECT","area":"Promotions","desc":"A unique or generic code that customers redeem to activate a Promotion or Discount.","status":"stub","properties":[{"n":"code","r":true,"t":"string","u":true},{"n":"couponId","r":true,"t":"string"},{"n":"expiryDate","t":"datetime"},{"n":"promotion","r":true,"t":"entityRef","re":"Promotion"},{"c":true,"n":"usageCount","t":"integer"},{"n":"usageLimit","t":"integer"}],"shopify":"DiscountCode resource","related":["Promotion","Sales Order"],"bv":{"rules":[],"lifecycle":null,"calculations":[{"name":"usageCount","formula":"COUNT(Sales Order WHERE coupons CONTAINS this)","trigger":"query-time"}],"crossEntityConstraints":[]}},{"name":"Currency","class":"Dictionary","subsystem":"ALLPOINT","area":"Organization","desc":"ISO 4217 currency definition. Defines the currencies available for use across the platform — pricing, costing, transactions, and vendor payments. Each currency carries its code (e.g. USD, EUR, GBP), display symbol, and decimal precision. Referenced by Market.baseCurrency, Location.baseCurrency, Vendor.orderCurrency, and monetary fields throughout the system.","status":"draft","properties":[{"n":"symbol","r":true,"t":"string","info":"Display symbol for the currency (e.g. $, €, £, ¥)."},{"n":"decimalPrecision","r":true,"t":"integer","info":"Number of decimal places for monetary amounts in this currency. Typically 2 (USD, EUR) or 0 (JPY, KRW)."},{"n":"isoNumericCode","t":"string","info":"ISO 4217 three-digit numeric code (e.g. 840 for USD, 978 for EUR)."}],"ext":"LookupEntity","related":["Market","Location","Vendor"]},{"name":"Custom Unit","class":"Dictionary","subsystem":"CONFIG","area":"Configuration","desc":"A user-defined unit of measure or business-specific enumeration extending platform defaults for a given Company or Market.","status":"stub","properties":[{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key. Unique within scope."},{"n":"name","r":true,"t":"string","u":true},{"n":"unitId","r":true,"t":"string"}],"service":"APR Config","shopify":"weight_unit on products/variants","related":["Company","Market","Item"],"bv":{"rules":[{"rule":"Must be unique within the Company","when":"create","field":"Code","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Customer","class":"Operational","subsystem":"CONNECT","area":"Customers","desc":"A person or account that purchases goods. Carries credit profile and purchase history.","status":"draft","properties":[{"n":"anniversaryDate","t":"datetime","info":"Customer anniversary date for marketing."},{"n":"billToAddresses","r":true,"t":"array","info":"Billing addresses."},{"n":"birthday","t":"datetime","info":"Customer birthday for marketing and age verification."},{"n":"contacts","r":true,"t":"array","info":"Customer contacts."},{"n":"creditLimit","t":"decimal"},{"n":"customerNo","r":true,"t":"integer"},{"n":"emails","r":true,"t":"array","info":"Customer email addresses with communication preferences."},{"n":"firstName","t":"string"},{"n":"gender","t":"string","info":"Customer gender."},{"n":"isActive","r":true,"t":"boolean","info":"Whether the customer account is active."},{"n":"isCompany","r":true,"t":"boolean","info":"Whether this customer represents a company/organization."},{"n":"isEmployee","r":true,"t":"boolean","info":"Whether this customer is also an employee."},{"n":"isTaxExempt","r":true,"t":"boolean","info":"Whether the customer is exempt from tax."},{"n":"isWholesale","r":true,"t":"boolean","info":"Whether this is a wholesale customer."},{"n":"lastName","t":"string"},{"n":"loyaltyPrograms","r":true,"t":"array","re":"Loyalty Program","info":"Loyalty program enrollments with points balances."},{"n":"memberships","r":true,"t":"array","info":"Customer membership records with type and expiration."},{"n":"middleName","t":"string","info":"Customer middle name."},{"n":"organization","t":"string","info":"Company or organization name if isCompany is true."},{"n":"phones","r":true,"t":"array","info":"Customer phone numbers with communication preferences."},{"n":"postalAddresses","r":true,"t":"array","info":"Postal addresses."},{"n":"rfmGroup","t":"entityDetail","re":"RFM Group","info":"Customer's current RFM (Recency, Frequency, Monetary) segmentation group. Computed by analytics from purchase history and used to drive targeted marketing, retention, and service tiering."},{"n":"shipToAddresses","r":true,"t":"array","info":"Shipping addresses."},{"n":"title","t":"string","info":"Name title/prefix (e.g. Mr, Mrs, Dr)."}],"ext":"OperationalDocument","shopify":"Customer resource","related":["Sales Order","Customer Credit","Sale"],"bv":{"rules":[{"rule":"Must be a valid email format and unique within the Company if provided","when":"always","field":"Email","severity":"error"},{"rule":"Must be >= 0 if provided","when":"always","field":"CreditLimit","severity":"error"}],"lifecycle":{"states":["Active","Inactive","Archived"],"transitions":[{"to":"Inactive","from":"Active","conditions":["No open orders"]},{"to":"Active","from":"Inactive","conditions":[]},{"to":"Archived","from":"Inactive","conditions":["No outstanding balance","Meets data-retention policy"]}],"initialState":"Active"},"calculations":[{"name":"salesOrderCount","formula":"COUNT(Sales Order WHERE customer = this)","trigger":"query-time"},{"name":"saleCount","formula":"COUNT(Sale WHERE customer = this)","trigger":"query-time"},{"name":"creditCount","formula":"COUNT(Customer Credit WHERE customer = this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Cannot archive a Customer with open Sales Orders","entity":"Sales Order"},{"rule":"Archiving a Customer does not expire their Gift Cards","entity":"Gift Card"}]},"inlineSchemas":[{"name":"CustomerMembership","extends":"OperationalSubDocument","properties":[{"info":"Date the membership expires.","name":"expirationDate","type":"datetime"},{"info":"Type or level of the membership.","name":"membershipType","type":"string","required":true},{"info":"Date the membership started.","name":"startDate","type":"datetime"}]},{"name":"CustomerLoyaltyProgram","extends":"OperationalSubDocument","properties":[{"info":"Date the customer enrolled in this loyalty program.","name":"enrolledDate","type":"datetime"},{"info":"Reference to the Loyalty Program.","name":"loyaltyProgramId","type":"string","required":true},{"info":"Current points balance in this program.","name":"pointsBalance","type":"integer","required":true},{"info":"Current loyalty tier within the program.","name":"tierId","type":"string"}]}]},{"name":"Customer Credit","class":"Balance","subsystem":"CONNECT","area":"Payments & Stored Value","desc":"Monetary credit balance held on a customer account for future purchases or refunds.","status":"stub","properties":[{"n":"balance","r":true,"t":"decimal"},{"n":"creditId","r":true,"t":"string"},{"n":"customer","r":true,"t":"entityRef","re":"Customer"},{"n":"expiryDate","t":"date"}],"shopify":"Store credit / Refund adjustment","related":["Customer","Sale"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Dashboard","class":"Core","subsystem":"CONNECT","area":"Analytics","desc":"A configured dashboard composed of one or more Report widgets. Provides a consolidated view of analytics across domains. Dashboards are scoped by franchise group and can be shared or personal.","status":"stub","properties":[{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key. Unique within scope."},{"n":"name","r":true,"t":"string"}],"ext":"BaseDocument","related":["Report"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Discount","class":"Dictionary","subsystem":"CONNECT","area":"Promotions","desc":"A price reduction rule (%, fixed amount, BOGO, etc.) referenced by Promotions and applied at Sale.","status":"stub","properties":[{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key. Unique within scope."},{"n":"discountId","r":true,"t":"string"},{"n":"name","r":true,"t":"string","u":true}],"shopify":"Discount types in GraphQL","related":["Promotion","Price Rule"],"bv":{"rules":[{"rule":"Must be before EndDate if both are set","when":"always","field":"StartDate","severity":"error"}],"lifecycle":{"states":["Draft","Active","Expired","Archived"],"transitions":[{"to":"Active","from":"Draft","conditions":["Start date reached or manually activated"]},{"to":"Expired","from":"Active","conditions":["End date passed"]},{"to":"Archived","from":"Active","conditions":[]},{"to":"Archived","from":"Expired","conditions":[]}],"initialState":"Draft"},"calculations":[{"name":"promotionCount","formula":"COUNT(Promotion WHERE discounts CONTAINS this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Applied discount amount recorded on Sale line","entity":"Sale"}]}},{"name":"Document Custom Data Config","class":"Core","subsystem":"CONNECT","area":"Platform","desc":"Metadata definition for extensible custom fields attached to documents. Configures field type, label, allowed values, and the target document type (32+ supported). System-wide schema extension mechanism.","status":"stub","properties":[{"n":"availableValues","r":true,"t":"array","info":"Allowed values for lookup or multiselect field types."},{"n":"fieldType","r":true,"t":"enumeration","v":["text","longtext","integer","decimal","date","flag","lookup","multiselect"]},{"n":"isDeleted","r":true,"t":"boolean"},{"n":"label","r":true,"t":"string","info":"User-facing display label."},{"n":"relatedDocumentType","r":true,"t":"enumeration","v":["PurchaseOrder","SalesOrder","TransferOrder","StockAdjustment","StockTake","GoodsReceipt","Sale","Bill","VendorInvoice","VendorCredit"],"info":"The document type this custom field attaches to (e.g. PurchaseOrder, SalesReceipt, Vendor, Customer)."},{"n":"sequence","t":"integer","info":"Display ordering hint."},{"n":"sourceFieldName","r":true,"t":"string","info":"Underlying field key in the custom data payload."}],"notes":"Supports 32+ document types including: PurchaseOrder, SalesOrder, SalesReceipt, Vendor, Customer, Employee, Location, StockTransfer, and more.","related":[]},{"name":"Document Type","class":"Dictionary","subsystem":"CONFIG","area":"Configuration","desc":"Defines a category of structured document managed within the platform (PO, ASN, Invoice).","status":"stub","properties":[{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key. Unique within scope."},{"n":"configuration","r":true,"t":"valueType","info":"Reference to this Document Type's policy configuration in the CONFIG subsystem. Uses the ConfigurationRef value type (config: entityDetail → Config, templateCode: string). Acts as the policy anchor for every document of this type — e.g., the Purchase Order Document Type's Config holds rules like autoExpireDaysAfterCancellation, requireApprovalOverAmount, numberingPrefix. Concrete documents (Purchase Order, Bill, ASN, Vendor Invoice) resolve their effective policy at runtime by reading documentType.configuration.config rather than carrying per-document overrides. The templateCode field names the Config Template that defines which policy keys are valid for this Document Type."},{"n":"description","t":"string"},{"n":"documentTypeId","r":true,"t":"string"},{"n":"name","r":true,"t":"string","u":true},{"n":"processingRules","t":"schema"},{"n":"schema","t":"schema"}],"service":"APR Config","related":["Config Template"],"bv":{"rules":[{"rule":"Must be unique","when":"create","field":"Code","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Email Log","class":"Ledger","subsystem":"CONNECT","area":"Messaging","desc":"Immutable delivery audit trail. One record per email sent via AWS SES. Tracks the full delivery lifecycle from queue through delivery, bounce, or complaint. Records are append-only — status updates are written as new action entries, not field overwrites. Links back to the triggering entity and template for traceability. Inherited sourceEntityType/sourceEntityId identify the entity that triggered the email (e.g. 'Sales Order' / orderId).","status":"draft","properties":[{"n":"bounceReason","t":"string","info":"SES bounce diagnostic message. Only populated when status is bounced."},{"n":"bounceType","t":"enumeration","v":["hard","soft","undetermined"],"info":"Bounce classification. Hard bounces trigger recipient suppression. Only set when status is bounced."},{"n":"deliveredAt","t":"datetime","info":"SES delivery confirmation timestamp. Null until delivery is confirmed."},{"n":"messageId","r":true,"t":"string","info":"SES Message-ID returned on send. Used for delivery status tracking via SES notifications."},{"n":"recipientEmail","r":true,"t":"string","info":"Destination email address."},{"n":"recipientUser","t":"entityRef","re":"User","info":"User reference if the recipient is a platform user. Null for external recipients (e.g. vendor contacts)."},{"n":"sentAt","t":"datetime","info":"When the email was handed to SES for delivery. Null while status is queued."},{"n":"status","r":true,"t":"schema","info":"Delivery status of the email. Tracks current state and who/when it was last changed."},{"n":"subject","r":true,"t":"string","info":"Rendered subject line after variable interpolation."},{"n":"template","t":"entityRef","re":"Email Template","info":"The Email Template used to render this email. Null for ad-hoc emails not based on a template."}],"ext":"LedgerEntry","related":["Email Template","User"],"bv":{"rules":[{"rule":"Must be unique — one log entry per SES send","when":"create","field":"MessageId","severity":"error"},{"rule":"Transitions are append-only — status can only move forward, never backward","when":"always","field":"Status","severity":"error"}],"lifecycle":{"states":["Queued","Sent","Delivered","Bounced","Complained","Failed"],"transitions":[{"to":"Sent","from":"Queued","conditions":["SES accepts the send request"]},{"to":"Failed","from":"Queued","conditions":["SES rejects the send request (invalid address, suppressed recipient)"]},{"to":"Delivered","from":"Sent","conditions":["SES delivery notification received"]},{"to":"Bounced","from":"Sent","conditions":["SES bounce notification received"]},{"to":"Complained","from":"Sent","conditions":["SES complaint notification received (recipient marked as spam)"]}],"initialState":"Queued"},"calculations":[],"crossEntityConstraints":[{"rule":"If template is set, the template must exist and be Active at send time","entity":"Email Template"},{"rule":"If recipientUser is set, must reference a valid User within the same Company","entity":"User"}]},"inlineSchemas":[{"name":"EmailLogStatus","schema":"schemas/email-log/EmailLogStatus.ts","properties":[{"info":"Current delivery status. Updated via SES notification webhooks.","name":"status","type":"enumeration","values":["queued","sent","delivered","bounced","complained","failed"],"required":true},{"info":"User or system actor who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"}]}]},{"name":"Email Template","class":"Operational","subsystem":"CONNECT","area":"Messaging","desc":"A reusable, tenant-scoped email layout with variable placeholders for merge-field interpolation. Templates define the subject, HTML body, plain-text fallback, and sender identity. Each template can be linked to an entity state transition to trigger automated transactional emails (e.g. Sales Order → Completed fires an order-confirmation email via AWS SES).","status":"draft","properties":[{"n":"bodyHtml","r":true,"t":"string","info":"HTML email body with {{variable}} placeholders for merge-field interpolation."},{"n":"bodyText","t":"string","info":"Plain-text fallback body. Used when the recipient's client does not render HTML."},{"n":"fromEmail","r":true,"t":"string","info":"Sender email address. Must be a verified identity in the Company's SES configuration."},{"n":"fromName","r":true,"t":"string","info":"Sender display name shown in the From header."},{"n":"isActive","r":true,"t":"boolean","info":"Whether this template is currently in use. Inactive templates are skipped by the delivery pipeline."},{"n":"subject","r":true,"t":"string","info":"Email subject line. Supports {{variable}} interpolation."},{"n":"templateCode","r":true,"t":"string","info":"Machine-readable template key (e.g. order-confirmation, po-submitted). Unique within the Company."},{"n":"triggerEntity","t":"string","info":"Entity name whose state transition fires this template (e.g. 'Sales Order'). Null for manually-triggered emails."},{"n":"triggerState","t":"string","info":"The lifecycle state transition that triggers delivery (e.g. 'Completed'). Used with triggerEntity to wire automatic sends."},{"n":"variables","r":true,"t":"array","info":"Documents available merge fields for this template."}],"ext":"OperationalDocument","related":["Email Log"],"bv":{"rules":[{"rule":"Must be unique within the Company and follow kebab-case naming","when":"always","field":"TemplateCode","severity":"error"},{"rule":"Must be a verified sender identity in the Company's SES configuration","when":"always","field":"FromEmail","severity":"error"},{"rule":"All {{variable}} placeholders must have a matching entry in the variables array","when":"always","field":"BodyHtml","severity":"warning"}],"lifecycle":{"states":["Draft","Active","Inactive"],"transitions":[{"to":"Active","from":"Draft","conditions":["Subject and bodyHtml are non-empty","fromEmail is a verified SES identity"]},{"to":"Inactive","from":"Active","conditions":["Manual deactivation or replacement by newer template"]},{"to":"Active","from":"Inactive","conditions":["Re-activation by admin"]},{"to":"Draft","from":"Active","conditions":["Template pulled back for revision"]}],"initialState":"Draft"},"calculations":[{"name":"sendCount","formula":"COUNT(Email Log WHERE template = this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Each sent email creates an immutable Email Log entry referencing this template","entity":"Email Log"}]}},{"name":"Employee","class":"Operational","subsystem":"CONNECT","area":"Organization","desc":"A staff member with login credentials, a role, a default location, and an hourly rate.","status":"draft","properties":[{"n":"address","t":"valueType","vt":"Address","info":"Employee's personal address."},{"n":"changePasswordOnNextLogin","r":true,"t":"boolean","info":"Flag requiring password change on next login."},{"n":"email","r":true,"t":"string","u":true},{"n":"employeeId","r":true,"t":"string"},{"n":"employeeNo","t":"string","info":"Human-readable employee number."},{"n":"expirationDate","t":"datetime","info":"Account expiration date. Null for non-expiring accounts."},{"n":"firstName","r":true,"t":"string"},{"n":"functions","r":true,"t":"array","v":["Buyer","Cashier","SalesPerson","StockHandler","Manager"],"info":"Operational functions this employee can perform. Used to filter employee references on transactional documents (e.g. PO → Buyer, Sale → Cashier / SalesPerson)."},{"n":"hourlyRate","t":"decimal"},{"n":"isManager","r":true,"t":"boolean","info":"Whether this employee has manager-level privileges."},{"n":"lastName","r":true,"t":"string"},{"n":"locations","r":true,"t":"array","re":"Location","info":"Array of EmployeeLocation sub-documents. Each entry references a Location with an isPrimary flag."},{"n":"middleName","t":"string"},{"n":"nickName","t":"string","info":"Preferred display name."},{"n":"passwordChangeDate","t":"datetime","info":"When the password was last changed."},{"n":"phone","t":"string"},{"n":"role","t":"entityDetail","re":"Employee Role"},{"n":"salesPersonType","t":"enumeration","v":["Location","Global"],"info":"Applicable when Functions includes 'SalesPerson'. Location-scoped reps sell at a single site; Global reps operate across all locations."},{"n":"status","r":true,"t":"schema","info":"EmployeeStatus inline schema capturing current document status."},{"n":"type","r":true,"t":"enumeration","v":["Location","Global"],"info":"Location: assigned to specific locations. Global: operates across all locations."},{"n":"username","r":true,"t":"string","info":"Login username for the employee."}],"ext":"OperationalDocument","shopify":"Staff / User resource","related":["Employee Role","Location","User","Purchase Order","Sale","Sales Order"],"bv":{"rules":[],"lifecycle":{"states":["Draft","Active","Archived"],"transitions":[{"to":"Active","from":"Draft","conditions":["Location assigned"]},{"to":"Archived","from":"Active","conditions":[]}],"initialState":"Draft"},"calculations":[],"crossEntityConstraints":[{"rule":"Must be assigned to a valid active Location","entity":"Location"},{"rule":"Cannot archive Employee with Sales in the current fiscal period","entity":"Sale"}]},"inlineSchemas":[{"name":"EmployeeStatus","extends":"OperationalSubDocument","properties":[{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"},{"info":"Current document lifecycle status of the employee.","name":"documentStatus","type":"enumeration","values":["Draft","Active","Archived"],"required":true}]},{"name":"EmployeeLocation","extends":"OperationalSubDocument","properties":[{"info":"Whether this is the employee's primary location.","name":"isPrimary","type":"boolean","required":true},{"info":"Reference to the assigned Location.","name":"location","type":"entityDetail","relatedEntity":"Location"}]}]},{"name":"Employee Role","class":"Dictionary","subsystem":"CONNECT","area":"Organization","desc":"Named role or job function assigned to employees defining their responsibilities and access.","status":"stub","properties":[{"n":"description","t":"string"},{"n":"name","r":true,"t":"string","u":true},{"n":"permissions","r":true,"t":"array"},{"n":"roleId","r":true,"t":"string"}],"ext":"LookupEntity","related":["Employee"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Entity Action Log","class":"Ledger","subsystem":"CONNECT","area":"Platform","desc":"Immutable, append-only audit trail of all actions performed on any entity in the system. Each record captures a single action — either a field-level change (FieldChange) or a domain operation (Operation) — along with the actor, timestamp, and optional before/after values. Entities carry a denormalized recentActions array (capped at the last 5 entries) for quick display; this ledger is the authoritative system of record for the full history. Inherited entryDate maps to the action timestamp; sourceEntityType/sourceEntityId identify the target entity. Inherited createdBy maps to the acting user.","status":"draft","properties":[{"n":"actionType","r":true,"t":"enumeration","v":["FieldChange","Operation"],"info":"Discriminator: FieldChange for property-level mutations, Operation for domain events (e.g. status transition, approval, publish)."},{"n":"changes","r":true,"t":"array","info":"Structured list of field-level changes that occurred as part of this action. For FieldChange actions, contains one or more entries detailing each property that was modified. For Operation actions, the array may be empty when the operation has no field-level side effects (e.g. 'print', 'export')."},{"n":"code","r":true,"t":"string","info":"Stable, machine-readable action identifier (e.g. 'status.transition', 'price.update', 'approve', 'line.add'). Provides an enumerable key for building reports, dashboards, and automation triggers without parsing the description or deriving intent from actionType alone. While actionType captures the category, code captures the semantic intent of the action."},{"n":"description","r":true,"t":"string","info":"Human-readable summary of what happened and why (e.g. 'Status changed from Draft to Active', 'Approved by manager', 'Quantity updated on 3 lines'). Serves as the display text in activity feeds and audit UIs."},{"n":"entityNo","t":"string","info":"Human-readable identifier from the source entity (e.g. productNo, salesOrderNo) at the time of the action. Denormalized for display without a join."},{"n":"metadata","t":"object","info":"Freeform context about the action. May include source IP, originating system, batch ID, or other operational context."},{"n":"subDocumentId","t":"uuid","info":"UUID of the sub-document/line item the action was performed on (e.g. a Purchase Order Line, Shipment Carton). Null when the action is at the header/entity level."},{"n":"subDocumentType","t":"string","info":"Sub-document type name (e.g. 'PurchaseOrderLine', 'ShipmentCarton'). Null when the action is at the header/entity level. Used alongside subDocumentId for indexed lookups."}],"ext":"LedgerEntry","related":[],"bv":{"rules":[{"rule":"When actionType is FieldChange, the changes array must contain at least one entry","when":"create","field":"actionType","severity":"error"},{"rule":"Must reference an existing entity of the specified entityType","when":"create","field":"entityId","severity":"error"},{"rule":"Records are immutable once written — no updates or deletes permitted","when":"always","field":"*","severity":"error"}],"calculations":[],"crossEntityConstraints":[]},"inlineSchemas":[{"name":"ActionChange","schema":"schemas/common/ActionChange.ts","properties":[{"info":"The property name that was modified (e.g. 'status', 'quantity', 'unitPrice').","name":"fieldName","type":"string","required":true},{"info":"Serialized new value after the change. Null when a property was removed/cleared.","name":"newValue","type":"string"},{"info":"Serialized old value before the change. Null when a property was set for the first time.","name":"previousValue","type":"string"}]}]},{"name":"Environment","class":"Dictionary","subsystem":"ACCESS","area":"Infrastructure","desc":"A named deployment context that scopes configuration, feature flags, and access control. Environments are dictionaries — they define the set of valid deployment targets rather than tracking runtime state. Typical values: dev (Development), qa (Quality Assurance), uat (User Acceptance Testing), prod (Production). A Client references an Environment to declare what context it operates in.","status":"draft","properties":[{"n":"code","r":true,"t":"string","info":"Human-readable key. Unique within scope."},{"n":"description","t":"string"},{"n":"name","r":true,"t":"string","u":true}],"service":"APR Access","ext":"LookupEntity","notes":"Canonical subsystem is PLATFORM (Infrastructure). Currently registered under ACCESS pending subsystem reassignment — needs delete-and-recreate to move to PLATFORM.","related":["Client"],"bv":{"rules":[{"rule":"Must be unique within the Company","when":"create","field":"Code","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Franchise Group","class":"Dictionary","subsystem":"CONNECT","area":"Organization","desc":"Organizational grouping of retail locations under a common franchise operator or ownership entity.","status":"draft","properties":[{"n":"billingAddress","t":"valueType","vt":"Address"},{"n":"code","r":true,"t":"string"},{"n":"description","t":"string"},{"n":"name","r":true,"t":"string","u":true},{"n":"postalAddress","t":"valueType","vt":"Address"}],"ext":"LookupEntity","related":["Location","Company"],"bv":{"rules":[{"rule":"Must be unique across the Company","when":"create","field":"Code","severity":"error"}],"lifecycle":null,"calculations":[{"name":"locationCount","formula":"COUNT(Location WHERE franchiseGroups CONTAINS this)","trigger":"query-time"},{"name":"userCount","formula":"COUNT(User WHERE franchiseGroups CONTAINS this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Cannot delete while Locations are assigned","entity":"Location"}]}},{"name":"Fulfillment","class":"Transactional","subsystem":"CONNECT","area":"Sales & Orders","desc":"Process of picking, packing, and shipping items to fulfill a customer order.","status":"stub","properties":[{"n":"fulfillmentId","r":true,"t":"string"},{"n":"fulfillmentOrder","r":true,"t":"entityRef","re":"Fulfillment Order"},{"n":"lines","r":true,"t":"array","info":"Array of FulfillmentLine sub-documents. Each line records the quantity fulfilled for an item."}],"ext":"TransactionalDocument","shopify":"Fulfillment resource","related":["Fulfillment Order","Shipment"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Fulfillment Order","class":"Order","subsystem":"CONNECT","area":"Sales & Orders","desc":"Request or instruction to fulfill specific items from an order, potentially routed to different locations.","status":"stub","properties":[{"n":"fulfillmentOrderId","r":true,"t":"string"},{"n":"lines","r":true,"t":"array","info":"Array of FulfillmentOrderLine sub-documents. Each line tracks pick/pack progress for an item."},{"n":"location","r":true,"t":"entityDetail","re":"Location"},{"n":"salesOrder","r":true,"t":"entityRef","re":"Sales Order"}],"ext":"OperationalDocument","shopify":"FulfillmentOrder resource","related":["Sales Order","Location","Fulfillment","Ship Order"],"bv":{"rules":[],"lifecycle":{"states":["New","InProgress","Fulfilled","Cancelled"],"transitions":[{"to":"InProgress","from":"New","conditions":["Items being picked"]},{"to":"Fulfilled","from":"InProgress","conditions":["All items packed and shipped/ready"]},{"to":"Cancelled","from":"New","conditions":[]}],"initialState":"New"},"calculations":[{"name":"lineCount","formula":"COUNT(lines)","trigger":"query-time"},{"name":"shipOrderCount","formula":"COUNT(Ship Order WHERE fulfillmentOrder = this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Must reference a valid Sales Order","entity":"Sales Order"},{"rule":"Generates Shipment records for delivery","entity":"Shipment"}]}},{"name":"Gift Card","class":"Balance","subsystem":"CONNECT","area":"Payments & Stored Value","desc":"A stored-value instrument issued and redeemed at Sale.","status":"stub","properties":[{"n":"balance","r":true,"t":"decimal"},{"n":"expiryDate","t":"date"},{"n":"giftCardNo","r":true,"t":"string"},{"n":"issuedDate","r":true,"t":"date"}],"shopify":"GiftCard resource","related":["Customer","Sale"],"bv":{"rules":[{"rule":"Must be >= 0 at all times","when":"always","field":"Balance","severity":"error"},{"rule":"Must be after IssuedDate","when":"create","field":"ExpiryDate","severity":"error"}],"lifecycle":{"states":["Active","Depleted","Expired","Cancelled"],"transitions":[{"to":"Depleted","from":"Active","conditions":["Balance reaches zero"]},{"to":"Expired","from":"Active","conditions":["ExpiryDate passed"]},{"to":"Cancelled","from":"Active","conditions":["Manual cancellation"]}],"initialState":"Active"},"calculations":[],"crossEntityConstraints":[{"rule":"Payment against Gift Card must not exceed current balance","entity":"Payment"}]}},{"name":"Goods Receipt","class":"Transactional","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"Confirms physical receipt of goods against a PO or ASN. Triggers inventory increase.","status":"draft","properties":[{"n":"asn","t":"entityRef","re":"Advanced Shipping Notice"},{"n":"fees","r":true,"t":"array","info":"Receipt-level fees."},{"n":"fiscalDate","t":"datetime","info":"Fiscal date for accounting period assignment."},{"n":"isMatched","r":true,"t":"boolean","info":"Whether this receipt has been matched to a purchase order for three-way matching."},{"n":"lines","r":true,"t":"array","info":"Array of GoodsReceiptLine sub-documents. Each line records a received Item, quantity, and cost against a Purchase Order."},{"n":"location","r":true,"t":"entityDetail","re":"Location"},{"n":"postedDate","t":"datetime","info":"Date the receipt was posted and inventory updated."},{"n":"purchaseOrder","r":true,"t":"entityRef","re":"Purchase Order"},{"n":"purchaseReceiptDate","r":true,"t":"datetime","info":"Date the goods were physically received."},{"n":"purchaseReceiptNo","r":true,"t":"string"},{"n":"reversedPurchaseReceiptId","t":"string","info":"ID of the receipt this reverses, if this is a reversal."},{"n":"reversingPurchaseReceiptId","t":"string","info":"ID of the reversal receipt, if this receipt has been reversed."},{"n":"status","r":true,"t":"schema","info":"GoodsReceiptStatus inline schema with documentStatus and operationalStatus."},{"n":"type","r":true,"t":"enumeration","v":["Purchase","Return"],"info":"Receipt type — Purchase for incoming goods, Return for vendor returns."},{"n":"vendor","r":true,"t":"entityDetail","re":"Vendor","info":"The vendor supplying the goods."},{"n":"vendorInvoiceId","t":"string","info":"Reference to associated vendor invoice for matching."}],"ext":"TransactionalDocument","related":["Purchase Order","Advanced Shipping Notice","Location","Stock Ledger"],"bv":{"rules":[{"rule":"Cannot exceed PO line outstanding quantity unless over-receive is enabled","when":"always","field":"ReceivedQty","severity":"error"}],"lifecycle":{"states":["Draft","Posted"],"transitions":[{"to":"Posted","from":"Draft","conditions":["All received quantities validated","Stock Ledger entries written"]}],"initialState":"Draft"},"calculations":[],"crossEntityConstraints":[{"rule":"Must reference a valid open or partially received PO","entity":"Purchase Order"},{"rule":"Posting writes Stock Ledger entries for received items","entity":"Stock Ledger"},{"rule":"Posting increments Item Stock at the receiving Location","entity":"Item Stock"}]},"inlineSchemas":[{"name":"GoodsReceiptStatus","extends":"OperationalSubDocument","properties":[{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"},{"info":"Current document lifecycle status.","name":"documentStatus","type":"enumeration","values":["Draft","Processing","Posted"],"required":true},{"info":"Operational sub-status for reversal tracking.","name":"operationalStatus","type":"enumeration","values":["Reversed","Complete","Reversal"]}]}]},{"name":"Hs Code","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"Harmonized System (HS) tariff classification code maintained by the World Customs Organization. Hierarchical 6–10 digit codes used for customs declarations, duty rate determination, and trade compliance. The first 6 digits are internationally standardized; digits 7–10 are country-specific extensions.","status":"draft","properties":[{"n":"chapter","r":true,"t":"string","info":"First 2 digits — the HS chapter (e.g. 61 = Articles of apparel, knitted or crocheted)."},{"n":"heading","r":true,"t":"string","info":"Digits 3–4 — the HS heading within the chapter (e.g. 6109 = T-shirts, singlets, and other vests)."},{"n":"subheading","t":"string","info":"Digits 5–6 — the HS subheading (e.g. 610910 = Of cotton). Null for chapter/heading-level codes."},{"n":"countryExtension","t":"string","info":"Digits 7–10 — country-specific tariff schedule extension (e.g. HTS for US, CN for EU). Null for internationally-standardized 6-digit codes."},{"n":"dutyRate","t":"decimal","info":"Default duty rate percentage for this classification. May be overridden by trade agreements or country-specific schedules."},{"n":"country","t":"entityDetail","re":"Country","info":"The country whose tariff schedule this extended code belongs to. Null for universal 6-digit HS codes."}],"ext":"LookupEntity","related":["Product","Country"]},{"name":"Inventory Position","class":"Balance","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Authoritative running inventory balance and valuation per Item × Location, maintained by the Stock Ledger Service by folding Stock Ledger entries in ledgerLine order. This is the source of truth behind Item Stock's cached cost and quantity fields — published to CONNECT via InventoryPositionChanged and CostRecalculated events. Mutable by design (recomputed on every posting); the immutable history lives in the Stock Ledger. Under FIFO/LIFO, valuation derives from Stock Cost Layer state; under WAC, from the running average maintained here.","status":"draft","properties":[{"c":true,"n":"baseTotalCost","t":"decimal","info":"totalCost converted to the Company's base/reporting currency using per-entry captured exchange rates. Null when currencyCode equals Company.baseCurrencyCode. Cached onto Item Stock.baseTotalCost for consolidated valuation reports."},{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"Tenant scope — hard isolation boundary for queries, events, and replication."},{"n":"costingMethod","r":true,"t":"enumeration","v":["FIFO","LIFO","WAC"],"info":"Effective costing method for this Item × Location. Inherits from Company.defaultCostingMethod on creation; overridable per the Stock Ledger Service PRD. Changing after entries have posted requires an offline replay job."},{"n":"currencyCode","r":true,"t":"string","info":"ISO 4217 currency code for unitCost and totalCost. Mirrored by Item Stock.currencyCode."},{"n":"item","r":true,"t":"entityRef","re":"Item","info":"The Item (SKU) this position tracks."},{"c":true,"n":"lastLedgerLine","r":true,"t":"integer","info":"ledgerLine of the most recent Stock Ledger entry folded into this position. Idempotency high-water mark — entries are applied only when their ledgerLine exceeds this value, making projection and event replay safe."},{"n":"location","r":true,"t":"entityDetail","re":"Location","info":"The Location this position tracks."},{"c":true,"n":"qtyOnHand","r":true,"t":"integer","info":"Running SUM(qty) of all Stock Ledger entries for this Item × Location. Reconciles with Item Stock onHand and with the latest entry's balanceQty."},{"c":true,"n":"totalCost","t":"decimal","info":"Total cost of on-hand inventory under costingMethod. FIFO/LIFO: Σ(remainingQty × unitCost) across Stock Cost Layers; WAC: qtyOnHand × running average. Cached onto Item Stock.totalCost."},{"c":true,"n":"unitCost","t":"decimal","info":"Current per-unit cost, method-dispatched: FIFO = oldest remaining layer's unitCost; LIFO = newest remaining layer's; WAC = totalCost / qtyOnHand. Cached onto Item Stock.unitCost via InventoryPositionChanged events."}],"service":"Stock Ledger Service","ext":"Identifiable","notes":"Documented 2026-07-24 to make the Stock Ledger Service's projection contract explicit — Item Stock (reviewed) already references InventoryPosition.unitCost/totalCost/baseTotalCost and the InventoryPositionChanged event. lastLedgerLine is the idempotency high-water mark: projections apply an entry only if entry.ledgerLine > lastLedgerLine, making event replay safe. Distinct from Item Stock: Inventory Position is the service-side authoritative balance; Item Stock is CONNECT's enriched cache (adds bins, quantity dimensions, activity dates).","related":["Item","Location","Stock Ledger","Item Stock","Stock Cost Layer"],"bv":{"rules":[{"rule":"qtyOnHand must equal the running SUM(qty) of Stock Ledger entries up to lastLedgerLine (and the balanceQty of that entry)","when":"always","field":"QtyOnHand","severity":"error"},{"rule":"Negative qtyOnHand allowed only when Company settings permit negative inventory","when":"always","field":"QtyOnHand","severity":"error"},{"rule":"One position per Item × Location — the (item, location) pair is unique","when":"create","field":"Item","severity":"error"}],"lifecycle":null,"calculations":[{"name":"qtyOnHand","formula":"SUM(StockLedger.qty) WHERE item AND location match, applied in ledgerLine order","trigger":"On every Stock Ledger posting for the Item × Location"},{"name":"unitCost","formula":"Method-dispatched: FIFO = oldest remaining Stock Cost Layer unitCost; LIFO = newest; WAC = totalCost / qtyOnHand","trigger":"On posting or CostRecalculated"},{"name":"totalCost","formula":"FIFO/LIFO: Σ(remainingQty × unitCost) across Stock Cost Layers; WAC: qtyOnHand × running average","trigger":"On posting or CostRecalculated"},{"name":"baseTotalCost","formula":"totalCost converted using per-entry captured exchange rates (null when currency = base)","trigger":"On posting or CostRecalculated"},{"name":"lastLedgerLine","formula":"MAX(StockLedger.ledgerLine) applied to this position","trigger":"On every applied posting"}],"crossEntityConstraints":[{"rule":"Recomputed from Stock Ledger entries in ledgerLine order; every posting updates the position atomically","entity":"Stock Ledger"},{"rule":"Item Stock caches unitCost, totalCost, baseTotalCost, and quantities from this position via InventoryPositionChanged / CostRecalculated events","entity":"Item Stock"}]}},{"name":"Item","class":"Operational","subsystem":"CONNECT","area":"Products & Pricing","desc":"A specific sellable variant of a Product (e.g. size/color combination). Has its own SKU, barcodes, costs, prices, and item-level commerce flags.","status":"draft","properties":[{"n":"allocationStartDate","t":"datetime"},{"n":"attributeValues","t":"array","re":"Attribute","info":"Nullish in Zod — not required at creation time."},{"n":"availableDate","t":"datetime"},{"n":"barcodes","t":"array","info":"Array of barcode objects, each with barcode string and isPrimary flag. Nullish in Zod — not required at creation time."},{"n":"basePrice","t":"decimal","info":"Item-level override of Product.DefaultBasePrice. The sticker price for this variant before price-level overrides."},{"n":"blockDiscount","r":true,"t":"boolean"},{"n":"canBackorder","r":true,"t":"boolean"},{"n":"canPreOrder","r":true,"t":"boolean"},{"c":true,"n":"currentUnitCost","t":"decimal","info":"Calculated, method-agnostic. Quantity-weighted roll-up across all Item Stock rows for this Item of Item Stock.unitCost — the current per-unit inventory value under each row's configured costing method (FIFO = oldest remaining layer, LIFO = newest, WAC = running average). Cached read from the Stock Ledger Service's InventoryPositionChanged events; the Stock Ledger is the source of truth. Null if the item has no Item Stock rows with positive on-hand qty."},{"n":"discontinuedDate","t":"datetime"},{"n":"dropshipEligibility","t":"enumeration","v":["Available","Required","Unavailable"]},{"c":true,"n":"firstPurchasedAt","t":"datetime","info":"Earliest timestamp this Item (SKU/variant) appeared on a purchase order. Named Purchased (not Ordered) to avoid confusion with sales/customer orders. Fold: MIN(locationActivity[].firstPurchasedAt). Ultimate SoR is the PO subsystem (PurchaseOrderLine); reconciles with Item Stock.firstPurchasedAt per location. Null if never purchased."},{"c":true,"n":"firstReceivedAt","t":"datetime","info":"Earliest timestamp this Item (SKU/variant) was received into any location from an external source (movementType Receipt or Return — transfers post as Transfer, tracked via firstTransferredAt). Fold: MIN(locationActivity[].firstReceivedAt). Ultimate SoR is the Stock Ledger; reconciles with Item Stock.firstReceivedAt per location. Null if never received."},{"c":true,"n":"firstSoldAt","t":"datetime","info":"Earliest timestamp this Item (SKU/variant) was sold at any location (movementType Sale — Sales Channel is an attribute of the sale, not a movement type). Fold: MIN(locationActivity[].firstSoldAt). Ultimate SoR is the Stock Ledger; reconciles with Item Stock.firstSoldAt per location. Null if never sold."},{"c":true,"n":"firstTransferredAt","t":"datetime","info":"Earliest timestamp this Item (SKU/variant) was transferred in or out of any location (movementType Transfer — paired entries, direction via qty sign, both directions count). Fold: MIN(locationActivity[].firstTransferredAt). Ultimate SoR is the Stock Ledger; reconciles with Item Stock.firstTransferredAt per location. Kept separate from firstReceivedAt so transfer activity is composable in or out of recency analysis. Null if never transferred."},{"n":"isDiscontinued","r":true,"t":"boolean"},{"n":"isFinalSale","r":true,"t":"boolean"},{"n":"isLrpEligible","r":true,"t":"boolean","info":"Eligible for loyalty rewards program."},{"n":"isReplenishment","r":true,"t":"boolean"},{"n":"itemName","t":"string","info":"Calculated field. Concatenation of the currently set AttributeValue names on this item separated by a space (e.g. 'Red Large Cotton')."},{"n":"itemNo","r":true,"t":"string"},{"c":true,"n":"lastActivityAt","t":"datetime","info":"Most recent activity for this Item across all locations, transfers excluded: MAX(lastPurchasedAt, lastReceivedAt, lastSoldAt). Equivalently MAX(locationActivity[].lastActivityAt). Transfer-inclusive recency = MAX(lastActivityAt, lastTransferredAt) at query time. Recency checks evaluate against a configurable window (e.g. Company.activityRecencyDays). For per-location recency, query locationActivity."},{"c":true,"n":"lastPurchasedAt","t":"datetime","info":"Most recent timestamp this Item (SKU/variant) appeared on a purchase order. Named Purchased (not Ordered) to avoid confusion with sales/customer orders. Fold: MAX(locationActivity[].lastPurchasedAt). Ultimate SoR is the PO subsystem (PurchaseOrderLine); reconciles with Item Stock.lastPurchasedAt per location. Null if never purchased."},{"c":true,"n":"lastPurchasedCost","t":"decimal","info":"Calculated field. Roll-up of VendorItemValue.lastPurchasedCost across all vendors for this Item — the unit cost from the single most recent Purchase Order line (regardless of vendor). Named Purchased (not Ordered) to avoid confusion with sales/customer orders. Null if the item has never been purchased. For the per-vendor value, see VendorItemValue.lastPurchasedCost. Renamed from lastOrderedCost on 2026-07-24."},{"c":true,"n":"lastReceivedAt","t":"datetime","info":"Most recent timestamp this Item (SKU/variant) was received into any location from an external source (movementType Receipt or Return — transfers post as Transfer, tracked via lastTransferredAt). Fold: MAX(locationActivity[].lastReceivedAt). Ultimate SoR is the Stock Ledger; reconciles with Item Stock.lastReceivedAt per location. Null if never received."},{"c":true,"n":"lastReceivedCost","t":"decimal","info":"Calculated field. Roll-up of VendorItemValue.lastReceivedCost across all vendors for this Item — the unit cost from the single most recent received inventory transaction (regardless of vendor). Sourced from the Cost Ledger. Null if the item has never been received. For the per-vendor value, see VendorItemValue.lastReceivedCost."},{"c":true,"n":"lastSoldAt","t":"datetime","info":"Most recent timestamp this Item (SKU/variant) was sold at any location (movementType Sale — Sales Channel is an attribute of the sale, not a movement type). Fold: MAX(locationActivity[].lastSoldAt). Ultimate SoR is the Stock Ledger; reconciles with Item Stock.lastSoldAt per location. Null if never sold."},{"c":true,"n":"lastTransferredAt","t":"datetime","info":"Most recent timestamp this Item (SKU/variant) was transferred in or out of any location (movementType Transfer — paired entries, direction via qty sign, both directions count). Fold: MAX(locationActivity[].lastTransferredAt). Ultimate SoR is the Stock Ledger; reconciles with Item Stock.lastTransferredAt per location. Kept separate from lastReceivedAt so transfer activity is composable in or out of recency analysis. Null if never transferred."},{"c":true,"n":"locationActivity","r":true,"t":"array","re":"Location","info":"Array of ItemLocationActivity inline schema objects — per-location lifecycle/activity dates at Item × Location grain. Required; defaults to [] at creation, entries created lazily on first activity at a Location. location unique within the array. Exists so dates are available consistently on Product and child Items without depending on Item Stock (which may be used independently of the product/item entities). Maintained from the same upstream events as Item Stock; Item Stock rows reconcile against these entries. The Item-level all-location scalars are folds (MIN/MAX) over this array; Product.locationActivity[location] aggregates these entries across the Product's child Items."},{"n":"measurements","t":"schema","info":"Item-level override of Product.defaultMeasurements. Variant-specific height/length/width/weight. Nullish — when unset, the Item inherits Product.defaultMeasurements at read time. Shape defined by the ItemMeasurements inline schema."},{"n":"media","t":"array","info":"Array of ItemMedia inline schema objects. Each entry represents a media asset (image, thumbnail, or video) associated with the item."},{"n":"optionValues","t":"array<OptionValue>","re":"Option","info":"Array of OptionValue objects ({ code: string, name: string|null, option: EntityDetail→Option, aliases: string[]|null, priceModifier: decimal|null, vendorCostModifiers: VendorOptionCostModifier[]|null }). Each VendorOptionCostModifier is { costModifier: decimal, vendor: EntityDetail→Vendor }. Price modifiers are flat; cost modifiers are per-vendor. IMPORTANT: These are relative deltas representing the impact of selecting this option on line-level price/cost (e.g. +$5 for engraving). They do NOT create entries in the Price Ledger or Cost Ledger. Contrast with the Price Adjustment and Cost Adjustment transactional entities which DO write to ledgers. Nullish in Zod — not required at creation time."},{"n":"preferredVendors","r":true,"t":"array","info":"Array of ItemPreferredVendor inline schema objects. One entry per Franchise Group, designating the Vendor that group prefers to source this Item from. Item-level override of Product.preferredVendors per Franchise Group — if an Item entry is absent for a given Franchise Group, Product.preferredVendors is used. Referenced Vendor must already exist in Item.vendors[] (VendorItemValue). At most one preferred vendor per Franchise Group. Nullish — not required at creation time."},{"n":"prices","t":"array<ItemPrice>","re":"Price Level","info":"Array of ItemPrice inline schema objects. Overrides Product.DefaultPrices for this Item. One entry per Price Level. Nullish in Zod — not required at creation time."},{"n":"salesChannels","t":"array<SalesChannelItemValue>","re":"Sales Channel","info":"Per-channel publish state for this item. Nullish in Zod — not required at creation time."},{"n":"salesVelocity","t":"enumeration","v":["High","Medium","Low"]},{"n":"sku","t":"string"},{"n":"status","r":true,"t":"schema","info":"Lifecycle status of the item. Tracks current state and who/when it was last changed."},{"n":"stockLimits","t":"array<StockLimitItemValue>","re":"Stock Limit Group","info":"Per stock-limit-group min/max thresholds by location and period. Nullish in Zod — not required at creation time."},{"c":true,"n":"vendorActivity","r":true,"t":"array","re":"Vendor","info":"Array of ItemVendorActivity inline schema objects — per-vendor activity dates at Item × Vendor grain (purchase/receipt legs only; sales and transfers have no vendor dimension). Required; defaults to []; entries created lazily on first activity with a vendor. vendor unique within the array. Deliberately separate from vendors[] (current sourcing config): entries are NEVER deleted on vendor unassignment, preserving historical activity across vendor changes. Aggregates up to Product.vendorActivity[vendor] across the Product's child Items. Amount fields to be added by the deferred amounts request (registry question #11)."},{"n":"vendors","t":"array<VendorItemValue>","re":"Vendor","info":"Array of VendorItemValue inline schema objects. Item-level override of Product.vendors[].costs per Vendor × Cost Level. Nullish in Zod — not required at creation time."},{"n":"weeksOfSupply","t":"integer","info":"Weeks of supply for this item. Inherited from the parent Product's weeksOfSupply on creation but can be overridden at the Item level."}],"ext":"OperationalDocument","shopify":"ProductVariant resource","related":["Product","Item Stock","Price Level","Vendor","Sales Channel","Stock Limit Group","Franchise Group"],"bv":{"rules":[{"rule":"Must be globally unique across the Company","when":"always","field":"SKU","severity":"error"},{"rule":"Must conform to GTIN-12 or GTIN-13 format if provided","when":"always","field":"UPC","severity":"warning"},{"rule":"Each preferredVendors[].vendor must reference a Vendor that is present in Item.vendors[].vendor (VendorItemValue)","when":"always","field":"preferredVendors","severity":"error"},{"rule":"franchiseGroup must be unique within preferredVendors — at most one preferred vendor per Franchise Group","when":"always","field":"preferredVendors","severity":"error"},{"rule":"For any Franchise Group not present in Item.preferredVendors, Product.preferredVendors is inherited at read time","when":"read","field":"preferredVendors","severity":"info"},{"rule":"location must be unique within locationActivity — at most one activity entry per Location","when":"always","field":"locationActivity","severity":"error"},{"rule":"vendor must be unique within vendorActivity — at most one activity entry per Vendor. Entries are never deleted on vendor unassignment; vendorActivity[].vendor need not be present in vendors[]","when":"always","field":"vendorActivity","severity":"error"}],"lifecycle":{"states":["Draft","Active","Archived"],"disallowed":[{"to":"Draft","from":"Active","reason":"Items cannot revert to Draft once activated"},{"to":"Draft","from":"Archived","reason":"Items cannot revert to Draft once activated"}],"transitions":[{"to":"Active","from":"Draft","conditions":["Parent Product is Active","SKU is assigned"]},{"to":"Archived","from":"Active","conditions":["No open order lines reference this Item"]},{"to":"Active","from":"Archived","conditions":["Parent Product is Active"]}],"initialState":"Draft","terminalExits":["Draft"]},"calculations":[{"info":"Concatenation of the currently set AttributeValue names separated by a space (e.g. 'Red Large Cotton'). Recalculated when attributeValues change.","name":"itemName","formula":"CONCAT(attributeValues[].name, ' ')","trigger":"on-write"},{"name":"vendorCount","formula":"COUNT(DISTINCT vendor via vendors)","trigger":"query-time"},{"name":"salesChannelCount","formula":"COUNT(DISTINCT salesChannel via Product.salesChannels)","trigger":"query-time"},{"name":"priceCount","formula":"COUNT(prices)","trigger":"query-time"},{"info":"Per-vendor. Unit cost from the most recent Receipt entry in the Stock Ledger Service for this Item × Vendor. Event-sourced — CONNECT does not compute this natively; it is a cached read updated when the Stock Ledger publishes LedgerEntryCreated with txType='Receipt' for this Item × Vendor pairing. Stock Ledger is the source of truth.","name":"VendorItemValue.lastReceivedCost","formula":"read-through from Stock Ledger Service: FIRST(LedgerEntry.unitCost ORDER BY transactionDate DESC WHERE item = self.item AND vendor = self.vendor AND txType = 'Receipt')","trigger":"event: LedgerEntryCreated(txType=Receipt) from Stock Ledger Service"},{"info":"Per-vendor. Unit cost from the most recent Purchase Order line for this Item × Vendor. Named Purchased (not Ordered) to avoid confusion with sales/customer orders — renamed from lastOrderedCost on 2026-07-24. POs live in CONNECT (out-of-scope for Stock Ledger Service v1), so this is computed from CONNECT's PO module on PO line write.","name":"VendorItemValue.lastPurchasedCost","formula":"FIRST(PurchaseOrderLine.unitCost ORDER BY PurchaseOrderLine.orderDate DESC WHERE PurchaseOrderLine.item = self.item AND PurchaseOrderLine.vendor = self.vendor)","trigger":"on-po-line-write (CONNECT PO module)"},{"info":"Item-level roll-up across all vendors. Unit cost from the single most recent Receipt LedgerEntry in the Stock Ledger Service for this Item regardless of vendor. Event-sourced cached read.","name":"lastReceivedCost","formula":"read-through from Stock Ledger Service: FIRST(LedgerEntry.unitCost ORDER BY transactionDate DESC WHERE item = self AND txType = 'Receipt')","trigger":"event: LedgerEntryCreated(txType=Receipt) from Stock Ledger Service"},{"info":"Item-level roll-up across all vendors. Unit cost from the single most recent Purchase Order line for this Item regardless of vendor. Named Purchased (not Ordered) to avoid confusion with sales/customer orders — renamed from lastOrderedCost on 2026-07-24. Computed from CONNECT PO module (POs are not in Stock Ledger Service v1 scope).","name":"lastPurchasedCost","formula":"FIRST(PurchaseOrderLine.unitCost ORDER BY PurchaseOrderLine.orderDate DESC WHERE PurchaseOrderLine.item = self)","trigger":"on-po-line-write (CONNECT PO module)"},{"info":"Method-agnostic per-unit inventory value for this Item, rolled up across all Item Stock rows (locations). Weighted by on-hand qty across rows, where each row's unitCost reflects its own configured costingMethod (FIFO=oldest remaining layer, LIFO=newest, WAC=running average). Cached read derived from Item Stock.unitCost values, which are themselves event-sourced from Stock Ledger Service's InventoryPositionChanged events. Null if no Item Stock rows have positive on-hand qty.","name":"currentUnitCost","formula":"SUM(ItemStock.unitCost * ItemStock.onHand) / SUM(ItemStock.onHand) WHERE ItemStock.item = self AND ItemStock.onHand > 0","trigger":"event: InventoryPositionChanged from Stock Ledger Service (propagated via Item Stock)"},{"info":"Per-location activity dates maintained directly from upstream events (Stock Ledger InventoryPositionChanged; CONNECT PO line events), in parallel with Item Stock — not read from it, so the product/item domain stays independent of the inventory projection. Entries created lazily; never removed while activity history stands.","name":"locationActivity","formula":"per Location: MIN/MAX of qualifying event timestamps for this Item at that Location (received = movementType Receipt/Return; sold = Sale; transferred = Transfer; purchased = PurchaseOrderLine.orderedAt WHERE deliverTo = location); lastActivityAt = MAX(lastPurchasedAt, lastReceivedAt, lastSoldAt)","trigger":"event: InventoryPositionChanged from Stock Ledger; PurchaseOrderLineCreated/Updated from CONNECT PO module"},{"info":"Per-vendor activity dates (purchase/receipt legs only — sales and transfers have no vendor dimension). Entries keyed by vendor, created lazily, NEVER deleted on vendor unassignment — vendors[] stays pure sourcing config while history survives vendor changes.","name":"vendorActivity","formula":"per Vendor: firstPurchasedAt/lastPurchasedAt = MIN/MAX(PurchaseOrderLine.orderedAt WHERE vendor); firstReceivedAt/lastReceivedAt = MIN/MAX of Receipt/Return events sourced from that vendor","trigger":"event: PurchaseOrderLineCreated/Updated from CONNECT PO module; goods-receipt/ledger events for received"}],"crossEntityConstraints":[{"rule":"Must belong to a valid Product","when":"always","entity":"Product"},{"rule":"Cannot archive Item with non-zero stock at any Location","when":"archive","entity":"Item Stock"},{"rule":"preferredVendors[].vendor must resolve to an Active Vendor within the Company","when":"always","entity":"Vendor"},{"rule":"locationActivity[].location must reference a valid Location","when":"always","entity":"Location"},{"rule":"locationActivity entries must reconcile with the corresponding Item Stock rows' same-named date fields (both are maintained from the same upstream events)","when":"always","entity":"Item Stock"},{"rule":"locationActivity entries aggregate up to Product.locationActivity[location] across the Product's child Items (MIN for first*, MAX for last*)","when":"always","entity":"Product"},{"rule":"vendorActivity[].vendor must reference a valid Vendor (need not be currently assigned in vendors[] — entries survive unassignment)","when":"always","entity":"Vendor"},{"rule":"vendorActivity entries aggregate up to Product.vendorActivity[vendor] across the Product's child Items (MIN for first*, MAX for last*)","when":"always","entity":"Product"}]},"inlineSchemas":[{"name":"ItemMedia","schema":"schemas/item/ItemMedia.ts","properties":[{"n":"images","t":"array","re":"Media","info":"Item image assets."},{"n":"thumbnails","t":"array","re":"Media","info":"Thumbnail image assets."},{"n":"videos","t":"array","re":"Media","info":"Video assets."}]},{"name":"ItemPrice","schema":"schemas/item/ItemPrice.ts","properties":[{"name":"price","type":"decimal","required":true},{"name":"priceLevel","type":"entityDetail","required":true,"relatedEntity":"Price Level"}]},{"name":"ItemStatus","schema":"schemas/item/ItemStatus.ts","properties":[{"info":"Current lifecycle state of the item.","name":"status","type":"enumeration","values":["Draft","Active","Archived"],"required":true},{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"}]},{"name":"VendorCost","info":"Shared with Product — see Product.inlineSchemas.VendorCost","schema":"schemas/product-common/VendorCost.ts"},{"name":"ItemBarcode","schema":"schemas/item/ItemBarcode.ts","properties":[{"info":"GTIN-validated string","name":"barcode","type":"string","required":true},{"name":"isPrimary","type":"boolean","required":true}]},{"name":"OptionValue","desc":"A single selectable value within an Option's value set, as assigned to an Item. Extends LookupEntityValue — inherits id, identifiers, code, name, aliases, isActive, isDeleted, isDefault, sequence, audit fields, and customData.","schema":"schemas/product-common/OptionValue.ts","extends":"LookupEntityValue","properties":[{"info":"Reference to the parent Option dictionary entity this value belongs to.","name":"option","type":"entityDetail","required":true,"relatedEntity":"Option"},{"info":"A +/- difference the option value applies to the line-level price. This is a relative modifier, not a transactional adjustment — does not write to the Price Ledger.","name":"priceModifier","type":"decimal"},{"name":"vendorCostModifiers","type":"array<VendorOptionCostModifier>"}]},{"name":"AttributeValue","schema":"schemas/product-common/AttributeValue.ts","properties":[{"info":"Reference to the parent Attribute dictionary entity this value belongs to.","name":"attribute","type":"entityDetail","required":true,"relatedEntity":"Attribute"},{"name":"code","type":"string","required":true},{"name":"name","type":"string"},{"info":"Ordinal position of this value within the parent Attribute (e.g. 1 = first colour, 2 = second colour).","name":"sequence","type":"integer"},{"name":"aliases","type":"array<string>"}]},{"name":"VendorItemValue","schema":"schemas/item/VendorItemValue.ts","properties":[{"name":"barcodes","type":"array<ItemBarcode>","required":true},{"name":"baseCost","type":"decimal"},{"name":"canDropship","type":"boolean","required":true},{"name":"costs","type":"array<VendorCost>","required":true},{"name":"isPrimary","type":"boolean","required":true},{"name":"minOrderQty","type":"decimal"},{"name":"sku","type":"string"},{"name":"unitOfMeasure","type":"enumeration","values":["Case","Unit"],"required":true},{"name":"unitOfMeasureQty","type":"decimal"},{"name":"vendor","type":"entityDetail","required":true,"relatedEntity":"Vendor"},{"info":"Calculated field. Unit cost from the most recent received inventory transaction for this Item from this specific Vendor. Sourced from the Cost Ledger. Null if never received from this vendor. Item.lastReceivedCost is the roll-up across all vendors.","name":"lastReceivedCost","type":"decimal","calculated":true},{"info":"Calculated field. Unit cost from the most recent Purchase Order line for this Item from this specific Vendor. Named Purchased (not Ordered) to avoid confusion with sales/customer orders. Null if never purchased from this vendor. Item.lastPurchasedCost is the roll-up across all vendors. Renamed from lastOrderedCost on 2026-07-24.","name":"lastPurchasedCost","type":"decimal","calculated":true}]},{"name":"ItemMeasurements","schema":"schemas/item/ItemMeasurements.ts","properties":[{"name":"height","type":"string"},{"name":"length","type":"string"},{"name":"weight","type":"string"},{"name":"width","type":"string"}]},{"name":"ItemVendorActivity","info":"Per-vendor lifecycle/activity dates for this Item (Item × Vendor grain). Activity projection keyed by vendor — deliberately separate from vendors[] (VendorItemValue), which is current sourcing config: entries here are NEVER deleted when a vendor is unassigned, so historical activity survives vendor changes (supports 'when/what did we last purchase from vendor X' after switching vendors). Entries created lazily on first activity with a vendor; vendor unique within the array. Only the purchase/receipt legs exist — sales and transfers have no vendor dimension. Maintained from PO line events (purchased) and goods-receipt/ledger events (received). Amount fields (lastPurchasedAmount, lastReceivedAmount) are scoped to be added by the deferred amounts request — see registry question #11.","schema":"schemas/item/ItemVendorActivity.ts","extends":"OperationalSubDocument","properties":[{"info":"The Vendor this activity entry applies to. Unique within vendorActivity. Need NOT be present in vendors[] — entries survive vendor unassignment.","name":"vendor","type":"entityDetail","required":true,"relatedEntity":"Vendor"},{"info":"Earliest PO line placed for this Item with this Vendor. Named Purchased (not Ordered) to avoid confusion with sales/customer orders.","name":"firstPurchasedAt","type":"datetime","calculated":true},{"info":"Most recent PO line placed for this Item with this Vendor.","name":"lastPurchasedAt","type":"datetime","calculated":true},{"info":"Earliest external receipt (movementType Receipt or Return) of this Item sourced from this Vendor.","name":"firstReceivedAt","type":"datetime","calculated":true},{"info":"Most recent external receipt (movementType Receipt or Return) of this Item sourced from this Vendor.","name":"lastReceivedAt","type":"datetime","calculated":true}]},{"name":"ItemPreferredVendor","info":"Per-Franchise-Group preferred vendor override for this Item. Item-level override of Product.preferredVendors for the same Franchise Group — if unset for a given Franchise Group, the Item inherits Product.preferredVendors. The referenced Vendor MUST already exist in Item.vendors[].vendor (VendorItemValue) — preferred vendor cannot be an un-linked vendor. At most one entry per Franchise Group.","schema":"schemas/item/ItemPreferredVendor.ts","extends":"OperationalSubDocument","properties":[{"info":"The Franchise Group this preferred vendor applies to.","name":"franchiseGroup","type":"entityDetail","required":true,"relatedEntity":"Franchise Group"},{"info":"The Vendor this Franchise Group prefers to source this Item from. Must reference a Vendor that is present in Item.vendors[].vendor (VendorItemValue).","name":"vendor","type":"entityDetail","required":true,"relatedEntity":"Vendor"}]},{"name":"StockLimitItemValue","schema":"schemas/item/StockLimitItemValue.ts","properties":[{"name":"maxStockLimit","type":"decimal"},{"name":"minStockLimit","type":"decimal"},{"name":"stockLimitGroup","type":"entityDetail","required":true,"relatedEntity":"Stock Limit Group"},{"name":"stockLimitLocation","type":"entityDetail","required":true,"relatedEntity":"Location"},{"info":"Time period for stock limit evaluation (e.g. Weekly, Monthly). Not a persisted entity — a code + name label pair representing the replenishment cycle.","name":"stockLimitPeriod","type":"nonIdentifiableEntityDetail","required":true}]},{"name":"ItemLocationActivity","info":"Per-location lifecycle/activity dates for this Item (Item × Location grain). Mirrors ProductLocationActivity so dates are available consistently on Product and child Items without depending on Item Stock — Item Stock may be used independently of the product/item entities (e.g. synced from an external IMS). Maintained directly from the same upstream events (InventoryPositionChanged from the Stock Ledger; PO line events from CONNECT), in parallel with Item Stock; Item Stock rows reconcile against these entries as a consistency check. Entries created lazily on first activity at a Location; location unique within the array. Movement categorization per the Stock Ledger movementType enum (received = Receipt/Return; sold = Sale; transferred = Transfer, direction via qty sign; Adjustments don't count as activity).","schema":"schemas/item/ItemLocationActivity.ts","extends":"OperationalSubDocument","properties":[{"info":"The Location this activity entry applies to. Unique within locationActivity.","name":"location","type":"entityDetail","required":true,"relatedEntity":"Location"},{"info":"Earliest PO line placed for this Item with deliverTo = this location. Named Purchased (not Ordered) to avoid confusion with sales/customer orders.","name":"firstPurchasedAt","type":"datetime","calculated":true},{"info":"Most recent PO line placed for this Item with deliverTo = this location.","name":"lastPurchasedAt","type":"datetime","calculated":true},{"info":"Earliest external receipt (movementType Receipt or Return — transfers post as Transfer, tracked separately) of this Item at this Location.","name":"firstReceivedAt","type":"datetime","calculated":true},{"info":"Most recent external receipt (movementType Receipt or Return) of this Item at this Location.","name":"lastReceivedAt","type":"datetime","calculated":true},{"info":"Earliest sale (movementType Sale — Sales Channel is an attribute of the sale, not a movement type) of this Item at this Location.","name":"firstSoldAt","type":"datetime","calculated":true},{"info":"Most recent sale (movementType Sale) of this Item at this Location.","name":"lastSoldAt","type":"datetime","calculated":true},{"info":"Earliest Transfer movement (paired entries; direction via qty sign; both directions count) of this Item at this Location.","name":"firstTransferredAt","type":"datetime","calculated":true},{"info":"Most recent Transfer movement of this Item at this Location. Kept separate so transfer activity is composable in or out of recency.","name":"lastTransferredAt","type":"datetime","calculated":true},{"info":"Most recent activity for this Item at this Location, transfers excluded: MAX(lastPurchasedAt, lastReceivedAt, lastSoldAt). Transfer-inclusive recency = MAX(lastActivityAt, lastTransferredAt) at query time.","name":"lastActivityAt","type":"datetime","calculated":true}]},{"name":"SalesChannelItemValue","schema":"schemas/item/SalesChannelItemValue.ts","properties":[{"name":"isPublished","type":"boolean","required":true},{"name":"salesChannel","type":"entityDetail","required":true,"relatedEntity":"Sales Channel"}]},{"name":"VendorOptionCostModifier","schema":"schemas/product-common/VendorOptionCostModifier.ts","properties":[{"info":"A +/- difference the option value applies to the vendor cost. This is a relative modifier, not a transactional adjustment — does not write to the Cost Ledger.","name":"costModifier","type":"decimal","required":true},{"name":"vendor","type":"entityDetail","required":true,"relatedEntity":"Vendor"}]}],"inheritance":{"parentEntity":"Product","overridableProperties":["basePrice","dropshipEligibility","measurements","prices","preferredVendors"]}},{"name":"Item Stock","class":"Operational","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Current-state inventory projection per Item × Location. A mutable, continuously-updated snapshot of stock quantities (on_hand, committed, incoming, reserved, safety_stock, damaged, QC) derived from Stock Ledger entries or synced from an external inventory management system. Not an immutable record — recalculated as movements flow in.","status":"reviewed","properties":[{"c":true,"n":"baseTotalCost","t":"decimal","info":"totalCost converted to the Company's base/reporting currency. Null if currencyCode already equals the Company's base currency. Used for cross-location consolidated valuation reports. Cached read from the Stock Ledger Service."},{"n":"costingMethod","r":true,"t":"enumeration","v":["FIFO","LIFO","WAC"],"info":"Effective inventory costing method for this Item × Location. On creation, inherits from Company.defaultCostingMethod; may be overridden per the Stock Ledger Service PRD. Changing this value after posting entries requires an offline replay job — it is not a hot-path edit."},{"n":"currencyCode","r":true,"t":"string","info":"ISO 4217 currency code for unitCost and totalCost. Typically matches the Company's baseCurrencyCode but may differ for locations operating in a different currency. Mirrors Stock Ledger InventoryPosition.currencyCode."},{"c":true,"n":"firstPurchasedAt","t":"datetime","info":"Earliest timestamp a purchase order line was placed for this Item × Location. Named Purchased (not Ordered) to avoid confusion with sales/customer orders — this is strictly PO activity. Derived from PurchaseOrderLine — PO is upstream of the Stock Ledger, so this is aggregated in CONNECT rather than read-through from the Stock Ledger Service. Null until the first PO line is created. Item-level firstPurchasedAt = MIN across Item Stock rows for the Item. Renamed from firstOrderedAt on 2026-07-24."},{"c":true,"n":"firstReceivedAt","t":"datetime","info":"Earliest timestamp this Item was received at this Location from an external source. Read-through projection from the Stock Ledger, movementType IN ('Receipt','Return') — goods receipts and returns-to-stock. Transfers never qualify: transfer movements post as movementType 'Transfer' (paired entries, direction via qty sign) and are tracked separately via firstTransferredAt so transfer activity can be included or excluded from recency analysis. Null until the first external receipt posts. Item-level firstReceivedAt = MIN across Item Stock rows for the Item."},{"c":true,"n":"firstSoldAt","t":"datetime","info":"Earliest timestamp this Item was sold at this Location. Read-through projection from Stock Ledger Service (movementType 'Sale' — a sale is a sale regardless of channel; the Sales Channel is an attribute of the sale transaction, not a distinct movement type). Null until the first sale posts. Introduction-date / sell-through analytics. Item-level firstSoldAt = MIN across Item Stock rows for the Item."},{"c":true,"n":"firstTransferredAt","t":"datetime","info":"Earliest timestamp this Item was transferred in or out of this Location. Read-through projection from the Stock Ledger, movementType 'Transfer' — transfers post paired entries (negative at source, positive at destination), so direction is the qty sign; both directions count as transfer activity. Kept separate from firstReceivedAt so transfer activity is composable in or out of recency analysis. Null if never transferred. Item-level firstTransferredAt = MIN across Item Stock rows for the Item."},{"n":"itemNo","r":true,"t":"integer"},{"c":true,"n":"lastActivityAt","t":"datetime","info":"Most recent activity timestamp for this Item × Location, transfers excluded: MAX(lastPurchasedAt, lastReceivedAt, lastSoldAt). Primary indexed key for recency filtering (recent-activity checks against a configurable window, e.g. Company.activityRecencyDays). For transfer-inclusive recency, evaluate MAX(lastActivityAt, lastTransferredAt) at query time — no separate stored field needed. Maintained on the same events that update its constituent fields."},{"c":true,"n":"lastPurchasedAt","t":"datetime","info":"Most recent timestamp a purchase order line was placed for this Item × Location. Named Purchased (not Ordered) to avoid confusion with sales/customer orders — this is strictly PO activity. Derived from PurchaseOrderLine (PO upstream of Ledger; aggregated in CONNECT, not read-through from Stock Ledger Service). Used for replenishment recency and open-to-buy. Item-level lastPurchasedAt = MAX across Item Stock rows for the Item. Renamed from lastOrderedAt on 2026-07-24."},{"c":true,"n":"lastReceivedAt","t":"datetime","info":"Most recent timestamp this Item was received at this Location from an external source. Read-through projection from the Stock Ledger, movementType IN ('Receipt','Return') — goods receipts and returns-to-stock. Transfers never qualify: transfer movements post as movementType 'Transfer' and are tracked separately via lastTransferredAt so transfer activity can be included or excluded from recency analysis. Drives replenishment cadence and aged-inbound reporting. Item-level lastReceivedAt = MAX across Item Stock rows for the Item."},{"c":true,"n":"lastSoldAt","t":"datetime","info":"Most recent timestamp this Item was sold at this Location. Read-through projection from Stock Ledger Service (movementType 'Sale' — a sale is a sale regardless of channel; the Sales Channel is an attribute of the sale transaction, not a distinct movement type). Primary signal for slow-mover / aged-stock reporting. Item-level lastSoldAt = MAX across Item Stock rows for the Item."},{"c":true,"n":"lastTransferredAt","t":"datetime","info":"Most recent timestamp this Item was transferred in or out of this Location. Read-through projection from the Stock Ledger, movementType 'Transfer' — transfers post paired entries (negative at source, positive at destination), so direction is the qty sign; both directions count as transfer activity. Kept separate from lastReceivedAt so transfer activity is composable in or out of recency analysis (transfer-inclusive recency = MAX(lastActivityAt, lastTransferredAt) at query time). Item-level lastTransferredAt = MAX across Item Stock rows for the Item."},{"n":"location","r":true,"t":"entityDetail","re":"Location"},{"n":"locationQtys","r":true,"t":"schema","info":"Map of location IDs to stock quantity breakdowns. Each entry contains on-hand, committed, incoming, reserved, and other quantity dimensions."},{"n":"productCode","r":true,"t":"string","info":"Product code for the item."},{"n":"stockBin","t":"entityRef","re":"StockBin"},{"c":true,"n":"totalCost","t":"decimal","info":"Total cost of on-hand inventory for this Item × Location under the configured costingMethod. Sum across remaining lots under FIFO/LIFO; qtyOnHand × running-average under WAC. Cached read from the Stock Ledger Service's InventoryPosition. Basis for inventory valuation reports."},{"c":true,"n":"unitCost","t":"decimal","info":"Current per-unit inventory cost under the configured costingMethod. FIFO = unit cost of the oldest remaining lot; LIFO = unit cost of the newest remaining lot; WAC = totalCost / qtyOnHand. Cached read from the Stock Ledger Service's InventoryPosition; the Stock Ledger is the source of truth. Updated via InventoryPositionChanged events."}],"shopify":"InventoryLevel resource","notes":"Sourcing for the computed datetime fields on Item Stock splits across two upstreams. Received/Sold/Transferred pairs are read-through projections from the Stock Ledger, updated via InventoryPositionChanged — consistent with the existing cost-field pattern. Movement categorization uses the Stock Ledger movementType enum (Sale, Receipt, Adjustment, Transfer, Return): received = Receipt/Return; sold = Sale (a sale is a sale regardless of channel — Sales Channel is an attribute of the sale transaction, not a movement type; the spurious 'EcomFulfilment' value was removed 2026-07-24); transferred = Transfer (paired entries, direction via qty sign — both directions count). Adjustments do not count as activity. The Purchased pair is sourced from PurchaseOrderLine in CONNECT, NOT the ledger: the ledger sees receipts, not PO creation, and expanding the Stock Ledger Service to consume PO events would breach its movement-of-inventory scope boundary from PRD v1.0. All first*/last* fields aggregate up to the identically-named Item-level fields via MIN (first*) / MAX (last*) per the product-read-through-must-aggregate-item convention, and roll up per-location to Product.locationActivity (Product × Location grain). first* are write-once; last* = MAX(stored, incoming) to tolerate out-of-order postings; reversals/voids do not bump recency. lastActivityAt (MAX of lastPurchasedAt/lastReceivedAt/lastSoldAt, transfers excluded) is the indexed recency key; transfer-inclusive recency = MAX(lastActivityAt, lastTransferredAt) at query time. Naming history: ...Date → ...At on 2026-04-21; first/lastOrderedAt → first/lastPurchasedAt on 2026-07-24 to avoid confusion with sales/customer orders; formulas aligned to the coarse Stock Ledger movementType enum on 2026-07-24 (earlier drafts used granular values POReceipt/ReturnToStock/TransferIn/TransferOut that do not exist in the enum).","related":["Item","Location","StockBin","Stock Ledger"],"bv":{"rules":[{"rule":"SOH (Stock on Hand) must not go negative unless Company settings allow negative inventory","when":"always","field":"Quantity","severity":"error"}],"lifecycle":null,"calculations":[{"name":"Available","formula":"SOH - Allocated - Reserved","trigger":"On stock movement or allocation change"},{"name":"SOH","formula":"Sum of Stock Ledger entries for Item + Location","trigger":"On any stock movement"},{"info":"Per-unit inventory cost for this Item × Location under the configured costingMethod. Cached read from the Stock Ledger Service's InventoryPosition.unitCost — not natively computed by CONNECT. Method dispatch happens inside the Stock Ledger Service: FIFO=unit cost of oldest remaining LedgerCost layer, LIFO=newest remaining layer, WAC=totalCost/qtyOnHand running average. Updated whenever the Stock Ledger publishes an InventoryPositionChanged or CostRecalculated event for this Item × Location.","name":"unitCost","formula":"read-through from Stock Ledger Service: InventoryPosition.unitCost WHERE item = self.item AND location = self.location (method-dispatched inside the service)","trigger":"event: InventoryPositionChanged or CostRecalculated from Stock Ledger Service"},{"info":"Total cost of on-hand inventory for this Item × Location under the configured costingMethod. Cached read from the Stock Ledger Service's InventoryPosition.totalCost. FIFO/LIFO: sum across remaining LedgerCost layers (qty × unitCost per layer). WAC: qtyOnHand × running-average unit cost. Source of truth is the Stock Ledger Service.","name":"totalCost","formula":"read-through from Stock Ledger Service: InventoryPosition.totalCost WHERE item = self.item AND location = self.location","trigger":"event: InventoryPositionChanged or CostRecalculated from Stock Ledger Service"},{"info":"totalCost converted to the Company's base/reporting currency for cross-location consolidated valuation. Cached read from the Stock Ledger Service's InventoryPosition.baseTotalCost. Null when currencyCode already equals Company.baseCurrencyCode. The Stock Ledger Service performs the FX conversion using the rate captured at the time of each LedgerEntry.","name":"baseTotalCost","formula":"read-through from Stock Ledger Service: InventoryPosition.baseTotalCost WHERE item = self.item AND location = self.location (null if currency = base)","trigger":"event: InventoryPositionChanged or CostRecalculated from Stock Ledger Service"},{"info":"Earliest timestamp this Item was received at this Location from an external source — movementType Receipt (goods receipts) or Return (returns-to-stock). Transfers never qualify (they post as movementType Transfer, tracked via firstTransferredAt). Write-once semantics per Item × Location. Aggregates up to Item.firstReceivedAt via MIN per the product-read-through-must-aggregate-item convention.","name":"firstReceivedAt","formula":"MIN(LedgerEntry.postedAt) WHERE item = self.item AND location = self.location AND movementType IN ('Receipt','Return')","trigger":"event: InventoryPositionChanged from Stock Ledger (first external receipt)"},{"info":"Most recent timestamp this Item was received at this Location from an external source — movementType Receipt or Return. Transfers never qualify (movementType Transfer, tracked via lastTransferredAt). Drives replenishment cadence and aged-inbound reporting. Aggregates up to Item.lastReceivedAt via MAX.","name":"lastReceivedAt","formula":"MAX(LedgerEntry.postedAt) WHERE item = self.item AND location = self.location AND movementType IN ('Receipt','Return')","trigger":"event: InventoryPositionChanged from Stock Ledger on any Receipt/Return movement"},{"info":"Earliest timestamp a purchase order line was placed for this Item × Location. Named Purchased (not Ordered) to avoid confusion with sales/customer orders. Sourced from PurchaseOrderLine.orderedAt — NOT a read-through from the Stock Ledger. The ledger sees receipts, not PO creation; POs are upstream. CONNECT owns this aggregation. Aggregates up to Item.firstPurchasedAt via MIN.","name":"firstPurchasedAt","formula":"MIN(PurchaseOrderLine.orderedAt) WHERE item = self.item AND deliverTo = self.location","trigger":"event: PurchaseOrderLineCreated (write-once semantics per Item × Location)"},{"info":"Most recent timestamp a purchase order line was placed for this Item × Location. Named Purchased (not Ordered) to avoid confusion with sales/customer orders. Sourced from PurchaseOrderLine.orderedAt (not Stock Ledger). Used for replenishment recency, open-to-buy, and reorder-gap analysis. Aggregates up to Item.lastPurchasedAt via MAX.","name":"lastPurchasedAt","formula":"MAX(PurchaseOrderLine.orderedAt) WHERE item = self.item AND deliverTo = self.location","trigger":"event: PurchaseOrderLineCreated or PurchaseOrderLineUpdated"},{"info":"Earliest timestamp this Item was sold at this Location — movementType Sale only. A sale is a sale regardless of channel; Sales Channel is an attribute of the sale transaction, not a movement type. Introduction-date / sell-through analytics. Write-once per Item × Location. Aggregates up to Item.firstSoldAt via MIN.","name":"firstSoldAt","formula":"MIN(LedgerEntry.postedAt) WHERE item = self.item AND location = self.location AND movementType = 'Sale'","trigger":"event: InventoryPositionChanged from Stock Ledger (first sale)"},{"info":"Most recent timestamp this Item was sold at this Location — movementType Sale only. A sale is a sale regardless of channel; Sales Channel is an attribute of the sale transaction, not a movement type. Primary signal for slow-mover / aged-stock reporting. Aggregates up to Item.lastSoldAt via MAX.","name":"lastSoldAt","formula":"MAX(LedgerEntry.postedAt) WHERE item = self.item AND location = self.location AND movementType = 'Sale'","trigger":"event: InventoryPositionChanged from Stock Ledger on any Sale movement"},{"info":"Earliest timestamp this Item was transferred in or out of this Location — movementType Transfer. Transfers post paired entries (negative at source, positive at destination); direction is the qty sign and both directions count. Kept separate from firstReceivedAt so transfer activity is composable in or out of recency analysis. Aggregates up to Item.firstTransferredAt via MIN.","name":"firstTransferredAt","formula":"MIN(LedgerEntry.postedAt) WHERE item = self.item AND location = self.location AND movementType = 'Transfer'","trigger":"event: InventoryPositionChanged from Stock Ledger (first Transfer movement)"},{"info":"Most recent timestamp this Item was transferred in or out of this Location — movementType Transfer (paired entries; direction via qty sign; both directions count). Kept separate from lastReceivedAt so transfer activity is composable in or out of recency analysis. Aggregates up to Item.lastTransferredAt via MAX.","name":"lastTransferredAt","formula":"MAX(LedgerEntry.postedAt) WHERE item = self.item AND location = self.location AND movementType = 'Transfer'","trigger":"event: InventoryPositionChanged from Stock Ledger on any Transfer movement"},{"info":"Most recent activity for this Item × Location, transfers excluded. The indexed recency key: recent-activity checks evaluate lastActivityAt >= NOW() - configurable window (e.g. Company.activityRecencyDays). Transfer-inclusive recency = MAX(lastActivityAt, lastTransferredAt) at query time. Aggregates up to Item.lastActivityAt via MAX.","name":"lastActivityAt","formula":"MAX(lastPurchasedAt, lastReceivedAt, lastSoldAt)","trigger":"derived: recomputed whenever lastPurchasedAt, lastReceivedAt, or lastSoldAt changes"}],"crossEntityConstraints":[{"rule":"Must reference a valid Item","entity":"Item"},{"rule":"Must reference a valid Location","entity":"Location"},{"rule":"SOH must reconcile with Stock Ledger running total for the Item/Location","entity":"Stock Ledger"},{"rule":"firstReceivedAt, lastReceivedAt, firstPurchasedAt, lastPurchasedAt, firstSoldAt, lastSoldAt, firstTransferredAt, lastTransferredAt, lastActivityAt must roll up to the parent Item's same-named fields (MIN for first*, MAX for last*) per the product-read-through-must-aggregate-item convention","entity":"Item"},{"rule":"The same fields must roll up per-location to Product.locationActivity[] (aggregated across the Product's Items at each Location)","entity":"Product"},{"rule":"Movement categorization must use the Stock Ledger movementType enum (Sale, Receipt, Adjustment, Transfer, Return); Adjustments do not count as activity","entity":"Stock Ledger"}]},"inlineSchemas":[{"name":"StockQuantities","extends":"OperationalSubDocument","properties":[{"info":"Available to Sell.","name":"ats","type":"integer","required":true},{"info":"Quantity available for backorder.","name":"availableForBackorder","type":"integer","required":true},{"info":"Quantity committed to open orders.","name":"committed","type":"integer","required":true},{"info":"Quantity available for e-commerce channels.","name":"ecommerceAvailable","type":"integer","required":true},{"info":"Quantity on hold.","name":"held","type":"integer","required":true},{"info":"Quantity expected from inbound transfers and purchase orders.","name":"incoming","type":"integer","required":true},{"info":"Last modification timestamp for this location's quantities.","name":"modifiedDate","type":"datetime"},{"info":"Next date when out-of-stock items become available.","name":"nextAvailableDate","type":"datetime"},{"info":"Quantity not available for sale (damaged, QC, etc.).","name":"notSellable","type":"integer","required":true},{"info":"Physical on-hand quantity at the location.","name":"onHand","type":"integer","required":true},{"info":"Quantity reserved for specific purposes.","name":"reserved","type":"integer","required":true}]}]},{"name":"Item Stock Group","class":"Dictionary","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Classification group for inventory items that share common stocking rules, replenishment strategies, or allocation behaviour.","status":"stub","properties":[{"n":"code","r":true,"t":"string"},{"n":"name","r":true,"t":"string"}],"ext":"LookupEntity","related":["Item"]},{"name":"Locale","class":"Dictionary","subsystem":"ALLPOINT","area":"Organization","desc":"IETF BCP 47 language-region definition. Determines available translations, content localization, and display formatting across the platform. Referenced by Market.localization to define supported languages per market.","status":"draft","properties":[{"n":"language","r":true,"t":"string","info":"ISO 639-1 two-letter language code (e.g. en, fr, es, de, ja, zh)."},{"n":"region","t":"entityDetail","re":"Country","info":"ISO 3166-1 country/region associated with this locale. Null for language-only locales (e.g. 'fr' without a region)."},{"n":"script","t":"string","info":"ISO 15924 script code for languages with multiple writing systems (e.g. Latn, Hans, Hant, Cyrl)."},{"n":"direction","r":true,"t":"enumeration","v":["LTR","RTL"],"info":"Text directionality. LTR for most languages, RTL for Arabic, Hebrew, etc."},{"n":"nativeName","r":true,"t":"string","info":"The locale's self-referential display name in its own language (e.g. Français, Español, 日本語)."}],"ext":"LookupEntity","related":["Market","Country"]},{"name":"Location","class":"Operational","subsystem":"CONNECT","area":"Organization","desc":"Physical or virtual point of presence (store, warehouse, online). The atomic unit for inventory and sales.","status":"draft","properties":[{"n":"baseCurrency","t":"string","info":"Default currency for transactions at this location."},{"n":"configuration","r":true,"t":"valueType","info":"Reference to this Location's configuration record in the CONFIG subsystem. Uses the ConfigurationRef value type (config: entityDetail → Config, templateCode: string). Resolved at runtime by services that need location-scoped configuration (POS behavior, fulfillment rules, pricing overrides)."},{"n":"contacts","r":true,"t":"array","info":"Array of LocationContact sub-documents."},{"n":"emails","r":true,"t":"array","info":"Email addresses associated with this location."},{"n":"fulfillmentMethods","r":true,"t":"array","v":["LocalDelivery","ShipToCustomer","StorePickup"],"info":"Supported fulfillment methods at this location."},{"n":"groups","r":true,"t":"array","info":"Location group memberships for reporting and operations."},{"n":"locationNo","r":true,"t":"string"},{"n":"marketId","t":"string","re":"Market","info":"Reference to the Market this location belongs to."},{"n":"name","r":true,"t":"string"},{"n":"phones","r":true,"t":"array","info":"Phone numbers associated with this location."},{"n":"postalAddresses","r":true,"t":"array","info":"Physical postal addresses for this location."},{"n":"priceLevel","t":"entityDetail","re":"Price Level","info":"Default pricing tier for this location."},{"n":"schedule","r":true,"t":"schema","info":"Operating schedule with regular hours and exception dates."},{"n":"shipToAddresses","r":true,"t":"array","info":"Ship-to addresses for receiving inventory at this location."},{"n":"status","r":true,"t":"schema","info":"LocationStatus inline schema capturing current document status with audit trail."},{"n":"stockBins","r":true,"t":"array","re":"StockBin","info":"One-to-many: a Location can have many StockBins; each StockBin belongs to exactly one Location."},{"n":"taxArea","t":"string","info":"Tax jurisdiction code for this location."},{"n":"timeZone","t":"string","info":"IANA time zone identifier (e.g. America/New_York)."},{"n":"type","r":true,"t":"enumeration","v":["Office","Store","Virtual","Warehouse"],"info":"Physical or logical type of the location."},{"n":"website","t":"string","info":"Location-specific website URL."}],"ext":"OperationalDocument","shopify":"Location resource","related":["Market","Franchise Group","Item Stock","StockBin"],"bv":{"rules":[{"rule":"Must be unique within the Company","when":"always","field":"Name","severity":"error"},{"rule":"operationalStatus may only be Open when documentStatus is Active; Draft and Archived Locations must have operationalStatus = Closed","when":"on save","field":"status.operationalStatus","severity":"error"}],"lifecycle":{"states":["Draft","Active","Archived"],"transitions":[{"to":"Active","from":"Draft","conditions":["Address is complete","At least one Market assigned"]},{"to":"Archived","from":"Active","conditions":["All stock transferred out","No pending transactions"]},{"to":"Active","from":"Archived","conditions":["Address is complete","At least one Market assigned","Name and locationNo do not collide with another Active Location in the Company"]}],"initialState":"Draft"},"calculations":[{"name":"stockBinCount","formula":"COUNT(StockBin WHERE location = this)","trigger":"query-time"},{"name":"employeeCount","formula":"COUNT(Employee WHERE location = this)","trigger":"query-time"},{"name":"itemStockCount","formula":"COUNT(Item Stock WHERE location = this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Cannot archive Location with non-zero inventory balances","entity":"Item Stock"},{"rule":"Must belong to at least one Market when Active","entity":"Market"}]},"inlineSchemas":[{"name":"LocationStatus","extends":"OperationalSubDocument","properties":[{"info":"Document lifecycle state of the Location. Drives lifecycle transitions (Draft -> Active -> Archived, with Archived -> Active reactivation).","name":"documentStatus","type":"enumeration","values":["Active","Archived","Draft"],"required":true},{"info":"Operational state independent of document lifecycle. Allows an Active Location to be temporarily Closed (e.g. holiday, remodel, weather) without archiving it.","name":"operationalStatus","type":"enumeration","values":["Closed","Open"],"required":true}]},{"name":"LocationSchedule","extends":"OperationalSubDocument","properties":[{"info":"Exception dates overriding the regular schedule (holidays, special hours).","name":"exceptionDates","type":"array","required":true},{"info":"Regular weekly operating hours per day.","name":"regularHours","type":"array","required":true},{"info":"IANA time zone for schedule interpretation.","name":"timeZone","type":"string"}]}]},{"name":"Loyalty Program","class":"Dictionary","subsystem":"CONNECT","area":"Sales & Orders","desc":"A customer loyalty or rewards program. Customers may be enrolled in one or more programs that influence pricing, promotions, or point accrual.","status":"stub","properties":[{"n":"code","r":true,"t":"string"},{"n":"name","r":true,"t":"string","u":true}],"ext":"LookupEntity","related":["Customer"]},{"name":"Market","class":"Operational","subsystem":"CONNECT","area":"Organization","desc":"Defines fiscal rules and hierarchy. Drives currency, tax treatment, catalogs, and price/cost levels. Supports parent-child nesting for regions.","status":"reviewed","properties":[{"n":"baseCurrency","r":true,"t":"entityDetail","re":"Currency","info":"The canonical accounting currency for all prices, costs, and monetary amounts within this market."},{"n":"catalogs","r":true,"t":"array","re":"Catalog"},{"n":"defaultCostLevel","t":"entityDetail","re":"Cost Level"},{"n":"defaultPriceLevel","t":"entityDetail","re":"Price Level"},{"n":"duties","t":"decimal"},{"n":"localization","r":true,"t":"schema","info":"Localization settings for this market — default locale, supported locales, and display formatting preferences."},{"n":"marketNo","r":true,"t":"integer"},{"n":"name","r":true,"t":"string"},{"n":"parentMarketId","t":"entityRef","re":"Market"},{"n":"salesTax","t":"decimal"}],"related":["Location","Catalog","Currency","Locale","Price Level","Cost Level","Business Entity"],"bv":{"rules":[{"rule":"Must be unique within the Company","when":"always","field":"Name","severity":"error"}],"lifecycle":null,"calculations":[{"name":"locationCount","formula":"COUNT(Location WHERE market = this)","trigger":"query-time"},{"name":"catalogCount","formula":"COUNT(catalogs)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Cannot delete a Market with assigned Locations","entity":"Location"},{"rule":"Removing a Market may orphan Price Level assignments","entity":"Price Level"}]},"inlineSchemas":[{"name":"LocalizationSettings","desc":"Display and formatting preferences for a market's locale configuration.","properties":[{"n":"defaultLocale","r":true,"t":"entityDetail","re":"Locale","info":"The primary locale for this market. Used as the fallback when no translation exists for a requested locale."},{"n":"supportedLocales","r":true,"t":"array","re":"Locale","info":"All locales available for content and UI in this market."},{"n":"dateFormat","r":true,"t":"enumeration","v":["MM/DD/YYYY","DD/MM/YYYY","YYYY-MM-DD"],"info":"Date display format for this market."},{"n":"timeFormat","r":true,"t":"enumeration","v":["12h","24h"],"info":"Time display format — 12-hour (AM/PM) or 24-hour clock."},{"n":"decimalSeparator","r":true,"t":"enumeration","v":[".",","],"info":"Character used as the decimal point in numeric display (period for US/UK, comma for EU)."},{"n":"thousandsSeparator","r":true,"t":"enumeration","v":[",","."," ",""],"info":"Character used as the thousands grouping separator in numeric display."},{"n":"measurementSystem","r":true,"t":"enumeration","v":["Imperial","Metric"],"info":"Default measurement system for weights, dimensions, and distances."},{"n":"defaultTimezone","r":true,"t":"string","info":"IANA timezone identifier (e.g. America/New_York, Europe/London). Used for date/time display and scheduling defaults."}]}],"inheritance":{"childEntity":"Market","parentEntity":"Market","overridableDefaults":["defaultCostLevel","defaultPriceLevel"]}},{"name":"Matched Invoice Set","class":"Operational","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"Three-way match aggregate linking a Purchase Order, its Goods Receipts, and the corresponding Vendor Invoice for AP reconciliation.","status":"stub","properties":[{"n":"purchaseOrder","r":true,"t":"entityRef","re":"Purchase Order"},{"n":"purchaseReceipts","r":true,"t":"array","re":"Goods Receipt"},{"n":"vendorInvoice","r":true,"t":"entityRef","re":"Vendor Invoice"}],"related":["Purchase Order","Goods Receipt","Vendor Invoice"]},{"name":"Media","class":"Operational","subsystem":"CONNECT","area":"Products & Pricing","desc":"A media asset (image or video) stored in the platform's media library. Tracks upload lifecycle, processing status, and original source metadata. Referenced by Product, Item, and Product Group via their media property (ProductMedia inline schema).","status":"draft","properties":[{"n":"alt","r":true,"t":"string","info":"Alt text for accessibility and SEO."},{"n":"fileErrors","t":"array<string>","info":"Error messages from file upload or processing failures."},{"n":"fileStatus","r":true,"t":"enumeration","v":["Queued","Uploading","Uploaded","Processing","Ready","Failed"],"info":"Upload and processing lifecycle state of the underlying file."},{"n":"isHostedExternal","t":"boolean","info":"Whether the media is hosted on an external CDN rather than the platform's storage. Only applicable to Image type."},{"n":"mediaErrors","t":"array<string>","info":"Errors encountered during media-specific processing (e.g. thumbnail generation, transcoding)."},{"n":"mediaStatus","r":true,"t":"schema","info":"Processing status of the media asset. Tracks whether the media is ready for use."},{"n":"mediaType","r":true,"t":"enumeration","v":["Image","Video"],"info":"Whether this asset is an image or video."},{"n":"mediaWarnings","t":"array<string>","info":"Non-fatal warnings from media processing (e.g. suboptimal resolution, missing metadata)."},{"n":"mimeType","r":true,"t":"string","info":"MIME type of the media file (e.g. image/jpeg, video/mp4)."},{"n":"originalMediaSource","r":true,"t":"schema","info":"Original source metadata including dimensions, file size, format, and source URL."},{"n":"previewUrl","t":"string","info":"URL of a lower-resolution preview image. Only applicable to Image type."},{"n":"thumbHash","t":"string","info":"ThumbHash-encoded placeholder for progressive image loading. Only applicable to Image type."},{"n":"url","r":true,"t":"string","info":"Primary URL of the media asset."}],"ext":"Identifiable","related":["Product","Item","Product Group"],"bv":{"rules":[{"rule":"Alt text is required for accessibility compliance","when":"always","field":"alt","severity":"error"},{"rule":"Must be Image or Video","when":"create","field":"mediaType","severity":"error"}],"lifecycle":{"states":["Pending","Processing","Ready","Failed"],"transitions":[{"to":"Processing","from":"Pending","conditions":["File upload completed"]},{"to":"Ready","from":"Processing","conditions":["Thumbnails generated, optimized versions created"]},{"to":"Failed","from":"Processing","conditions":["Processing error (corrupt file, unsupported format)"]},{"to":"Pending","from":"Failed","conditions":["Re-upload initiated"]}],"initialState":"Pending"},"calculations":[],"crossEntityConstraints":[{"rule":"Deleting media referenced by a Product requires confirmation","entity":"Product"},{"rule":"Deleting media referenced by an Item requires confirmation","entity":"Item"}]},"inlineSchemas":[{"name":"MediaSource","schema":"schemas/media/MediaSource.ts","properties":[{"info":"Original filename as uploaded.","name":"filename","type":"string"},{"info":"File size in bytes.","name":"fileSize","type":"integer","required":true},{"info":"File format (e.g. jpeg, png, mp4, webm).","name":"format","type":"string","required":true},{"info":"Height in pixels.","name":"height","type":"integer","required":true},{"info":"MIME type of the original file.","name":"mimeType","type":"string","required":true},{"info":"URL of the original uploaded file before any processing.","name":"url","type":"string","required":true},{"info":"Width in pixels.","name":"width","type":"integer","required":true}]},{"name":"MediaStatus","schema":"schemas/media/MediaStatus.ts","properties":[{"info":"Current processing state of the media asset.","name":"status","type":"enumeration","values":["Pending","Processing","Ready","Failed"],"required":true},{"info":"User or system actor who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"}]}]},{"name":"Option","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"A configurable choice on a product (e.g. Engraving, Gift Wrap). OptionValues on an Item carry relative price/cost adjustment deltas that modify line-level pricing at time of sale/purchase — these are NOT transactional and do not write to Price Ledger or Cost Ledger.","status":"draft","properties":[{"n":"alias","t":"string"},{"n":"code","r":true,"t":"string"},{"n":"description","t":"string"},{"n":"group","t":"string","info":"Logical grouping label for related options."},{"n":"name","r":true,"t":"string","u":true},{"n":"vendorCode","t":"string"},{"n":"optionValues","t":"array<OptionValue>","info":"The set of selectable values for this option (e.g. for a 'Color' option: Red, Blue, Green)."}],"ext":"LookupEntity","related":["Product","Item"],"bv":{"rules":[],"lifecycle":null,"calculations":[{"name":"productCount","formula":"COUNT(Product WHERE options CONTAINS this)","trigger":"query-time"}],"crossEntityConstraints":[]},"inlineSchemas":[{"name":"OptionValue","desc":"A single selectable value within an Option's value set. Extends LookupEntityValue — inherits id, identifiers, code, name, aliases, isActive, isDeleted, isDefault, sequence, audit fields, and customData.","schema":"schemas/product-common/OptionValue.ts","extends":"LookupEntityValue","properties":[{"info":"A +/- difference the option value applies to the line-level price. This is a relative modifier, not a transactional adjustment — does not write to the Price Ledger.","name":"priceModifier","type":"decimal"},{"name":"vendorCostModifiers","type":"array<VendorOptionCostModifier>"}]},{"name":"VendorOptionCostModifier","schema":"schemas/product-common/VendorOptionCostModifier.ts","properties":[{"info":"A +/- difference the option value applies to the vendor cost. This is a relative modifier, not a transactional adjustment — does not write to the Cost Ledger.","name":"costModifier","type":"decimal","required":true},{"name":"vendor","type":"entityDetail","required":true,"relatedEntity":"Vendor"}]}]},{"name":"Organization","class":"Core","subsystem":"ALLPOINT","area":"Organization","desc":"Top-level SaaS account entity. The root container for Companies, Business Entities, and platform-wide configuration. An Organization represents the subscriber — the entity that holds a contract with All Point. Multi-tenancy is enforced at the Company level, not the Organization level — and so deploymentModel (Shared/Dedicated) is a Company property, not an Organization property. An Organization can hold multiple Companies, and in principle different Companies under the same Organization can sit on different Clients with different deployment models. Supports parent/child self-referencing to model franchise relationships — a Franchisor Organization is the parent of its Franchisee Organizations, enabling cross-org catalog distribution, reporting, and shared product masters while maintaining full data isolation per tenant.","status":"stub","properties":[{"n":"children","r":true,"t":"array","re":"Organization","info":"Child Organizations in a franchise hierarchy. A Franchisor Org has Franchisee Orgs as children. Empty for standalone or leaf organizations."},{"n":"companies","r":true,"t":"array","re":"Company","info":"One-to-many. An Organization has many Companies; a Company belongs to exactly one Organization."},{"n":"configuration","r":true,"t":"valueType","info":"Reference to this Organization's configuration record in the CONFIG subsystem. Uses the ConfigurationRef value type (config: entityDetail → Config, templateCode: string). Holds org-wide platform settings inherited by child Companies unless overridden."},{"n":"name","r":true,"t":"string"},{"n":"organizationNo","r":true,"t":"string","u":true,"info":"Human-readable business identifier"},{"n":"parentOrganization","t":"entityRef","re":"Organization","info":"Self-referencing parent Organization. Null for root organizations (e.g. a standalone brand or a Franchisor). Set to the Franchisor Organization for Franchisee Organizations. Enables franchise hierarchy without conflating the business relationship with SaaS tenancy."},{"n":"status","r":true,"t":"schema","info":"Lifecycle status of the organization. Tracks current state and who/when it was last changed."},{"n":"type","t":"enumeration","v":["Standard","Franchisor","Franchisee"],"info":"Discriminator for organizational role. Standard: independent brand. Franchisor: parent organization that licenses to franchisees. Franchisee: operates under a franchisor's brand and catalog."}],"related":["Company","Business Entity"],"inlineSchemas":[{"name":"OrganizationStatus","schema":"schemas/organization/OrganizationStatus.ts","properties":[{"info":"Current lifecycle state of the organization.","name":"status","type":"enumeration","values":["Draft","Active","Archived"],"required":true},{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"}]}],"inheritance":{"extends":"CoreEntity"}},{"name":"Permission","class":"Dictionary","subsystem":"ACCESS","area":"Security & Permissions","desc":"An atomic capability claim (e.g. 'inventory:read', 'orders:write'). Grouped into Areas, bundled into Scopes, and assigned to Roles.","status":"draft","properties":[{"n":"area","r":true,"t":"entityDetail","re":"Area"},{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key. Unique within scope."},{"n":"description","t":"string"},{"n":"name","r":true,"t":"string","u":true},{"n":"permissionId","r":true,"t":"string"}],"service":"APR Access","shopify":"Access scopes (read_products, write_orders)","related":["Area","Scope","Role"],"bv":{"rules":[{"rule":"Must be unique and follow resource:action format","when":"create","field":"Key","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Permissions may be narrowed by Scope","entity":"Scope"}]}},{"name":"Price Adjustment","class":"Transactional","subsystem":"CONNECT","area":"Products & Pricing","desc":"Transactional document recording a price change applied to items, reflecting markdowns, markups, or promotional pricing. Writes entries to the Price Ledger. Distinct from option-level priceModifier deltas on OptionValues which are relative and non-transactional.","status":"stub","properties":[{"n":"adjustmentId","r":true,"t":"string"},{"n":"item","r":true,"t":"entityRef","re":"Item"},{"n":"newPrice","r":true,"t":"decimal"},{"n":"oldPrice","r":true,"t":"decimal"},{"n":"priceLevel","t":"entityDetail","re":"Price Level"},{"n":"reason","t":"string"}],"ext":"TransactionalDocument","related":["Item","Price Level","Price Ledger"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Must reference a valid Price Level","entity":"Price Level"},{"rule":"Must reference a valid active Item or Product","entity":"Item"}]}},{"name":"Price Ledger","class":"Ledger","subsystem":"CONNECT","area":"Products & Pricing","desc":"Immutable chronological audit log of all price changes for items across time.","status":"stub","properties":[{"n":"item","r":true,"t":"entityRef","re":"Item"},{"c":true,"n":"ledgerLine","r":true,"t":"integer"},{"n":"price","r":true,"t":"decimal"},{"n":"priceLevel","t":"entityDetail","re":"Price Level"}],"ext":"LedgerEntry","related":["Item","Price Level","Price Adjustment"],"bv":{"rules":[{"rule":"Append-only — existing entries must not be modified or deleted","when":"always","field":"LedgerLine","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Price Level","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"Named pricing tier or schedule defining different prices for different customer groups, channels, or regions. Extends LookupEntity — no additional fields beyond the base.","status":"stub","properties":[],"ext":"LookupEntity","shopify":"Market-specific pricing and B2B catalogs","related":["Market","Product","Item","Price Rule"],"bv":{"rules":[{"rule":"Must be unique within the Company","when":"create","field":"Code","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Should be associated with at least one Market","entity":"Market"}]}},{"name":"Price Rule","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"Conditional logic defining when and how prices are calculated or discounts applied.","status":"stub","properties":[{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key. Unique within scope."},{"n":"conditions","t":"schema"},{"n":"name","r":true,"t":"string","u":true},{"n":"priceRuleId","r":true,"t":"string"}],"shopify":"PriceRule resource","related":["Price Level","Promotion","Discount"],"bv":{"rules":[{"rule":"Must resolve deterministically when multiple rules overlap","when":"always","field":"Priority","severity":"warning"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Must reference a valid Price Level","entity":"Price Level"}]}},{"name":"Product","class":"Operational","subsystem":"CONNECT","area":"Products & Pricing","desc":"Master product record (style/SKU group). Defines classification, attributes, options, pricing, vendors, and channel availability. Contains child Items.","status":"draft","properties":[{"n":"approval","t":"schema","info":"Approval workflow state: status (Pending Review / In Review / Approved / Rejected), approvedBy, approvedDate, reason."},{"n":"attributes","t":"array","info":"Array of ProductAttribute sub-documents. Each entry references an Attribute and records its selection position on the Product. Nullish — not required at creation time."},{"n":"brand","t":"entityDetail","re":"Brand"},{"n":"catalogs","t":"array","re":"Catalog","info":"Array of Catalog IDs the product is assigned to."},{"n":"class","r":true,"t":"enumeration","v":["Style","Single","Service","Digital","Wallet"],"info":"Determines product behavior. Style = physical, 2+ Items, requires ≥1 Attribute, inventory optionally tracked. Single = physical, 1 Item, Attributes optional, inventory optionally tracked. Service = non-physical, 1 Item, Attributes N/A, inventory cannot be tracked. Digital = non-physical/non-service, 1 Item, inventory optionally tracked. Wallet = stored-value (e.g. gift cards), may be physical, 1 Item, inventory not tracked, creates a liability on sale."},{"n":"classification","r":true,"t":"entityDetail","re":"Classification"},{"n":"code","r":true,"t":"string"},{"n":"countryOfOrigin","t":"entityDetail","re":"Country"},{"n":"defaultBasePrice","t":"decimal","info":"Sticker price before price-level overrides. Inherited by child Items unless Item.BasePrice is set."},{"n":"defaultDropshipEligibility","t":"enumeration","v":["Available","Required","Unavailable"]},{"n":"defaultMeasurements","t":"schema","info":"Default measurements (height/length/width/weight). Inherited by child Items unless Item.measurements is set. Shape defined by the ProductMeasurements inline schema."},{"n":"defaultPrices","t":"array","re":"Price Level","info":"Array of ProductPrice objects ({ price: decimal, priceLevel: EntityDetail→Price Level }). One entry per Price Level. Inherited by child Items unless overridden at Item.prices. Nullish in Zod — not required at creation time."},{"n":"description","t":"string"},{"c":true,"n":"firstPurchasedAt","t":"datetime","info":"Earliest timestamp this Product appeared on a purchase order across all variants and locations. Named Purchased (not Ordered) to avoid confusion with sales/customer orders. Fold: MIN(locationActivity[].firstPurchasedAt). Cascade: PO subsystem → Item Stock → locationActivity → this scalar. Null if never purchased. Renamed from firstOrderedAt on 2026-07-24."},{"c":true,"n":"firstReceivedAt","t":"datetime","info":"Earliest timestamp this Product was received into any location from an external source (movementType 'Receipt' or 'Return' — transfers post as 'Transfer' and are tracked via firstTransferredAt). Fold: MIN(locationActivity[].firstReceivedAt). Cascade: Stock Ledger → Item Stock → locationActivity → this scalar. Null if never received."},{"c":true,"n":"firstSoldAt","t":"datetime","info":"Earliest timestamp this Product was sold at any location across all variants. Fold: MIN(locationActivity[].firstSoldAt). Cascade: Stock Ledger Service → Item Stock → locationActivity → this scalar. Null if never sold."},{"c":true,"n":"firstTransferredAt","t":"datetime","info":"Earliest timestamp this Product was transferred in or out of any location across all variants. Fold: MIN(locationActivity[].firstTransferredAt). Kept separate from firstReceivedAt so transfer activity is composable in or out of recency analysis. Null if never transferred."},{"n":"hasSerialNumber","r":true,"t":"boolean"},{"n":"hsCode","t":"entityDetail","re":"Hs Code","info":"Harmonized System tariff code for customs and duty classification."},{"n":"items","r":true,"t":"array","re":"Item"},{"c":true,"n":"lastActivityAt","t":"datetime","info":"Most recent activity for this Product across all locations, transfers excluded: MAX(lastPurchasedAt, lastReceivedAt, lastSoldAt). Equivalently MAX(locationActivity[].lastActivityAt). Transfer-inclusive recency = MAX(lastActivityAt, lastTransferredAt) at query time. Recency checks evaluate against a configurable window (e.g. Company.activityRecencyDays). For per-location or per-franchise recency, query locationActivity instead."},{"c":true,"n":"lastPurchasedAt","t":"datetime","info":"Most recent timestamp this Product appeared on a purchase order across all variants and locations. Named Purchased (not Ordered) to avoid confusion with sales/customer orders. Fold: MAX(locationActivity[].lastPurchasedAt). Cascade: PO subsystem → Item Stock → locationActivity → this scalar. Null if never purchased. Renamed from lastOrderedAt on 2026-07-24."},{"c":true,"n":"lastReceivedAt","t":"datetime","info":"Most recent timestamp this Product was received into any location from an external source (movementType 'Receipt' or 'Return' — transfers post as 'Transfer' and are tracked via lastTransferredAt). Fold: MAX(locationActivity[].lastReceivedAt). Cascade: Stock Ledger → Item Stock → locationActivity → this scalar. Null if never received."},{"c":true,"n":"lastSoldAt","t":"datetime","info":"Most recent timestamp this Product was sold at any location across all variants. Fold: MAX(locationActivity[].lastSoldAt). Cascade: Stock Ledger Service → Item Stock → locationActivity → this scalar. Null if never sold."},{"c":true,"n":"lastTransferredAt","t":"datetime","info":"Most recent timestamp this Product was transferred in or out of any location across all variants. Fold: MAX(locationActivity[].lastTransferredAt). Kept separate from lastReceivedAt so transfer activity is composable in or out of recency analysis. Null if never transferred."},{"c":true,"n":"locationActivity","r":true,"t":"array","re":"Location","info":"Array of ProductLocationActivity inline schema objects — per-location lifecycle/activity dates at Product × Location grain. Required; defaults to [] at creation, entries created lazily on first activity at a Location (array size bounded by touched locations). Each entry aggregates the child Items' locationActivity entries for that Location (MIN for first*, MAX for last*) — immediate source is Item.locationActivity, not Item Stock, so the product/item domain stays independent of the inventory projection; Item Stock reconciles against the same values. location unique within the array. Enables per-location and per-franchise recency: resolve Franchise Group → Locations, then match entries with lastActivityAt (or MAX(lastActivityAt, lastTransferredAt) for transfer-inclusive) >= NOW() - recency window. The Product-level all-location scalars are folds (MIN/MAX) over this array."},{"n":"media","t":"array","info":"Array of ProductMedia inline schema objects. Each entry represents a media asset (image, thumbnail, or video) associated with the product."},{"n":"menus","r":true,"t":"array","re":"Product Menu"},{"n":"name","r":true,"t":"string"},{"n":"options","t":"array","re":"Option","info":"Nullish in Zod — not required at creation time."},{"n":"preferredVendors","r":true,"t":"array","info":"Array of ProductPreferredVendor inline schema objects. One entry per Franchise Group, designating the Vendor that group prefers to source this Product from. The preferred vendor may differ from ProductVendor.isPrimary (the tenant-wide primary). Inherited by child Items for the same Franchise Group unless overridden at Item.preferredVendors. Referenced Vendor must already exist in Product.vendors[]. At most one preferred vendor per Franchise Group. Nullish — not required at creation time."},{"n":"productGroups","t":"array","re":"Product Group","info":"Nullish in Zod — not required at creation time."},{"n":"productNo","r":true,"t":"string"},{"n":"salesChannels","r":true,"t":"array","re":"Sales Channel"},{"n":"seasons","t":"array","re":"Season","info":"One or more merchandising seasons, with a primary flag. Nullish in Zod — not required at creation time."},{"n":"shortName","t":"string"},{"n":"slug","t":"string","u":true,"info":"URL-friendly identifier for storefront routing and public API lookups. Equivalent to Shopify's handle property. Auto-generated from product name if not provided. Must be unique across all products."},{"n":"status","r":true,"t":"schema","info":"Lifecycle status of the product. Tracks current state and who/when it was last changed."},{"n":"stockLimitGroups","t":"array","re":"Stock Limit Group","info":"Nullish in Zod — not required at creation time."},{"n":"taxClass","r":true,"t":"entityDetail","re":"Tax Class"},{"n":"trackInventory","r":true,"t":"boolean","info":"Whether inventory quantities are tracked for this product. Optional for Style, Single, and Digital. Must be false for Service and Wallet products."},{"n":"type","t":"entityDetail","re":"Product Type"},{"c":true,"n":"vendorActivity","r":true,"t":"array","re":"Vendor","info":"Array of ProductVendorActivity inline schema objects — per-vendor activity dates at Product × Vendor grain (purchase/receipt legs only). Required; defaults to []; entries created lazily on first activity with a vendor. vendor unique within the array. Aggregates child Items' vendorActivity entries (MIN/MAX per vendor). Deliberately separate from vendors[] (current sourcing config): entries are NEVER deleted on vendor unassignment, preserving historical activity across vendor changes. Amount fields to be added by the deferred amounts request (registry question #11)."},{"n":"vendors","r":true,"t":"array","info":"Array of ProductVendor inline schema objects. Each entry links a Vendor to the product with optional primary designation and cost-level pricing. Product-level cost baseline per Vendor × Cost Level, inherited by Items unless overridden at VendorItemValues."},{"n":"weeksOfSupply","t":"integer","info":"Default weeks of supply for this product. Serves as the default value for newly created Items — Items inherit this value but can override it at the Item level."}],"ext":"OperationalDocument","shopify":"Product resource","related":["Item","Classification","Tax Class","Season","Brand","Vendor","Sales Channel","Product Menu","Product Group","Stock Limit Group","Catalog","Price Level","Franchise Group","Location"],"bv":{"rules":[{"rule":"Must be unique across the Company","when":"create","field":"ProductNo","severity":"error"},{"rule":"Must be unique across the Company if provided","when":"always","field":"ProductCode","severity":"error"},{"rule":"The combination of attributeValues across Items must be unique within the Product — no two Items may share the same attribute value set","when":"always","field":"items","severity":"error"},{"rule":"Cannot add an Item until at least one Vendor is selected. For Style products, at least one Attribute must also be selected.","when":"item-create","field":"items","severity":"error"},{"rule":"Cannot deselect or change an Attribute if at least one child Item has a value for that Attribute","when":"update","field":"attributes","severity":"error"},{"rule":"Style products must have 2 or more Items","when":"always","field":"class","severity":"error"},{"rule":"Single, Service, Digital, and Wallet products can have at most 1 Item","when":"always","field":"class","severity":"error"},{"rule":"Attributes are not relevant to Service products","when":"always","field":"class","severity":"info"},{"rule":"Service and Wallet products cannot have trackInventory set to true","when":"always","field":"trackInventory","severity":"error"},{"rule":"Wallet products create a stored value / liability on sale","when":"always","field":"class","severity":"info"},{"rule":"Each preferredVendors[].vendor must reference a Vendor that is present in Product.vendors[].vendor","when":"always","field":"preferredVendors","severity":"error"},{"rule":"franchiseGroup must be unique within preferredVendors — at most one preferred vendor per Franchise Group","when":"always","field":"preferredVendors","severity":"error"},{"rule":"location must be unique within locationActivity — at most one activity entry per Location","when":"always","field":"locationActivity","severity":"error"},{"rule":"vendor must be unique within vendorActivity — at most one activity entry per Vendor. Entries are never deleted on vendor unassignment; vendorActivity[].vendor need not be present in vendors[]","when":"always","field":"vendorActivity","severity":"error"}],"lifecycle":{"states":["Draft","Active","Archived"],"disallowed":[{"to":"Draft","from":"Active","reason":"Products cannot revert to Draft once activated"},{"to":"Draft","from":"Archived","reason":"Products cannot revert to Draft once activated"}],"transitions":[{"to":"Active","from":"Draft","conditions":["At least one Item exists","Base price is set"]},{"to":"Archived","from":"Active","conditions":["No open Sales Orders referencing active Items","Total qty on hand across all Items is zero"]},{"to":"Active","from":"Archived","conditions":[]}],"initialState":"Draft","terminalExits":["Draft"]},"calculations":[{"name":"itemCount","formula":"COUNT(items)","trigger":"query-time"},{"name":"vendorCount","formula":"COUNT(DISTINCT vendor via Item.vendors)","trigger":"query-time"},{"name":"salesChannelCount","formula":"COUNT(salesChannels)","trigger":"query-time"},{"name":"optionCount","formula":"COUNT(options)","trigger":"query-time"},{"name":"attributeCount","formula":"COUNT(attributes)","trigger":"query-time"},{"info":"Whether this Product has recent activity (purchased on a PO, externally received, or sold) at a given Location, transfers excluded. Window is configurable (e.g. Company.activityRecencyDays, default 90). Evaluated at query time against the stored locationActivity timestamps — recency is never stored as a boolean because it goes stale as the clock moves with no triggering event.","name":"hasRecentActivityAtLocation(location, window)","formula":"EXISTS locationActivity entry WHERE location = $location AND lastActivityAt >= NOW() - $window","trigger":"query-time"},{"info":"Transfer-inclusive variant: same as hasRecentActivityAtLocation but also counts Transfer movements as activity.","name":"hasRecentActivityAtLocation(location, window, includeTransfers=true)","formula":"EXISTS locationActivity entry WHERE location = $location AND MAX(lastActivityAt, lastTransferredAt) >= NOW() - $window","trigger":"query-time"},{"info":"Whether this Product has recent activity at any Location belonging to a Franchise Group. Resolves Franchise Group → member Locations at query time (via Location.groups) — franchiseGroup is deliberately NOT denormalized into locationActivity because group membership can change. Pass includeTransfers to compose transfers in.","name":"hasRecentActivityForFranchiseGroup(franchiseGroup, window, includeTransfers)","formula":"EXISTS locationActivity entry WHERE location IN (Locations of $franchiseGroup) AND (includeTransfers ? MAX(lastActivityAt, lastTransferredAt) : lastActivityAt) >= NOW() - $window","trigger":"query-time"},{"info":"Per-location activity dates. Immediate source is the child Items' locationActivity entries (MIN for first*, MAX for last* per Location) — not Item Stock, so the product/item domain stays independent of the inventory projection; Item Stock reconciles against the same values. Entries created lazily.","name":"locationActivity","formula":"per Location: MIN/MAX(Item.locationActivity[location].*) across the Product's child Items","trigger":"derived: propagated when any child Item's locationActivity entry changes"},{"info":"Per-vendor activity dates (purchase/receipt legs only). Immediate source is the child Items' vendorActivity entries (MIN/MAX per Vendor). Entries keyed by vendor, created lazily, NEVER deleted on vendor unassignment — vendors[] stays pure sourcing config while history survives vendor changes.","name":"vendorActivity","formula":"per Vendor: MIN/MAX(Item.vendorActivity[vendor].*) across the Product's child Items","trigger":"derived: propagated when any child Item's vendorActivity entry changes"}],"crossEntityConstraints":[{"rule":"Cannot archive Product if any child Items are Active","when":"archive","entity":"Item"},{"rule":"Cannot archive Product if total qty on hand across child Items is greater than zero","when":"archive","entity":"Item Stock"},{"rule":"Must reference a valid Classification hierarchy","when":"always","entity":"Classification"},{"rule":"preferredVendors[].vendor must resolve to an Active Vendor within the Company","when":"always","entity":"Vendor"},{"rule":"locationActivity[].location must reference a valid Location","when":"always","entity":"Location"},{"rule":"locationActivity entries aggregate the child Items' locationActivity entries per Location (MIN for first*, MAX for last*); Product-level scalars are MIN/MAX folds over locationActivity","when":"always","entity":"Item"},{"rule":"vendorActivity[].vendor must reference a valid Vendor (need not be currently assigned in vendors[] — entries survive unassignment)","when":"always","entity":"Vendor"},{"rule":"vendorActivity entries aggregate the child Items' vendorActivity entries per Vendor (MIN for first*, MAX for last*)","when":"always","entity":"Item"}]},"inlineSchemas":[{"name":"VendorCost","schema":"schemas/product-common/VendorCost.ts","properties":[{"name":"cost","type":"decimal","required":true},{"name":"costLevel","type":"entityDetail","required":true,"relatedEntity":"Cost Level"}]},{"name":"ProductMedia","schema":"schemas/product/ProductMedia.ts","properties":[{"info":"Product image assets.","name":"images","type":"array","relatedEntity":"Media"},{"info":"Thumbnail image assets.","name":"thumbnails","type":"array","relatedEntity":"Media"},{"info":"Video assets.","name":"videos","type":"array","relatedEntity":"Media"}]},{"name":"ProductPrice","schema":"schemas/product-common/ProductPrice.ts","properties":[{"name":"price","type":"decimal","required":true},{"name":"priceLevel","type":"entityDetail","required":true,"relatedEntity":"Price Level"}]},{"name":"ProductSeason","schema":"schemas/product/ProductSeason.ts","properties":[{"name":"isPrimary","type":"boolean","required":true},{"name":"seasons","type":"array<entityDetail>","required":true,"relatedEntity":"Season"}]},{"name":"ProductStatus","schema":"schemas/product/ProductStatus.ts","properties":[{"info":"Current lifecycle state of the product.","name":"status","type":"enumeration","values":["Draft","Active","Archived"],"required":true},{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"}]},{"name":"ProductVendor","schema":"schemas/product/ProductVendor.ts","properties":[{"info":"Reference to the Vendor entity.","name":"vendor","type":"entityDetail","required":true,"relatedEntity":"Vendor"},{"info":"Whether this is the primary/default vendor for the product.","name":"isPrimary","type":"boolean"},{"info":"Cost per Cost Level for this vendor. Inherited by Items unless overridden.","name":"costs","type":"array<VendorCost>"}]},{"name":"AttributeValue","schema":"schemas/product-common/AttributeValue.ts","properties":[{"info":"Reference to the parent Attribute dictionary entity this value belongs to.","name":"attribute","type":"entityDetail","required":true,"relatedEntity":"Attribute"},{"name":"code","type":"string","required":true},{"name":"name","type":"string"},{"info":"Ordinal position of this value within the parent Attribute (e.g. 1 = first colour, 2 = second colour).","name":"sequence","type":"integer"},{"name":"aliases","type":"array<string>"}]},{"name":"ProductApproval","schema":"schemas/product/ProductApproval.ts","properties":[{"name":"status","type":"enumeration","values":["Pending Review","In Review","Approved","Rejected"],"required":true},{"name":"approvedBy","type":"string"},{"name":"approvedDate","type":"datetime"},{"name":"reason","type":"string"}]},{"name":"ProductAttribute","schema":"schemas/product/ProductAttribute.ts","properties":[{"info":"Reference to the selected Attribute dictionary entity (e.g. Color, Size).","name":"attribute","type":"entityDetail","required":true,"relatedEntity":"Attribute"},{"info":"The ordinal position of this attribute on the product (e.g. 1 = Attribute 1, 2 = Attribute 2).","name":"position","type":"integer","required":true}]},{"name":"ProductMeasurements","schema":"schemas/product/ProductMeasurements.ts","properties":[{"name":"height","type":"string"},{"name":"length","type":"string"},{"name":"weight","type":"string"},{"name":"width","type":"string"}]},{"name":"ProductSalesChannel","schema":"schemas/product/ProductSalesChannel.ts","properties":[{"name":"code","type":"string","required":true},{"name":"isPublished","type":"boolean","required":true},{"name":"name","type":"string"}]},{"name":"ProductVendorActivity","info":"Per-vendor lifecycle/activity dates for this Product (Product × Vendor grain). Aggregates the child Items' vendorActivity entries for that Vendor (MIN for first*, MAX for last*). Activity projection keyed by vendor — deliberately separate from vendors[] (ProductVendor, current sourcing config): entries are NEVER deleted when a vendor is unassigned, so historical activity survives vendor changes. Entries created lazily on first activity with a vendor; vendor unique within the array. Only the purchase/receipt legs exist — sales and transfers have no vendor dimension. Amount fields scoped to the deferred amounts request — see registry question #11.","schema":"schemas/product/ProductVendorActivity.ts","extends":"OperationalSubDocument","properties":[{"info":"The Vendor this activity entry applies to. Unique within vendorActivity. Need NOT be present in vendors[] — entries survive vendor unassignment.","name":"vendor","type":"entityDetail","required":true,"relatedEntity":"Vendor"},{"info":"Earliest PO line placed for any of the Product's Items with this Vendor. MIN(Item.vendorActivity[].firstPurchasedAt) across child Items.","name":"firstPurchasedAt","type":"datetime","calculated":true},{"info":"Most recent PO line placed for any of the Product's Items with this Vendor. MAX(Item.vendorActivity[].lastPurchasedAt) across child Items.","name":"lastPurchasedAt","type":"datetime","calculated":true},{"info":"Earliest external receipt (movementType Receipt or Return) of any of the Product's Items sourced from this Vendor. MIN(Item.vendorActivity[].firstReceivedAt).","name":"firstReceivedAt","type":"datetime","calculated":true},{"info":"Most recent external receipt (movementType Receipt or Return) of any of the Product's Items sourced from this Vendor. MAX(Item.vendorActivity[].lastReceivedAt).","name":"lastReceivedAt","type":"datetime","calculated":true}]},{"name":"ProductPreferredVendor","info":"Per-franchise-group preferred vendor override for this Product. Each entry maps one Franchise Group to the Vendor that group prefers to source this Product from, which may differ from ProductVendor.isPrimary (the tenant-wide primary). The referenced Vendor MUST already exist in Product.vendors[].vendor — preferred vendor cannot be an un-linked vendor. At most one entry per Franchise Group (unique constraint on franchiseGroup within Product.preferredVendors).","schema":"schemas/product/ProductPreferredVendor.ts","extends":"OperationalSubDocument","properties":[{"info":"The Franchise Group this preferred vendor applies to.","name":"franchiseGroup","type":"entityDetail","required":true,"relatedEntity":"Franchise Group"},{"info":"The Vendor this Franchise Group prefers to source this Product from. Must reference a Vendor that is present in Product.vendors[].vendor.","name":"vendor","type":"entityDetail","required":true,"relatedEntity":"Vendor"}]},{"name":"ProductLocationActivity","info":"Per-location lifecycle/activity dates for this Product (Product × Location grain). One entry per Location with any activity — entries are created lazily on first activity, so array size is bounded by touched locations, not the full location list. All datetime fields are computed MIN/MAX aggregations of Item Stock rows across the Product's Items at this Location, maintained by the same events that update Item Stock (InventoryPositionChanged from the Stock Ledger; PO line events from CONNECT). Movement categorization uses the Stock Ledger movementType enum (Sale, Receipt, Adjustment, Transfer, Return); Adjustments do not count as activity. location is unique within the array. Franchise-level recency resolves at query time: Franchise Group → member Locations → $elemMatch(location IN [...], lastActivityAt >= cutoff). Do NOT denormalize franchiseGroup into entries — location group membership can change and would silently stale product documents.","schema":"schemas/product/ProductLocationActivity.ts","extends":"OperationalSubDocument","properties":[{"info":"The Location this activity entry applies to. Unique within locationActivity.","name":"location","type":"entityDetail","required":true,"relatedEntity":"Location"},{"info":"Earliest PO line placed for any of the Product's Items with deliverTo = this location. MIN(Item Stock.firstPurchasedAt) across the Product's Items at this Location. Named Purchased (not Ordered) to avoid confusion with sales/customer orders.","name":"firstPurchasedAt","type":"datetime","calculated":true},{"info":"Most recent PO line placed for any of the Product's Items with deliverTo = this location. MAX(Item Stock.lastPurchasedAt) across the Product's Items at this Location.","name":"lastPurchasedAt","type":"datetime","calculated":true},{"info":"Earliest external receipt (movementType Receipt or Return — transfers post as Transfer and are tracked separately) of any of the Product's Items at this Location. MIN(Item Stock.firstReceivedAt).","name":"firstReceivedAt","type":"datetime","calculated":true},{"info":"Most recent external receipt (movementType Receipt or Return — transfers post as Transfer and are tracked separately) of any of the Product's Items at this Location. MAX(Item Stock.lastReceivedAt).","name":"lastReceivedAt","type":"datetime","calculated":true},{"info":"Earliest sale (movementType Sale — a sale is a sale regardless of channel; Sales Channel is an attribute of the sale) of any of the Product's Items at this Location. MIN(Item Stock.firstSoldAt).","name":"firstSoldAt","type":"datetime","calculated":true},{"info":"Most recent sale (movementType Sale — a sale is a sale regardless of channel; Sales Channel is an attribute of the sale) of any of the Product's Items at this Location. MAX(Item Stock.lastSoldAt).","name":"lastSoldAt","type":"datetime","calculated":true},{"info":"Earliest Transfer movement (paired entries; direction via qty sign; both directions count) of any of the Product's Items at this Location. MIN(Item Stock.firstTransferredAt). Kept separate so transfer activity is composable in or out of recency.","name":"firstTransferredAt","type":"datetime","calculated":true},{"info":"Most recent Transfer movement (paired entries; direction via qty sign; both directions count) of any of the Product's Items at this Location. MAX(Item Stock.lastTransferredAt). Kept separate so transfer activity is composable in or out of recency.","name":"lastTransferredAt","type":"datetime","calculated":true},{"info":"Most recent activity for this Product at this Location, transfers excluded: MAX(lastPurchasedAt, lastReceivedAt, lastSoldAt). The indexed key for per-location and per-franchise recency filtering. Transfer-inclusive recency = MAX(lastActivityAt, lastTransferredAt) at query time.","name":"lastActivityAt","type":"datetime","calculated":true}]}],"inheritance":{"childEntity":"Item","overridableDefaults":["defaultBasePrice","defaultDropshipEligibility","defaultMeasurements","defaultPrices","preferredVendors"],"nonOverridableProperties":["brand","class","classification","countryOfOrigin","hasSerialNumber","hsCode","menus","options","productGroups","salesChannels","seasons","stockLimitGroups","taxClass","trackInventory","type"]}},{"name":"Product Category","class":"Taxonomy","subsystem":"CONNECT","area":"Products & Pricing","desc":"Hierarchical product classification tree. Supports unlimited nesting depth for organizing products into browsable categories. Products can belong to multiple categories. Used for storefront navigation, reporting rollups, and merchandising rules.","status":"draft","properties":[{"n":"categoryAttributes","t":"array","info":"Attributes associated with this category. When a product is assigned to this category, these attributes are suggested or required on the product. Enables attribute inheritance from taxonomy to product."},{"n":"image","t":"entityRef","re":"Media","info":"Optional category image for storefront display."},{"n":"slug","t":"string","u":true,"info":"URL-friendly identifier for storefront routing. Auto-generated from name if not provided."}],"ext":"TaxonomyEntity","notes":"Category values follow the Google Product Taxonomy standard. This ensures consistent, industry-recognized classification across sales channels and simplifies feed generation for Google Shopping, Meta, and other marketplace integrations.","related":["Sales Channel","Media","Attribute"]},{"name":"Product Group","class":"Operational","subsystem":"CONNECT","area":"Products & Pricing","desc":"Logical grouping of products for merchandising, promotions, channel publishing, or reporting. Maps to Shopify Collection. Can be Manual (hand-picked products) or Smart (rule-driven automatic membership).","status":"draft","properties":[{"n":"code","r":true,"t":"string","u":true,"info":"URL-friendly identifier. Maps to Shopify handle. Used as EntityDetail.code when referenced from Product."},{"n":"description","t":"string","info":"Rich text or HTML description of the group. Maps to Shopify body_html."},{"n":"disjunctive","r":true,"t":"boolean","info":"Rule matching logic: true = product matches ANY rule (OR), false = product must match ALL rules (AND). Only applicable when Type = Smart."},{"n":"displayOrder","t":"enumeration","v":["Manual","AlphaAsc","AlphaDesc","BestSelling","CreatedAsc","CreatedDesc","PriceAsc","PriceDesc"],"info":"Controls product display ordering within the group. Maps directly to Shopify sort_order."},{"n":"media","t":"schema","info":"ProductGroupMedia inline schema — images, thumbnails, and videos referencing the Media entity."},{"n":"name","r":true,"t":"string","info":"Display name. Maps to Shopify Collection title."},{"n":"products","r":true,"t":"array","re":"Product","info":"Explicit product membership (Manual type) or computed result (Smart type)."},{"n":"rules","t":"schema","info":"Smart collection rules. Array of { column, relation, condition } objects. Only applicable when Type = Smart. Maps to Shopify SmartCollection rules."},{"n":"salesChannels","r":true,"t":"array","re":"Sales Channel","info":"Which channels this group is published to. Maps to Shopify publication/published_scope."},{"n":"seo","t":"schema","info":"SEO title and description overrides for search engines. Maps to Shopify Collection SEO fields."},{"n":"sequence","t":"integer","info":"Display ordering among groups. Aligns with EntityDetail.sequence pattern on Product."},{"n":"status","r":true,"t":"schema","info":"Lifecycle status of the product group. Tracks current state and who/when it was last changed."},{"n":"type","r":true,"t":"enumeration","v":["Manual","Smart"],"info":"Manual = hand-picked product list. Smart = rule-driven automatic membership. Maps to Shopify CustomCollection vs SmartCollection."}],"shopify":"Collection resource (CustomCollection + SmartCollection)","related":["Product","Sales Channel","Promotion","Product Menu"],"bv":{"rules":["Rules array is required when Type = Smart, ignored when Type = Manual","Disjunctive is only applicable when Type = Smart","Products array is manually managed when Type = Manual, computed when Type = Smart"],"lifecycle":"Draft → Active → Archived","calculations":[{"name":"productCount","formula":"COUNT(products)","trigger":"query-time"},{"name":"salesChannelCount","formula":"COUNT(salesChannels)","trigger":"query-time"}],"crossEntityConstraints":[]},"inlineSchemas":[{"name":"ProductGroupMedia","properties":[{"n":"images","t":"array","re":"Media","info":"Product group image assets."},{"n":"thumbnails","t":"array","re":"Media","info":"Thumbnail image assets."},{"n":"videos","t":"array","re":"Media","info":"Video assets."}]},{"name":"ProductGroupStatus","schema":"schemas/product-group/ProductGroupStatus.ts","properties":[{"info":"Draft = unpublished, Active = published and visible, Archived = hidden/retired.","name":"status","type":"enumeration","values":["Draft","Active","Archived"],"required":true},{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"}]}]},{"name":"Product Menu","class":"Operational","subsystem":"CONNECT","area":"Products & Pricing","desc":"Curated product assortment or navigation structure for specific contexts (in-store menus, kiosk displays).","status":"stub","properties":[{"n":"menuId","r":true,"t":"string"},{"n":"name","r":true,"t":"string"},{"n":"productGroups","r":true,"t":"array","re":"Product Group"},{"n":"products","r":true,"t":"array","re":"Product"},{"n":"salesChannels","r":true,"t":"array","re":"Sales Channel"}],"related":["Product","Product Group","Sales Channel"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Product Type","class":"Taxonomy","subsystem":"CONNECT","area":"Products & Pricing","desc":"Hierarchical product type classification (e.g. Physical → Apparel, Digital → Subscription). Determines processing rules for inventory, fulfillment, and taxation. Supports unlimited nesting depth.","status":"draft","properties":[],"ext":"TaxonomyEntity","related":["Product"]},{"name":"Promotion","class":"Operational","subsystem":"CONNECT","area":"Promotions","desc":"A marketing campaign applying one or more Discounts under defined eligibility rules.","status":"stub","properties":[{"n":"discounts","r":true,"t":"array","re":"Discount"},{"n":"eligibility","t":"schema"},{"n":"endDate","t":"datetime"},{"n":"name","r":true,"t":"string"},{"n":"promotionId","r":true,"t":"string"},{"n":"startDate","t":"datetime"}],"shopify":"DiscountAutomaticNode / DiscountCodeNode","related":["Discount","Coupon","Sales Order"],"bv":{"rules":[{"rule":"StartDate must be before EndDate","when":"always","field":"DateRange","severity":"error"}],"lifecycle":{"states":["Draft","Scheduled","Active","Expired","Archived"],"transitions":[{"to":"Scheduled","from":"Draft","conditions":["Valid date range and at least one rule defined"]},{"to":"Active","from":"Scheduled","conditions":["Start date reached"]},{"to":"Expired","from":"Active","conditions":["End date passed"]},{"to":"Archived","from":"Active","conditions":[]}],"initialState":"Draft"},"calculations":[],"crossEntityConstraints":[{"rule":"May reference one or more Discount definitions","entity":"Discount"}]}},{"name":"Purchase","class":"Transactional","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"System-generated settlement record produced when the three-way match (Purchase Order + Goods Receipt + Vendor Invoice) reconciles successfully. Not independently created — represents the completed financial fact of a procurement cycle.","status":"stub","properties":[{"n":"amount","r":true,"t":"decimal","info":"Reconciled purchase amount from the matched invoice"},{"n":"date","r":true,"t":"datetime","info":"Date the three-way match was reconciled"},{"n":"goodsReceipt","r":true,"t":"entityRef","re":"Goods Receipt"},{"c":true,"n":"purchaseId","r":true,"t":"string","info":"System-generated identifier"},{"n":"purchaseOrder","r":true,"t":"entityRef","re":"Purchase Order"},{"n":"vendor","r":true,"t":"entityRef","re":"Vendor"},{"n":"vendorInvoice","r":true,"t":"entityRef","re":"Vendor Invoice"}],"ext":"TransactionalDocument","notes":"Buy-side counterpart to Sale. Automatically generated upon successful three-way match and payment reconciliation.","related":["Vendor","Purchase Order","Goods Receipt","Vendor Invoice"],"bv":{"rules":[{"rule":"System-generated — cannot be manually created or edited","when":"always","field":"PurchaseId","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Must reference a fully or partially received PO","entity":"Purchase Order"},{"rule":"Must reference a posted Goods Receipt linked to the PO","entity":"Goods Receipt"},{"rule":"Must reference a matched and approved Vendor Invoice for the same PO","entity":"Vendor Invoice"}]}},{"name":"Purchase Order","class":"Order","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"Authorization to buy goods from a Vendor. Header discounts/fees for invoice matching; line discounts/fees affect item cost.","status":"draft","properties":[{"n":"actualArrivalDate","t":"datetime","info":"Actual arrival date at the ship-to location. Populated when the Goods Receipt is posted."},{"n":"actualShipDate","t":"datetime","info":"Actual ship date from the vendor's facility. Populated from the Advanced Shipping Notice or Goods Receipt."},{"n":"buyer","t":"entityRef","re":"Employee","info":"The purchasing agent responsible for this order. References an Employee whose Functions includes 'Buyer'."},{"n":"cancelIfNotShippedByDate","t":"datetime","info":"If goods have not shipped by this date, the order may be automatically cancelled per vendor agreement."},{"n":"cancelReason","t":"string","info":"Reason the purchase order was cancelled, if applicable."},{"n":"currency","t":"entityDetail","re":"Currency","info":"Order currency. Defaults from the Vendor's orderCurrency."},{"n":"defaultCostLevel","t":"entityDetail","re":"Cost Level","info":"Header-level default cost level for the purchase order. New lines default their unitCost from the vendor's unit cost at this cost level; can be overridden per line without changing the vendor's unit cost. Defaults from the Vendor / Market defaultCostLevel on creation. Parallels Market.defaultCostLevel and Cost Ledger.costLevel. Company-scoped, so modeled as entityDetail per the tenant-isolation convention."},{"n":"discountAmount","t":"decimal","info":"Header-level discount amount. Below-the-line — used for invoice matching only, does not affect item cost."},{"n":"discounts","r":true,"t":"array","info":"Header-level discount entries. Below-the-line — used for invoice matching only."},{"n":"discounts","r":true,"t":"array","info":"Array of PurchaseOrderDiscount sub-documents. Header-level discounts applied to the purchase order."},{"n":"expectedArrivalDate","t":"datetime","info":"Expected arrival date at the ship-to location. Defaults from vendor leadTimeDays + orderDate."},{"n":"expectedShipDate","t":"datetime","info":"Expected ship date from the vendor's facility."},{"n":"fees","r":true,"t":"array","info":"Array of PurchaseOrderFee sub-documents. Header-level fees (freight, handling, duties). Below-the-line — used for invoice matching only."},{"n":"isArchived","r":true,"t":"boolean","info":"Soft archive flag. Archived POs are excluded from active views."},{"n":"lines","r":true,"t":"array","info":"Array of PurchaseOrderLine sub-documents. Each line specifies an Item, quantity, and cost for the order."},{"n":"orderDate","r":true,"t":"datetime","info":"Date the purchase order was placed."},{"n":"paymentTerms","t":"entityDetail","re":"Vendor Payment Term","info":"Payment terms for this order. Defaults from the Vendor's defaultPaymentTerms for the applicable franchise group."},{"n":"purchaseOrderNo","r":true,"t":"integer"},{"n":"shipToLocation","r":true,"t":"entityDetail","re":"Location","info":"Receiving location where goods will be delivered."},{"n":"status","r":true,"t":"schema","info":"PurchaseOrderStatus inline schema capturing current document status."},{"n":"type","r":true,"t":"enumeration","v":["Standard","Blanket","Drop Ship","Special Order"],"info":"Classification of the purchase order by purpose."},{"n":"vendor","r":true,"t":"entityRef","re":"Vendor"},{"n":"vendorReferenceNo","t":"string","info":"Vendor's own reference or confirmation number for this order."}],"ext":"OperationalDocument","shopify":"Shopify PO (Stocky)","notes":"Header Discounts/Fees are below-the-line (invoice matching only). Line Discounts/Fees affect item cost.","related":["Vendor","Employee","Goods Receipt","Purchase","Vendor Invoice","Advanced Shipping Notice","Location","Vendor Payment Term","Purchasing Fee"],"bv":{"rules":[{"rule":"Must have at least one line item before submitting","when":"submit","field":"Lines","severity":"error"},{"rule":"Defaults from latest vendor unit cost for the item × cost level when a line is added; can be overridden per PO line without changing the vendor's unit cost","when":"create","field":"LineUnitCost","severity":"info"},{"rule":"Only Style, Single, and Service products can be added to a Purchase Order","when":"line-add","field":"lines","severity":"error"},{"rule":"Must be an active Location that can receive inventory","when":"submit","field":"shipToLocation","severity":"error"},{"rule":"Defaults from Vendor orderCurrency on creation; cannot be changed after lines exist","when":"edit","field":"currency","severity":"error"}],"lifecycle":{"states":["Draft","Open","PartiallyReceived","Complete","Cancelled"],"transitions":[{"to":"Open","from":"Draft","conditions":["At least one line item exists","Vendor is Active","ShipToLocation is valid"]},{"to":"Cancelled","from":"Draft","conditions":[]},{"to":"PartiallyReceived","from":"Open","conditions":["At least one line has received qty > 0"]},{"to":"Complete","from":"Open","conditions":["All lines fully received"]},{"to":"Cancelled","from":"Open","conditions":["No Goods Receipts posted against this PO"]},{"to":"Complete","from":"PartiallyReceived","conditions":["All lines fully received or cancelled"]}],"initialState":"Draft"},"calculations":[{"name":"AmountOrdered","formula":"Sum of line (qty × unitCost)","trigger":"On line add/edit/remove"},{"name":"AmountReceived","formula":"Sum of line receivedQty × unitCost","trigger":"On receipt posting"},{"name":"AmountDue","formula":"AmountOrdered - AmountReceived","trigger":"On receipt posting"},{"name":"TotalLines","formula":"Sum of line extCost","trigger":"On line add/edit/remove"},{"name":"TotalFees","formula":"Sum of header fee amounts + sum of all line fee amounts","trigger":"On fee change"},{"name":"TotalDiscountAmount","formula":"Header discountAmount + sum of line discountAmounts","trigger":"On discount change"},{"name":"TotalAmount","formula":"TotalLines + TotalFees - TotalDiscountAmount","trigger":"On line or fee change"},{"name":"DiscountPercentage","formula":"(TotalDiscountAmount / TotalLines) × 100","trigger":"On discount change"},{"name":"QuantityOrdered","formula":"Sum of line quantities","trigger":"On line change"},{"name":"QuantityReceived","formula":"Sum of line received quantities","trigger":"On receipt posting"},{"name":"QuantityDue","formula":"QuantityOrdered - QuantityReceived","trigger":"On receipt posting"},{"name":"lineCount","formula":"COUNT(lines)","trigger":"query-time"},{"name":"receiptCount","formula":"COUNT(Goods Receipt WHERE purchaseOrder = this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Must reference a valid active Vendor","entity":"Vendor"},{"rule":"ShipToLocation must be a valid active Location with receiving capability","entity":"Location"},{"rule":"Receiving creates Goods Receipt documents","entity":"Goods Receipt"},{"rule":"Invoice matching validates against PO lines and receipts","entity":"Vendor Invoice"},{"rule":"If specified, must be valid for the selected Vendor","entity":"Vendor Payment Term"}]},"inlineSchemas":[{"name":"PurchaseOrderFee","extends":"OperationalSubDocument","properties":[{"info":"Fee amount.","name":"amount","type":"decimal","required":true},{"info":"Description of this fee.","name":"description","type":"string"},{"info":"Reference to the Purchasing Fee type.","name":"feeType","type":"entityDetail","relatedEntity":"Purchasing Fee"}]},{"name":"PurchaseOrderLine","extends":"OperationalSubDocument","properties":[{"info":"Line-level discount amount. Affects item cost.","name":"discountAmount","type":"decimal"},{"info":"Line-level expected delivery date override.","name":"expectedDate","type":"datetime"},{"info":"Extended cost (qty × unitCost − discountAmount).","name":"extCost","type":"decimal","calculated":true},{"info":"Array of PurchaseOrderFee sub-documents. Line-level fees (e.g. per-item freight, duties). Affect item cost.","name":"fees","type":"array"},{"info":"The Item being ordered.","name":"item","type":"entityRef","required":true,"relatedEntity":"Item"},{"info":"Line number within the purchase order.","name":"lineNo","type":"integer","required":true},{"info":"Ordered quantity.","name":"qty","type":"decimal","required":true},{"info":"Quantity cancelled from this line.","name":"qtyCancelled","type":"decimal"},{"info":"Quantity received against this line via Goods Receipts.","name":"qtyReceived","type":"decimal"},{"info":"Unit cost per item. Defaults from vendor unit cost at the applicable cost level.","name":"unitCost","type":"decimal","required":true}]},{"name":"PurchaseOrderStatus","extends":"OperationalSubDocument","properties":[{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"},{"info":"Current document lifecycle status of the purchase order.","name":"documentStatus","type":"enumeration","values":["Draft","Open","PartiallyReceived","Complete","Cancelled"],"required":true},{"info":"Operational status indicating whether the purchase order is actively being processed.","name":"operationalStatus","type":"enumeration","values":["Open","Closed"],"required":true}]},{"name":"PurchaseOrderDiscount","extends":"OperationalSubDocument","properties":[{"info":"Discount amount.","name":"amount","type":"decimal","required":true},{"info":"Description of this discount.","name":"description","type":"string"},{"info":"Whether the discount is a percentage or fixed amount.","name":"discountType","type":"enumeration","values":["Percentage","Fixed"],"required":true},{"info":"Discount percentage. Applicable when discountType is 'Percentage'.","name":"percentage","type":"decimal"}]}]},{"name":"Purchase Order Acknowledgement","class":"Operational","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"Vendor's confirmation of a Purchase Order — indicates acceptance, changes, backorders, or cancellation at header and line level. Key EDI document (855).","status":"stub","properties":[{"n":"ackDate","r":true,"t":"datetime"},{"n":"controlNumber","t":"string","info":"EDI control number for cross-referencing."},{"n":"lines","r":true,"t":"array","info":"Array of PurchaseOrderAcknowledgementLine sub-documents. Each line confirms the vendor's acceptance of a PO line."},{"n":"purchaseOrder","r":true,"t":"entityRef","re":"Purchase Order"},{"n":"status","r":true,"t":"schema","info":"Acknowledgement status from the vendor. Tracks current state and who/when it was last changed."}],"related":["Purchase Order","Vendor"],"inlineSchemas":[{"name":"POAcknowledgementStatus","schema":"schemas/purchase-order-acknowledgement/POAcknowledgementStatus.ts","properties":[{"info":"Vendor's acknowledgement response to the purchase order.","name":"status","type":"enumeration","values":["Accepted","Accepted with Changes","Backordered","Cancelled"],"required":true},{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"}]}]},{"name":"Purchasing Fee","class":"Dictionary","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"A fee type applied to purchase orders (e.g. freight, handling, duties). May apply globally or at the item level.","status":"stub","properties":[{"n":"code","r":true,"t":"string"},{"n":"isGlobalFee","r":true,"t":"boolean","info":"When true the fee applies to the entire PO rather than individual lines."},{"n":"isItemFee","r":true,"t":"boolean","info":"When true the fee applies at the line-item level."},{"n":"name","r":true,"t":"string","u":true}],"ext":"LookupEntity","related":["Purchase Order"]},{"name":"RFM Group","class":"Dictionary","subsystem":"CONNECT","area":"Customers","desc":"Customer segmentation classification derived from RFM (Recency, Frequency, Monetary) analysis. Groups customers by purchase behavior — typical segments include Champions, Loyal Customers, Potential Loyalists, At Risk, Hibernating, and Lost. Used to drive targeted marketing, retention campaigns, and service tiering.","status":"draft","properties":[{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key for the RFM segment (e.g. CHAMPIONS, AT_RISK, HIBERNATING). Unique within the Company."},{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"The Company tenant this RFM Group belongs to. Required for tenant isolation."},{"n":"description","t":"string","info":"Description of the segment definition and criteria — e.g. 'Customers who purchased within the last 30 days with high frequency and spend'."},{"n":"frequencyScore","t":"integer","info":"Frequency score (typically 1–5) representing how often customers in this segment purchase."},{"n":"monetaryScore","t":"integer","info":"Monetary score (typically 1–5) representing the spend tier of customers in this segment."},{"n":"name","r":true,"t":"string","u":true,"info":"Display name of the segment (e.g. 'Champions', 'At Risk')."},{"n":"recencyScore","t":"integer","info":"Recency score (typically 1–5) representing how recently customers in this segment last purchased."}],"ext":"LookupEntity","related":["Customer","Company"]},{"name":"Report","class":"Core","subsystem":"CONNECT","area":"Analytics","desc":"A configured analytics view — either internal or via Looker — categorized by domain.","status":"draft","properties":[{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key. Unique within scope."},{"n":"name","r":true,"t":"string","u":true}],"ext":"BaseDocument","shopify":"Reports API","bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Role","class":"Dictionary","subsystem":"ACCESS","area":"Security & Permissions","desc":"A named set of Permissions assigned to Users. Roles provide coarse-grained access control scoped to a Client, Application, or Environment.","status":"draft","properties":[{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key. Unique within scope."},{"n":"name","r":true,"t":"string","u":true},{"n":"permissions","r":true,"t":"array","re":"Permission"},{"n":"roleId","r":true,"t":"string"}],"service":"APR Access","shopify":"Staff roles and collaborator permissions","notes":"Role scope targets are: Client, Application, Environment.","related":["Permission","User"],"bv":{"rules":[{"rule":"Must be unique within the Company","when":"create","field":"Name","severity":"error"}],"lifecycle":null,"calculations":[{"name":"permissionCount","formula":"COUNT(permissions)","trigger":"query-time"},{"name":"userCount","formula":"COUNT(User WHERE roles CONTAINS this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Must have at least one Permission assigned","entity":"Permission"}]}},{"name":"Sale","class":"Transactional","subsystem":"CONNECT","area":"Sales & Orders","desc":"The completed revenue transaction. Records final quantities, prices, taxes, and payment. Triggers inventory decrement. Can exist independently (e.g. POS cash-and-carry) or originate from an Order. A single Order may produce multiple Sales when fulfilled in separate shipments or pickups.","status":"draft","properties":[{"n":"associate","t":"entityDetail","re":"Employee","info":"Sales associate who initiated this sale."},{"n":"billCustomer","t":"entityRef","re":"Customer","info":"Billing customer."},{"n":"cashier","t":"entityRef","re":"Employee","info":"The employee who processed this sale. References an Employee whose Functions includes 'Cashier'."},{"n":"channel","r":true,"t":"entityDetail","re":"Sales Channel"},{"n":"charges","r":true,"t":"array","info":"Customer charges (store credit, gift card, house charge)."},{"n":"coupons","r":true,"t":"array","info":"Coupons applied to this sale."},{"n":"customer","t":"entityRef","re":"Customer","info":"The customer making the purchase."},{"n":"destTaxArea","t":"string","info":"Destination tax area for tax calculation."},{"n":"deviceNo","t":"string","info":"POS device number where the sale was processed."},{"n":"discounts","r":true,"t":"array","info":"Sale-level discounts applied."},{"n":"drawerMemoNo","t":"string","info":"Cash drawer memo number for reconciliation."},{"n":"fees","r":true,"t":"array","info":"Sale-level fees (e.g. restocking, shipping)."},{"n":"fillFromLocation","t":"entityDetail","re":"Location","info":"Location fulfilling the order, if different from sale location."},{"n":"fiscalDate","r":true,"t":"string","info":"Fiscal date for accounting period assignment."},{"n":"lines","r":true,"t":"array","info":"Array of SaleLine sub-documents. Each line records a product sold in the transaction."},{"n":"location","r":true,"t":"entityDetail","re":"Location"},{"n":"payments","r":true,"t":"array"},{"n":"salesOrder","t":"entityRef","re":"Sales Order","info":"Optional — null for standalone POS sales; references the originating Sales Order when sale is part of order fulfillment"},{"n":"salesOrderNo","t":"string","info":"Reference to originating sales order number."},{"n":"salesPersons","r":true,"t":"array","re":"Employee","info":"One or more sales associates credited on this transaction. References Employees whose Functions includes 'SalesPerson'."},{"n":"salesReceiptNo","r":true,"t":"string"},{"n":"saleType","r":true,"t":"enumeration","v":["DEPOSIT","MIXED","RETURN","SALE"],"info":"Transaction type classification."},{"n":"sourceTaxArea","t":"string","info":"Source tax area for tax calculation."},{"n":"status","r":true,"t":"schema","info":"SaleStatus inline schema capturing current document status."},{"n":"taxes","r":true,"t":"array"},{"c":true,"n":"totalAmount","r":true,"t":"decimal"}],"ext":"TransactionalDocument","shopify":"Order (POS source)","related":["Sales Order","Location","Sales Channel","Employee","Stock Ledger"],"bv":{"rules":[{"rule":"Must equal sum of line totals + taxes - discounts","when":"always","field":"TotalAmount","severity":"error"},{"rule":"Sum of payment amounts must equal TotalAmount (balanced tender)","when":"post","field":"Payments","severity":"error"},{"rule":"Optional — Sale can exist without a Sales Order (standalone POS transaction)","when":"always","field":"SalesOrder","severity":"info"},{"rule":"All product classes (Style, Single, Service, Digital, Wallet) can be added","when":"line-add","field":"lines","severity":"info"}],"lifecycle":{"states":["Held","Posted"],"transitions":[{"to":"Posted","from":"Held","conditions":["Payment tendered and balanced","Stock Ledger entries written"]}],"initialState":"Held"},"calculations":[{"name":"TotalAmount","formula":"Sum(LineTotals) + Sum(Taxes) - Sum(Discounts)","trigger":"On line or payment change"},{"name":"lineCount","formula":"COUNT(lines)","trigger":"query-time"},{"name":"paymentCount","formula":"COUNT(payments)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"One Sales Order may produce multiple Sales (e.g. partial shipments). Each Sale references the originating Sales Order when applicable.","entity":"Sales Order"},{"rule":"Posting writes Stock Ledger debit entries per item/location","entity":"Stock Ledger"},{"rule":"Posting decrements SOH at the sale Location","entity":"Item Stock"},{"rule":"If customer-linked, updates purchase history","entity":"Customer"}]},"inlineSchemas":[{"name":"SaleStatus","extends":"OperationalSubDocument","properties":[{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"},{"info":"Current document status of the sale.","name":"value","type":"enumeration","values":["HELD","POSTED"],"required":true}]}]},{"name":"Sales Channel","class":"Operational","subsystem":"CONNECT","area":"Sales & Orders","desc":"Distribution channel through which products are sold. Controls inventory publication and product visibility.","status":"reviewed","properties":[{"n":"autoPublish","r":true,"t":"boolean","info":"When true, newly created products are automatically published to this channel. When false, products must be explicitly assigned. Defaults to false."},{"n":"channelType","r":true,"t":"enumeration","v":["Online","PointOfSale","Marketplace","Social"],"info":"Classification of the sales channel. Determines channel-specific behaviour such as checkout flow and fulfilment routing."},{"n":"configuration","r":true,"t":"valueType","info":"Reference to this Sales Channel's configuration record in the CONFIG subsystem. Uses the ConfigurationRef value type (config: entityDetail → Config, templateCode: string). Holds channel-specific behavior — catalog scope, pricing strategy, fulfillment routing — consumed at order-capture time."},{"n":"connector","t":"string","info":"Name of the external connector that powers this channel (e.g. 'shopify', 'teamwork'). Null for native/internal channels."},{"n":"items","r":true,"t":"array","re":"Item","info":"Array of Item IDs published to this sales channel. Derived from Items whose Product is published to this channel and whose salesChannels indicate isPublished = true."},{"n":"locations","r":true,"t":"array","re":"Location","info":"Array of Location IDs available in this sales channel. Determines which locations can fulfil orders and hold inventory for this channel."},{"n":"name","r":true,"t":"string"},{"n":"products","r":true,"t":"array","re":"Product","info":"Array of Product IDs published to this sales channel. A Product is published when its salesChannels array contains an entry for this channel with isPublished = true."},{"n":"salesChannelNo","r":true,"t":"integer"},{"n":"status","r":true,"t":"schema","info":"Lifecycle status of the sales channel. Tracks current state and who/when it was last changed."}],"ext":"OperationalDocument","shopify":"Channel and Publication resources","related":["Item","Location","Product","Sales Order"],"bv":{"rules":[{"rule":"Must be unique within the Company","when":"create","field":"salesChannelNo","severity":"error"}],"lifecycle":{"states":["Draft","Active","Archived"],"disallowed":[{"to":"Draft","from":"Active","reason":"Sales Channels cannot revert to Draft once activated"},{"to":"Draft","from":"Archived","reason":"Sales Channels cannot revert to Draft once activated"}],"transitions":[{"to":"Active","from":"Draft","conditions":["At least one Location is assigned","Name is set"]},{"to":"Archived","from":"Active","conditions":["No open Sales Orders referencing this channel","No Products are published to this channel"]},{"to":"Active","from":"Archived","conditions":[]}],"initialState":"Draft","terminalExits":["Draft"]},"calculations":[{"name":"productCount","formula":"COUNT(Product WHERE salesChannels CONTAINS this)","trigger":"query-time"},{"name":"itemCount","formula":"COUNT(Item via Product WHERE salesChannels CONTAINS this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Cannot delete or archive a Sales Channel while any Products are published to it","when":"delete, archive","entity":"Product"},{"rule":"Cannot delete or archive a Sales Channel while any Items are published to it","when":"delete, archive","entity":"Item"}]},"inlineSchemas":[{"name":"SalesChannelStatus","schema":"schemas/sales-channel/SalesChannelStatus.ts","properties":[{"info":"Current lifecycle state of the sales channel.","name":"status","type":"enumeration","values":["Draft","Active","Archived"],"required":true},{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"}]}]},{"name":"Sales Order","class":"Order","subsystem":"CONNECT","area":"Sales & Orders","desc":"Sales intent document capturing items, pricing, promotions, and fulfillment instructions before completion. A single Sales Order may result in multiple Sales when fulfilled in separate shipments or pickups.","status":"draft","properties":[{"n":"associate","t":"string","info":"Sales associate assigned to this order."},{"n":"billCustomer","t":"entityRef","re":"Customer","info":"Billing customer, if different from the ordering customer."},{"n":"canOnlyShipOnce","r":true,"t":"boolean","info":"If true, all items must ship in a single fulfillment."},{"n":"canShipPartial","r":true,"t":"boolean","info":"Whether partial shipments are allowed."},{"n":"createLocation","t":"string","info":"Location where the order was created."},{"n":"customer","t":"entityRef","re":"Customer"},{"n":"customerServiceNotes","r":true,"t":"array","info":"Array of customer service notes."},{"n":"defaultShipMethod","t":"entityDetail","re":"Shipping Method","info":"Default shipping method for the order."},{"n":"deliveryInfo","t":"schema","info":"Delivery scheduling information including date, time window, and destination."},{"n":"discounts","r":true,"t":"array","info":"Order-level discounts applied."},{"n":"estimatedArrivalDate","t":"datetime","info":"Estimated delivery date to the customer."},{"n":"estimatedShipDate","t":"datetime","info":"Estimated ship date from the fulfillment location."},{"n":"fees","r":true,"t":"array","info":"Order-level fees (shipping, handling, etc.)."},{"n":"fillLocation","r":true,"t":"string","info":"Primary fulfillment location for this order."},{"n":"fulfillmentMethod","r":true,"t":"enumeration","v":["ShipToCustomer","StorePickup","ShipToStore","SameDay","Mixed"],"info":"How the order will be fulfilled."},{"n":"isArchived","r":true,"t":"boolean","info":"Soft archive flag."},{"n":"isFillLocationLocked","r":true,"t":"boolean","info":"If true, the fill location cannot be changed."},{"n":"isGuestCheckout","r":true,"t":"boolean","info":"Whether this is a guest checkout without customer account."},{"n":"isTaxExempt","r":true,"t":"boolean","info":"Whether the order is tax exempt."},{"n":"lines","r":true,"t":"array","info":"Array of SalesOrderLine sub-documents. Each line represents a product ordered by the customer."},{"n":"orderDate","r":true,"t":"datetime","info":"Date the order was placed."},{"n":"payments","r":true,"t":"array","info":"Payment methods and amounts applied to this order."},{"n":"promotions","r":true,"t":"array","re":"Promotion"},{"n":"salesChannel","t":"string","info":"Sales channel code where the order originated."},{"n":"salesOrderNo","r":true,"t":"string"},{"n":"salesReceiptId","t":"string","info":"Reference to the associated Sales Receipt (Sale)."},{"n":"shipCustomer","t":"entityRef","re":"Customer","info":"Ship-to customer, if different from the ordering customer."},{"n":"shipments","r":true,"t":"array","info":"Array of shipment references with tracking information."},{"n":"status","r":true,"t":"schema","info":"SalesOrderStatus inline schema capturing current document status."},{"n":"type","r":true,"t":"enumeration","v":["Customer Order","Online Order","Pre-Order","Special Order","Store Order"],"info":"Classification of the sales order by origin or purpose."}],"ext":"OperationalDocument","shopify":"Order / DraftOrder resource","related":["Customer","Sale","Fulfillment Order","Ship Order","Sales Channel"],"bv":{"rules":[{"rule":"Must have at least one order line","when":"create","field":"Lines","severity":"error"},{"rule":"Must reference a valid active Customer","when":"create","field":"Customer","severity":"error"},{"rule":"All product classes (Style, Single, Service, Digital, Wallet) can be added","when":"line-add","field":"lines","severity":"info"}],"lifecycle":{"states":["Accepted","Processing","Reviewing","OnHold","DeliveryPending","PickUpReady","Completed","Cancelled","Mixed"],"transitions":[{"to":"Processing","from":"Accepted","conditions":["Payment authorized or COD"]},{"to":"DeliveryPending","from":"Processing","conditions":["All items shipped"]},{"to":"PickUpReady","from":"Processing","conditions":["All items picked and staged"]},{"to":"Mixed","from":"Processing","conditions":["Lines are in multiple states"]},{"to":"Reviewing","from":"Processing","conditions":["Fraud or review flag triggered"]},{"to":"Processing","from":"Reviewing","conditions":["Review cleared"]},{"to":"Cancelled","from":"Reviewing","conditions":["Review rejected"]},{"to":"OnHold","from":"Processing","conditions":["Manual or system hold"]},{"to":"Processing","from":"OnHold","conditions":["Hold released"]},{"to":"Completed","from":"DeliveryPending","conditions":["Delivery confirmed"]},{"to":"Completed","from":"PickUpReady","conditions":["Customer picked up"]},{"to":"Cancelled","from":"Accepted","conditions":["Customer request before processing"]}],"initialState":"Accepted"},"calculations":[{"name":"QuantityOrdered","formula":"Sum of line quantities","trigger":"On line change"},{"name":"QuantityFilled","formula":"Sum of line filled quantities","trigger":"On fulfillment update"},{"name":"QuantityDue","formula":"QuantityOrdered - QuantityFilled - QuantityCancelled","trigger":"On fulfillment or cancellation"},{"name":"QuantityCancelled","formula":"Sum of line cancelled quantities","trigger":"On cancellation"},{"name":"lineCount","formula":"COUNT(lines)","trigger":"query-time"},{"name":"fulfillmentOrderCount","formula":"COUNT(Fulfillment Order WHERE salesOrder = this)","trigger":"query-time"},{"name":"saleCount","formula":"COUNT(Sale WHERE salesOrder = this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"One Order may produce multiple Sales (e.g. partial shipments). Each Sale references the originating Order.","entity":"Sale"},{"rule":"Generates Fulfillment Orders for shipment/pickup","entity":"Fulfillment Order"},{"rule":"Shipment creation updates Order line status","entity":"Shipment"}]},"inlineSchemas":[{"name":"SalesOrderStatus","extends":"OperationalSubDocument","properties":[{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"},{"info":"Current document status of the sales order.","name":"value","type":"enumeration","values":["Accepted","Cancelled","Completed","DeliveryPending","Mixed","OnHold","PickUpReady","Processing","Reviewing"],"required":true}]}]},{"name":"Scope","class":"Dictionary","subsystem":"ACCESS","area":"Security & Permissions","desc":"An OAuth2 scope string that maps to one or more Areas or Permissions. Presented during authorization to define the access surface of a token.","status":"draft","properties":[{"n":"areas","r":true,"t":"array","re":"Area"},{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key. Unique within scope."},{"n":"description","t":"string"},{"n":"name","r":true,"t":"string","u":true},{"n":"permissions","r":true,"t":"array","re":"Permission"},{"n":"scopeId","r":true,"t":"string"}],"service":"APR Access","related":["Area","Permission","Client"],"bv":{"rules":[{"rule":"Must be unique within the permission context","when":"create","field":"Key","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Narrows the applicability of a Permission to a resource subset","entity":"Permission"}]}},{"name":"Season","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"Time-bound merchandising period defining when products are available or relevant (e.g. Spring 2026, Holiday).","status":"draft","properties":[{"n":"code","r":true,"t":"string","u":true,"info":"Human-readable key. Unique within scope."},{"n":"endDate","t":"date"},{"n":"months","r":true,"t":"array","v":["January","February","March","April","May","June","July","August","September","October","November","December"],"info":"Calendar months included in this season."},{"n":"name","r":true,"t":"string","u":true},{"n":"seasonId","r":true,"t":"string"},{"n":"startDate","t":"date"}],"ext":"LookupEntity","related":["Product"],"bv":{"rules":[{"rule":"Must be unique within the Company","when":"create","field":"Code","severity":"error"}],"lifecycle":null,"calculations":[{"name":"productCount","formula":"COUNT(Product WHERE seasons CONTAINS this)","trigger":"query-time"}],"crossEntityConstraints":[]}},{"name":"Secret","class":"Core","subsystem":"CONFIG","area":"Configuration","desc":"An encrypted sensitive value (API key, connection string, certificate). Referenced by Configs; never stored in plain text.","status":"draft","properties":[{"n":"actor","r":true,"t":"entityRef","re":"Actor"},{"n":"encryptedValue","r":true,"t":"encrypted","info":"AES-256 encrypted secret value. Decrypted only at point of use."},{"n":"environment","r":true,"t":"entityDetail","re":"Environment"},{"n":"expiresAt","t":"datetime"},{"n":"name","r":true,"t":"string"},{"n":"rotationPolicy","t":"schema"},{"n":"secretId","r":true,"t":"string"}],"service":"APR Config","shopify":"API keys and access tokens","related":["Actor","Environment","Config"],"bv":{"rules":[{"rule":"Must be stored encrypted at rest (AES-256 or equivalent)","when":"always","field":"Value","severity":"error"},{"rule":"Must never appear in logs, API responses, or error messages","when":"always","field":"Value","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Scoped to a specific Environment","entity":"Environment"}]}},{"name":"Ship Order","class":"Order","subsystem":"CONNECT","area":"Sales & Orders","desc":"Fulfillment instruction derived from an Order; directs picking, packing, and shipping from a specific location.","status":"stub","properties":[{"n":"fulfillmentOrder","t":"entityRef","re":"Fulfillment Order"},{"n":"lines","r":true,"t":"array","info":"Array of ShipOrderLine sub-documents. Each line specifies a quantity to ship for an item."},{"n":"location","r":true,"t":"entityDetail","re":"Location"},{"n":"rejectReason","t":"entityDetail","re":"Ship Reject Reason"},{"n":"salesOrder","r":true,"t":"entityRef","re":"Sales Order"},{"n":"shipOrderId","r":true,"t":"string"}],"ext":"OperationalDocument","shopify":"FulfillmentOrder → Fulfillment workflow","related":["Sales Order","Fulfillment Order","Shipment","Location"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Must reference a valid Fulfillment Order","entity":"Fulfillment Order"}]}},{"name":"Ship Reject Reason","class":"Dictionary","subsystem":"CONNECT","area":"Sales & Orders","desc":"Codified reason for rejecting or canceling a ship order (out of stock, address issue, customer request).","status":"stub","properties":[{"n":"code","r":true,"t":"string"},{"n":"description","t":"string"},{"n":"name","r":true,"t":"string","u":true}],"ext":"LookupEntity","related":["Ship Order"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Shipment","class":"Operational","subsystem":"CONNECT","area":"Sales & Orders","desc":"Physical dispatch of goods against a Ship Order, broken into Cartons with carrier tracking.","status":"draft","properties":[{"n":"canShipPartial","r":true,"t":"boolean","info":"Whether this shipment allows partial delivery."},{"n":"carrier","t":"string"},{"n":"cartons","r":true,"t":"array","re":"Shipment Carton"},{"n":"deliveredDate","t":"datetime"},{"n":"deliveryInfo","t":"schema","info":"Delivery scheduling and routing information."},{"n":"fillLocation","r":true,"t":"entityDetail","re":"Location","info":"Location fulfilling this shipment."},{"n":"fulfillmentMethod","r":true,"t":"enumeration","v":["ShipToCustomer","StorePickup","ShipToStore","SameDay"],"info":"How this shipment will be delivered."},{"n":"isArchived","r":true,"t":"boolean","info":"Soft archive flag."},{"n":"isDropShip","r":true,"t":"boolean","info":"Whether this is a drop-ship directly from vendor."},{"n":"lines","r":true,"t":"array","info":"Shipment line items with quantities and status."},{"n":"saleId","t":"string","info":"Reference to the associated Sale."},{"n":"salesOrderId","r":true,"t":"string","info":"Reference to the originating Sales Order."},{"n":"shipmentId","r":true,"t":"string"},{"n":"shipMethod","t":"entityDetail","re":"Shipping Method","info":"Shipping method used."},{"n":"shipOrder","r":true,"t":"entityRef","re":"Ship Order"},{"n":"shipToAddress","r":true,"t":"schema","info":"Destination address for the shipment."},{"n":"shipToCustomerId","r":true,"t":"string","info":"Customer receiving the shipment."},{"n":"shippedDate","t":"datetime"},{"n":"status","r":true,"t":"schema","info":"ShipmentStatus inline schema with documentStatus and operationalStatus."},{"n":"trackingNo","t":"string"}],"ext":"OperationalDocument","shopify":"Fulfillment resource with tracking","related":["Ship Order","Shipment Carton"],"bv":{"rules":[],"lifecycle":{"states":["Open","Rejected","Completed"],"transitions":[{"to":"Completed","from":"Open","conditions":["Delivery confirmed or picked up"]},{"to":"Rejected","from":"Open","conditions":["Shipment refused by customer"]}],"initialState":"Open"},"calculations":[],"operationalStates":["PrepareShipment","ReadyToShip","Shipped","ReadyForPickup","PickedUp","ReadyForDropShip","DropShipComplete","PartiallyRejected","PreparePickup","Processing"],"crossEntityConstraints":[{"rule":"Completion updates parent Sales Order line status","entity":"Sales Order"},{"rule":"Must reference a valid Fulfillment Order","entity":"Fulfillment Order"}]},"inlineSchemas":[{"name":"ShipLineStatus","extends":"OperationalSubDocument","properties":[{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"},{"info":"Status of this shipment line.","name":"documentStatus","type":"enumeration","values":["Open","Completed","Cancelled"],"required":true}]},{"name":"ShipmentStatus","extends":"OperationalSubDocument","properties":[{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"},{"info":"Current document lifecycle status of the shipment.","name":"documentStatus","type":"enumeration","values":["Open","Rejected","Completed"],"required":true},{"info":"Operational sub-status for workflow tracking.","name":"operationalStatus","type":"enumeration","values":["PrepareShipment","ReadyToShip","Shipped","ReadyForPickup","PickedUp","ReadyForDropShip","DropShipComplete","PartiallyRejected","PreparePickup","Processing"]}]}]},{"name":"Shipment Carton","class":"Operational","subsystem":"CONNECT","area":"Sales & Orders","desc":"Individual carton or package within a multi-package shipment, tracking contents at box level.","status":"stub","properties":[{"n":"cartonId","r":true,"t":"string"},{"n":"cartonNo","t":"string"},{"n":"lines","r":true,"t":"array","info":"Array of ShipmentLine sub-documents. Each line records items packed into this carton."},{"n":"shipment","r":true,"t":"entityRef","re":"Shipment"},{"n":"trackingNo","t":"string"},{"n":"weight","t":"decimal"}],"ext":"OperationalSubDocument","related":["Shipment"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Shipping Method","class":"Dictionary","subsystem":"CONNECT","area":"Sales & Orders","desc":"Carrier service or delivery method available for fulfilment (e.g. Standard Ground, Express, Click & Collect).","status":"stub","properties":[{"n":"code","r":true,"t":"string"},{"n":"daysInTransit","t":"integer","info":"Estimated transit time in business days."},{"n":"name","r":true,"t":"string","u":true}],"ext":"LookupEntity","related":["Ship Order","Sales Order"]},{"name":"Stock Adjustment","class":"Transactional","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Records an inventory correction with a reason code (shrinkage, receiving error, etc.).","status":"stub","properties":[{"n":"adjustmentAction","r":true,"t":"enumeration","v":["Adjust","Restate"],"info":"Adjust (delta change) or Restate (absolute override)."},{"n":"adjustmentNo","r":true,"t":"string","info":"Human-readable adjustment number / identifier."},{"n":"effectiveDate","t":"datetime","info":"Date the adjustment takes effect for reporting purposes. When different from inherited transactionDate, used for fiscal period alignment."},{"n":"fiscalDate","t":"datetime","info":"Fiscal period date for financial reporting."},{"n":"lines","r":true,"t":"array","info":"Array of StockAdjustmentLine sub-documents."},{"n":"location","r":true,"t":"entityDetail","re":"Location","info":"The location (store/warehouse) where inventory is being adjusted."},{"n":"reason","t":"entityDetail","re":"Stock Adjustment Reason","info":"Reason code for the adjustment (shrinkage, damage, correction, etc.)."},{"n":"status","r":true,"t":"schema","info":"Lifecycle status of the stock adjustment. Tracks current state and who/when it was last changed."},{"n":"stockBin","t":"entityRef","re":"StockBin","info":"The specific bin within the location where inventory is being adjusted."},{"n":"stockCountId","t":"string","info":"Reference to the originating Stock Take if this adjustment was generated from a count."},{"n":"totalCost","t":"decimal","info":"Calculated: sum of all line extended costs."},{"n":"totalLines","t":"decimal","info":"Calculated: count of lines on the adjustment."},{"n":"totalQty","t":"decimal","info":"Calculated: sum of all line quantities."}],"ext":"TransactionalDocument","shopify":"InventoryAdjustment operations","related":["Location","StockBin","Stock Adjustment Reason","Stock Ledger","Item Stock","Stock Take"],"bv":{"rules":[{"rule":"Must reference a valid Reason Code","when":"always","field":"reason","severity":"error"},{"rule":"Must be Adjust or Restate","when":"always","field":"adjustmentAction","severity":"error"},{"rule":"Only physical products (Style or Single class) can be added to a Stock Adjustment","when":"line-add","field":"lines","severity":"error"}],"lifecycle":{"states":["Draft","Processing","Posted"],"transitions":[{"to":"Processing","from":"Draft","conditions":["At least one line exists","Reason code is set"]},{"to":"Posted","from":"Processing","conditions":["Stock Ledger entries written","Item Stock updated"]}],"initialState":"Draft"},"calculations":[{"name":"totalCost","formula":"Sum of line extCost values","trigger":"On line add/edit/remove"},{"name":"totalLines","formula":"Count of lines","trigger":"On line add/remove"},{"name":"totalQty","formula":"Sum of line qty values","trigger":"On line add/edit/remove"}],"crossEntityConstraints":[{"rule":"Posting writes one Stock Ledger entry per line","entity":"Stock Ledger"},{"rule":"Posting updates Item Stock SOH","entity":"Item Stock"}]},"inlineSchemas":[{"name":"StockAdjustmentStatus","schema":"schemas/stock-adjustment/StockAdjustmentStatus.ts","properties":[{"info":"Current lifecycle state of the adjustment.","name":"status","type":"enumeration","values":["Draft","Processing","Posted"],"required":true},{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"}]}]},{"name":"Stock Adjustment Reason","class":"Dictionary","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Codified reason for inventory adjustments (e.g. damage, theft, correction). Required for audit trails.","status":"stub","properties":[{"n":"code","r":true,"t":"string"},{"n":"description","t":"string"},{"n":"isShrinkage","r":true,"t":"boolean"},{"n":"name","r":true,"t":"string","u":true}],"ext":"LookupEntity","related":["Stock Adjustment"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Stock Cost Layer","class":"Balance","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Mutable consumption state for FIFO/LIFO inventory valuation — one layer (lot) per inbound Stock Ledger entry, maintained by the Stock Ledger Service (PRD name: LedgerCost). Outbound movements consume layers in method order (FIFO = oldest acquiredAt first, LIFO = newest first), decrementing remainingQty; the consumed layers' unitCost values determine COGS on the outbound ledger entry. Not used under WAC, which keeps a running average on Inventory Position instead. Unlike ledger entries, layers are mutable (remainingQty / isExhausted change) but are never deleted — exhausted layers are retained for audit and cost-recalculation replay.","status":"draft","properties":[{"n":"acquiredAt","r":true,"t":"datetime","info":"Business timestamp of the inbound movement that created this layer (source entry's entryDate). Consumption ordering key: FIFO consumes oldest acquiredAt first, LIFO newest first. Immutable."},{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"Tenant scope. Always matches the source ledger entry's company."},{"n":"currencyCode","r":true,"t":"string","info":"ISO 4217 currency code for unitCost. Inherited from the source ledger entry."},{"n":"exchangeRate","t":"decimal","info":"Exchange rate to the Company's base currency, carried from the source ledger entry at creation. Null when currencyCode equals the base currency."},{"n":"isExhausted","r":true,"t":"boolean","info":"True when remainingQty = 0. Exhausted layers are skipped during consumption but retained for audit. Default false."},{"n":"item","r":true,"t":"entityRef","re":"Item","info":"The Item (SKU) this layer belongs to."},{"n":"location","r":true,"t":"entityDetail","re":"Location","info":"The Location this layer belongs to."},{"n":"originalQty","r":true,"t":"integer","info":"Quantity received into the layer at creation — the source entry's qty. Immutable."},{"n":"remainingQty","r":true,"t":"integer","info":"Unconsumed quantity: decremented as outbound movements draw down the layer under FIFO/LIFO. Invariant: 0 ≤ remainingQty ≤ originalQty."},{"n":"sourceEntry","r":true,"t":"entityRef","re":"Stock Ledger","info":"The inbound Stock Ledger entry (Receipt, Return, Transfer-in, or positive Adjustment) that created this layer."},{"n":"unitCost","r":true,"t":"decimal","info":"Acquisition unit cost of the layer in currencyCode, from the source entry. Immutable for the life of the layer; determines COGS when the layer is consumed."}],"service":"Stock Ledger Service","ext":"Identifiable","notes":"Documented 2026-07-24. Item Stock (reviewed) already references 'LedgerCost layers' in its unitCost/totalCost calculations; this entity makes that structure explicit under the registry naming conventions (Title Case, no compound-camel service names). unitCost and acquiredAt are immutable after creation — only remainingQty and isExhausted change. Under FIFO/LIFO, Inventory Position.totalCost = Σ(remainingQty × unitCost) across a position's layers.","related":["Item","Location","Stock Ledger","Inventory Position"],"bv":{"rules":[{"rule":"Invariant 0 ≤ remainingQty ≤ originalQty","when":"always","field":"RemainingQty","severity":"error"},{"rule":"unitCost, acquiredAt, originalQty, and sourceEntry are immutable after creation — only remainingQty and isExhausted may change","when":"always","field":"UnitCost","severity":"error"},{"rule":"Layers exist only for Item × Location positions whose costingMethod is FIFO or LIFO","when":"create","field":"Item","severity":"error"},{"rule":"Layers are never deleted; exhausted layers are flagged via isExhausted","when":"always","field":"IsExhausted","severity":"error"}],"lifecycle":null,"calculations":[{"name":"isExhausted","formula":"remainingQty === 0","trigger":"On any consumption that changes remainingQty"}],"crossEntityConstraints":[{"rule":"Created by inbound Stock Ledger entries (Receipt, Return, Transfer-in, positive Adjustment); consumed by outbound entries in method order","entity":"Stock Ledger"},{"rule":"Under FIFO/LIFO, Σ(remainingQty) across a position's layers must equal Inventory Position.qtyOnHand, and Σ(remainingQty × unitCost) must equal Inventory Position.totalCost","entity":"Inventory Position"}]}},{"name":"Stock Ledger","class":"Ledger","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Immutable, append-only audit trail of all inventory quantity and cost changes — the system of record for stock movement history. Each entry records a signed quantity movement and its cost impact for an Item at a Location, with a running balance (balanceQty) and a per-Item × Location sequence (ledgerLine). Written by Sale, Stock Transfer, Stock Adjustment, and Goods Receipt (Purchase) transactions, one entry per document line. The Stock Ledger Service folds entries into Inventory Position (running balance/valuation) and, under FIFO/LIFO, maintains Stock Cost Layer consumption state. Corrections are posted as compensating entries via reversalOf, never edits. Inherited entryDate is the business posting timestamp; inherited sourceEntityType/sourceEntityId identify the triggering document.","status":"draft","properties":[{"c":true,"n":"balanceQty","r":true,"t":"integer","info":"Running on-hand quantity for this Item × Location immediately after this entry, computed in ledgerLine order. Enables point-in-time balance queries and reconciliation against Item Stock SOH without replaying the full ledger."},{"c":true,"n":"baseExtendedCost","t":"decimal","info":"extendedCost converted to the Company's base/reporting currency using the exchangeRate captured at posting. Null when currencyCode equals Company.baseCurrencyCode. Feeds Inventory Position.baseTotalCost and cross-location consolidated valuation."},{"n":"costingMethod","r":true,"t":"enumeration","v":["FIFO","LIFO","WAC"],"info":"Inventory costing method used to value this movement and calculate COGS. Effective method for the Item × Location at posting time (see Inventory Position.costingMethod). Enum aligned with Item Stock: WAC = weighted average cost (renamed from WeightedAverage 2026-07-24)."},{"n":"currencyCode","r":true,"t":"string","info":"ISO 4217 currency code for unitCost and extendedCost. Mirrors Item Stock.currencyCode — typically the Company's base currency but may differ for locations operating in another currency."},{"n":"exchangeRate","t":"decimal","info":"Exchange rate to the Company's base currency, captured at posting time so historical valuation is stable (per decimal-monetary-inherits-currency). Null when currencyCode equals the base currency."},{"c":true,"n":"extendedCost","t":"decimal","info":"Qty × unitCost — signed total cost impact of this movement in currencyCode. Positive for inbound, negative for outbound."},{"n":"item","r":true,"t":"entityRef","re":"Item"},{"c":true,"n":"ledgerLine","r":true,"t":"integer","info":"Monotonic, gap-free sequence per Item × Location, assigned by the Stock Ledger Service at posting. Ordering key for balance replay, reconciliation, and idempotent projection (Inventory Position.lastLedgerLine high-water mark)."},{"n":"location","r":true,"t":"entityDetail","re":"Location"},{"n":"movementType","r":true,"t":"enumeration","v":["Sale","Receipt","Adjustment","Transfer","Return"],"info":"Categorizes the type of inventory movement. The inherited sourceEntityType identifies the specific entity class (e.g. 'Sale', 'Stock Transfer'); movementType provides a higher-level category for reporting."},{"n":"pairedEntry","t":"entityRef","re":"Stock Ledger","info":"For Transfer movements, the counterpart entry at the other location (negative at source ↔ positive at destination; direction is the qty sign). Null for all other movement types."},{"n":"qty","r":true,"t":"integer","info":"Signed quantity change (positive = stock in, negative = stock out)"},{"n":"reversalOf","t":"entityRef","re":"Stock Ledger","info":"Compensating-entry back-reference. Ledger entries are immutable (ledger-entry-immutability convention); corrections and voids post a new entry with negated qty pointing at the original. Null for normal entries."},{"n":"sourceLineId","t":"string","info":"UUID of the specific line item within the source document — entries post per line (e.g. one entry per Sale line). Complements inherited sourceEntityType/sourceEntityId, which identify the document."},{"n":"unitCost","t":"decimal","info":"Unit cost of the movement in currencyCode. Inbound movements (Receipt, Return, Transfer-in, positive Adjustment) carry acquisition/source cost; outbound movements carry the method-resolved COGS cost (FIFO = oldest remaining Stock Cost Layer, LIFO = newest, WAC = running average). Null only for zero-cost corrections."}],"service":"Stock Ledger Service","ext":"LedgerEntry","notes":"Property set expanded from stub on 2026-07-24. Key decisions: (1) costingMethod enum aligned to Item Stock — WeightedAverage renamed WAC; canonical enum is FIFO | LIFO | WAC. (2) No postedAt property — Item Stock projection formulas that reference 'LedgerEntry.postedAt' map to the inherited entryDate (business event timestamp); createdDate is the system write time, and out-of-order postings are tolerated by projections via MAX(stored, incoming) per Item Stock notes. (3) Currency/FX follows the decimal-monetary-inherits-currency convention: currencyCode is required, exchangeRate is captured at posting so historical valuation is stable; baseExtendedCost feeds consolidated (base-currency) valuation. (4) Transfers post paired entries — negative at source, positive at destination — linked via pairedEntry; direction is the qty sign. (5) movementType stays coarse (Sale, Receipt, Adjustment, Transfer, Return); the specific document class lives in sourceEntityType. (6) company is inherited from LedgerEntry (tenant partition key).","related":["Item","Location","Item Stock","Sale","Stock Transfer","Stock Adjustment","Goods Receipt","Inventory Position","Stock Cost Layer"],"bv":{"rules":[{"rule":"Append-only — entries must never be modified or deleted (immutable audit trail); corrections post a new compensating entry via reversalOf","when":"always","field":"LedgerLine","severity":"error"},{"rule":"Must be non-zero","when":"create","field":"Qty","severity":"error"},{"rule":"Must be a valid ISO 4217 code and match the Location's operating currency","when":"create","field":"CurrencyCode","severity":"error"},{"rule":"Required when currencyCode differs from Company.baseCurrencyCode; null otherwise","when":"create","field":"ExchangeRate","severity":"error"},{"rule":"Required (and only allowed) when movementType = 'Transfer' — must reference the counterpart entry for the same transfer with negated qty","when":"create","field":"PairedEntry","severity":"error"},{"rule":"When set, must reference an entry for the same Item × Location, and qty must be the negation of the referenced entry's qty","when":"create","field":"ReversalOf","severity":"error"},{"rule":"Required for inbound movements (qty > 0); for outbound movements it is resolved by the costing method at posting time","when":"create","field":"UnitCost","severity":"warning"}],"lifecycle":null,"calculations":[{"name":"ExtendedCost","formula":"Qty × UnitCost","trigger":"On ledger entry creation"},{"name":"BaseExtendedCost","formula":"ExtendedCost × ExchangeRate (null when currencyCode = Company.baseCurrencyCode)","trigger":"On ledger entry creation"},{"name":"LedgerLine","formula":"Previous max ledgerLine for Item × Location + 1 (monotonic, gap-free)","trigger":"On ledger entry creation (assigned by Stock Ledger Service)"},{"name":"BalanceQty","formula":"Previous balanceQty for Item × Location + Qty, in ledgerLine order","trigger":"On ledger entry creation"},{"name":"UnitCost (outbound)","formula":"Method-dispatched COGS: FIFO = oldest remaining Stock Cost Layer, LIFO = newest remaining layer, WAC = Inventory Position running average","trigger":"On posting of an outbound (negative qty) movement"}],"crossEntityConstraints":[{"rule":"Running quantity sum must match corresponding Item Stock on-hand balance","entity":"Item Stock"},{"rule":"Sale posting creates a negative-qty ledger entry per line item","entity":"Sale"},{"rule":"Goods Receipt posting creates a positive-qty ledger entry per received item","entity":"Goods Receipt"},{"rule":"Transfer creates paired entries: negative at source, positive at destination, linked via pairedEntry","entity":"Stock Transfer"},{"rule":"Adjustment creates a signed entry per adjusted item with reason code","entity":"Stock Adjustment"}]},"inheritance":{"inherited":["id","entryDate","sourceEntityType","sourceEntityId","createdBy","createdDate","company"]}},{"name":"Stock Limit Group","class":"Dictionary","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Defines minimum/maximum stock thresholds and reorder parameters for a set of items or locations.","status":"stub","properties":[{"n":"code","r":true,"t":"string"},{"n":"name","r":true,"t":"string","u":true}],"ext":"LookupEntity","related":["Item Stock"]},{"name":"Stock Take","class":"Operational","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"A physical count cycle counting event against one or more locations.","status":"stub","properties":[{"n":"countedQty","t":"integer"},{"n":"lines","r":true,"t":"array","info":"Array of StockTakeLine sub-documents. Each line records the expected vs. counted quantity for one Item at the count location."},{"n":"location","r":true,"t":"entityDetail","re":"Location"},{"n":"stockTakeId","r":true,"t":"string"}],"ext":"OperationalDocument","related":["Item","Location","Stock Adjustment"],"bv":{"rules":[],"lifecycle":{"states":["Draft","InProgress","Completed","Posted"],"transitions":[{"to":"InProgress","from":"Draft","conditions":["At least one count line exists"]},{"to":"Completed","from":"InProgress","conditions":["All lines have been counted"]},{"to":"Posted","from":"Completed","conditions":["Variance review approved"]}],"initialState":"Draft"},"calculations":[{"name":"Variance","formula":"CountedQty - ExpectedQty per line","trigger":"On count entry"},{"name":"lineCount","formula":"COUNT(lines)","trigger":"query-time"},{"name":"varianceLineCount","formula":"COUNT(lines WHERE countedQty != expectedQty)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Posting a Stock Take generates Stock Adjustments for variances","entity":"Stock Adjustment"},{"rule":"Must reference a valid active Location","entity":"Location"}]}},{"name":"Stock Transfer","class":"Transactional","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Records the actual movement of stock between locations (fulfills a Transfer Order).","status":"draft","properties":[{"n":"appliedDiscrepancyRule","t":"enumeration","v":["UseInQty","UseOutQty"],"info":"Corrective action applied when in/out quantities don't match."},{"n":"cartons","r":true,"t":"array","re":"Stock Transfer Carton"},{"n":"destinationLocation","r":true,"t":"entityDetail","re":"Location","info":"The destination location receiving the transferred stock."},{"n":"fiscalDate","t":"datetime","info":"Fiscal date for accounting period assignment."},{"n":"hasDiscrepancy","r":true,"t":"boolean","info":"Whether there is a quantity discrepancy between out and in."},{"n":"isReviewed","r":true,"t":"boolean","info":"Whether discrepancies have been reviewed."},{"n":"lines","r":true,"t":"array","info":"Array of StockTransferLine sub-documents. Each line records an Item, quantity, and cost for the transfer movement."},{"n":"originLocation","r":true,"t":"entityDetail","re":"Location","info":"The origin location sending the transferred stock."},{"n":"postedInDate","t":"datetime","info":"Date the receiving side was posted."},{"n":"postedOutDate","t":"datetime","info":"Date the sending side was posted."},{"n":"purchaseOrderId","t":"string","info":"Associated purchase order, if transfer is PO-driven."},{"n":"reason","t":"entityDetail","re":"Stock Transfer Reason","info":"Reason for the transfer."},{"n":"receivedDate","t":"datetime"},{"n":"reversedStockTransferId","t":"string","info":"ID of the stock transfer this reverses."},{"n":"reversingStockTransferId","t":"string","info":"ID of the stock transfer that reverses this one."},{"n":"shippedDate","t":"datetime"},{"n":"status","r":true,"t":"schema","info":"StockTransferStatus inline schema with documentStatus and operationalStatus."},{"n":"stockTransferNo","r":true,"t":"string"},{"n":"transferDate","r":true,"t":"datetime","info":"Date the transfer was initiated."},{"n":"transferOrder","r":true,"t":"entityRef","re":"Transfer Order"}],"ext":"TransactionalDocument","shopify":"Transfer resource","related":["Transfer Order","Location","Stock Transfer Carton","Stock Ledger"],"bv":{"notes":"Supports reversal via Reversed/Reversal operational status","rules":[{"rule":"Only physical products (Style or Single class) can be added to a Stock Transfer","when":"line-add","field":"lines","severity":"error"}],"lifecycle":{"states":["Draft","InTransit","Processing","Posted","Cancelled"],"transitions":[{"to":"InTransit","from":"Draft","conditions":["Items picked and dispatched from source"]},{"to":"Processing","from":"InTransit","conditions":["Received at destination"]},{"to":"Posted","from":"Processing","conditions":["Stock Ledger entries written for both locations"]},{"to":"Cancelled","from":"Draft","conditions":[]},{"to":"Cancelled","from":"InTransit","conditions":["Items returned to source location"]}],"initialState":"Draft"},"calculations":[{"name":"lineCount","formula":"COUNT(lines)","trigger":"query-time"},{"name":"cartonCount","formula":"COUNT(cartons)","trigger":"query-time"},{"name":"qtyDifference","formula":"SUM(lines.qtyOut) - SUM(lines.qtyIn)","trigger":"On line quantity change"},{"name":"qtyIn","formula":"SUM(lines.qtyIn)","trigger":"On line quantity change"},{"name":"qtyInTransit","formula":"SUM(lines.qtyOut) - SUM(lines.qtyIn)","trigger":"On line quantity change"},{"name":"qtyOut","formula":"SUM(lines.qtyOrdered)","trigger":"On line quantity change"},{"name":"taxAmount","formula":"SUM(lines.taxAmount)","trigger":"On line tax change"},{"name":"totalAmount","formula":"SUM(lines.totalAmount)","trigger":"On line total change"},{"name":"totalCartons","formula":"COUNT(cartons)","trigger":"query-time"},{"name":"totalCost","formula":"SUM(lines.extCost)","trigger":"On line cost change"},{"name":"totalLines","formula":"COUNT(lines)","trigger":"query-time"},{"name":"totalQty","formula":"SUM(lines.qty)","trigger":"On line quantity change"}],"crossEntityConstraints":[{"rule":"Must reference a valid Transfer Order","entity":"Transfer Order"},{"rule":"Posting writes debit entry at source and credit entry at destination","entity":"Stock Ledger"},{"rule":"Posting decrements source and increments destination SOH","entity":"Item Stock"}]},"inlineSchemas":[{"name":"ShipLineStatus","extends":"OperationalSubDocument","properties":[{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"},{"info":"Status of this transfer line.","name":"documentStatus","type":"enumeration","values":["Open","Completed","Cancelled"],"required":true}]},{"name":"StockTransferStatus","extends":"OperationalSubDocument","properties":[{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"},{"info":"Current document lifecycle status.","name":"documentStatus","type":"enumeration","values":["Draft","InTransit","Processing","Posted","Cancelled"],"required":true},{"info":"Operational sub-status for reversal tracking.","name":"operationalStatus","type":"enumeration","values":["Reversed","Complete","Reversal"]}]}]},{"name":"Stock Transfer Carton","class":"Operational","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Physical shipping container associated with a stock transfer, tracking items packed per container.","status":"stub","properties":[{"n":"cartonId","r":true,"t":"string"},{"n":"cartonNo","t":"string"},{"n":"lines","r":true,"t":"array","info":"Array of StockTransferLine sub-documents within this carton."},{"n":"stockTransfer","r":true,"t":"entityRef","re":"Stock Transfer"},{"n":"weight","t":"decimal"}],"ext":"OperationalSubDocument","related":["Stock Transfer"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Stock Transfer Reason","class":"Dictionary","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Codified reason for initiating inventory transfers (rebalancing, replenishment, consolidation).","status":"stub","properties":[{"n":"code","r":true,"t":"string"},{"n":"description","t":"string"},{"n":"name","r":true,"t":"string","u":true}],"ext":"LookupEntity","related":["Transfer Order"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"Stock Transfer Reject Reason","class":"Dictionary","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Codified reason for rejecting a stock transfer at the receiving location.","status":"stub","properties":[{"n":"code","r":true,"t":"string"},{"n":"description","t":"string"},{"n":"name","r":true,"t":"string","u":true}],"ext":"LookupEntity","related":["Stock Transfer"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}},{"name":"StockBin","class":"Operational","subsystem":"CONNECT","area":"Organization","desc":"A specific storage position within a Location (e.g. aisle-rack-shelf-bin). Inventory transactions reference bins for put-away, picking, counting, and replenishment. Modeled as an independent entity following SAP EWM / Dynamics 365 / NetSuite conventions.","status":"draft","properties":[{"n":"binId","r":true,"t":"string"},{"n":"binType","t":"enumeration","v":["Shelf","Pallet","Bulk Floor","Case Flow","Tote"],"info":"Physical form factor of the bin. Drives capacity rules and putaway logic. Aligned with SAP EWM storage bin types."},{"n":"code","r":true,"t":"string","info":"Human-readable coordinate label encoding the physical position (e.g. 'A-03-02'). Follows a Location-level naming convention of aisle–rack–shelf–position."},{"n":"description","t":"string"},{"n":"isMixable","r":true,"t":"boolean","info":"Whether the bin can hold multiple SKUs or lots simultaneously. When false, only one SKU/lot per bin (common for high-value or regulated items)."},{"n":"location","r":true,"t":"entityDetail","re":"Location","info":"Parent Location. A StockBin always belongs to exactly one Location."},{"n":"maxVolume","t":"decimal","info":"Maximum volumetric capacity. Enforced during putaway to prevent overfilling."},{"n":"maxWeight","t":"decimal","info":"Maximum weight capacity. Enforced during putaway to prevent overloading."},{"n":"name","t":"string","info":"Display name for the bin (e.g. 'Aisle A – Shelf 3 – Bin 2')."},{"n":"operationalStatus","t":"enumeration","v":["Available","Hold","Quarantine","Damaged","Blocked"],"info":"Operational status controlling whether inventory can be placed or picked. Distinct from Status which represents the lifecycle state."},{"n":"sequence","t":"integer","info":"Sort/pick order within a zone or location. Controls the sequence in which pickers visit bins."},{"n":"status","r":true,"t":"schema","info":"Lifecycle status of the bin. Tracks current state and who/when it was last changed."},{"n":"zone","t":"string","info":"Functional area the bin belongs to (e.g. Receiving, Forward Pick, Reserve, Staging, Quarantine). Controls putaway and picking strategy selection."}],"related":["Location","Item Stock"],"bv":{"rules":[{"rule":"Must be unique within the Location","when":"create","field":"BinId","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Must reference a valid active Location","entity":"Location"},{"rule":"Cannot delete a bin that holds non-zero stock","entity":"Item Stock"}]},"inlineSchemas":[{"name":"StockBinStatus","schema":"schemas/stock-bin/StockBinStatus.ts","properties":[{"info":"Current lifecycle state of the bin.","name":"status","type":"enumeration","values":["Draft","Active","Archived"],"required":true},{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"}]}]},{"name":"Tax Class","class":"Dictionary","subsystem":"CONNECT","area":"Products & Pricing","desc":"Tax classification assigned to products determining applicable tax rates and rules. Pure LookupEntity — no additional fields beyond the base.","status":"stub","properties":[],"ext":"LookupEntity","shopify":"tax_code on product variants","related":["Product","Classification"],"bv":{"rules":[{"rule":"Must be unique within the Company","when":"create","field":"Code","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Cannot delete a Tax Class assigned to active Products","entity":"Product"}]}},{"name":"Transfer Order","class":"Order","subsystem":"CONNECT","area":"Inventory & Allocation","desc":"Authorization to move stock between locations before the physical movement occurs.","status":"stub","properties":[{"n":"fromLocation","r":true,"t":"entityDetail","re":"Location"},{"n":"lines","r":true,"t":"array","info":"Array of TransferOrderLine sub-documents. Each line specifies an Item and quantity to transfer between locations."},{"n":"reason","t":"entityDetail","re":"Stock Transfer Reason"},{"n":"toLocation","r":true,"t":"entityDetail","re":"Location"},{"n":"transferOrderId","r":true,"t":"string"}],"ext":"OperationalDocument","shopify":"Transfer (draft status)","related":["Location","Stock Transfer","Stock Transfer Reason"],"bv":{"rules":[{"rule":"Must differ from ToLocation","when":"always","field":"FromLocation","severity":"error"},{"rule":"Only physical products (Style or Single class) can be added to a Transfer Order","when":"line-add","field":"lines","severity":"error"}],"lifecycle":{"states":["Draft","Processing","Open","Complete","Cancelled"],"transitions":[{"to":"Processing","from":"Draft","conditions":["At least one item line exists"]},{"to":"Cancelled","from":"Draft","conditions":[]},{"to":"Open","from":"Processing","conditions":["Stock Transfer created for shipment"]},{"to":"Complete","from":"Open","conditions":["All lines received at destination"]},{"to":"Cancelled","from":"Open","conditions":["No Stock Transfers in transit"]}],"initialState":"Draft"},"calculations":[],"crossEntityConstraints":[{"rule":"Generates one or more Stock Transfers for fulfillment","entity":"Stock Transfer"},{"rule":"Both FromLocation and ToLocation must be valid active Locations","entity":"Location"}]}},{"name":"User","class":"Core","subsystem":"ACCESS","area":"Security & Permissions","desc":"A human identity within the platform — employee, admin, or external partner. Carries credentials, Roles, and Franchise Group assignments. The User's franchiseGroups property drives data sanitization — queries and API responses are filtered to return only documents tagged with the User's assigned Franchise Groups.","status":"draft","properties":[{"n":"applications","r":true,"t":"array","re":"Application","info":"Applications this User is allowed to access. Controls which platform applications (POS, eComm, Admin, etc.) the User can authenticate into. Enforced at login — the User's token is scoped to the intersection of their Roles and allowed Applications."},{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"The Company tenant this user belongs to. Defines the hard isolation boundary for queries, permissions, replication, and export."},{"n":"email","r":true,"t":"string","u":true},{"n":"firstName","r":true,"t":"string"},{"n":"franchiseGroups","r":true,"t":"array","re":"Franchise Group","info":"Franchise Groups this User is assigned to. Drives data sanitization — the platform filters all query results and API responses to return only documents whose franchiseGroups overlap with the User's assignments. An empty array means no franchise-level filtering is applied (user sees all data within the Company tenant). This is the sub-tenant data segmentation mechanism within the Company boundary."},{"n":"lastName","r":true,"t":"string"},{"n":"roles","r":true,"t":"array","re":"Role"},{"n":"userId","r":true,"t":"string"},{"n":"userTier","r":true,"t":"schema","info":"The User's position within the franchise hierarchy. Drives UI scoping, agent tool RBAC, and default Franchise Group assignment behavior. Audited (UserTier inline schema) because tier transitions cross trust boundaries — promoting from Store to Franchise grants cross-location visibility, and from Franchise to Corporate grants cross-franchise visibility. Orthogonal to roles (permissions) and franchiseGroups (data sanitization scope). System / service identities are represented via the Actor entity, not via a tier value on User."}],"service":"APR Access","shopify":"Staff and User resources","related":["Application","Role","Company","Employee","Franchise Group"],"bv":{"rules":[{"rule":"Must be unique within the Company and valid email format","when":"always","field":"Email","severity":"error"}],"lifecycle":null,"calculations":[],"crossEntityConstraints":[{"rule":"Must have at least one Role assigned","entity":"Role"},{"rule":"Must belong to a valid Company","entity":"Company"}]},"inlineSchemas":[{"name":"UserTier","schema":"schemas/user/UserTier.ts","properties":[{"info":"Current tier classification within the franchise hierarchy. Corporate = corporate / franchisor staff. Franchise = regional or franchise-owner / multi-unit operator. Store = store-level staff working at a single location.","name":"tier","type":"enumeration","values":["Corporate","Franchise","Store"],"required":true},{"info":"User ID who last changed the tier.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last tier change.","name":"changedDate","type":"datetime"}]}]},{"name":"Vendor","class":"Operational","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"A supplier of goods. Has terms, item-level cost structures, and payment conditions.","status":"draft","properties":[{"n":"accountNos","r":true,"t":"array","info":"Vendor account numbers, one per franchise group with primary flag."},{"n":"contacts","r":true,"t":"array","info":"Vendor contacts with position/title."},{"n":"defaultPaymentTerms","r":true,"t":"array","info":"Default payment terms per franchise group."},{"n":"emails","r":true,"t":"array","info":"Email addresses associated with this vendor."},{"n":"leadTimeDays","t":"integer","info":"Default lead time in days for orders from this vendor."},{"n":"minimumPurchaseAmount","t":"decimal","info":"Minimum purchase amount for orders to this vendor."},{"n":"name","r":true,"t":"string"},{"n":"orderCurrency","t":"entityDetail","re":"Currency","info":"Default currency for purchase orders to this vendor."},{"n":"phones","r":true,"t":"array","info":"Phone numbers associated with this vendor."},{"n":"physicalAddress","t":"valueType","vt":"Address","info":"Vendor's physical/business address."},{"n":"referenceNo","t":"string","info":"External reference number for the vendor."},{"n":"shippingAddress","t":"valueType","vt":"Address","info":"Vendor's shipping/returns address."},{"n":"status","r":true,"t":"schema","info":"VendorStatus inline schema capturing current document status."},{"n":"vendorNo","r":true,"t":"integer"},{"n":"vendorWebsiteUrl","t":"string","info":"Vendor's website URL."}],"ext":"OperationalDocument","related":["Purchase Order","Vendor Invoice","Vendor Payment Term","Item"],"bv":{"rules":[{"rule":"Must be unique within the Company","when":"create","field":"VendorNo","severity":"error"}],"lifecycle":{"states":["Draft","Active","Inactive","Archived"],"transitions":[{"to":"Active","from":"Draft","conditions":[]},{"to":"Inactive","from":"Active","conditions":["No open POs"]},{"to":"Active","from":"Inactive","conditions":[]},{"to":"Archived","from":"Inactive","conditions":["No outstanding balance"]}],"initialState":"Draft"},"calculations":[{"name":"productCount","formula":"COUNT(DISTINCT Product via VendorItemValue.item.product)","trigger":"query-time"},{"name":"itemCount","formula":"COUNT(DISTINCT Item via VendorItemValue.item)","trigger":"query-time"},{"name":"purchaseOrderCount","formula":"COUNT(Purchase Order WHERE vendor = this)","trigger":"query-time"}],"crossEntityConstraints":[{"rule":"Cannot archive Vendor with open Purchase Orders","entity":"Purchase Order"}]},"inlineSchemas":[{"name":"VendorStatus","extends":"OperationalSubDocument","properties":[{"info":"User ID who last changed the status.","name":"changedBy","type":"string"},{"info":"ISO 8601 timestamp of the last status change.","name":"changedDate","type":"datetime"},{"info":"Current document lifecycle status of the vendor.","name":"documentStatus","type":"enumeration","values":["Draft","Active","Archived"],"required":true}]},{"name":"VendorAccountNo","extends":"OperationalSubDocument","properties":[{"info":"Account number assigned by this vendor.","name":"accountNo","type":"string","required":true},{"info":"Franchise group this account number applies to.","name":"franchiseGroupId","type":"string"},{"info":"Whether this is the primary account number.","name":"isPrimary","type":"boolean","required":true}]},{"name":"VendorDefaultPaymentTerms","extends":"OperationalSubDocument","properties":[{"info":"Franchise group these payment terms apply to.","name":"franchiseGroupId","type":"string"},{"info":"Default payment terms for this franchise group.","name":"paymentTerms","type":"entityDetail","relatedEntity":"Vendor Payment Term"}]}]},{"name":"Vendor Credit","class":"Operational","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"Standalone credit memo received from a Vendor — reduces an outstanding payable. Matched against the original Vendor Invoice or Purchase Order and produces a Bill of type 'credit' when approved. Parallel to Vendor Invoice but represents money owed back to the buyer.","status":"draft","properties":[{"n":"creditNo","r":true,"t":"string","info":"Vendor-assigned credit memo number."},{"n":"lines","r":true,"t":"array","info":"Array of VendorCreditLine sub-documents. Each line records a credited Item, quantity, and cost for matching against the original invoice or PO."},{"n":"reason","t":"string","info":"Reason for the credit — e.g. return, pricing dispute, damaged goods, vendor goodwill."},{"n":"sourceInvoice","t":"entityRef","re":"Vendor Invoice","info":"Optional reference to the originating Vendor Invoice. Null for standalone credits (e.g. rebates, goodwill)."},{"n":"vendor","r":true,"t":"entityRef","re":"Vendor"}],"ext":"OperationalDocument","notes":"Vendor Credits are external input documents (like Vendor Invoices), not system-generated results. They go through their own matching and approval workflow before producing a credit-type Bill.","related":["Vendor","Vendor Invoice","Purchase Order","Bill"],"bv":{"rules":[{"rule":"If linked to a source invoice, credit total must not exceed original invoice total","when":"match","field":"CreditTotal","severity":"error"}],"lifecycle":{"states":["Draft","Matched","Approved","Posted","Disputed"],"transitions":[{"to":"Matched","from":"Draft","conditions":["Matched against source invoice, PO, or standalone approval"]},{"to":"Approved","from":"Matched","conditions":["Approval workflow complete"]},{"to":"Posted","from":"Approved","conditions":["Credit-type Bill generated"]},{"to":"Disputed","from":"Draft","conditions":["Credit amount or line items contested"]},{"to":"Draft","from":"Disputed","conditions":["Dispute resolved, credit corrected"]}],"initialState":"Draft"},"calculations":[],"crossEntityConstraints":[{"rule":"Must reference a valid active Vendor","entity":"Vendor"},{"rule":"When linked, line items should correspond to the original invoice lines","entity":"Vendor Invoice"},{"rule":"Produces a Bill with type 'credit' upon posting","entity":"Bill"}]}},{"name":"Vendor Invoice","class":"Operational","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"Vendor's billing document matched against POs and Goods Receipts. Header/line discounts and fees parallel the PO structure.","status":"draft","properties":[{"n":"invoiceNo","r":true,"t":"string"},{"n":"lines","r":true,"t":"array","info":"Array of VendorInvoiceLine sub-documents. Each line records an invoiced Item, quantity, and cost for three-way matching."},{"n":"vendor","r":true,"t":"entityRef","re":"Vendor"}],"ext":"OperationalDocument","notes":"Header Discounts/Fees are below-the-line. Line Discounts/Fees affect line cost.","related":["Vendor","Purchase Order","Goods Receipt","Purchase"],"bv":{"rules":[{"rule":"Must match PO/Receipt totals within configured tolerance","when":"match","field":"InvoiceTotal","severity":"warning"}],"lifecycle":{"states":["Draft","Matched","Approved","Posted","Disputed"],"transitions":[{"to":"Matched","from":"Draft","conditions":["Three-way match: PO, Receipt, Invoice within tolerance"]},{"to":"Approved","from":"Matched","conditions":["Approval workflow complete"]},{"to":"Posted","from":"Approved","conditions":["Payment scheduled"]},{"to":"Disputed","from":"Draft","conditions":["Variance exceeds tolerance"]},{"to":"Draft","from":"Disputed","conditions":["Dispute resolved, invoice corrected"]}],"initialState":"Draft"},"calculations":[],"crossEntityConstraints":[{"rule":"Three-way match against PO and Purchase","entity":"Purchase Order"},{"rule":"Line quantities must reconcile with received quantities","entity":"Goods Receipt"}]}},{"name":"Vendor Payment Term","class":"Dictionary","subsystem":"CONNECT","area":"Purchasing & Receiving","desc":"Negotiated payment conditions with a vendor (e.g. Net 30, 2/10 Net 30).","status":"stub","properties":[{"n":"code","r":true,"t":"string"},{"n":"description","t":"string"},{"n":"discountDays","t":"integer"},{"n":"discountPercent","t":"decimal"},{"n":"name","r":true,"t":"string","u":true},{"n":"netDays","t":"integer"}],"ext":"LookupEntity","related":["Vendor"],"bv":{"rules":[],"lifecycle":null,"calculations":[],"crossEntityConstraints":[]}}],"baseSchemas":[{"name":"Identifiable","desc":"Root base schema. Provides a UUID primary key. All persisted entities extend this, either directly or via BaseDocument / OperationalDocument / OperationalSubDocument / TransactionalDocument / LedgerEntry.","provides":["id"],"properties":[{"n":"id","r":true,"t":"string","info":"UUID v4 primary key."}],"extendedBy":["Attribute Set","Brand","Classification","Franchise Group","Sales Channel","Vendor Dictionary","Advanced Shipping Notice","Advanced Shipping Notice Item","Bill","Bill Item","Delivery Notification","Purchase Order Acknowledgement Line","Ship Notification","Vendor Credit","Vendor Credit Item","Vendor Invoice"]},{"name":"BaseDocument","desc":"Standard document base for non-operational entities (e.g. reports, configurations). Adds audit trail, soft-delete, custom data, and tagging.","provides":["id","recentActions","createdBy","createdDate","customData","franchiseGroups","isDeleted","modifiedBy","modifiedDate","tags","uniqueValues"],"properties":[{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"The Company tenant this document belongs to. Defines the hard isolation boundary for queries, permissions, replication, and export. Paired with franchiseGroups which provides sub-tenant segmentation within this Company."},{"n":"createdBy","t":"string","info":"User ID who created the document."},{"n":"createdDate","r":true,"t":"datetime","info":"ISO 8601 creation timestamp."},{"n":"customData","r":true,"t":"array","info":"Custom fields with label, fieldType, value, and sequence. Supports text, decimal, integer, date, flag, multiselect, lookup, and longtext."},{"n":"franchiseGroups","r":true,"t":"array","info":"EntityDetail references (code, name, alias, sequence) linking the document to franchise groups."},{"n":"isDeleted","r":true,"t":"boolean","info":"Soft-delete flag."},{"n":"modifiedBy","t":"string","info":"User ID who last modified the document."},{"n":"modifiedDate","r":true,"t":"datetime","info":"ISO 8601 last-modification timestamp."},{"n":"recentActions","r":true,"t":"array","info":"Denormalized snapshot of the most recent actions performed on this document (capped at the last 5 entries). The Entity Action Log is the system of record for the full audit trail. Each entry follows the Action inline schema: actionDate, actionedBy, code, label, actionType (FieldChange | Operation), and optional change detail."},{"n":"tags","r":true,"t":"array","info":"Free-form string tags for categorization."},{"n":"uniqueValues","r":true,"t":"array","info":"Alternative identifiers — array of Identifier (isPrimary, label, name, value)."}],"ext":"Identifiable","extendedBy":["Company"],"inlineSchemas":[{"name":"Action","schema":"schemas/common/Action.ts","properties":[{"info":"ISO 8601 timestamp when the action occurred.","name":"actionDate","type":"datetime","required":true},{"info":"User ID who performed the action.","name":"actionedBy","type":"string","required":true},{"info":"Discriminator: FieldChange for property-level mutations, Operation for domain events (e.g. status transition, approval).","name":"actionType","type":"enumeration","values":["FieldChange","Operation"],"required":true},{"info":"Field-level changes that occurred. Each entry is an ActionChange (fieldName, previousValue, newValue). Empty for Operation actions with no field-level side effects.","name":"changes","type":"array","required":true},{"info":"Stable, machine-readable action identifier (e.g. 'status.transition', 'price.update', 'approve', 'line.add'). Provides an enumerable key for reports, dashboards, and automation triggers. While actionType captures the category, code captures the semantic intent of the action.","name":"code","type":"string","required":true},{"info":"Human-readable summary of what happened (e.g. 'Status changed from Draft to Active').","name":"description","type":"string","required":true}]}]},{"name":"OperationalDocument","desc":"Extended document base for operational entities (Location, Vendor, Employee, etc.) and order-lifecycle entities (Sales Order, Purchase Order, etc.). Adds integration identifiers and an idempotency key on top of BaseDocument fields. Transactional entities extend this indirectly via TransactionalDocument.","provides":["id","recentActions","createdBy","createdDate","customData","franchiseGroups","identifiers","idmpKey","isDeleted","modifiedBy","modifiedDate","notes","tags","uniqueValues"],"properties":[{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"The Company tenant this document belongs to. Defines the hard isolation boundary for queries, permissions, replication, and export. Paired with franchiseGroups which provides sub-tenant segmentation within this Company."},{"n":"createdBy","t":"string","info":"User ID who created the document."},{"n":"createdDate","r":true,"t":"datetime","info":"ISO 8601 creation timestamp."},{"n":"customData","r":true,"t":"array","info":"Custom fields with label, fieldType, value, and sequence. Supports text, decimal, integer, date, flag, multiselect, lookup, and longtext."},{"n":"franchiseGroups","r":true,"t":"array","info":"EntityDetail references (code, name, alias, sequence) linking the document to franchise groups."},{"n":"identifiers","r":true,"t":"array","info":"OperationalIdentifier array — extends Identifier with originatingSystemName for cross-system integration."},{"n":"idmpKey","r":true,"t":"string","info":"Idempotency / integration key for data-management deduplication."},{"n":"isDeleted","r":true,"t":"boolean","info":"Soft-delete flag."},{"n":"modifiedBy","t":"string","info":"User ID who last modified the document."},{"n":"modifiedDate","r":true,"t":"datetime","info":"ISO 8601 last-modification timestamp."},{"n":"notes","t":"string","info":"Free-text notes on this document."},{"n":"recentActions","r":true,"t":"array","info":"Denormalized snapshot of the most recent actions performed on this document (capped at the last 5 entries). The Entity Action Log is the system of record for the full audit trail. Each entry follows the Action inline schema defined on BaseDocument."},{"n":"tags","r":true,"t":"array","info":"Free-form string tags for categorization."},{"n":"uniqueValues","r":true,"t":"array","info":"Alternative identifiers — array of Identifier (isPrimary, label, name, value)."}],"ext":"Identifiable","extendedBy":["Product","Location","Employee","Vendor","Style","Sales Order","Purchase Order","Transfer Order","Shipment","Fulfillment Order","Ship Order","Catalog","Stock Take","Advanced Shipping Notice","Vendor Invoice","Vendor Credit","Customer","TransactionalDocument"]},{"name":"OperationalSubDocument","desc":"Lightweight operational base for line-items and child documents. Similar to OperationalDocument but without idmpKey or franchiseGroups. Used for entities that live within a parent document context.","provides":["id","recentActions","createdBy","createdDate","customData","identifiers","isDeleted","modifiedBy","modifiedDate","notes","tags","uniqueValues"],"properties":[{"n":"recentActions","r":true,"t":"array","info":"Denormalized snapshot of the most recent actions performed on this sub-document (capped at the last 5 entries). The Entity Action Log is the system of record for the full audit trail. Each entry follows the Action inline schema defined on BaseDocument."},{"n":"createdBy","t":"string","info":"User ID who created the sub-document."},{"n":"createdDate","r":true,"t":"datetime","info":"ISO 8601 creation timestamp."},{"n":"customData","r":true,"t":"array","info":"Custom fields with label, fieldType, value, and sequence."},{"n":"identifiers","r":true,"t":"array","info":"OperationalIdentifier array — extends Identifier with originatingSystemName."},{"n":"isDeleted","r":true,"t":"boolean","info":"Soft-delete flag."},{"n":"modifiedBy","t":"string","info":"User ID who last modified the sub-document."},{"n":"modifiedDate","r":true,"t":"datetime","info":"ISO 8601 last-modification timestamp."},{"n":"notes","t":"string","info":"Free-text notes on this sub-document."},{"n":"tags","r":true,"t":"array","info":"Free-form string tags for categorization."},{"n":"uniqueValues","r":true,"t":"array","info":"Alternative identifiers — array of Identifier (isPrimary, label, name, value)."}],"ext":"Identifiable","extendedBy":["Item","Advanced Shipping Notice Carton","Shipment Carton","Stock Transfer Carton"],"usedAsLinesBy":["Stock Adjustment","Stock Take","Stock Transfer","Transfer Order","Purchase Order","Goods Receipt","Vendor Invoice","Bill","Sales Order","Fulfillment Order","Ship Order","Fulfillment","Shipment","Sale","Advanced Shipping Notice Carton","Shipment Carton","Stock Transfer Carton","Purchase Order Acknowledgement"]},{"name":"TransactionalSubDocument","desc":"Lightweight transactional base for line-items and child documents within transactional entities. Extends OperationalSubDocument with transactionDate, fiscalDate, postedDate, and isVoided semantics. Used for sub-documents that participate in transactional workflows and may write to ledgers upon posting.","provides":["id","recentActions","createdBy","createdDate","customData","fiscalDate","identifiers","isDeleted","isVoided","modifiedBy","modifiedDate","notes","postedDate","tags","transactionDate","uniqueValues"],"properties":[{"n":"transactionDate","r":true,"t":"datetime","info":"The business date/time when the line-level transaction occurred. Typically inherited from the parent document but can be overridden for split-date scenarios."},{"n":"fiscalDate","r":true,"t":"datetime","info":"The fiscal calendar date assigned to this line. Used for period-based reporting. Typically inherited from the parent document."},{"n":"postedDate","t":"datetime","info":"Timestamp when this line was finalized and committed to its target ledger(s). Null while in a pre-posted state."},{"n":"isVoided","r":true,"t":"boolean","info":"Whether this line has been voided or reversed. Defaults to false. Voided lines remain for audit but are excluded from active reporting."}],"ext":"OperationalSubDocument","extendedBy":[]},{"name":"LookupEntity","desc":"Abstract base schema for all reference-data / dictionary entities. Provides the standard shape: code, name, alias, franchise-group scoping, active/default/deleted/readOnly flags, audit fields, and custom-data support. Not instantiated directly.","provides":["id","code","name","alias","franchiseGroups","idmpKey","isActive","isDefault","isDeleted","isReadOnly","sequence","createdBy","createdDate","modifiedBy","modifiedDate","customData"],"properties":[{"n":"alias","t":"schema","info":"Optional label/value pair for alternate display text (LookupEntityAlias)."},{"n":"code","r":true,"t":"string","info":"Human-readable key. Unique within scope."},{"n":"company","t":"entityDetail","re":"Company","info":"The Company tenant this lookup value belongs to. OPTIONAL on this base schema because a small set of dictionaries are platform-global (Currency, Locale, Country, Environment) and must have company null. All other Dictionary entities must declare company: required per the tenant-scoped-dictionary-declares-company convention."},{"n":"createdBy","t":"string","info":"User ID who created the record."},{"n":"createdDate","t":"datetime","info":"ISO 8601 creation timestamp."},{"n":"customData","r":true,"t":"array","info":"Extensible key/value pairs for tenant-specific metadata (DocumentCustomData)."},{"n":"franchiseGroups","r":true,"t":"array","info":"EntityDetail references scoping this lookup value to one or more franchise groups."},{"n":"idmpKey","r":true,"t":"string","info":"Idempotency / integration key for data-management deduplication."},{"n":"isActive","r":true,"t":"boolean","info":"Whether this lookup value is active. Defaults to true."},{"n":"isDefault","r":true,"t":"boolean","info":"Whether this is the default selection. Defaults to false."},{"n":"isDeleted","r":true,"t":"boolean","info":"Soft-delete flag. Defaults to false."},{"n":"isReadOnly","r":true,"t":"boolean","info":"Whether this lookup value is system-managed and not editable. Defaults to false."},{"n":"modifiedBy","t":"string","info":"User ID who last modified the record."},{"n":"modifiedDate","t":"datetime","info":"ISO 8601 last-modification timestamp."},{"n":"name","r":true,"t":"string","info":"Display name."},{"n":"sequence","t":"integer","info":"Display ordering hint."}],"ext":"Identifiable","extendedBy":["Currency","Locale","Tax Class","Price Level","Cost Level","Item Stock Group","Stock Limit Group","Purchasing Fee","Shipping Method","Loyalty Program"]},{"name":"LookupEntityValue","desc":"Base schema for inline value schemas within LookupEntity entities. Analogous to OperationalSubDocument for OperationalDocument — provides a lightweight identity and audit shape for nested value objects that exist only within the context of their parent LookupEntity. Carries its own id, identifiers, code, name, aliases, and audit trail.","provides":["id","identifiers","code","name","aliases","isActive","isDeleted","isDefault","sequence","createdBy","createdDate","modifiedBy","modifiedDate","customData"],"properties":[{"n":"identifiers","r":true,"t":"array","info":"OperationalIdentifier array — extends Identifier with originatingSystemName for cross-system integration."},{"n":"code","r":true,"t":"string","info":"Human-readable key. Unique within the parent entity's value collection."},{"n":"name","r":true,"t":"string","info":"Display name of the value."},{"n":"aliases","t":"array","info":"Alternative display names or label/value pairs for this value."},{"n":"isActive","r":true,"t":"boolean","info":"Whether this value is active. Defaults to true."},{"n":"isDeleted","r":true,"t":"boolean","info":"Soft-delete flag. Defaults to false."},{"n":"isDefault","r":true,"t":"boolean","info":"Whether this is the default selection within the parent entity's value collection. Defaults to false."},{"n":"sequence","t":"integer","info":"Display ordering hint within the parent entity's value collection."},{"n":"createdBy","t":"string","info":"User ID who created the value."},{"n":"createdDate","t":"datetime","info":"ISO 8601 creation timestamp."},{"n":"modifiedBy","t":"string","info":"User ID who last modified the value."},{"n":"modifiedDate","t":"datetime","info":"ISO 8601 last-modification timestamp."},{"n":"customData","r":true,"t":"array","info":"Extensible key/value pairs for tenant-specific metadata (DocumentCustomData)."}],"ext":"Identifiable"},{"name":"TransactionalDocument","desc":"Base schema for Transactional-class entities that record business events (sales, adjustments, receipts, transfers). Extends OperationalDocument with a standardized transaction date, posted date, and void/reversal semantics. Every TransactionalDocument writes to at least one Ledger upon posting.","provides":["id","recentActions","createdBy","createdDate","customData","fiscalDate","franchiseGroups","identifiers","idmpKey","isDeleted","isVoided","modifiedBy","modifiedDate","notes","postedDate","tags","transactionDate","uniqueValues"],"properties":[{"n":"transactionDate","r":true,"t":"datetime","info":"The business date/time when the transaction occurred. Distinct from createdDate (when the record was persisted) and postedDate (when it was finalized to ledgers). Maps to entity-specific names like Sale.date, StockAdjustment.adjustmentDate, Bill.billDate, GoodsReceipt.receivedDate, PriceAdjustment.effectiveDate, etc."},{"n":"fiscalDate","r":true,"t":"datetime","info":"The fiscal calendar date assigned to this transaction. Used for period-based reporting and accounting period assignment. May differ from transactionDate when transactions are recorded near period boundaries or when fiscal calendars do not align with calendar dates."},{"n":"postedDate","t":"datetime","info":"Timestamp when the transaction was finalized and committed to its target ledger(s). Null while the transaction is in a pre-posted state (e.g. Draft, Processing). Once set, the transaction is considered immutable for accounting purposes."},{"n":"isVoided","r":true,"t":"boolean","info":"Whether this transaction has been voided or reversed. Defaults to false. Voided transactions remain in the system for audit purposes but are excluded from active reporting. The void action writes a corresponding reversal entry to the relevant ledger(s)."}],"ext":"OperationalDocument","extendedBy":["Sale","Stock Adjustment","Stock Transfer","Goods Receipt","Purchase","Bill","Fulfillment","Price Adjustment","Cost Adjustment"]},{"name":"LedgerEntry","desc":"Base schema for Ledger-class entities — immutable, append-only audit records. Extends Identifiable directly (not OperationalDocument) because ledger entries must not be modified or deleted. Provides source-entity traceability and creation audit fields only. Individual ledger entities add domain-specific properties (e.g. ledgerLine, qty, amounts) on top.","provides":["id","entryDate","sourceEntityType","sourceEntityId","createdBy","createdDate"],"properties":[{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"The Company tenant this ledger entry belongs to. Required at write time — ledger entries are immutable, so there is no opportunity to backfill company later. Also serves as the tenant partition key for ledger storage and per-tenant retention policies."},{"n":"createdBy","t":"string","info":"User ID or system principal that wrote this ledger entry."},{"n":"createdDate","r":true,"t":"datetime","info":"ISO 8601 system timestamp when the entry was persisted. Always system-generated, never back-dated (unlike entryDate which may reflect the business event date)."},{"n":"entryDate","r":true,"t":"datetime","info":"Chronological timestamp of the ledger entry — the business date/time the recorded event occurred. May differ from createdDate when entries are back-dated or batch-processed."},{"n":"sourceEntityId","r":true,"t":"string","info":"UUID of the specific entity instance that triggered this ledger entry. Together with sourceEntityType, provides a complete back-reference to the originating transaction or event."},{"n":"sourceEntityType","r":true,"t":"string","info":"Entity class name that triggered this ledger entry (e.g. 'Sale', 'Stock Adjustment', 'Price Adjustment'). Used as a partition/index key alongside sourceEntityId for traceability queries."}],"ext":"Identifiable","extendedBy":["Stock Ledger","Price Ledger","Cost Ledger","Email Log","Entity Action Log"]},{"name":"TaxonomyEntity","desc":"Base schema for hierarchical classification entities that form tree structures (categories, departments, classes). Extends Identifiable with parent/child relationships, depth tracking, and path materialization for efficient tree traversal. Not instantiated directly — concrete taxonomy entities (ProductCategory, etc.) extend this.","provides":["id","ancestorIds","code","name","fullName","parent","children","depth","path","isLeaf","isRoot","sequence","isActive","isDeleted","createdBy","createdDate","modifiedBy","modifiedDate"],"properties":[{"n":"ancestorIds","r":true,"t":"array","info":"Ordered array of ancestor node IDs from root to immediate parent. Enables fast breadcrumb rendering and subtree queries without recursive lookups. Empty array for root nodes."},{"n":"children","t":"array","info":"Child nodes in the taxonomy tree."},{"n":"code","r":true,"t":"string","info":"Human-readable identifier. Unique within the taxonomy."},{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"The Company tenant this taxonomy tree belongs to. Product categories and types are always tenant-specific."},{"n":"createdBy","t":"string","info":"User ID who created the node."},{"n":"createdDate","r":true,"t":"datetime","info":"ISO 8601 creation timestamp."},{"n":"depth","r":true,"t":"integer","info":"Zero-based depth in the tree. Root nodes have depth 0."},{"n":"fullName","t":"string","info":"Calculated field. The fully qualified name built from the materialized path, concatenating ancestor names from root to this node (e.g. 'Apparel > Men > Outerwear')."},{"n":"isActive","r":true,"t":"boolean","info":"Whether this taxonomy node is active. Defaults to true."},{"n":"isDeleted","r":true,"t":"boolean","info":"Soft-delete flag. Defaults to false."},{"n":"isLeaf","r":true,"t":"boolean","info":"Whether this node has no children. Computed from children array. Leaf nodes are selectable for entity assignment; branch nodes are for navigation only."},{"n":"isRoot","r":true,"t":"boolean","info":"Whether this is a top-level node with no parent. Computed from depth (depth === 0)."},{"n":"modifiedBy","t":"string","info":"User ID who last modified the node."},{"n":"modifiedDate","r":true,"t":"datetime","info":"ISO 8601 last-modification timestamp."},{"n":"name","r":true,"t":"string","info":"Display name of the taxonomy node."},{"n":"parent","t":"entityRef","info":"Self-referencing parent node. Null for root nodes."},{"n":"path","r":true,"t":"string","info":"Materialized path for efficient ancestor/descendant queries (e.g. '/root/parent/this'). Enables tree traversal without recursive queries."},{"n":"sequence","t":"integer","info":"Display ordering hint among siblings at the same depth."}],"ext":"Identifiable","extendedBy":["Product Category","Product Type"]},{"name":"TaxonomyEntityNode","desc":"Base schema for individual nodes within a taxonomy hierarchy (e.g. Classification Department, Classification Class, Classification Subclass). Extends Identifiable with the common properties shared by all taxonomy node entities — code, name, sequence, and audit fields. Simpler than TaxonomyEntity which represents the full tree structure; TaxonomyEntityNode represents a single level/node within that tree.","provides":["id","code","name","sequence","isActive","isDeleted","createdBy","createdDate","modifiedBy","modifiedDate"],"properties":[{"n":"code","r":true,"t":"string","info":"Human-readable identifier. Unique within the node type."},{"n":"company","r":true,"t":"entityDetail","re":"Company","info":"The Company tenant this taxonomy node belongs to. Classification Department/Class/Subclass are always tenant-specific."},{"n":"createdBy","t":"string","info":"User ID who created the node."},{"n":"createdDate","r":true,"t":"datetime","info":"ISO 8601 creation timestamp."},{"n":"isActive","r":true,"t":"boolean","info":"Whether this node is active. Defaults to true."},{"n":"isDeleted","r":true,"t":"boolean","info":"Soft-delete flag. Defaults to false."},{"n":"modifiedBy","t":"string","info":"User ID who last modified the node."},{"n":"modifiedDate","r":true,"t":"datetime","info":"ISO 8601 last-modification timestamp."},{"n":"name","r":true,"t":"string","info":"Display name of the taxonomy node."},{"n":"sequence","t":"integer","info":"Display ordering hint among sibling nodes."}],"ext":"Identifiable","extendedBy":["Classification Department","Classification Class","Classification Subclass"]},{"name":"CoreEntity","desc":"Base schema extending Identifiable. Shared foundation for entities that need common core fields beyond just `id` but don't fit the BaseDocument / OperationalDocument / TransactionalDocument lineage. Provides audit fields (createdBy/createdDate, modifiedBy/modifiedDate), soft-delete, customData, tags, slug, and a recentActions snapshot of the last five entity actions.","provides":["id","createdBy","createdDate","customData","isDeleted","modifiedBy","modifiedDate","recentActions","slug","tags"],"properties":[{"n":"createdBy","t":"string","info":"User ID who created the entity."},{"n":"createdDate","r":true,"t":"datetime","info":"ISO 8601 creation timestamp."},{"n":"customData","r":true,"t":"array","info":"Custom fields with label, fieldType, value, and sequence. Supports text, decimal, integer, date, flag, multiselect, lookup, and longtext."},{"n":"isDeleted","r":true,"t":"boolean","info":"Soft-delete flag."},{"n":"modifiedBy","t":"string","info":"User ID who last modified the entity."},{"n":"modifiedDate","r":true,"t":"datetime","info":"ISO 8601 last-modification timestamp."},{"n":"recentActions","r":true,"t":"array","info":"Denormalized snapshot of the most recent actions performed on this entity (capped at the last 5 entries). The Entity Action Log is the system of record for the full audit trail. Each entry follows the Action inline schema: actionDate, actionedBy, code, label, actionType (FieldChange | Operation), and optional change detail."},{"n":"slug","t":"string","u":true,"info":"URL-safe, human-readable identifier for the entity (e.g. used in routes or stable external references). Unique within the entity scope."},{"n":"tags","r":true,"t":"array","info":"Free-form string tags for categorization."}],"ext":"Identifiable","extendedBy":["Company","Organization","Business Entity"],"inlineSchemas":[{"name":"Action","schema":"schemas/common/Action.ts","properties":[{"n":"actionDate","r":true,"t":"datetime","info":"ISO 8601 timestamp when the action occurred."},{"n":"actionedBy","r":true,"t":"string","info":"User ID who performed the action."},{"n":"actionType","r":true,"t":"enumeration","v":["FieldChange","Operation"],"info":"Discriminator: FieldChange for property-level mutations, Operation for domain events (e.g. status transition, approval)."},{"n":"changes","r":true,"t":"array","info":"Field-level changes that occurred. Each entry is an ActionChange (fieldName, previousValue, newValue). Empty for Operation actions with no field-level side effects."},{"n":"code","r":true,"t":"string","info":"Stable, machine-readable action identifier (e.g. 'status.transition', 'price.update', 'approve', 'line.add'). Provides an enumerable key for reports, dashboards, and automation triggers. While actionType captures the category, code captures the semantic intent of the action."},{"n":"description","r":true,"t":"string","info":"Human-readable summary of what happened (e.g. 'Status changed from Draft to Active')."}]}]}],"valueTypes":[{"name":"BasicAddress","desc":"Core postal address fields. Base type for all address structures.","properties":[{"n":"city","r":true,"t":"string"},{"n":"countryCode","r":true,"t":"string","info":"ISO 3166-1 alpha-2"},{"n":"line1","r":true,"t":"string"},{"n":"line2","t":"string"},{"n":"line3","t":"string"},{"n":"organization","t":"string"},{"n":"postalCode","r":true,"t":"string"},{"n":"state","t":"string"}]},{"name":"Address","desc":"Extended address with contact details and labeling. Used for entity mailing/billing addresses.","properties":[{"n":"email","t":"string"},{"n":"label","t":"string","info":"e.g. 'Headquarters', 'Billing', 'Shipping'"},{"n":"name","t":"string","info":"Addressee name"},{"n":"notes","t":"string"},{"n":"phone","t":"string"}],"ext":"BasicAddress","usedBy":["Business Entity.Address","Franchise Group.PostalAddress","Franchise Group.BillingAddress","Vendor.Address","Customer.Address"]},{"name":"AddressBook","desc":"Ordered collection of addresses with primary flag and identifiers. Used when an entity maintains multiple addresses.","properties":[{"n":"addressNo","t":"integer","info":"Sequence number within the book"},{"n":"identifiers","r":true,"t":"array","info":"Array of Identifier value objects"},{"n":"isDeleted","r":true,"t":"boolean","info":"Soft-delete flag"},{"n":"isPrimary","r":true,"t":"boolean"}],"ext":"Address"},{"name":"Phone","desc":"A phone number entry with label and primary flag.","properties":[{"n":"isPrimary","r":true,"t":"boolean"},{"n":"label","t":"string","info":"e.g. 'Mobile', 'Work', 'Fax'"},{"n":"phoneNumber","r":true,"t":"string"}]},{"name":"Email","desc":"An email address entry with label and primary flag.","properties":[{"n":"email","r":true,"t":"string"},{"n":"isPrimary","r":true,"t":"boolean"},{"n":"label","t":"string","info":"e.g. 'Work', 'Personal'"}]},{"name":"PersonName","desc":"Structured person name with title and middle name support.","properties":[{"n":"firstName","r":true,"t":"string"},{"n":"lastName","r":true,"t":"string"},{"n":"middleName","t":"string"},{"n":"title","t":"string","info":"e.g. 'Mr', 'Ms', 'Dr'"}]},{"name":"Contact","desc":"A full contact record combining name, phones, emails, address, and custom data. Used for vendor contacts, customer contacts, etc.","properties":[{"n":"address","t":"valueType","info":"Address value type"},{"n":"contactNo","t":"integer","info":"Sequence number"},{"n":"customData","t":"schema","info":"Extensible key-value data"},{"n":"emails","r":true,"t":"array","info":"Array of Email value objects"},{"n":"franchiseGroups","r":true,"t":"array","info":"Associated Franchise Group codes"},{"n":"isActive","r":true,"t":"boolean"},{"n":"isDeleted","r":true,"t":"boolean","info":"Soft-delete flag"},{"n":"isPrimary","r":true,"t":"boolean"},{"n":"name","t":"valueType","info":"PersonName value type"},{"n":"phones","r":true,"t":"array","info":"Array of Phone value objects"}]},{"name":"EntityDetail","desc":"Denormalized snapshot of a referenced entity's identity fields (id + code + name + alias + sequence). Embedded inline on the parent document to avoid a separate query for basic display information. Extends Identifiable (adds UUID id). Used for Location and Dictionary/Lookup entity references.","properties":[{"n":"alias","t":"string"},{"n":"code","r":true,"t":"string"},{"n":"name","r":true,"t":"string"},{"n":"sequence","t":"integer"}]},{"name":"Identifier","desc":"A labeled identifier value (e.g. tax ID, loyalty number, external system reference).","properties":[{"n":"isPrimary","r":true,"t":"boolean"},{"n":"label","t":"string"},{"n":"name","r":true,"t":"string","info":"Identifier type label"},{"n":"value","r":true,"t":"string"}]},{"name":"Payment","desc":"A payment tender line within a transaction — captures method, amount, and change.","properties":[{"n":"accountNo","t":"string"},{"n":"accountType","t":"string"},{"n":"amount","r":true,"t":"decimal"},{"n":"changeAmount","t":"decimal"},{"n":"code","r":true,"t":"string","info":"Payment method code"},{"n":"name","r":true,"t":"string","info":"Payment method display name"},{"n":"paymentCardType","t":"string","info":"e.g. Visa, Mastercard, AMEX"},{"n":"paymentDate","t":"datetime"},{"n":"sequence","t":"integer"}]},{"name":"Money","desc":"Amount with currency code. Used wherever a monetary value needs explicit currency context.","properties":[{"n":"amount","r":true,"t":"decimal"},{"n":"currencyCode","r":true,"t":"string","info":"ISO 4217 currency code"}]},{"name":"ConfigurationRef","desc":"Embedded reference from a configurable entity to its configuration record in the CONFIG subsystem. The `config` field carries a denormalized snapshot of the Config entity (id, code, name) so runtime services can resolve configuration without an extra round-trip; `templateCode` optionally names the Config Template the config was derived from, which lets consumers validate expected shape and discover available keys. This value type is the canonical shape of every configurable entity's link to CONFIG — do not declare ad-hoc configId strings on entities. Consumers that need richer linkage (environment scope, version pinning, override chain) should add fields here rather than on each entity.","properties":[{"n":"config","r":true,"t":"entityDetail","re":"Config","info":"Denormalized snapshot of the referenced Config record (id, code, name). Follows the standard entityDetail pattern so display never requires a lookup. The Config itself holds Actor, Environment, and resolved values."},{"n":"templateCode","r":false,"t":"string","info":"Code of the Config Template the referenced Config was derived from. Optional, but recommended so consumers can validate against the expected schema and discover which keys are available."}],"usedBy":["Location","Organization","Company","Connector","Sales Channel"]},{"name":"AnnouncementAudience","desc":"Audience targeting filter for an Announcement. Works in conjunction with the Announcement's inherited franchiseGroups and its targetLocations/targetRoles properties to resolve the final delivery set. Resolution: franchiseGroups (inherited) ∩ targetLocations ∩ targetRoles, plus includeUsers, minus excludeUsers. The scope field declares the primary targeting strategy used by the authoring UI.","properties":[{"n":"allLocationsInFranchiseGroup","r":true,"t":"boolean","info":"When true, audience expands to every Location within the matched franchiseGroups. When false, only Locations explicitly listed in Announcement.targetLocations receive delivery."},{"n":"excludeUsers","r":true,"t":"array","re":"User","info":"Explicit User exclusions removed from the resolved audience after all other filters apply. EntityRef array. Empty array by default."},{"n":"includeUsers","r":true,"t":"array","re":"User","info":"Explicit User additions appended to the resolved audience regardless of franchiseGroup/Location/Role filters. EntityRef array. Empty array by default."},{"n":"scope","r":true,"t":"enumeration","v":["all","franchiseGroups","locations","roles","users"],"info":"Primary targeting strategy. 'all' = entire Company; 'franchiseGroups' = inherited franchiseGroups only; 'locations' = explicit targetLocations; 'roles' = explicit targetRoles; 'users' = explicit includeUsers only."}],"usedBy":["Announcement"]}],"conventions":[{"id":"array-must-be-required","scope":"property","severity":"error","when":"type === 'array'","expect":"required === true","reason":"Array properties must be required. An empty array [] is the minimum default; null/undefined arrays cause runtime errors in iteration and spread operations.","active":true},{"id":"boolean-must-be-required","scope":"property","severity":"error","when":"type === 'boolean'","expect":"required === true","reason":"Boolean properties must be required with a default of false. undefined/null booleans cause subtle bugs: `if (!flag)` is true for both false and undefined, masking missing data as intentional 'off'.","active":true},{"id":"datetime-stored-as-utc","scope":"property","severity":"error","when":"type === 'datetime'","expect":"value is stored as ISO 8601 UTC string (Z suffix). Zod schema uses z.coerce.date().","reason":"All datetime values must be persisted in UTC (e.g. 2024-01-15T14:30:00.000Z). Display conversion to the device timezone is a presentation-layer concern handled by Intl.DateTimeFormat or date-fns-tz.","active":true},{"id":"date-is-calendar-only","scope":"property","severity":"error","when":"type === 'date'","expect":"value is stored as ISO 8601 date string (YYYY-MM-DD) with no time or timezone component. Zod schema uses z.string().date().","reason":"Date properties represent calendar dates (e.g. birthDate, effectiveDate) with no point-in-time semantics. Storing a bare YYYY-MM-DD string avoids timezone-shift bugs where a UTC midnight rolls to the previous day in western timezones.","active":true},{"id":"decimal-monetary-inherits-currency","scope":"property","severity":"warning","when":"type === 'decimal' and property represents a monetary amount (price, cost, total, fee, amount)","expect":"currency context is provided by the parent entity's Market baseCurrency (for single-currency operations) or the Money value type is used (for cross-currency transactions). Financial documents that cross currency boundaries should capture exchangeRate and exchangeRateCurrency at transaction time.","reason":"Bare decimal amounts without currency context are ambiguous in multi-market operations. The Market.baseCurrency defines the implicit currency for all prices and costs within that market. Cross-currency values (e.g. vendor invoices in a foreign currency) must use the Money value type to pair amount with ISO 4217 currencyCode. Display conversion to the user's local currency is a presentation-layer concern.","active":true},{"id":"entity-detail-needs-related-entity","scope":"property","severity":"error","when":"type === 'entityDetail'","expect":"relatedEntity is defined","reason":"entityDetail properties must specify which entity they denormalize via relatedEntity.","active":true},{"id":"entity-detail-requires-id-code-name","scope":"property","severity":"error","when":"type === 'entityDetail'","expect":"the denormalized snapshot always includes id, code, and name fields from the referenced entity","reason":"entityDetail properties embed a denormalized snapshot following the EntityDetailSchema shape (id + code + name + alias? + sequence?). The three mandatory fields — id for foreign-key integrity, code for human-readable lookups, and name for display — must always be present so consumers never need a separate round-trip for basic identification or rendering.","active":true},{"id":"entity-ref-needs-related-entity","scope":"property","severity":"error","when":"type === 'entityRef'","expect":"relatedEntity is defined","reason":"entityRef properties must specify the target entity via relatedEntity.","active":true},{"id":"enumeration-needs-values","scope":"property","severity":"error","when":"type === 'enumeration'","expect":"values is defined and non-empty","reason":"Enumeration properties must list their allowed values for schema generation and UI dropdowns.","active":true},{"id":"value-type-needs-name","scope":"property","severity":"warning","when":"type === 'valueType'","expect":"valueTypeName is defined OR info mentions the value type name","reason":"valueType properties should reference a specific ValueObjectDef by name for traceability.","active":true},{"id":"encrypted-needs-description","scope":"property","severity":"warning","when":"type === 'encrypted'","expect":"info is defined and non-empty","reason":"Encrypted properties should describe what is stored to aid security audits.","active":true},{"id":"property-names-unique","scope":"entity","severity":"error","when":"always","expect":"no duplicate property names within the same entity","reason":"Duplicate property names cause schema conflicts and ambiguous data access.","active":true},{"id":"properties-alphabetical-order","scope":"entity","severity":"warning","when":"entity has two or more own properties","expect":"properties are sorted alphabetically by name (a → z)","reason":"Alphabetical property ordering makes entities scannable, eliminates merge conflicts from arbitrary insertion, and ensures consistency across the registry.","active":true},{"id":"entity-needs-description","scope":"entity","severity":"error","when":"always","expect":"desc is defined and non-empty","reason":"Every entity must have a description for documentation and developer onboarding.","active":true},{"id":"entity-needs-at-least-one-property","scope":"entity","severity":"warning","when":"always","expect":"properties array has at least one entry","reason":"Entities with zero properties provide no schema value. At minimum, define the primary identifier.","active":true},{"id":"operational-document-declares-inherited","scope":"entity","severity":"warning","when":"extends === 'OperationalDocument'","expect":"inherited field is defined and lists base schema fields","reason":"Entities extending OperationalDocument should declare their inherited fields for audit completeness.","active":true},{"id":"transactional-document-declares-inherited","scope":"entity","severity":"warning","when":"extends === 'TransactionalDocument'","expect":"inherited field is defined and lists base schema fields (including transactionDate, fiscalDate, postedDate, isVoided from TransactionalDocument)","reason":"Entities extending TransactionalDocument should declare their inherited fields for audit completeness.","active":true},{"id":"ledger-entry-declares-inherited","scope":"entity","severity":"warning","when":"extends === 'LedgerEntry'","expect":"inherited field is defined and lists base schema fields (entryDate, sourceEntityType, sourceEntityId, createdBy, createdDate)","reason":"Entities extending LedgerEntry should declare their inherited fields for audit completeness.","active":true},{"id":"ledger-entry-immutability","scope":"entity","severity":"error","when":"extends === 'LedgerEntry'","expect":"entity does not declare modifiedBy, modifiedDate, or isDeleted properties","reason":"Ledger entries are immutable and append-only. Corrections must be made via new compensating entries, not by modifying or deleting existing records.","active":true},{"id":"operational-subdocument-declares-inherited","scope":"entity","severity":"warning","when":"extends === 'OperationalSubDocument'","expect":"inherited field is defined and lists base schema fields","reason":"Entities extending OperationalSubDocument should declare their inherited fields.","active":true},{"id":"related-entities-exist-in-registry","scope":"entity","severity":"error","when":"relatedEntities is defined","expect":"every name in relatedEntities matches an entity name in the registry","reason":"Dangling relatedEntities references create broken links in the entity graph.","active":true},{"id":"entity-ref-target-exists","scope":"property","severity":"error","when":"type === 'entityRef' or type === 'entityDetail'","expect":"relatedEntity matches an entity name in the registry","reason":"Entity reference properties must point to a valid entity in the registry.","active":true},{"id":"dictionary-ref-uses-entity-detail","scope":"property","severity":"warning","when":"relatedEntity points to a Dictionary-class entity","expect":"type === 'entityDetail'","reason":"References to Dictionary/Lookup entities should use entityDetail to embed display fields (code, name, alias) and avoid unnecessary lookups.","active":true},{"id":"location-ref-uses-entity-detail","scope":"property","severity":"warning","when":"relatedEntity === 'Location'","expect":"type === 'entityDetail'","reason":"Location references should use entityDetail (confirmed from Zod schemas) since location name/code is frequently displayed.","active":true},{"id":"line-entities-are-inline-schemas","scope":"entity","severity":"error","when":"entity name contains 'Line' and represents a child row of a parent document (e.g. PO Line, Sales Order Line, Transfer Order Line)","expect":"modeled as an inline schema (type: 'schema' or type: 'array' with inline properties) on the parent entity — NOT as an independent entity in the registry","reason":"Line entities exist only within the context of their parent document. They do not have their own idmpKey, independent lifecycle, or standalone persistence. A PO Line cannot exist without a PO. Modeling them as independent entities creates orphaned references and misrepresents the domain. Use OperationalSubDocument-based inline schemas on the parent entity's line-item array property instead.","active":true},{"id":"inline-schema-has-properties","scope":"inlineSchema","severity":"error","when":"property has extends (inline line schema)","expect":"properties array is defined and non-empty","reason":"Inline line schemas must define their own properties; an empty extension of the base schema adds no domain value.","active":true},{"id":"inline-schema-registered","scope":"inlineSchema","severity":"warning","when":"property has extends on an array property","expect":"parent entity + property name appears in OperationalSubDocument.inlineSchemas","reason":"All inline line schemas should be registered in the OperationalSubDocument.inlineSchemas list for discoverability.","active":true},{"id":"property-name-camel-case","scope":"property","severity":"error","when":"any property","expect":"name is camelCase (first letter lowercase, no underscores/hyphens/spaces)","reason":"All property names must use camelCase for consistency with TypeScript/JavaScript conventions and Zod schema field names.","active":true},{"id":"entity-name-title-case","scope":"entity","severity":"error","when":"any entity","expect":"name is Title Case (each word capitalized, spaces between words, e.g. 'Purchase Order', 'Sales Channel')","reason":"Entity names are human-readable domain terms used in documentation, UI, and the entity graph. Title Case ensures consistency and readability. Code identifiers (variable names, file names) derive from the entity name programmatically.","active":true},{"id":"array-name-plural","scope":"property","severity":"warning","when":"property type is array","expect":"property name is plural (ends with 's', 'es', 'ies', etc.)","reason":"Array properties represent collections and should use plural names for clarity.","active":true},{"id":"entity-uuid-id","scope":"entity","severity":"error","when":"entity class is Core, Operational, Transactional, Dictionary, Order, Ledger, or Balance","expect":"entity has an 'id' property of type UUID (inherited from a base schema or declared directly)","reason":"Every entity must have a universally unique identifier (UUID) for cross-system referencing, replication, and foreign-key integrity. The id field is the primary key across all entity classes.","active":true},{"id":"dictionary-code-property","scope":"entity","severity":"error","when":"entity class === 'Dictionary'","expect":"entity has a 'code' property of type string that is required and unique — serving as the human-readable identifier","reason":"Dictionary entities are reference/lookup data (e.g. Season, Brand, Tax Class). A short, human-readable 'code' property provides a stable, memorable identifier for use in imports, API filters, and UI dropdowns. Using 'code' consistently across all Dictionary entities enables generic lookup patterns.","active":true},{"id":"entity-no-property","scope":"entity","severity":"error","when":"entity class is Core, Operational, Transactional, Order, or Balance","expect":"entity has a human-readable identifier property named '{entityName}No' in camelCase (e.g. productNo, itemNo, purchaseOrderNo, salesChannelNo). The property should be required and unique.","reason":"A human-readable number property provides a business-friendly identifier for use in documents, labels, search, and customer communication. The {entity}No naming convention ensures consistency across all non-Dictionary entity classes and makes the identifier predictable for API consumers and UI generation.","active":true},{"id":"calculated-count-naming","scope":"property","severity":"error","when":"calculated === true and field represents a count","expect":"name follows {relatedEntity}Count pattern in camelCase (e.g. vendorCount, salesChannelCount). Scoped counts add a qualifier prefix (e.g. publishedSalesChannelCount, activeItemCount). Type is always integer.","reason":"Consistent naming for count fields enables predictable API responses, automated dashboard generation, and clear developer expectations. The {entity}Count pattern mirrors industry conventions (SQL COUNT, GraphQL connection counts).","active":true},{"id":"calculated-field-documented","scope":"entity","severity":"warning","when":"entity has array properties referencing other entities or significant cross-entity relationships","expect":"businessValidation.calculations includes derived count fields with formula and trigger","reason":"Calculated fields should be documented in the entity registry so the query layer, API consumers, and documentation all agree on what derived fields are available. The calculations array serves as the contract between schema documentation and runtime implementation.","active":true},{"id":"deprecated-property-not-required","scope":"property","severity":"error","when":"property is marked deprecated","expect":"required is false or undefined","reason":"Deprecated properties must not be required — consumers need to stop depending on them before removal. Making a deprecated property optional is the first step in the deprecation lifecycle.","active":true},{"id":"deprecated-needs-replacement","scope":"property","severity":"error","when":"property is marked deprecated","expect":"deprecation annotation includes reason and either replacedBy (property or entity name) or an explicit migration note","reason":"Deprecated properties must tell consumers what to use instead. A bare deprecation flag without guidance leaves consumers stranded. See Schema Evolution in the glossary for the full lifecycle.","active":true},{"id":"deprecated-needs-sunset-date","scope":"property","severity":"warning","when":"property is marked deprecated","expect":"deprecation annotation includes a sunset date (YYYY-MM format)","reason":"A sunset date sets expectations for consumers and enables automated compliance checks. Internal schemas: minimum 2 release cycles. Webhook payloads: 3 months. Public API: 6 months.","active":true},{"id":"overridable-default-prefix","scope":"property","severity":"error","when":"entity declares inheritance.childEntity and property is listed in inheritance.overridableDefaults","expect":"property name starts with 'default' (e.g. defaultBasePrice, defaultPrices)","reason":"The default* prefix signals that this value serves as a fallback for child entities. Children override by defining the same property without the prefix. Resolution: effectiveValue = child.{name} ?? parent.default{Name}.","active":true},{"id":"child-override-matches-parent","scope":"property","severity":"error","when":"entity declares inheritance.parentEntity and property is listed in inheritance.overridableProperties","expect":"property name matches the parent's default* property with the 'default' prefix removed and first letter lowercased (e.g. Product.defaultBasePrice → Item.basePrice)","reason":"Consistent naming between parent defaults and child overrides enables automatic effective-value resolution and makes the inheritance chain self-documenting.","active":true},{"id":"company-scoping-required","scope":"entity","severity":"error","when":"entity class is Operational, Transactional, Dictionary, Ledger, Taxonomy, or Balance AND entity name is NOT in the control-plane exemption set (Organization, Client, Company, Environment, Scope, Application)","expect":"entity has a 'company' property of type entityDetail pointing to Company, OR inherits one from a base schema (BaseDocument, OperationalDocument, TaxonomyEntity, TaxonomyEntityNode, LedgerEntry). For tenant-plane Dictionary/LookupEntity entities, the company property is required; for platform-global dictionaries (Currency, Locale, Country, Environment), it is absent — see tenant-scoped-dictionary-declares-company.","reason":"Every tenant-plane entity must carry a Company reference to define the hard isolation boundary for queries, permissions, replication, and export. Control-plane entities (Organization, Client, Company itself, Environment, Scope, Application) are exempt because they describe the deployed infrastructure, not tenant data.","active":true},{"id":"tenant-scoped-dictionary-declares-company","scope":"entity","severity":"error","when":"entity extends LookupEntity or LookupEntityValue AND entity name is NOT in the platform-global dictionary exemption set (Currency, Locale, Country, Environment)","expect":"entity declares a required 'company' property of type entityDetail → Company. LookupEntity's base-schema company property is optional by design so platform-global dictionaries can omit it; tenant-scoped dictionaries must override by declaring it required.","reason":"LookupEntity is shared between tenant-scoped dictionaries (Tax Class, Brand, Season) and platform-global dictionaries (Currency, Locale, Country, Environment). Because the base schema leaves company optional, each tenant-scoped dictionary must explicitly declare company required. Platform-global dictionaries are exempt and must NOT declare company.","active":true},{"id":"subdoc-tenant-inheritance","scope":"entity","severity":"warning","when":"entity extends OperationalSubDocument or TransactionalSubDocument","expect":"entity does NOT declare its own 'company' property. Sub-documents inherit their tenant boundary from the parent document and must not duplicate the reference — this prevents drift between parent and sub-doc company values.","reason":"Sub-documents (line items, status records, nested schedules) exist only within the context of a parent Operational or Transactional Document, which already carries the Company reference. Declaring company on the sub-document creates redundant state that can drift from the parent and bloats denormalized snapshots.","active":true},{"id":"configurable-entity-declares-configuration","scope":"entity","severity":"error","when":"entity is in the configurable entity set (Location, Organization, Company, Connector, Sales Channel, Document Type) — i.e. entities whose runtime behavior is shaped by a Config record in the CONFIG subsystem, including Document Type which acts as the policy anchor for all documents of its category (PO, ASN, Bill, etc.)","expect":"entity declares a required property named `configuration` of type `valueType` whose info or valueTypeName references `ConfigurationRef`","reason":"Configurable entities must carry an explicit, uniformly-typed link to their Config record so runtime services can resolve configuration generically. Using a named value type (ConfigurationRef) instead of ad-hoc configId strings keeps the contract in one place, supports entityDetail denormalization of the Config identity, and leaves room to evolve the reference shape (adding scope, version, override chain) without migrating every entity. The single-inheritance model of the registry means this trait cannot be expressed as a mixin base schema; this convention is the enforcement mechanism. Document Type specifically is included as a *policy anchor* — concrete document entities (Purchase Order, Bill, ASN, Vendor Invoice) inherit rules like expiration, approval thresholds, and numbering from their Document Type's configuration rather than declaring their own, which keeps lifecycle behavior uniform across all documents of a category.","active":true},{"id":"product-read-through-must-aggregate-item","scope":"property","severity":"warning","when":"Property is on Product, is calculated:true, type=datetime, name ends in 'At', AND a property of the same name exists on Item with calculated:true and type=datetime","expect":"Product-level value must be the aggregation (MIN for properties starting with 'first', MAX for properties starting with 'last') of the corresponding Item-level values across every Item belonging to the Product. Stock Ledger Service (or other SoR providers) must implement Product-level projections by rolling up Item-level results, not by issuing a separate query path.","reason":"Product is the parent of Item (SKU/variant). Defining the aggregation contract in one place keeps SoR projection logic consistent and prevents drift between Product and Item answers to the same question (e.g., Product.lastSoldAt should never be older than the most recent Item.lastSoldAt for that product). Without this rule, two implementations could diverge silently.","active":true},{"id":"item-read-through-must-aggregate-item-stock","scope":"property","severity":"warning","when":"Property is on Item, is calculated:true, type=datetime, name ends in 'At', AND a property of the same name exists on Item Stock with calculated:true and type=datetime","expect":"Item-level value must be the aggregation (MIN for properties starting with 'first', MAX for properties starting with 'last') of the corresponding Item Stock-level values across every Item Stock row (Item × Location) belonging to the Item. SoR providers must implement Item-level projections by rolling up Item Stock-level results, not by issuing a separate query path against the Stock Ledger or PO subsystem.","reason":"Item Stock is the per-Location leaf for these date projections, Item is the per-SKU rollup across Locations, and Product is the per-parent rollup across SKUs (governed by product-read-through-must-aggregate-item). This convention completes the three-level chain Item Stock → Item → Product so projection logic stays consistent end-to-end. Without it, an SoR could compute Item.lastReceivedAt directly from the Stock Ledger by Item without going through Item Stock, then drift from the per-Location values surfaced on Item Stock rows. Sibling rule to product-read-through-must-aggregate-item.","active":true}]}