--- id: GridflockMappingReady name: GridFlock Mapping Ready version: 0.0.1 schemaPath: schema.ts summary: | Published when the GridFlock pipeline completes (STL → slice → upload → mapping) or an existing mapping is found. The Order Service can now create print jobs for this SKU. badges: - content: BullMQ backgroundColor: blue textColor: blue - content: GridFlock backgroundColor: purple textColor: purple --- Published when the GridFlock pipeline successfully completes the full generation cycle (STL → slice → upload → product mapping creation), or when an existing mapping is found for the requested SKU. The Order Service orchestration proceeds to create print jobs. ## Flow `GridflockPipelineService.processOrderLineItem()` → EventEmitter `grid.mapping-ready` → `EventPublisherService` → BullMQ ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `grid.mapping-ready` | | **Channel** | BullMQ Event Bus | | **Publisher** | Grid Service | | **Subscriber(s)** | Order Service | ## Example Payload ```json { "eventId": "d0e1f2a3-b4c5-6789-defa-012345678901", "eventType": "grid.mapping-ready", "source": "grid-service", "tenantId": "tenant-abc", "timestamp": "2026-02-18T11:00:00.000Z", "orderId": "order-456", "lineItemId": "li-789", "sku": "GRID-2x3-MAG-NONE" } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface GridflockMappingReadyEvent extends ServiceEvent { eventType: 'grid.mapping-ready'; orderId: string; lineItemId: string; sku: string; } --- id: GridflockPipelineFailed name: GridFlock Pipeline Failed version: 0.0.1 schemaPath: schema.ts summary: | Published when the GridFlock pipeline fails at any step (STL generation, slicing, SimplyPrint upload, or mapping creation). Includes the failed step and error message. badges: - content: BullMQ backgroundColor: blue textColor: blue - content: GridFlock backgroundColor: purple textColor: purple - content: Error backgroundColor: red textColor: red --- Published when the GridFlock pipeline fails at any step. The Order Service marks the line item as FAILED and may trigger notifications. ## Flow `GridflockPipelineService.processOrderLineItem()` → EventEmitter `grid.pipeline-failed` → `EventPublisherService` → BullMQ ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `grid.pipeline-failed` | | **Channel** | BullMQ Event Bus | | **Publisher** | Grid Service | | **Subscriber(s)** | Order Service | ## Pipeline Steps The `failedStep` field indicates which step failed: | Step | Description | | --- | --- | | `stl-generation` | OpenSCAD parametric STL generation failed | | `slicing` | BambuStudio CLI slicing failed | | `simplyprint-upload` | Uploading gcode to SimplyPrint failed | | `mapping-creation` | Creating the product mapping in the database failed | ## Example Payload ```json { "eventId": "e1f2a3b4-c5d6-7890-efab-123456789012", "eventType": "grid.pipeline-failed", "source": "grid-service", "tenantId": "tenant-abc", "timestamp": "2026-02-18T11:05:00.000Z", "orderId": "order-456", "lineItemId": "li-789", "sku": "GRID-2x3-MAG-NONE", "errorMessage": "Slicer container returned HTTP 500: slicing timeout exceeded", "failedStep": "slicing" } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface GridflockPipelineFailedEvent extends ServiceEvent { eventType: 'grid.pipeline-failed'; orderId: string; lineItemId: string; sku: string; errorMessage: string; failedStep: | 'stl-generation' | 'slicing' | 'simplyprint-upload' | 'mapping-creation'; } --- id: IntegrationSendcloudChanged name: Integration Sendcloud Changed version: 0.0.1 schemaPath: schema.ts summary: | Published when the Sendcloud integration is connected or disconnected via the Settings UI. Consumer services reload their API client credentials from the database. badges: - content: BullMQ backgroundColor: blue textColor: blue - content: Infrastructure backgroundColor: gray textColor: gray --- Published by the Shipping Service when a user connects or disconnects the Sendcloud integration via Settings > Integrations. The Order Service subscribes and re-initializes (or disables) its own `SendcloudApiClient` so that all services stay in sync without requiring a container restart. ## Flow `SendcloudConnectionService.saveConnection()` / `.disconnect()` → `BullMQEventBus.publish()` → BullMQ `integration.sendcloud-changed` ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `integration.sendcloud-changed` | | **Channel** | BullMQ Event Bus | | **Publisher** | Shipping Service | | **Subscriber(s)** | Order Service | ## Behavior | Action | Publisher Side | Subscriber Side | | --- | --- | --- | | `connected` | API client re-initialized with new credentials | Loads credentials from DB, calls `initializeWithCredentials()` | | `disconnected` | API client disabled | Calls `disable()` on local API client | ## Example Payload ```json { "eventId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "eventType": "integration.sendcloud-changed", "source": "shipping-service", "tenantId": "00000000-0000-0000-0000-000000000001", "timestamp": "2026-02-20T17:25:00.000Z", "action": "disconnected" } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface IntegrationChangedEvent extends ServiceEvent { eventType: 'integration.simplyprint-changed' | 'integration.sendcloud-changed'; action: 'connected' | 'disconnected'; } --- id: IntegrationSimplyPrintChanged name: Integration SimplyPrint Changed version: 0.0.1 schemaPath: schema.ts summary: | Published when the SimplyPrint integration is connected or disconnected via the Settings UI. Consumer services reload their API client credentials from the database. badges: - content: BullMQ backgroundColor: blue textColor: blue - content: Infrastructure backgroundColor: gray textColor: gray --- Published by the Print Service when a user connects or disconnects the SimplyPrint integration via Settings > Integrations. The Order Service subscribes and re-initializes (or disables) its own `SimplyPrintApiClient` so that all services stay in sync without requiring a container restart. ## Flow `SimplyPrintConnectionService.saveConnection()` / `.disconnect()` → `BullMQEventBus.publish()` → BullMQ `integration.simplyprint-changed` ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `integration.simplyprint-changed` | | **Channel** | BullMQ Event Bus | | **Publisher** | Print Service | | **Subscriber(s)** | Order Service | ## Behavior | Action | Publisher Side | Subscriber Side | | --- | --- | --- | | `connected` | API client re-initialized with new credentials | Loads credentials from DB, calls `initializeWithCredentials()` | | `disconnected` | API client disabled | Calls `disable()` on local API client | ## Example Payload ```json { "eventId": "f1a2b3c4-d5e6-7890-abcd-ef1234567890", "eventType": "integration.simplyprint-changed", "source": "print-service", "tenantId": "00000000-0000-0000-0000-000000000001", "timestamp": "2026-02-20T17:25:00.000Z", "action": "connected" } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface IntegrationChangedEvent extends ServiceEvent { eventType: 'integration.simplyprint-changed' | 'integration.sendcloud-changed'; action: 'connected' | 'disconnected'; } --- id: OrderCancelled name: Order Cancelled version: 0.0.1 schemaPath: schema.ts summary: | Published when an order is cancelled. Triggers cancellation of print jobs in Print Service and shipments in Shipping Service. badges: - content: BullMQ backgroundColor: blue textColor: blue --- Published when an order is cancelled. Both Print Service and Shipping Service subscribe to initiate their respective cancellation flows. ## Flow `OrdersService.cancelOrder()` → EventEmitter `order.cancelled` → `EventPublisherService` → BullMQ ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `order.cancelled` | | **Channel** | BullMQ Event Bus | | **Publisher** | Order Service | | **Subscriber(s)** | Print Service, Shipping Service | ## Example Payload ```json { "eventId": "c3d4e5f6-a7b8-9012-cdef-345678901234", "eventType": "order.cancelled", "source": "order-service", "tenantId": "tenant-abc", "timestamp": "2026-02-18T15:00:00.000Z", "orderId": "order-456" } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface OrderCancelledEvent extends ServiceEvent { eventType: 'order.cancelled'; orderId: string; } --- id: OrderCreated name: Order Created version: 0.0.1 schemaPath: schema.ts summary: | Published when a new order is ingested from Shopify (including via backfill). Triggers print job creation in the Print Service. badges: - content: BullMQ backgroundColor: blue textColor: blue --- Published when a new order is ingested from Shopify, either via webhook or backfill. ## Flow `OrdersService.createFromShopify()` → EventEmitter `order.created` → `EventPublisherService` → BullMQ queue `order.created` ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `order.created` | | **Channel** | BullMQ Event Bus | | **Publisher** | Order Service | | **Subscriber(s)** | Print Service | ## Example Payload ```json { "eventId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "eventType": "order.created", "source": "order-service", "tenantId": "tenant-abc", "timestamp": "2026-02-18T10:30:00.000Z", "correlationId": "corr-123", "orderId": "order-456", "lineItems": [ { "lineItemId": "li-789", "productSku": "GRID-2x3-MAG-NONE", "quantity": 2 } ] } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface OrderCreatedEvent extends ServiceEvent { eventType: 'order.created'; orderId: string; lineItems: Array<{ lineItemId: string; productSku: string; quantity: number; }>; } --- id: OrderReadyForFulfillment name: Order Ready for Fulfillment version: 0.0.1 schemaPath: schema.ts summary: | Published when all print jobs for an order are complete. Triggers shipment creation in the Shipping Service. badges: - content: BullMQ backgroundColor: blue textColor: blue --- Published when the orchestration service determines all print jobs for an order have completed successfully. This triggers the Shipping Service to create a shipment. ## Flow `OrchestrationService.markOrderReadyForFulfillment()` → EventEmitter `order.ready-for-fulfillment` → `EventPublisherService` → BullMQ ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `order.ready-for-fulfillment` | | **Channel** | BullMQ Event Bus | | **Publisher** | Order Service | | **Subscriber(s)** | Shipping Service | ## Example Payload ```json { "eventId": "b2c3d4e5-f6a7-8901-bcde-f23456789012", "eventType": "order.ready-for-fulfillment", "source": "order-service", "tenantId": "tenant-abc", "timestamp": "2026-02-18T14:00:00.000Z", "orderId": "order-456" } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface OrderReadyForFulfillmentEvent extends ServiceEvent { eventType: 'order.ready-for-fulfillment'; orderId: string; } --- id: PrintJobCancelled name: Print Job Cancelled version: 0.0.1 schemaPath: schema.ts summary: | Published when a print job is cancelled (typically as part of an order cancellation flow). The Order Service updates the line item status. badges: - content: BullMQ backgroundColor: blue textColor: blue --- Published when a print job is cancelled, typically triggered by an order cancellation cascade. The Order Service orchestration updates the line item status accordingly. ## Flow `PrintJobsService.cancelJob()` → EventEmitter `printjob.cancelled` → `EventPublisherService` → BullMQ `print-job.cancelled` ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `print-job.cancelled` | | **Channel** | BullMQ Event Bus | | **Publisher** | Print Service | | **Subscriber(s)** | Order Service | ## Example Payload ```json { "eventId": "a7b8c9d0-e1f2-3456-abcd-789012345678", "eventType": "print-job.cancelled", "source": "print-service", "tenantId": "tenant-abc", "timestamp": "2026-02-18T17:00:00.000Z", "printJobId": "pj-003", "orderId": "order-456", "lineItemId": "li-791" } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface PrintJobCancelledEvent extends ServiceEvent { eventType: 'print-job.cancelled'; printJobId: string; orderId: string; lineItemId: string | null; } --- id: PrintJobCompleted name: Print Job Completed version: 0.0.1 schemaPath: schema.ts summary: | Published when a print job finishes successfully. The Order Service uses this to track completion progress and determine when all jobs are done. badges: - content: BullMQ backgroundColor: blue textColor: blue --- Published when a print job completes successfully on SimplyPrint. The Order Service `EventSubscriberService` routes this event based on the print job's `purpose` field: - **`ORDER` purpose** — Standard flow: updates line item progress, checks if all jobs complete → triggers `OrderReadyForFulfillment` - **`STOCK` purpose** — Inventory flow: routes to `InventoryService.handleStockJobCompleted()` which increments the `StockBatch` progress counter. When the batch is fully complete, `currentStock` is atomically incremented. ## Flow `PrintJobsService.updateJobStatus()` → EventEmitter `printjob.completed` → `EventPublisherService` → BullMQ `print-job.completed` ### Subscriber Routing (Order Service) ``` EventSubscriberService receives print-job.completed ├─ purpose === 'STOCK' → InventoryService.handleStockJobCompleted() └─ purpose === 'ORDER' → EventEmitter emit('printjob.completed') → OrchestrationService ``` ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `print-job.completed` | | **Channel** | BullMQ Event Bus | | **Publisher** | Print Service | | **Subscriber(s)** | Order Service (with purpose-based routing) | ## Example Payload (ORDER purpose) ```json { "eventId": "d4e5f6a7-b8c9-0123-defa-456789012345", "eventType": "print-job.completed", "source": "print-service", "tenantId": "tenant-abc", "timestamp": "2026-02-18T16:30:00.000Z", "printJobId": "pj-001", "orderId": "order-456", "lineItemId": "li-789" } ``` ## Example Payload (STOCK purpose) ```json { "eventId": "e5f6a7b8-c9d0-1234-efab-567890123456", "eventType": "print-job.completed", "source": "print-service", "tenantId": "tenant-abc", "timestamp": "2026-03-08T03:12:00.000Z", "printJobId": "pj-stock-001", "orderId": "", "lineItemId": null } ``` ## Related Events - **ORDER purpose**: When all print jobs for an order complete → `OrderReadyForFulfillment` is published - **ORDER purpose**: If some jobs fail → `PrintJobFailed` is published instead - **STOCK purpose**: When all jobs in a `StockBatch` complete → `StockBatchCompleted` is published and stock is incremented ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface PrintJobCompletedEvent extends ServiceEvent { eventType: 'print-job.completed'; printJobId: string; orderId: string; lineItemId: string | null; } --- id: PrintJobFailed name: Print Job Failed version: 0.0.1 schemaPath: schema.ts summary: | Published when a print job fails on SimplyPrint. Includes the error message. The Order Service orchestration updates the line item status accordingly. badges: - content: BullMQ backgroundColor: blue textColor: blue --- Published when a print job fails on SimplyPrint. The Order Service updates the line item status and may retry or mark the order as failed depending on the failure pattern. If the failed job is later requeued (triggering a `print-job.status-changed` event with FAILED→QUEUED), the Order Service automatically reverts the order from FAILED/PARTIALLY_COMPLETED back to PROCESSING via the order revival mechanism. ## Flow `PrintJobsService.updateJobStatus()` → EventEmitter `printjob.failed` → `EventPublisherService` → BullMQ `print-job.failed` ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `print-job.failed` | | **Channel** | BullMQ Event Bus | | **Publisher** | Print Service | | **Subscriber(s)** | Order Service | ## Example Payload ```json { "eventId": "e5f6a7b8-c9d0-1234-efab-567890123456", "eventType": "print-job.failed", "source": "print-service", "tenantId": "tenant-abc", "timestamp": "2026-02-18T16:45:00.000Z", "printJobId": "pj-002", "orderId": "order-456", "lineItemId": "li-790", "errorMessage": "Printer head temperature exceeded safe limit" } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface PrintJobFailedEvent extends ServiceEvent { eventType: 'print-job.failed'; printJobId: string; orderId: string; lineItemId: string | null; errorMessage: string; } --- id: PrintJobStatusChanged name: Print Job Status Changed version: 0.0.1 schemaPath: schema.ts summary: | Published when a print job transitions between statuses (e.g., QUEUED → ASSIGNED → PRINTING). Used for real-time UI updates via Socket.IO. badges: - content: BullMQ backgroundColor: blue textColor: blue --- Published whenever a print job status changes on SimplyPrint. This event is used for real-time UI updates via Socket.IO, tracking status history, and **order revival** — when a terminal→active transition is detected (e.g., CANCELLED→QUEUED or FAILED→QUEUED), the OrchestrationService automatically reverts the order from FAILED/PARTIALLY_COMPLETED/CANCELLED back to PROCESSING. ## Flow `PrintJobsService.updateJobStatus()` → EventEmitter `printjob.status-changed` → `EventPublisherService` → BullMQ `print-job.status-changed` ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `print-job.status-changed` | | **Channel** | BullMQ Event Bus | | **Publisher** | Print Service | | **Subscriber(s)** | Order Service | ## Example Payload ```json { "eventId": "f6a7b8c9-d0e1-2345-fabc-678901234567", "eventType": "print-job.status-changed", "source": "print-service", "tenantId": "tenant-abc", "timestamp": "2026-02-18T16:00:00.000Z", "printJobId": "pj-001", "orderId": "order-456", "lineItemId": "li-789", "previousStatus": "QUEUED", "newStatus": "PRINTING" } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface PrintJobStatusChangedEvent extends ServiceEvent { eventType: 'print-job.status-changed'; printJobId: string; orderId: string; lineItemId: string | null; previousStatus: string; newStatus: string; } --- id: ShipmentCreated name: Shipment Created version: 0.0.1 schemaPath: schema.ts summary: | Published when a shipment is created via Sendcloud. The Order Service uses this to create a Shopify fulfillment with tracking information. badges: - content: BullMQ backgroundColor: blue textColor: blue --- Published when the Shipping Service successfully creates a shipment via the Sendcloud API. The Order Service's FulfillmentService uses this event to create a Shopify fulfillment with the tracking information. ## Flow `SendcloudService.createShipment()` → EventEmitter `shipment.created` → `EventPublisherService` → BullMQ `shipment.created` ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `shipment.created` | | **Channel** | BullMQ Event Bus | | **Publisher** | Shipping Service | | **Subscriber(s)** | Order Service | ## Example Payload ```json { "eventId": "b8c9d0e1-f2a3-4567-bcde-890123456789", "eventType": "shipment.created", "source": "shipping-service", "tenantId": "tenant-abc", "timestamp": "2026-02-18T18:00:00.000Z", "shipmentId": "ship-001", "orderId": "order-456", "trackingNumber": "3SPOST1234567890", "trackingUrl": "https://tracking.sendcloud.sc/forward?carrier=postnl&code=3SPOST1234567890", "carrier": "PostNL" } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface ShipmentCreatedEvent extends ServiceEvent { eventType: 'shipment.created'; shipmentId: string; orderId: string; trackingNumber: string | null; trackingUrl: string | null; carrier: string | null; } --- id: ShipmentStatusChanged name: Shipment Status Changed version: 0.0.1 schemaPath: schema.ts summary: | Published when a shipment status changes (e.g., label created → in transit → delivered). Originates from Sendcloud webhooks or reconciliation polling. badges: - content: BullMQ backgroundColor: blue textColor: blue --- Published when a shipment's status changes, typically triggered by Sendcloud webhooks or the periodic reconciliation polling service. ## Flow `SendcloudWebhookService.processStatusChange()` or `SendcloudReconciliationService` → EventEmitter `sendcloud.shipment.status_changed` → `EventPublisherService` → BullMQ `shipment.status-changed` ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `shipment.status-changed` | | **Channel** | BullMQ Event Bus | | **Publisher** | Shipping Service | | **Subscriber(s)** | Order Service | ## Example Payload ```json { "eventId": "c9d0e1f2-a3b4-5678-cdef-901234567890", "eventType": "shipment.status-changed", "source": "shipping-service", "tenantId": "tenant-abc", "timestamp": "2026-02-18T20:00:00.000Z", "shipmentId": "ship-001", "orderId": "order-456", "previousStatus": "LABEL_CREATED", "newStatus": "IN_TRANSIT" } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface ShipmentStatusChangedEvent extends ServiceEvent { eventType: 'shipment.status-changed'; shipmentId: string; orderId: string; previousStatus: string; newStatus: string; } --- id: StockBatchCompleted name: Stock Batch Completed version: 0.0.1 schemaPath: schema.ts summary: | Published when all print jobs in a stock batch have completed. Triggers the atomic stock increment and transaction recording. badges: - content: Not Yet Published backgroundColor: red textColor: red - content: Inventory backgroundColor: green textColor: green --- > **Status:** The `SERVICE_EVENTS.STOCK_BATCH_COMPLETED` constant and `StockBatchCompletedEvent` interface are defined in `event-types.ts`, but **no `eventBus.publish()` call** exists for this event. The stock batch completion logic in `InventoryService.handleStockJobCompleted()` updates the database directly without publishing a BullMQ event. This event is reserved for future use (e.g., dashboard notifications). When the last print job in a `StockBatch` completes, `InventoryService` updates the database directly: ## Current Implementation (no BullMQ event) `EventSubscriberService` receives `print-job.completed` with `purpose: STOCK` → `InventoryService.handleStockJobCompleted()` → checks `completedJobs >= totalJobs` → `InventoryRepository.recordBatchCompletion()` → atomically increments `ProductMapping.currentStock` and records a `PRODUCED` transaction ## Queue Configuration (reserved, not active) | Property | Value | | --- | --- | | **Queue Name** | `inventory.stock-batch-completed` | | **Channel** | BullMQ Event Bus | | **Publisher** | None (not yet published) | | **Subscriber(s)** | None (future: dashboard notifications) | ## Side Effects When a stock batch completes: 1. `StockBatch.status` → `COMPLETED`, `completedAt` is set 2. `ProductMapping.currentStock` is atomically incremented by 1 3. An `InventoryTransaction` of type `PRODUCED` with direction `IN` is recorded 4. All operations happen inside a single Prisma `$transaction` for consistency ## Example Payload ```json { "eventId": "b2c3d4e5-f6a7-8901-bcde-f23456789012", "eventType": "inventory.stock-batch-completed", "source": "order-service", "tenantId": "tenant-abc", "timestamp": "2026-03-08T03:15:00.000Z", "stockBatchId": "batch-456", "productMappingId": "pm-789", "totalJobs": 3 } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface StockBatchCompletedEvent extends ServiceEvent { eventType: 'inventory.stock-batch-completed'; stockBatchId: string; productMappingId: string; totalJobs: number; } --- id: StockReplenishmentScheduled name: Stock Replenishment Scheduled version: 0.0.2 schemaPath: schema.ts summary: | Published when the stock replenishment scheduler creates a print job for stock building. One event per print job in the batch. Consumed by the EventSubscriberService to queue the job in SimplyPrint. badges: - content: BullMQ backgroundColor: blue textColor: blue - content: Inventory backgroundColor: green textColor: green --- Published by the `StockReplenishmentService` when it creates print jobs for stock building. Each print job in a `StockBatch` triggers a separate event. The `EventSubscriberService` (order-service) subscribes to this event and queues each job in the SimplyPrint print queue via `SimplyPrintApiClient.addToQueue()`. ## Flow `StockReplenishmentScheduler` (Cron) → `StockReplenishmentService.evaluateAndSchedule()` → `InventoryRepository.createStockBatch()` → BullMQ queue `inventory.stock-replenishment-scheduled` (per job) → `EventSubscriberService.queueStockJobToSimplyPrint()` → `SimplyPrintApiClient.addToQueue()` → SimplyPrint API ## Queue Configuration | Property | Value | | --- | --- | | **Queue Name** | `inventory.stock-replenishment-scheduled` | | **Channel** | BullMQ Event Bus | | **Publisher** | Order Service (`StockReplenishmentService`) | | **Subscriber(s)** | Order Service (`EventSubscriberService`) | ## Subscriber Behavior When the `EventSubscriberService` receives this event, it: 1. Validates the `fileId` is present (skips if null) 2. Checks that `SimplyPrintApiClient` is enabled 3. Looks up the `PrintJob` by ID in the database 4. Skips if the job already has a `simplyPrintJobId` (idempotency) 5. Calls `SimplyPrintApiClient.addToQueue({ fileId, amount: 1 })` 6. Releases any stale `simplyPrintJobId` from old jobs (ID reuse handling) 7. Updates the `PrintJob` with `simplyPrintJobId` and `simplyPrintQueueItemId` STOCK jobs are added to the SimplyPrint queue **without** priority reordering — they queue behind ORDER jobs naturally. See ADR-063 for how ORDER jobs are repositioned ahead of STOCK jobs. ## Trigger Conditions The scheduler only creates stock batches when ALL conditions are met: 1. `STOCK_REPLENISHMENT_ENABLED` is `true` 2. Current hour is within `allowedHours` window 3. Current day is within `allowedDays` 4. Active order queue is below `orderQueueThreshold` 5. Active stock jobs are below `maxConcurrentStockJobs` 6. Product has `minimumStock > 0` and `currentStock < minimumStock` ## Example Payload ```json { "eventId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "eventType": "inventory.stock-replenishment-scheduled", "source": "order-service", "tenantId": "tenant-abc", "timestamp": "2026-03-08T02:00:00.000Z", "printJobId": "pj-stock-001", "fileId": "sp-file-123", "purpose": "STOCK", "stockBatchId": "batch-456" } ``` ## Schema ## Raw Schema:schema.ts interface ServiceEvent { eventId: string; eventType: string; source: string; tenantId: string; timestamp: string; correlationId?: string; } export interface StockReplenishmentScheduledEvent extends ServiceEvent { eventType: 'inventory.stock-replenishment-scheduled'; printJobId: string; fileId: string | null; purpose: 'STOCK'; stockBatchId: string; } --- id: CancelJobsForOrder name: Cancel Jobs for Order version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP command from Order Service to Print Service to cancel all print jobs for a given order. badges: - content: POST backgroundColor: green textColor: green - content: Internal HTTP backgroundColor: orange textColor: orange --- Cancels all active print jobs for a given order in the Print Service. Called by the Order Service during order cancellation. ## Endpoint | Property | Value | | --- | --- | | **Method** | `POST` | | **Route** | `/internal/print-jobs/order/:orderId/cancel` | | **Channel** | Internal HTTP | | **Caller** | Order Service | | **Handler** | Print Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface CancelJobsForOrderRequest { orderId: string; tenantId: string; } export interface CancelJobsForOrderResponse { cancelledCount: number; skippedCount: number; } --- id: CancelShipment name: Cancel Shipment version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP command from Order Service to Shipping Service to cancel a shipment for a cancelled order. badges: - content: POST backgroundColor: green textColor: green - content: Internal HTTP backgroundColor: orange textColor: orange --- Cancels an active shipment in the Shipping Service. Called by the Order Service during order cancellation. ## Endpoint | Property | Value | | --- | --- | | **Method** | `POST` | | **Route** | `/internal/shipments/order/:orderId/cancel` | | **Channel** | Internal HTTP | | **Caller** | Order Service | | **Handler** | Shipping Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface CancelShipmentRequest { orderId: string; tenantId: string; } export interface CancelShipmentResponse { shipmentId: string; cancelled: boolean; labelVoided: boolean; } --- id: CreatePrintJobs name: Create Print Jobs version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP command from Order Service to Print Service to create print jobs for an order's line items. badges: - content: POST backgroundColor: green textColor: green - content: Internal HTTP backgroundColor: orange textColor: orange --- Creates print jobs in the Print Service for all line items of an order. Called by the Order Service orchestration after order creation. ## Endpoint | Property | Value | | --- | --- | | **Method** | `POST` | | **Route** | `/internal/print-jobs` | | **Channel** | Internal HTTP | | **Caller** | Order Service | | **Handler** | Print Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Request Body ```json { "orderId": "order-456", "tenantId": "tenant-abc", "lineItems": [ { "lineItemId": "li-789", "productSku": "GRID-2x3-MAG-NONE", "quantity": 2, "printFileUrl": "https://simplyprint.io/files/abc123.gcode" } ] } ``` ## Response ```json { "printJobs": [ { "id": "pj-001", "lineItemId": "li-789", "status": "QUEUED" } ] } ``` ## Raw Schema:schema.ts export interface CreatePrintJobsRequest { orderId: string; tenantId: string; lineItems: Array<{ lineItemId: string; productSku: string; quantity: number; printFileUrl: string; }>; } export interface CreatePrintJobsResponse { printJobs: Array<{ id: string; lineItemId: string; status: 'QUEUED'; }>; } --- id: CreateShipment name: Create Shipment version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP command from Order Service to Shipping Service to create a shipment for a fulfilled order. badges: - content: POST backgroundColor: green textColor: green - content: Internal HTTP backgroundColor: orange textColor: orange --- Creates a shipment in the Shipping Service via Sendcloud. Called by the Order Service when an order reaches the `READY_FOR_FULFILLMENT` status. ## Endpoint | Property | Value | | --- | --- | | **Method** | `POST` | | **Route** | `/internal/shipments` | | **Channel** | Internal HTTP | | **Caller** | Order Service | | **Handler** | Shipping Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface CreateShipmentRequest { orderId: string; tenantId: string; } export interface CreateShipmentResponse { shipmentId: string; sendcloudParcelId: number; trackingNumber: string | null; trackingUrl: string | null; carrier: string | null; labelUrl: string | null; } --- id: GenerateForOrder name: Generate for Order version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP command from Order Service to Grid Service to trigger parametric STL generation for GridFlock-eligible line items. badges: - content: POST backgroundColor: green textColor: green - content: Internal HTTP backgroundColor: orange textColor: orange - content: GridFlock backgroundColor: purple textColor: purple --- Triggers the GridFlock pipeline for order line items with GridFlock-eligible SKUs. The Grid Service processes these asynchronously and publishes `GridflockMappingReady` or `GridflockPipelineFailed` events when complete. ## Endpoint | Property | Value | | --- | --- | | **Method** | `POST` | | **Route** | `/internal/grid/generate-for-order` | | **Channel** | Internal HTTP | | **Caller** | Order Service | | **Handler** | Grid Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface GenerateForOrderRequest { orderId: string; tenantId: string; lineItems: Array<{ lineItemId: string; sku: string; quantity: number; }>; } export interface GenerateForOrderResponse { accepted: boolean; pipelineId: string; } --- id: SliceSTL name: Slice STL version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP command from Grid Service to Slicer Container to convert an STL file to gcode using BambuStudio CLI. badges: - content: POST backgroundColor: green textColor: green - content: Internal HTTP backgroundColor: orange textColor: orange - content: GridFlock backgroundColor: purple textColor: purple --- Sends an STL buffer to the Slicer Container for conversion to gcode using the BambuStudio CLI headless slicing engine. This is a CPU-intensive operation that may take 10-60 seconds. ## Endpoint | Property | Value | | --- | --- | | **Method** | `POST` | | **Route** | `/slice` | | **Channel** | Internal HTTP | | **Caller** | Grid Service | | **Handler** | Slicer Container | | **Auth** | None (internal network only) | ## Raw Schema:schema.ts export interface SliceSTLRequest { stlBuffer: Buffer; printerModel: string; filamentType: string; layerHeight?: number; infillPercentage?: number; } export interface SliceSTLResponse { gcodeBuffer: Buffer; estimatedPrintTimeSeconds: number; estimatedFilamentGrams: number; } --- id: UpdateTracking name: Update Tracking version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP command from Shipping Service to Order Service to update tracking information for an order. badges: - content: PATCH backgroundColor: yellow textColor: yellow - content: Internal HTTP backgroundColor: orange textColor: orange --- Updates tracking information (tracking number, URL, carrier) on an order. Called by the Shipping Service after shipment creation. ## Endpoint | Property | Value | | --- | --- | | **Method** | `PATCH` | | **Route** | `/internal/orders/:id/tracking` | | **Channel** | Internal HTTP | | **Caller** | Shipping Service | | **Handler** | Order Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface UpdateTrackingRequest { trackingNumber: string | null; trackingUrl: string | null; carrier: string | null; } export interface UpdateTrackingResponse { orderId: string; updated: boolean; } --- id: UploadToSimplyPrint name: Upload to SimplyPrint version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP command from Grid Service to Print Service to upload a sliced gcode file to SimplyPrint. badges: - content: POST backgroundColor: green textColor: green - content: Internal HTTP backgroundColor: orange textColor: orange - content: GridFlock backgroundColor: purple textColor: purple --- Uploads a sliced gcode file to SimplyPrint via the Print Service's SimplyPrint integration. Called by the Grid Service after successful slicing. ## Endpoint | Property | Value | | --- | --- | | **Method** | `POST` | | **Route** | `/internal/simplyprint/upload` | | **Channel** | Internal HTTP | | **Caller** | Grid Service | | **Handler** | Print Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface UploadToSimplyPrintRequest { tenantId: string; orderId: string; lineItemId: string; sku: string; fileName: string; gcodeBuffer: Buffer; } export interface UploadToSimplyPrintResponse { simplyPrintFileId: string; uploadedAt: string; } --- id: GetJobsByOrderId name: Get Jobs by Order ID version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP query from Order Service to Print Service to retrieve all print jobs for a given order. badges: - content: GET backgroundColor: blue textColor: blue - content: Internal HTTP backgroundColor: orange textColor: orange --- Retrieves all print jobs associated with a given order from the Print Service. ## Endpoint | Property | Value | | --- | --- | | **Method** | `GET` | | **Route** | `/internal/print-jobs/order/:orderId` | | **Channel** | Internal HTTP | | **Caller** | Order Service | | **Handler** | Print Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface GetJobsByOrderIdResponse { printJobs: Array<{ id: string; orderId: string; lineItemId: string; productSku: string; status: 'QUEUED' | 'PRINTING' | 'COMPLETED' | 'FAILED' | 'CANCELLED'; simplyPrintJobId: string | null; createdAt: string; updatedAt: string; }>; } --- id: GetJobStatusSummary name: Get Job Status Summary version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP query from Order Service to Print Service to get a summary of print job statuses for an order. badges: - content: GET backgroundColor: blue textColor: blue - content: Internal HTTP backgroundColor: orange textColor: orange --- Retrieves a summary of print job statuses for a given order — how many are queued, printing, completed, failed, etc. ## Endpoint | Property | Value | | --- | --- | | **Method** | `GET` | | **Route** | `/internal/print-jobs/order/:orderId/status-summary` | | **Channel** | Internal HTTP | | **Caller** | Order Service | | **Handler** | Print Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface GetJobStatusSummaryResponse { orderId: string; total: number; queued: number; printing: number; completed: number; failed: number; cancelled: number; allCompleted: boolean; } --- id: GetMappingStatus name: Get Mapping Status version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP query from Order Service to Grid Service to check whether a product mapping exists for a given SKU. badges: - content: GET backgroundColor: blue textColor: blue - content: Internal HTTP backgroundColor: orange textColor: orange - content: GridFlock backgroundColor: purple textColor: purple --- Checks whether the Grid Service has an existing product mapping for a given SKU. Used by the Order Service orchestration to determine whether to trigger the GridFlock pipeline or use an existing mapping. ## Endpoint | Property | Value | | --- | --- | | **Method** | `GET` | | **Route** | `/internal/grid/mapping-status/:sku` | | **Channel** | Internal HTTP | | **Caller** | Order Service | | **Handler** | Grid Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface GetMappingStatusResponse { sku: string; exists: boolean; mapping: { simplyPrintFileId: string; createdAt: string; } | null; } --- id: GetOrderByShopifyId name: Get Order by Shopify ID version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP query from Shipping Service to Order Service to look up an order by its Shopify order ID. badges: - content: GET backgroundColor: blue textColor: blue - content: Internal HTTP backgroundColor: orange textColor: orange --- Looks up an order in the Order Service using the Shopify order ID. Used by the Shipping Service when processing Sendcloud webhooks that reference Shopify order numbers. ## Endpoint | Property | Value | | --- | --- | | **Method** | `GET` | | **Route** | `/internal/orders/shopify/:shopifyOrderId` | | **Channel** | Internal HTTP | | **Caller** | Shipping Service | | **Handler** | Order Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface GetOrderByShopifyIdResponse { id: string; shopifyOrderId: string; tenantId: string; status: string; createdAt: string; updatedAt: string; } --- id: GetOrderDetails name: Get Order Details version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP query from Shipping Service to Order Service to retrieve full order details (shipping address, line items, etc.). badges: - content: GET backgroundColor: blue textColor: blue - content: Internal HTTP backgroundColor: orange textColor: orange --- Retrieves full order details from the Order Service, including shipping address and line items. Used by the Shipping Service to create Sendcloud shipments with correct address information. ## Endpoint | Property | Value | | --- | --- | | **Method** | `GET` | | **Route** | `/internal/orders/:id` | | **Channel** | Internal HTTP | | **Caller** | Shipping Service | | **Handler** | Order Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface GetOrderDetailsResponse { id: string; shopifyOrderId: string; tenantId: string; status: string; shippingAddress: { name: string; company: string | null; street: string; houseNumber: string; city: string; postalCode: string; countryCode: string; phone: string | null; email: string; }; lineItems: Array<{ id: string; productSku: string; quantity: number; title: string; }>; createdAt: string; updatedAt: string; } --- id: GetOrderLineItems name: Get Order Line Items version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP query from Grid Service to Order Service to retrieve line items for an order (used to determine GridFlock-eligible SKUs). badges: - content: GET backgroundColor: blue textColor: blue - content: Internal HTTP backgroundColor: orange textColor: orange - content: GridFlock backgroundColor: purple textColor: purple --- Retrieves the line items for a given order from the Order Service. Used by the Grid Service to determine which line items have GridFlock-eligible SKUs and need parametric generation. ## Endpoint | Property | Value | | --- | --- | | **Method** | `GET` | | **Route** | `/internal/orders/:id/line-items` | | **Channel** | Internal HTTP | | **Caller** | Grid Service | | **Handler** | Order Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface GetOrderLineItemsResponse { orderId: string; lineItems: Array<{ id: string; productSku: string; quantity: number; title: string; isGridEligible: boolean; gridConfig?: { gridWidth: number; gridDepth: number; magnetType: 'none' | 'standard' | 'strong'; insertTypes: string[]; }; }>; } --- id: GetShipmentsByOrderId name: Get Shipments by Order ID version: 0.0.1 schemaPath: schema.ts summary: | Synchronous HTTP query from Order Service to Shipping Service to retrieve all shipments for a given order. badges: - content: GET backgroundColor: blue textColor: blue - content: Internal HTTP backgroundColor: orange textColor: orange --- Retrieves all shipments associated with a given order from the Shipping Service. ## Endpoint | Property | Value | | --- | --- | | **Method** | `GET` | | **Route** | `/internal/shipments/order/:orderId` | | **Channel** | Internal HTTP | | **Caller** | Order Service | | **Handler** | Shipping Service | | **Auth** | `X-Internal-Key: INTERNAL_API_KEY` | ## Raw Schema:schema.ts export interface GetShipmentsByOrderIdResponse { shipments: Array<{ id: string; orderId: string; sendcloudParcelId: number; status: string; trackingNumber: string | null; trackingUrl: string | null; carrier: string | null; labelUrl: string | null; createdAt: string; updatedAt: string; }>; } --- id: Gateway name: API Gateway version: 0.0.1 summary: | Single entry point for all external traffic. Handles authentication, HTTP routing to downstream services, WebSocket proxy, and BullMQ monitoring via Bull Board. badges: - content: NestJS backgroundColor: red textColor: red - content: Port 3000 backgroundColor: gray textColor: gray --- The API Gateway is the single entry point for all external traffic into Forma3D.Connect. It does not publish or consume any BullMQ events — it serves three roles: 1. **HTTP Proxy** — Routes REST requests to downstream services (Order, Print, Shipping, GridFlock) 2. **WebSocket Proxy** — Socket.IO proxy on namespace `/events` with Redis adapter for horizontal scaling 3. **Bull Board Dashboard** — Read-only monitoring of all 11 BullMQ queues at `/admin/queues` ## Technology | Property | Value | | --- | --- | | **Framework** | NestJS | | **Port** | 3000 | | **Container** | `forma3d-gateway` | | **Health** | `GET /health/live` | ## Architecture See [C4 Component — Gateway](../../c4-model/3-component/) for internal structure. --- id: GridflockService name: Grid Service version: 0.0.1 summary: | Parametric Gridfinity grid generation service — STL generation via OpenSCAD, slicing via BambuStudio CLI, SimplyPrint upload, and product mapping creation. sends: - id: GridflockMappingReady - id: GridflockPipelineFailed - id: UploadToSimplyPrint - id: SliceSTL - id: GetOrderLineItems receives: - id: GenerateForOrder - id: GetMappingStatus flows: - id: GridFlockPipeline badges: - content: NestJS backgroundColor: red textColor: red - content: Port 3004 backgroundColor: gray textColor: gray - content: Feature-Flagged backgroundColor: yellow textColor: yellow --- The Grid Service handles parametric Gridfinity grid generation. It orchestrates an end-to-end pipeline: STL generation (OpenSCAD) → slicing (BambuStudio CLI) → upload (SimplyPrint) → product mapping creation. ## Technology | Property | Value | | --- | --- | | **Framework** | NestJS | | **Port** | 3004 | | **Container** | `forma3d-grid-service` | | **Health** | `GET /health` | | **Database** | PostgreSQL via Prisma | | **Worker** | BullMQ local job queue for CPU-intensive STL generation | ## Responsibilities - Parametric STL generation via OpenSCAD (baseplates and plate sets) - Orchestration of the generation → slice → upload → map pipeline - Communication with the Slicer Container for gcode generation - Upload of sliced gcode to SimplyPrint via Print Service - Product mapping creation for generated SKUs ## Architecture See [C4 Component — Grid Service](../../c4-model/3-component/) for internal structure. --- id: OrderService name: Order Service version: 0.0.1 summary: | Core service managing the full order lifecycle — Shopify webhook ingestion, orchestration of downstream workflows, fulfillment tracking, and cancellation flows. sends: - id: OrderCreated - id: OrderReadyForFulfillment - id: OrderCancelled - id: GenerateForOrder - id: GetMappingStatus - id: StockReplenishmentScheduled receives: - id: PrintJobCompleted - id: PrintJobFailed - id: PrintJobStatusChanged - id: PrintJobCancelled - id: ShipmentCreated - id: ShipmentStatusChanged - id: GridflockMappingReady - id: GridflockPipelineFailed - id: IntegrationSimplyPrintChanged - id: IntegrationSendcloudChanged - id: StockReplenishmentScheduled - id: UpdateTracking - id: GetOrderDetails - id: GetOrderLineItems - id: GetOrderByShopifyId flows: - id: OrderFulfillment - id: OrderCancellation - id: GridFlockPipeline - id: ShippingTracking - id: StockReplenishment badges: - content: NestJS backgroundColor: red textColor: red - content: Port 3001 backgroundColor: gray textColor: gray - content: Core Service backgroundColor: blue textColor: blue --- The Order Service is the central orchestrator of the Forma3D.Connect platform. It manages the full order lifecycle from Shopify webhook ingestion through fulfillment. ## Technology | Property | Value | | --- | --- | | **Framework** | NestJS | | **Port** | 3001 | | **Container** | `forma3d-order-service` | | **Health** | `GET /health` | | **Database** | PostgreSQL via Prisma | ## Responsibilities - Shopify webhook ingestion and order creation - Product mapping resolution (standard + GridFlock) - Orchestration: coordinates print jobs (via shared `PrintJobsBaseService` in-process), shipping, and GridFlock pipelines (via `GridServiceClient` HTTP) - Fulfillment: creates Shopify fulfillments when shipments are confirmed (triggered by BullMQ `shipment.created` event) - Cancellation: cancels print jobs in-process via SimplyPrint API, publishes `OrderCancelled` event for downstream services - Inventory tracking: stock levels, adjustments, transaction ledger - Stock replenishment: scheduled stock building with STOCK-purpose print jobs - Stock-aware fulfillment: consumes from stock before creating print jobs - Push notifications and Socket.IO real-time updates - Integration client re-initialization on settings changes (SimplyPrint, Sendcloud) ## Architecture See [C4 Component — Order Service](../../c4-model/3-component/) for internal structure. --- id: PrintService name: Print Service version: 0.0.1 summary: | Manages 3D print job lifecycle and SimplyPrint integration — creating jobs, tracking status via webhooks and polling, handling file uploads. sends: - id: PrintJobCompleted - id: PrintJobFailed - id: PrintJobStatusChanged - id: PrintJobCancelled - id: IntegrationSimplyPrintChanged receives: - id: OrderCreated - id: OrderCancelled - id: UploadToSimplyPrint - id: GetJobsByOrderId - id: GetJobStatusSummary - id: CreatePrintJobs - id: CancelJobsForOrder flows: - id: OrderFulfillment - id: OrderCancellation - id: GridFlockPipeline badges: - content: NestJS backgroundColor: red textColor: red - content: Port 3002 backgroundColor: gray textColor: gray --- The Print Service manages the lifecycle of 3D print jobs. It integrates with SimplyPrint for printer management, job tracking via webhooks and status polling, and file uploads. ## Technology | Property | Value | | --- | --- | | **Framework** | NestJS | | **Port** | 3002 | | **Container** | `forma3d-print-service` | | **Health** | `GET /health` | | **Database** | PostgreSQL via Prisma | ## Responsibilities - Print job creation from order line items - SimplyPrint API integration (upload files, create jobs, track status) - **ORDER-over-STOCK queue priority** — ORDER-purpose jobs are automatically positioned before STOCK-purpose jobs in the SimplyPrint queue (ADR-063) - Webhook handling for SimplyPrint status changes - Periodic reconciliation polling to catch missed status updates - Job cancellation and retry - SimplyPrint integration credential management and live re-initialization ## Architecture See [C4 Component — Print Service](../../c4-model/3-component/) for internal structure. --- id: ShippingService name: Shipping Service version: 0.0.1 summary: | Handles shipment creation, label generation, and tracking via Sendcloud. Activates after all print jobs for an order are complete. sends: - id: ShipmentCreated - id: ShipmentStatusChanged - id: UpdateTracking - id: IntegrationSendcloudChanged - id: GetOrderDetails - id: GetOrderByShopifyId receives: - id: OrderReadyForFulfillment - id: OrderCancelled - id: CreateShipment - id: CancelShipment - id: GetShipmentsByOrderId flows: - id: OrderFulfillment - id: OrderCancellation - id: ShippingTracking badges: - content: NestJS backgroundColor: red textColor: red - content: Port 3003 backgroundColor: gray textColor: gray --- The Shipping Service handles shipment creation, label generation, and tracking through the Sendcloud integration. It activates when the Order Service publishes the `order.ready-for-fulfillment` event, indicating all print jobs are complete. ## Technology | Property | Value | | --- | --- | | **Framework** | NestJS | | **Port** | 3003 | | **Container** | `forma3d-shipping-service` | | **Health** | `GET /health` | | **Database** | PostgreSQL via Prisma | ## Responsibilities - Shipment creation via Sendcloud API - Label generation and carrier selection - Webhook handling for Sendcloud status changes - Periodic reconciliation polling to catch missed status updates - Tracking information sync back to Order Service - Sendcloud integration credential management and live re-initialization ## Architecture See [C4 Component — Shipping Service](../../c4-model/3-component/) for internal structure. --- id: SlicerContainer name: Slicer Container version: 0.0.1 summary: | Headless BambuStudio CLI container for STL-to-gcode slicing. Called by the Grid Service via internal HTTP. receives: - id: SliceSTL flows: - id: GridFlockPipeline badges: - content: BambuStudio CLI backgroundColor: orange textColor: orange - content: Port 3010 backgroundColor: gray textColor: gray --- The Slicer Container provides headless STL-to-gcode slicing using BambuStudio CLI. It is a standalone Docker container with no database or event bus connections — it exposes a simple HTTP API that the Grid Service calls synchronously. ## Technology | Property | Value | | --- | --- | | **Runtime** | BambuStudio CLI (headless) | | **Port** | 3010 | | **Container** | `forma3d-slicer` | | **Health** | `GET /health` | ## API | Endpoint | Method | Description | | --- | --- | --- | | `POST /slice` | POST | Accepts STL buffer, returns sliced gcode buffer | | `GET /health` | GET | Health check endpoint | ## Notes - The Slicer Container does not publish or consume any BullMQ events - It does not connect to the database - The Grid Service handles retry logic if the slicer is temporarily unavailable - CPU-intensive: slicing operations may take 10-60 seconds depending on model complexity --- id: GridFlock name: GridFlock version: 0.0.1 summary: | Parametric Gridfinity grid generation — CPU-intensive STL generation (OpenSCAD), slicing (BambuStudio CLI), and dynamic product mapping. Feature-flagged per tenant. services: - id: GridflockService - id: SlicerContainer flows: - id: GridFlockPipeline badges: - content: Generic Domain backgroundColor: purple textColor: purple - content: Feature-Flagged backgroundColor: yellow textColor: yellow --- The GridFlock domain handles parametric Gridfinity grid generation. It provides an automated pipeline that generates STL files using OpenSCAD, slices them with BambuStudio CLI, uploads the gcode to SimplyPrint, and creates product mappings — all triggered by incoming orders with GridFlock-eligible SKUs. ## Subdomains | Subdomain | Description | | --- | --- | | **STL Generation** | Parametric OpenSCAD-based generation of Gridfinity baseplates and plate sets | | **Slicing** | Headless BambuStudio CLI slicing of STL to gcode | | **Pipeline Orchestration** | End-to-end pipeline: generate → slice → upload → map | | **Grid Configuration** | Preset management for baseplate parameters (dimensions, magnets, etc.) | ## Key Flows - **GridFlock Pipeline** — See [C4_Seq_10_GridFlockPipeline.puml](../../sequences/) ## GridFlock Pipeline Flow ```mermaid sequenceDiagram participant OrderService as Order Service participant GridService as Grid Service participant Slicer as Slicer Container participant PrintService as Print Service participant BullMQ as BullMQ Event Bus OrderService->>GridService: POST /internal/grid/generate-for-order GridService->>GridService: Check existing mapping alt Mapping exists GridService->>BullMQ: publish grid.mapping-ready BullMQ->>OrderService: consume grid.mapping-ready else No mapping — full pipeline GridService->>GridService: Generate STL (OpenSCAD) GridService->>Slicer: POST /slice (STL buffer) Slicer->>GridService: Sliced gcode buffer GridService->>PrintService: POST /internal/simplyprint/upload (gcode) PrintService->>GridService: SimplyPrint file ID GridService->>GridService: Create product mapping GridService->>BullMQ: publish grid.mapping-ready BullMQ->>OrderService: consume grid.mapping-ready end OrderService->>PrintService: POST /internal/print-jobs (with mapping) ``` ## Pipeline Error Flow ```mermaid sequenceDiagram participant GridService as Grid Service participant Slicer as Slicer Container participant BullMQ as BullMQ Event Bus participant OrderService as Order Service GridService->>GridService: Generate STL (OpenSCAD) alt STL generation fails GridService->>BullMQ: publish grid.pipeline-failed
(failedStep: stl-generation) else Slicing fails GridService->>Slicer: POST /slice Slicer-->>GridService: HTTP 500 GridService->>BullMQ: publish grid.pipeline-failed
(failedStep: slicing) else Upload fails GridService->>BullMQ: publish grid.pipeline-failed
(failedStep: simplyprint-upload) else Mapping creation fails GridService->>BullMQ: publish grid.pipeline-failed
(failedStep: mapping-creation) end BullMQ->>OrderService: consume grid.pipeline-failed OrderService->>OrderService: Mark line item FAILED ``` ## Ubiquitous Language | Term | Definition | | --- | --- | | **Gridfinity** | An open-source modular storage system based on a 42 mm grid unit. GridFlock generates parametric Gridfinity-compatible baseplates and plate sets. | | **Grid Unit** | The standard Gridfinity cell size of 42 mm (`GRID_UNIT_MM`). All grid dimensions are multiples of this unit. | | **Baseplate** | A single Gridfinity base grid, generated as an STL from dimensional parameters (width × depth in grid units). One of two GridFlock job types (`BASEPLATE`). | | **Plate Set** | A multi-plate Gridfinity configuration that exceeds the printer's build area. The system automatically calculates the optimal tiling of plates. The second GridFlock job type (`PLATE_SET`). Parent jobs spawn child jobs for individual plates. | | **GridFlock Job** | A unit of work for parametric grid generation. Has a `type` (BASEPLATE or PLATE_SET), JSON `parameters`, generated file metadata, and `progress` tracking. Plate set jobs have parent/child relationships. | | **GridFlock Job Status** | Lifecycle state: `QUEUED` (awaiting processing), `PROCESSING` (actively generating/slicing), `COMPLETED` (pipeline finished), `FAILED` (error at any step), `EXPIRED` (cleanup after configurable hours). | | **GridFlock Pipeline** | The end-to-end process: STL generation (OpenSCAD) → slicing (BambuStudio CLI) → upload to SimplyPrint → product mapping creation. Runs buffer-based with sequential plate processing to manage memory. | | **Failed Step** | The specific pipeline stage where an error occurred: `stl-generation`, `slicing`, `simplyprint-upload`, or `mapping-creation`. Reported in the `grid.pipeline-failed` event. | | **OpenSCAD** | Open-source script-based CAD engine used to parametrically generate Gridfinity STL geometry from grid dimensions, connector types, and magnet configurations. | | **Slicer / Slicing** | The process of converting an STL 3D model into gcode machine instructions. Performed by a headless BambuStudio CLI running in a dedicated Docker container (`SlicerContainer`). | | **Slicer Container** | A standalone HTTP service wrapping BambuStudio CLI. Accepts STL buffers via `POST /slice` and returns gcode buffers. Decoupled from the Grid Service for resource isolation. | | **Connector Type** | The interlocking mechanism between Gridfinity baseplates: `intersection-puzzle` (IP), `edge-puzzle` (EP), or `none`. Determines the geometry at plate boundaries. | | **GridFlock Preset** | A saved set of generation parameters (dimensions, connector type, magnet config, etc.). Can be `isPublic` (shared across tenants), `isSystem` (built-in defaults), or tenant-scoped. | | **Magnet Parameters** | Configuration for optional magnet holes in the baseplate: presence, diameter, depth. Part of the generation parameters. | | **Slicer Margin / Brim** | Parameters controlling the border area added during slicing for build-plate adhesion. Affects the printable area calculation for plate sets. | | **Max Dimension** | The configurable maximum grid size (`grid.max_dimension_mm`). Orders exceeding this are rejected. A separate `MAX_SYNC_GRID_SIZE` threshold determines whether generation runs synchronously or is queued. | | **Printer Profile** | The target printer model specification (e.g. "Bambu Lab A1") used by the slicer to select appropriate settings. Configured per tenant via `grid.print_settings`. | | **Grid Pricing** | Per-tenant pricing model for custom grids: `grid.basePrice` (fixed fee), `grid.unitPrice` (per grid unit), `grid.unitSizeMm`, and `grid.currency`. Stored in SystemConfig. | | **Feature Flag** | System configuration key `feature.grid.enabled` that controls whether GridFlock is active for a tenant. Exposed via the gateway's `TenantFeaturesService`. | | **GRID-CUSTOM SKU** | The Shopify SKU prefix that identifies a line item as a GridFlock custom product. Triggers the GridFlock pipeline during order orchestration. | --- id: Inventory name: Inventory version: 0.0.1 summary: | Inventory tracking, stock replenishment, and hybrid fulfillment domain — enables pre-printing products to stock and consuming stock before print-to-order. services: - id: OrderService flows: - id: StockReplenishment - id: OrderFulfillment badges: - content: Supporting Domain backgroundColor: green textColor: green --- The Inventory domain adds stock management capabilities to the Forma3D.Connect platform, enabling a hybrid fulfillment model where products can be fulfilled from pre-built stock or printed on demand. ## Subdomains | Subdomain | Description | | --- | --- | | **Inventory Tracking** | Tracks `currentStock` per product mapping, with configurable `minimumStock` thresholds | | **Stock Replenishment** | Scheduled Cron job creates `StockBatch` records with `STOCK`-purpose print jobs when stock falls below minimum. Mutex prevents concurrent evaluations. Batches can be cancelled by operators. | | **Stock-Aware Fulfillment** | Orchestration checks available stock before creating print jobs — consumes from shelf first. Orders fully fulfilled from stock skip printing and go directly to fulfillment. | | **Transaction Ledger** | Immutable `InventoryTransaction` log records every stock change (PRODUCED, CONSUMED, ADJUSTMENT, SCRAPPED) | ## Key Flows - **Scheduled Replenishment** — Cron evaluates products with `currentStock < minimumStock` and creates stock print batches - **Stock Consumption** — When `order.created` fires, the orchestrator tries to consume from stock before creating print jobs. If all units are fulfilled from stock, the order skips printing and goes directly to fulfillment. - **Batch Completion** — When all print jobs in a `StockBatch` complete, `currentStock` is atomically incremented - **Batch Cancellation** — Operators can cancel active stock batches via the UI or API, which cancels pending print jobs and marks the batch as `CANCELLED` ## Data Model ```mermaid erDiagram ProductMapping ||--o{ StockBatch : "has" ProductMapping ||--o{ InventoryTransaction : "tracks" StockBatch ||--o{ PrintJob : "contains" ProductMapping { int currentStock "Units on shelf" int minimumStock "Replenishment trigger" int maximumStock "Optional cap" int replenishmentPriority "Higher = first" int replenishmentBatchSize "Jobs per cycle" } StockBatch { string status "IN_PROGRESS | COMPLETED | FAILED | CANCELLED" int totalJobs "PrintJobs in batch" int completedJobs "Progress counter" } InventoryTransaction { string transactionType "PRODUCED | CONSUMED | ADJUSTMENT_IN | ADJUSTMENT_OUT | SCRAPPED" int quantity "Always positive" string direction "IN | OUT" string referenceType "STOCK_BATCH | ORDER | LINE_ITEM | MANUAL" } ``` ## API Endpoints | Method | Endpoint | Permission | Description | | --- | --- | --- | --- | | `GET` | `/api/v1/inventory/stock` | `inventory.read` | List stock levels for all managed products | | `PUT` | `/api/v1/inventory/stock/:id/config` | `inventory.write` | Update stock configuration (minimumStock, etc.) | | `POST` | `/api/v1/inventory/stock/:id/adjust` | `inventory.write` | Manual stock adjustment (in or out) | | `POST` | `/api/v1/inventory/stock/:id/scrap` | `inventory.write` | Record scrapped units | | `GET` | `/api/v1/inventory/stock/:id/transactions` | `inventory.read` | Transaction history with pagination | | `GET` | `/api/v1/inventory/replenishment/status` | `inventory.read` | Replenishment scheduler status | | `GET` | `/api/v1/inventory/batches` | `inventory.read` | List active stock batches | | `POST` | `/api/v1/inventory/batches/:id/cancel` | `inventory.write` | Cancel an active stock batch | ## Ubiquitous Language | Term | Definition | | --- | --- | | **Current Stock** | The number of pre-printed units of a product currently on the shelf, tracked on the ProductMapping as `currentStock`. Atomically incremented/decremented by inventory transactions. | | **Minimum Stock** | The `minimumStock` threshold on a ProductMapping. When `currentStock` drops below this value, the replenishment scheduler creates a stock batch to print more units. | | **Maximum Stock** | An optional `maximumStock` cap on a ProductMapping. Limits how many units the replenishment system will produce. | | **Replenishment Priority** | A numeric `replenishmentPriority` on ProductMapping. Higher values are evaluated first when the replenishment scheduler runs, ensuring critical products are restocked before others. | | **Replenishment Batch Size** | The `replenishmentBatchSize` on ProductMapping, specifying how many print jobs to create per replenishment cycle for that product. | | **Stock Batch** | A group of `STOCK`-purpose print jobs created by a single replenishment cycle. Tracks `totalJobs`, `completedJobs`, and a lifecycle status. When all jobs complete, `currentStock` is atomically incremented. | | **Stock Batch Status** | Lifecycle state of a StockBatch: `IN_PROGRESS` (jobs printing), `COMPLETED` (all jobs done, stock updated), `FAILED` (jobs failed), `CANCELLED` (operator cancelled the batch). | | **Inventory Transaction** | An immutable ledger entry recording a stock change. Each transaction has a `transactionType`, `quantity` (always positive), `direction` (IN or OUT), `referenceType`, and `referenceId`. | | **Transaction Type** | The reason for a stock change: `PRODUCED` (print job completed → stock in), `CONSUMED` (stock allocated to an order → stock out), `ADJUSTMENT_IN` (manual increase), `ADJUSTMENT_OUT` (manual decrease), `SCRAPPED` (damaged/defective units removed). | | **Stock Direction** | Whether a transaction adds to (`IN`) or removes from (`OUT`) the available stock. | | **Reference Type** | Links an InventoryTransaction to its source: `STOCK_BATCH` (replenishment), `ORDER` (order consumption), `LINE_ITEM` (line-level consumption), `MANUAL` (operator adjustment or scrap). | | **Stock-Aware Fulfillment** | The orchestration logic that checks available stock before creating print jobs. If sufficient stock exists, units are consumed from the shelf and the order skips printing entirely, going directly to fulfillment. | | **Hybrid Fulfillment** | The business model where orders can be fulfilled either from pre-printed stock or via print-on-demand, depending on stock availability at the time of order creation. | | **Replenishment Scheduler** | A cron-based process that periodically evaluates all managed products and creates stock batches for any whose `currentStock < minimumStock`. Protected by a mutex to prevent concurrent evaluations. | | **Stock Management Feature Flag** | System configuration key `feature.stock-management.enabled` that controls whether inventory tracking and replenishment are active for a tenant. | | **Manual Adjustment** | An operator-initiated stock change (increase or decrease) with a required reason. Recorded as an `ADJUSTMENT_IN` or `ADJUSTMENT_OUT` transaction. | | **Scrap** | Recording that pre-printed units are damaged or defective and should be removed from stock. Creates a `SCRAPPED` transaction with direction `OUT`. | --- id: Orders name: Orders version: 0.0.1 summary: | The order lifecycle domain — from Shopify webhook ingestion through orchestration and fulfillment. The core business domain; everything starts here. services: - id: OrderService flows: - id: OrderFulfillment - id: OrderCancellation - id: StockReplenishment badges: - content: Core Domain backgroundColor: blue textColor: blue --- The Orders domain encompasses the full order lifecycle within Forma3D.Connect. It handles ingestion of orders from Shopify, orchestration of downstream workflows (printing, shipping, GridFlock generation), fulfillment tracking, and cancellation flows. ## Subdomains | Subdomain | Description | | --- | --- | | **Order Management** | CRUD operations, status tracking, and lifecycle management for orders and line items | | **Shopify Integration** | Webhook ingestion, OAuth flow, order sync, and backfill capabilities | | **Orchestration** | Coordinates downstream services — determines when print jobs are ready, when to trigger shipping | | **Fulfillment** | Creates Shopify fulfillments when shipments are confirmed | | **Cancellation** | Cascading cancellation across print jobs and shipments | | **Product Catalog** | Product-to-print-file mappings and assembly part configurations | | **Analytics** | Order metrics and status distribution dashboards | | **Audit & Event Log** | Structured business event logging to PostgreSQL | | **Notifications** | Web Push and in-app notifications for order lifecycle events | | **Inventory & Stock** | Stock tracking, replenishment scheduling, and stock-aware hybrid fulfillment | | **Retry Queue** | Exponential backoff retry mechanism for failed downstream operations | ## Key Flows - **Shopify Webhook → Order Created** — See [C4_Seq_01_ShopifyWebhook.puml](../../sequences/) - **Order → Print Jobs → Fulfillment** — See [C4_Seq_05_Fulfillment.puml](../../sequences/) - **Order Cancellation** — See [C4_Seq_06_Cancellation.puml](../../sequences/) - **Stock Replenishment** — See [C4_Seq_11_StockReplenishment.puml](../../sequences/) - **Stock-Aware Fulfillment** — See [C4_Seq_12_StockAwareFulfillment.puml](../../sequences/) ## Standard Order Lifecycle ```mermaid sequenceDiagram participant Shopify participant OrderService as Order Service participant PrintService as Print Service participant ShippingService as Shipping Service participant BullMQ as BullMQ Event Bus Shopify->>OrderService: Webhook: orders/create OrderService->>BullMQ: publish order.created BullMQ->>PrintService: consume order.created PrintService->>PrintService: Create print jobs Note over PrintService: Jobs progress through
QUEUED → PRINTING → COMPLETED PrintService->>BullMQ: publish print-job.completed (per job) BullMQ->>OrderService: consume print-job.completed OrderService->>OrderService: Check all jobs complete OrderService->>BullMQ: publish order.ready-for-fulfillment BullMQ->>ShippingService: consume order.ready-for-fulfillment ShippingService->>ShippingService: Create Sendcloud shipment ShippingService->>BullMQ: publish shipment.created BullMQ->>OrderService: consume shipment.created OrderService->>Shopify: Create fulfillment with tracking ``` ## Error / Retry Flow ```mermaid sequenceDiagram participant PrintService as Print Service participant BullMQ as BullMQ Event Bus participant OrderService as Order Service PrintService->>BullMQ: publish print-job.failed BullMQ->>OrderService: consume print-job.failed alt All jobs failed OrderService->>OrderService: Mark order FAILED else Some jobs failed OrderService->>OrderService: Mark order PARTIALLY_COMPLETED end Note over OrderService: Retry queue processes
failed operations with
exponential backoff
(1s, 2s, 4s up to 5 retries) ``` ## Order Revival Flow When a print job is requeued (terminal → active), the order automatically reverts to PROCESSING: ```mermaid sequenceDiagram participant Operator participant PrintService as Print Service participant BullMQ as BullMQ Event Bus participant OrderService as Order Service Note over OrderService: Order is FAILED or
PARTIALLY_COMPLETED Operator->>PrintService: Requeue failed/cancelled job PrintService->>BullMQ: publish print-job.status-changed
(CANCELLED → QUEUED) BullMQ->>OrderService: consume print-job.status-changed OrderService->>OrderService: Detect terminal → active transition OrderService->>OrderService: Revert order to PROCESSING OrderService->>OrderService: Recalculate part counts OrderService->>OrderService: Log order.revived event Note over OrderService: Order lifecycle continues
normally from PROCESSING ``` ## Ubiquitous Language | Term | Definition | | --- | --- | | **Order** | A tenant-scoped purchase record ingested from Shopify via webhook. Contains customer details, shipping address, pricing, and tracks fulfillment progress through `totalParts` / `completedParts` counters. | | **Line Item** | A single product line within an Order, identified by `shopifyLineItemId` and `productSku`. Links to a ProductMapping for printing. Carries its own status and part counts independently of the parent Order. | | **Product Mapping** | The link between a Shopify SKU/variant and the printable file(s) in SimplyPrint. Includes a `defaultPrintProfile` and optional assembly part definitions. Shared with the Printing and Inventory domains. | | **Assembly Part** | One printable component of a multi-part product. Defined by `partName`, `partNumber`, `quantityPerProduct`, and a `simplyPrintFileId`. A single ProductMapping may have many AssemblyParts. | | **Orchestration** | The coordination logic that monitors downstream progress (print jobs, GridFlock pipelines) and determines when an order is ready for shipping and Shopify fulfillment. Lives in the `OrchestrationService`. | | **Fulfillment** | The act of creating a Shopify fulfillment record with tracking information once a shipment is confirmed. Distinct from the physical shipping step. | | **Order Status** | The lifecycle state of an Order: `PENDING` (received, awaiting processing), `PROCESSING` (print jobs active), `PARTIALLY_COMPLETED` (some jobs done, some failed), `COMPLETED` (all parts fulfilled), `FAILED` (all jobs failed), `CANCELLED` (operator or Shopify cancellation). | | **Line Item Status** | The lifecycle state of a Line Item: `PENDING`, `PRINTING`, `PARTIALLY_COMPLETED`, `COMPLETED`, `FAILED`. | | **Order Revival** | Automatic reversion of a `FAILED` or `PARTIALLY_COMPLETED` order back to `PROCESSING` when a terminal print job is requeued by an operator. Part counts are recalculated. | | **Shopify Webhook** | An HTTP callback from Shopify (e.g. `orders/create`, `orders/updated`) that triggers order ingestion. Deduplicated via `X-Shopify-Webhook-Id` stored in `ProcessedWebhook`. | | **Processed Webhook** | An idempotency record keyed by `webhookId` and `webhookType` that prevents duplicate processing of the same Shopify event. | | **Shopify Shop** | A tenant's Shopify store connection, holding the encrypted `accessToken`, granted `scopes`, `webhookSecret`, and install/uninstall timestamps. | | **Event Log** | A structured business event record with `eventType`, `severity` (INFO, WARNING, ERROR), and optional links to an Order or PrintJob. Used for operational visibility, not security audit. | | **Retry Queue** | An exponential-backoff retry mechanism for failed downstream operations. Each entry has a `RetryJobType` (FULFILLMENT, PRINT_JOB_CREATION, CANCELLATION, NOTIFICATION, SHIPMENT, GRIDFLOCK_PIPELINE) and a `RetryStatus` (PENDING, PROCESSING, COMPLETED, FAILED). | | **Service Point Delivery** | An alternative delivery mode where the package is sent to a pickup location instead of the customer's home. Captured by `deliveryType` (`home` vs `service_point`) and Sendcloud service-point metadata on the Order. | | **GridFlock Custom Product** | A line item whose SKU starts with `GRID-CUSTOM`, indicating it requires parametric Gridfinity grid generation before printing. Triggers the GridFlock pipeline during orchestration. | | **Part Counts** | `totalParts` and `completedParts` tracked on both Order and LineItem. Orchestration uses these to determine when all printable components are done and the order is ready for fulfillment. | --- id: Platform name: Platform version: 0.0.1 summary: | Cross-cutting infrastructure: authentication, multi-tenancy, API routing, observability, database access, event bus. Not a business domain — shared capabilities used by all services. services: - id: Gateway flows: - id: IntegrationReinitialization badges: - content: Infrastructure backgroundColor: gray textColor: gray --- The Platform domain provides cross-cutting infrastructure capabilities shared by all business domains. It is not a business domain itself — it provides the foundation on which business services operate. ## Subdomains | Subdomain | Description | | --- | --- | | **Authentication & Authorization** | Session-based auth, RBAC, API key validation | | **Multi-tenancy** | Tenant isolation and context propagation | | **API Gateway** | HTTP routing, WebSocket proxy, rate limiting | | **Observability** | Sentry integration, structured logging, health checks | | **Configuration** | Environment-based configuration management | | **Database Access** | Prisma ORM, PostgreSQL connection management | | **Event Bus** | BullMQ event infrastructure, Bull Board monitoring | | **Integration Management** | Live re-initialization of third-party API clients on settings changes | ## Ubiquitous Language | Term | Definition | | --- | --- | | **Tenant** | An isolated organizational unit within Forma3D.Connect, identified by a `slug`. Each tenant has its own users, roles, integrations, feature flags, and configuration. The `isDefault` tenant serves as the system default. | | **User** | An authenticated operator or administrator within a tenant. Assigned one or more Roles that grant Permissions. | | **Role** | A named set of permissions assigned to users: `admin` (full access), `operator` (day-to-day operations), `viewer` (read-only), `legacy-admin` (API key backward compatibility). | | **Permission** | A granular access right following the pattern `resource.action` (e.g. `orders.read`, `printJobs.write`, `inventory.write`, `admin.operations`). Assigned to Roles via RolePermission join records. | | **Session-Based Auth** | The authentication mechanism using `express-session` backed by PostgreSQL. No JWTs — sessions are server-side with cookie-based identity. | | **API Key** | A legacy authentication mechanism for machine-to-machine access, validated by the gateway. Used by the `legacy-admin` role. | | **System Config** | Per-tenant key-value configuration stored as JSON in the `SystemConfig` entity. Controls feature flags, integration settings, pricing, and operational parameters (e.g. `feature.grid.enabled`, `stock.replenishment.enabled`). | | **Feature Flag** | A SystemConfig entry that enables or disables functionality per tenant (e.g. `feature.grid.enabled`, `feature.stock-management.enabled`). Exposed via the gateway's `TenantFeaturesService`. | | **Gateway** | The API gateway application that handles HTTP routing, WebSocket proxying, authentication, rate limiting, and Swagger aggregation. Proxies requests to downstream services: order-service, print-service, shipping-service, grid-service, and slicer. | | **BullMQ Event Bus** | The cross-service asynchronous messaging infrastructure built on BullMQ (Redis-backed queues). Each event type corresponds to a named queue. The canonical event names are defined in `SERVICE_EVENTS`. | | **EventEmitter2** | The in-process event system (NestJS) used for intra-service communication. Bridge services translate between local EventEmitter2 events and cross-service BullMQ events. | | **Socket.IO** | The real-time WebSocket transport used to push events to the PWA frontend. Operates on the `/events` namespace with `WsApiKeyGuard` authentication. Emits events like `order:created`, `printjob:status-changed`, etc. | | **Correlation ID** | A unique identifier propagated across service boundaries to trace a single business operation through logs and events. Used by `BusinessObservabilityService`. | | **Audit Log** | A security-focused record of sensitive actions (e.g. `auth.login.success`, role changes). Captures `actorUserId`, `targetType`, `targetId`, and `metadata`. Distinct from the business EventLog. | | **Health Check** | Aggregated downstream service health verification exposed by the gateway. Checks connectivity to all proxied services and infrastructure dependencies. | | **Push Subscription** | A Web Push API subscription stored per user, enabling browser push notifications. Each subscription has toggles for order, print, shipment, and system notification categories. | | **Integration Reinitialization** | The process of live-reloading third-party API clients (SimplyPrint, Sendcloud) when a tenant's integration settings change, without requiring a service restart. Triggered by `integration.simplyprint-changed` and `integration.sendcloud-changed` events. | | **Retry Queue** | A platform-level exponential-backoff mechanism for failed operations. Supports job types across all domains (FULFILLMENT, PRINT_JOB_CREATION, CANCELLATION, NOTIFICATION, SHIPMENT, GRIDFLOCK_PIPELINE) with statuses PENDING, PROCESSING, COMPLETED, FAILED. | | **Bull Board** | A monitoring UI for BullMQ queues, providing visibility into event bus queue depths, failed jobs, and processing rates. | | **Sentry** | The observability platform integrated for error tracking, performance monitoring, and structured logging across all services. | --- id: Printing name: Printing version: 0.0.1 summary: | 3D print job management and SimplyPrint integration — creating jobs, tracking status, handling webhooks, uploading files. services: - id: PrintService flows: - id: OrderFulfillment - id: OrderCancellation - id: GridFlockPipeline badges: - content: Supporting Domain backgroundColor: green textColor: green --- The Printing domain manages the lifecycle of 3D print jobs within Forma3D.Connect. It integrates with SimplyPrint for printer management and job tracking. ## Subdomains | Subdomain | Description | | --- | --- | | **Print Job Management** | Creation, status tracking, cancellation, and retry of print jobs | | **SimplyPrint Integration** | API client, webhook handling, status polling, reconciliation, and file upload | ## Key Flows - **Print Job Creation** — See [C4_Seq_03_PrintJobCreation.puml](../../sequences/) - **Print Job Sync** — See [C4_Seq_04_PrintJobSync.puml](../../sequences/) ## Ubiquitous Language | Term | Definition | | --- | --- | | **Print Job** | A unit of work representing a single 3D print. Linked to a LineItem and optionally an AssemblyPart (for ORDER purpose) or a StockBatch (for STOCK purpose). Identified externally by `simplyPrintJobId` and `simplyPrintQueueItemId`. | | **Print Job Status** | Lifecycle state of a PrintJob: `QUEUED` (awaiting printer assignment), `ASSIGNED` (printer selected), `PRINTING` (actively printing), `COMPLETED` (finished successfully), `FAILED` (print error), `CANCELLED` (operator or cascade cancellation). | | **Print Job Purpose** | Distinguishes why a job exists: `ORDER` (fulfilling a customer order) or `STOCK` (pre-printing for inventory replenishment). Drives orchestration and inventory transaction logic. | | **Copy Number** | When a line item has `quantity > 1`, each PrintJob is assigned a `copyNumber` to track which copy of the product it represents. | | **SimplyPrint** | Third-party 3D printer management platform. Forma3D.Connect uses its API for file uploads, job creation, printer monitoring, and status synchronization. | | **SimplyPrint Queue Item ID** | The persistent `created_id` returned by SimplyPrint when a job is added to the queue. Used as the stable reference for job operations, distinct from the transient `simplyPrintJobId`. | | **SimplyPrint Job Status** | The status reported by the SimplyPrint API: `queued`, `assigned`, `preparing`, `printing`, `completed`, `failed`, `cancelled`. Mapped to the internal `PrintJobStatus` enum. | | **SimplyPrint Printer Status** | The state of a physical printer as reported by SimplyPrint: `offline`, `operational`, `printing`, `paused`, `error`. Cached locally in the `Printer` entity. | | **Printer** | A cached representation of a physical 3D printer from SimplyPrint, tracking `simplyPrintId`, `isOnline`, `currentStatus`, and `currentJobId`. | | **Assembly Part** | A single printable component within a multi-part product. Defined by `partName`, `partNumber`, `simplyPrintFileId`, `printProfile`, estimated print time and filament usage, and `quantityPerProduct`. | | **Product Mapping** | The bridge between a Shopify SKU/variant and the printable file(s) in SimplyPrint. Contains a `defaultPrintProfile` and zero or more AssemblyParts. | | **Print Profile** | A JSON configuration specifying slicer/printer settings (layer height, infill, supports, etc.) used when submitting a job to SimplyPrint. Stored on ProductMapping and AssemblyPart. | | **Gcode** | Machine instructions generated by slicing an STL file. The format uploaded to SimplyPrint for 3D printing. | | **STL** | Stereolithography file format representing 3D geometry. Source format for printable models before slicing. | | **File Upload** | The process of sending gcode or STL files to SimplyPrint's file system, returning a `fileId` used to create print jobs. | | **Status Polling** | Periodic synchronization of print job statuses from SimplyPrint, controlled by `SIMPLYPRINT_POLLING_ENABLED` and `SIMPLYPRINT_POLLING_INTERVAL_MS`. Supplements webhook-based updates. | | **Reconciliation** | The process of comparing local PrintJob records against SimplyPrint's state to detect and correct drift caused by missed webhooks or polling gaps. | | **Cancel Reason** | A numeric code from SimplyPrint indicating why a job was cancelled (e.g. print failed, nozzle clog, operator cancellation). | | **Requeue** | An operator action that moves a `FAILED` or `CANCELLED` print job back to `QUEUED`, triggering an order revival in the Orders domain. | --- id: Shipping name: Shipping version: 0.0.1 summary: | Shipment creation, label generation, and tracking via Sendcloud. Activates after all print jobs complete. services: - id: ShippingService flows: - id: OrderFulfillment - id: OrderCancellation - id: ShippingTracking badges: - content: Supporting Domain backgroundColor: green textColor: green --- The Shipping domain handles shipment creation, label generation, and tracking via the Sendcloud integration. It activates when the Order Service signals that all print jobs for an order are complete (`order.ready-for-fulfillment`). ## Subdomains | Subdomain | Description | | --- | --- | | **Shipment Management** | Creation, status tracking, and label generation for shipments | | **Sendcloud Integration** | API client, webhook handling, status reconciliation, and carrier management | ## Key Flows - **Shipping Label Creation** — See [C4_Seq_08_ShippingLabel.puml](../../sequences/) - **Fulfillment Flow** — See [C4_Seq_05_Fulfillment.puml](../../sequences/) ## Ubiquitous Language | Term | Definition | | --- | --- | | **Shipment** | A one-to-one record linked to an Order, representing a parcel to be shipped. Contains carrier details, tracking info, label URL, weight, and dimensions. Identified externally by `sendcloudParcelId`. | | **Shipment Status** | Lifecycle state of a Shipment: `PENDING` (awaiting label creation), `LABEL_CREATED` (shipping label generated), `ANNOUNCED` (parcel registered with carrier), `IN_TRANSIT` (picked up and moving), `DELIVERED` (successfully received), `FAILED` (delivery or creation error), `CANCELLED` (shipment voided). | | **Sendcloud** | Third-party shipping aggregation platform. Forma3D.Connect uses its API for label generation, carrier selection, parcel tracking, and webhook-based status updates. | | **Sendcloud Connection** | Per-tenant API credentials for Sendcloud, consisting of encrypted `publicKey` and `secretKey`, plus defaults for shipping method and sender address. | | **Sendcloud Parcel ID** | The external identifier assigned by Sendcloud when a parcel (shipment) is created. Used for all subsequent API operations on that shipment. | | **Shipping Label** | A carrier-specific document (PDF) generated by Sendcloud, containing barcode, routing info, and sender/recipient addresses. Retrieved via `labelUrl`. | | **Shipping Method** | A carrier service option in Sendcloud (e.g. PostNL Standard, DPD Express), identified by `shippingMethodId` and `shippingMethodName`. A default is configured per tenant. | | **Carrier** | The logistics company performing physical delivery (e.g. PostNL, DPD, DHL). Identified by `carrierName` on the Shipment. | | **Tracking Number** | The carrier-assigned identifier for a parcel, enabling package tracking. Mirrored on both the Shipment and the parent Order. | | **Tracking URL** | A carrier-provided URL where the recipient can track their shipment's delivery progress. Passed to Shopify during fulfillment creation. | | **Sendcloud Webhook** | An HTTP callback from Sendcloud triggered by status changes (e.g. `parcel_status_changed`, `integration_connected`). Processed by the `SendcloudWebhookService`. | | **Sendcloud Status Ranges** | Numeric ID ranges that Sendcloud uses to categorize parcel statuses: label created, announced, in transit, delivered, and exception ranges. Mapped to internal `ShipmentStatus` values. | | **Sender Address** | The shipper's return address in Sendcloud, identified by `defaultSenderAddressId`. Used when creating parcels. | | **Service Point** | A pickup location (e.g. parcel shop, locker) where the recipient collects their package instead of home delivery. Captured on the Order with `servicePointId`, `servicePointName`, `servicePointAddress`, and `servicePointPostNumber`. | | **Weight & Dimensions** | Physical parcel attributes (`weight` in grams, `dimensions` as JSON with length/width/height in cm) required by Sendcloud for label generation and carrier rate calculation. | --- id: BullMQEventBus name: BullMQ Event Bus version: 1.0.0 summary: | Asynchronous inter-service event bus using BullMQ (Redis). Each event type maps to a dedicated BullMQ queue. Provides at-least-once delivery with configurable retries. address: redis://redis:6379 protocols: - redis badges: - content: Async backgroundColor: blue textColor: blue - content: At-Least-Once backgroundColor: green textColor: green --- The BullMQ Event Bus is the primary asynchronous communication channel between Forma3D.Connect microservices. Each event type corresponds to a dedicated BullMQ queue backed by Redis. ## Delivery Guarantees | Property | Value | | --- | --- | | **Delivery** | At-least-once | | **Ordering** | Per-queue FIFO (no cross-queue ordering) | | **Retries** | 3 attempts with exponential backoff (1s, 2s, 4s) | | **Concurrency** | 5 workers per queue per service instance | | **Dead Letter** | Failed events retained (`removeOnFail: 5000`) | | **Completed Cleanup** | `removeOnComplete: 1000` | | **Idempotency** | Required for all handlers | ## Infrastructure - **Technology**: BullMQ on Redis 7 - **Library**: `@forma3d/service-common` `BullMqEventBus` - **Monitoring**: Bull Board dashboard at `/admin/queues` on the Gateway ## Event Types All 14 cross-service events flow through this channel: | Queue Name | Publisher | Subscriber(s) | | --- | --- | --- | | `order.created` | Order Service | Print Service | | `order.ready-for-fulfillment` | Order Service | Shipping Service | | `order.cancelled` | Order Service | Print Service, Shipping Service | | `print-job.completed` | Print Service | Order Service | | `print-job.failed` | Print Service | Order Service | | `print-job.status-changed` | Print Service | Order Service | | `print-job.cancelled` | Print Service | Order Service | | `integration.simplyprint-changed` | Print Service | Order Service | | `shipment.created` | Shipping Service | Order Service | | `shipment.status-changed` | Shipping Service | Order Service | | `integration.sendcloud-changed` | Shipping Service | Order Service | | `grid.mapping-ready` | Grid Service | Order Service | | `grid.pipeline-failed` | Grid Service | Order Service | | `inventory.stock-replenishment-scheduled` | Order Service | Order Service (self-consume) | --- id: InternalHTTP name: Internal HTTP version: 1.0.0 summary: | Synchronous service-to-service HTTP communication protected by INTERNAL_API_KEY. Used for commands (POST/PATCH) and queries (GET) between microservices. protocols: - http badges: - content: Sync backgroundColor: orange textColor: orange - content: Internal Only backgroundColor: red textColor: red --- The Internal HTTP channel provides synchronous service-to-service communication for commands and queries. These endpoints are not exposed through the API Gateway — they are only accessible within the Docker network. ## Authentication All internal HTTP calls are authenticated via the `x-internal-api-key` header, which must match the `INTERNAL_API_KEY` environment variable configured on each service. ## Commands (10) | Command | Method | Route | Caller → Handler | Status | | --- | --- | --- | --- | --- | | CreatePrintJobs | POST | `/internal/print-jobs` | (no runtime caller) → Print | Endpoint exists, no caller | | CancelJobsForOrder | POST | `/internal/print-jobs/order/:orderId/cancel` | (no runtime caller) → Print | Endpoint exists, no caller | | UploadToSimplyPrint | POST | `/internal/simplyprint/upload` | Grid → Print | Active | | CreateShipment | POST | `/internal/shipments` | (no runtime caller) → Shipping | Endpoint exists, no caller | | CancelShipment | POST | `/internal/shipments/order/:orderId/cancel` | (no runtime caller) → Shipping | Endpoint exists, no caller | | GenerateForOrder | POST | `/internal/grid/generate-for-order` | Order → Grid | Active | | UpdateOrderStatus | POST | `/internal/orders/:id/status` | Grid, Shipping → Order | Active | | UpdateTracking | PATCH | `/internal/orders/:id/tracking` | Shipping → Order | Active | | SliceSTL | POST | `/slice` | Grid → Slicer | Active | | CreateProductMapping | POST | `/internal/product-mappings` | Grid → Order | Active | ## Queries (8) | Query | Method | Route | Caller → Handler | Status | | --- | --- | --- | --- | --- | | GetJobsByOrderId | GET | `/internal/print-jobs/order/:orderId` | (no runtime caller) → Print | Endpoint exists, no caller | | GetJobStatusSummary | GET | `/internal/print-jobs/order/:orderId/status-summary` | (no runtime caller) → Print | Endpoint exists, no caller | | GetShipmentsByOrderId | GET | `/internal/shipments/order/:orderId` | (no runtime caller) → Shipping | Endpoint exists, no caller | | GetOrderDetails | GET | `/internal/orders/:id` | Shipping → Order | Active | | GetOrderLineItems | GET | `/internal/orders/:id/line-items` | Grid → Order | Active | | GetOrderByShopifyId | GET | `/internal/orders/shopify/:shopifyOrderId` | Shipping → Order | Active | | GetMappingStatus | GET | `/internal/grid/mapping-status/:sku` | Order → Grid | Active | | GetProductMappingBySku | GET | `/internal/product-mappings/sku/:sku` | Grid → Order | Active | --- id: GridFlockPipeline name: GridFlock Pipeline version: 0.0.1 summary: | Parametric Gridfinity grid generation pipeline — from order line items through STL generation, slicing, and upload to SimplyPrint. badges: - content: Feature-Flagged backgroundColor: purple textColor: purple steps: - id: order_service_trigger title: Order Service summary: Detects GridFlock-eligible line items and triggers generation service: id: OrderService version: 0.0.1 next_step: id: generate_for_order_cmd label: Trigger GridFlock generation - id: generate_for_order_cmd title: Generate for Order message: id: GenerateForOrder version: 0.0.1 next_step: id: gridflock_service label: Send to Grid Service - id: gridflock_service title: Grid Service summary: Generates parametric Gridfinity STL files based on order configuration service: id: GridflockService version: 0.0.1 next_steps: - id: get_order_line_items_query label: Fetch line item config - id: slice_stl_cmd label: STL ready for slicing - id: pipeline_failed_event label: Generation failed - id: get_order_line_items_query title: Get Order Line Items message: id: GetOrderLineItems version: 0.0.1 next_step: id: gridflock_service label: Return line item data - id: slice_stl_cmd title: Slice STL message: id: SliceSTL version: 0.0.1 next_step: id: slicer_container label: Send to Slicer - id: slicer_container title: Slicer Container summary: Headless BambuStudio CLI converts STL to printer-ready gcode service: id: SlicerContainer version: 0.0.1 next_step: id: upload_to_simplyprint_cmd label: Gcode ready - id: upload_to_simplyprint_cmd title: Upload to SimplyPrint message: id: UploadToSimplyPrint version: 0.0.1 next_step: id: print_service_upload label: Send to Print Service - id: print_service_upload title: Print Service summary: Receives the sliced gcode and creates a print job in SimplyPrint service: id: PrintService version: 0.0.1 next_step: id: mapping_ready_event label: Upload successful - id: mapping_ready_event title: GridFlock Mapping Ready message: id: GridflockMappingReady version: 0.0.1 next_step: id: order_service_confirm label: Notify Order Service - id: pipeline_failed_event title: GridFlock Pipeline Failed message: id: GridflockPipelineFailed version: 0.0.1 next_step: id: order_service_confirm label: Notify Order Service - id: order_service_confirm title: Order Service summary: Records pipeline result and continues order orchestration service: id: OrderService version: 0.0.1 --- ## Overview The GridFlock Pipeline is a feature-flagged flow that generates parametric Gridfinity grid inserts for orders that include customizable 3D-printed organizer parts. ### Pipeline stages 1. **Trigger** — Order Service detects GridFlock-eligible line items during order orchestration 2. **Configuration fetch** — Grid Service queries Order Service for line item configuration (grid dimensions, insert types) 3. **STL generation** — Parametric STL files are generated based on the configuration 4. **Slicing** — STL files are sent to the Slicer Container (headless BambuStudio CLI) to produce gcode 5. **Upload** — Sliced gcode is uploaded to SimplyPrint via the Print Service 6. **Confirmation** — `GridflockMappingReady` event notifies Order Service that print jobs are queued ### Failure handling - If any stage fails, `GridflockPipelineFailed` is emitted - Order Service records the failure and may retry depending on the error type - STL generation and slicing are stateless operations and can be safely retried ### Feature flag This flow is gated behind the `GRIDFLOCK_ENABLED` feature flag. When disabled, orders with GridFlock-eligible items are processed without grid generation. --- id: IntegrationReinitialization name: Integration Re-initialization version: 0.0.1 summary: | Live re-initialization flow for third-party integration clients (SimplyPrint, Sendcloud) when settings are updated via the UI, without requiring service restarts. badges: - content: Infrastructure backgroundColor: gray textColor: gray steps: - id: settings_ui title: Settings UI summary: User connects or disconnects an integration in Settings > Integrations externalSystem: name: Web Application summary: Forma3D.Connect frontend - id: owning_service_saves title: Owning Service summary: The service that owns the integration saves credentials to the database, then immediately re-initializes its own API client and publishes a change event service: id: PrintService version: 0.0.1 next_steps: - id: integration_changed_event label: Publish integration changed event - id: local_reinit label: Local client re-initialized - id: local_reinit title: Local Re-initialization summary: The owning service calls initializeWithCredentials() or disable() on its local API client — no restart needed - id: integration_changed_event title: Integration Changed Event summary: Published to BullMQ so other services can react message: id: IntegrationSimplyPrintChanged version: 0.0.1 next_step: id: subscriber_reinit label: Notify subscriber services - id: subscriber_reinit title: Subscriber Service summary: The Order Service receives the event and re-initializes or disables its own copy of the API client by loading credentials from the database service: id: OrderService version: 0.0.1 --- ## Overview When a user changes an integration's credentials in the Settings UI, all services that use that integration must update their API client immediately — without a container restart. ### Before this flow existed API clients were only initialized at container startup via `*StartupService` classes. If credentials were changed at runtime, the services would continue using stale (or missing) credentials until the next restart. This caused issues like print jobs being created locally but never submitted to SimplyPrint. ### How it works now 1. **Settings update** — User connects or disconnects SimplyPrint / Sendcloud in the UI 2. **Owning service** — The `*ConnectionService` in the owning service (Print Service for SimplyPrint, Shipping Service for Sendcloud) saves/deletes credentials and immediately re-initializes its local API client 3. **Cross-service notification** — A BullMQ event (`integration.simplyprint-changed` or `integration.sendcloud-changed`) is published with an `action` field (`connected` | `disconnected`) 4. **Subscriber re-initialization** — The Order Service's `EventSubscriberService` receives the event and calls the appropriate `*InitializerService` to reload credentials from the database and re-initialize the client ### Affected integrations | Integration | Owning Service | Event | Subscribers | | --- | --- | --- | --- | | SimplyPrint | Print Service | `integration.simplyprint-changed` | Order Service | | Sendcloud | Shipping Service | `integration.sendcloud-changed` | Order Service | ### Key classes | Class | Service | Role | | --- | --- | --- | | `SimplyPrintConnectionService` | Print Service | Saves credentials, publishes event | | `SimplyPrintInitializerService` | Print Service / Order Service | Loads credentials from DB, initializes API client | | `SendcloudConnectionService` | Shipping Service | Saves credentials, publishes event | | `SendcloudInitializerService` | Shipping Service / Order Service | Loads credentials from DB, initializes API client | | `EventSubscriberService` | Order Service | Listens for integration change events | --- id: OrderCancellation name: Order Cancellation version: 0.0.1 summary: | Cascading order cancellation flow — propagates cancellation from Order Service to Print Service and Shipping Service, rolling back all downstream work. badges: - content: Core Flow backgroundColor: red textColor: red steps: - id: cancellation_trigger title: Cancellation Requested summary: Admin or system triggers order cancellation actor: name: Admin next_step: id: order_service_cancel label: Request cancellation - id: order_service_cancel title: Order Service summary: Validates cancellation eligibility, cancels local print jobs via SimplyPrint API, and publishes OrderCancelled event service: id: OrderService version: 0.0.1 next_steps: - id: order_cancelled_event label: Emit OrderCancelled via BullMQ - id: local_print_cancel label: Cancel print jobs locally - id: local_print_cancel title: Local Print Job Cancellation summary: CancellationService cancels QUEUED/ASSIGNED print jobs directly via SimplyPrint API and updates local database. PRINTING jobs are flagged for operator review. service: id: OrderService version: 0.0.1 - id: order_cancelled_event title: Order Cancelled message: id: OrderCancelled version: 0.0.1 next_steps: - id: print_service_cancel label: Notify Print Service - id: shipping_service_cancel label: Notify Shipping Service - id: print_service_cancel title: Print Service summary: Receives order.cancelled event via BullMQ and handles any Print Service-side cleanup service: id: PrintService version: 0.0.1 next_step: id: print_job_cancelled_event label: Jobs cancelled - id: print_job_cancelled_event title: Print Job Cancelled message: id: PrintJobCancelled version: 0.0.1 next_step: id: order_service_confirm label: Confirm cancellation to Order Service - id: shipping_service_cancel title: Shipping Service summary: Receives order.cancelled event via BullMQ and cancels shipment / voids label if not yet shipped service: id: ShippingService version: 0.0.1 - id: order_service_confirm title: Order Service summary: Updates order status to fully cancelled after all downstream confirmations service: id: OrderService version: 0.0.1 --- ## Overview The Order Cancellation flow handles the cascading rollback of an order across all downstream services. Cancellation can be triggered by an admin or by the system (e.g., payment failure detected from Shopify). ### Cancellation rules - Orders can only be cancelled if they are **not yet fulfilled** - If print jobs are already completed, cancellation still proceeds (physical items are scrapped) - If a shipment label has already been generated, the label is voided via Sendcloud - If the shipment is already in transit, cancellation is rejected ### Cascading behavior 1. Order Service's `CancellationService` cancels QUEUED/ASSIGNED print jobs locally via the SimplyPrint API (shared `PrintJobsBaseService`). PRINTING jobs are flagged for operator review. 2. Order Service emits `OrderCancelled` event via BullMQ 3. Print Service and Shipping Service subscribe to `order.cancelled` and handle their respective cleanup 4. Print Service emits `PrintJobCancelled` events for cancelled jobs 5. Shipping Service voids the Sendcloud label if applicable 6. Order Service updates final status after downstream confirmations > **Note:** Cancellation uses BullMQ events for cross-service coordination. The `CancelJobsForOrder` and `CancelShipment` internal HTTP endpoints exist on Print and Shipping services but are not called by Order Service in the current implementation. ### Idempotency All cancellation operations are idempotent — calling cancel on an already-cancelled resource is a no-op. --- id: OrderFulfillment name: Order Fulfillment version: 0.0.1 summary: | End-to-end order fulfillment flow — from Shopify webhook ingestion through print job creation, shipping, and final fulfillment back to Shopify. badges: - content: Core Flow backgroundColor: blue textColor: blue steps: - id: shopify_webhook title: Shopify Webhook summary: Customer places an order on the Shopify storefront externalSystem: name: Shopify summary: E-commerce platform url: https://shopify.dev/docs/api/webhooks next_step: id: order_service_ingestion label: Webhook received - id: order_service_ingestion title: Order Service summary: Ingests webhook payload, persists order, and begins orchestration service: id: OrderService version: 0.0.1 next_steps: - id: order_created_event label: Emit OrderCreated - id: stock_consumption label: Try stock consumption - id: create_print_jobs_cmd label: Create print jobs (remaining) - id: stock_consumption title: Stock Consumption summary: | OrchestrationService checks if the product has available stock (currentStock > 0). Consumes from stock first — fully, partially, or not at all — before creating print jobs. service: id: OrderService version: 0.0.1 next_steps: - id: order_service_orchestration label: Fully from stock (skip printing) - id: create_print_jobs_cmd label: Partial or no stock - id: order_created_event title: Order Created message: id: OrderCreated version: 0.0.1 next_step: id: print_service_receives label: Notify downstream services - id: create_print_jobs_cmd title: Create Print Jobs message: id: CreatePrintJobs version: 0.0.1 next_step: id: print_service_receives label: Send to Print Service - id: print_service_receives title: Print Service summary: Creates ORDER-purpose print jobs in SimplyPrint, prioritizes them ahead of any STOCK jobs in the queue (ADR-063), and monitors progress service: id: PrintService version: 0.0.1 next_steps: - id: simplyprint_integration label: Submit to SimplyPrint - id: print_job_completed_event label: All jobs completed - id: print_job_failed_event label: Job failed - id: simplyprint_integration title: SimplyPrint summary: Cloud-based 3D printer management platform externalSystem: name: SimplyPrint summary: 3D printer farm management url: https://simplyprint.io next_step: id: print_service_receives label: Status webhook callback - id: print_job_completed_event title: Print Job Completed message: id: PrintJobCompleted version: 0.0.1 next_step: id: order_service_orchestration label: Notify Order Service - id: print_job_failed_event title: Print Job Failed message: id: PrintJobFailed version: 0.0.1 next_step: id: order_service_orchestration label: Notify Order Service - id: order_service_orchestration title: Order Service summary: Evaluates whether all print jobs are done and the order is ready for shipping service: id: OrderService version: 0.0.1 next_steps: - id: order_ready_event label: All items printed - id: create_shipment_cmd label: Create shipment - id: order_ready_event title: Order Ready for Fulfillment message: id: OrderReadyForFulfillment version: 0.0.1 next_step: id: create_shipment_cmd label: Proceed to shipping - id: create_shipment_cmd title: Create Shipment message: id: CreateShipment version: 0.0.1 next_step: id: shipping_service label: Send to Shipping Service - id: shipping_service title: Shipping Service summary: Creates shipment and generates label via Sendcloud service: id: ShippingService version: 0.0.1 next_steps: - id: sendcloud_integration label: Request label from Sendcloud - id: shipment_created_event label: Shipment created - id: sendcloud_integration title: Sendcloud summary: Shipping label and tracking provider externalSystem: name: Sendcloud summary: Shipping automation platform url: https://www.sendcloud.com next_step: id: shipping_service label: Label generated - id: shipment_created_event title: Shipment Created message: id: ShipmentCreated version: 0.0.1 next_step: id: order_service_fulfillment label: Notify Order Service - id: order_service_fulfillment title: Order Service summary: Creates fulfillment in Shopify to close the order service: id: OrderService version: 0.0.1 next_step: id: shopify_fulfillment label: Create Shopify fulfillment - id: shopify_fulfillment title: Shopify Fulfillment summary: Order is marked as fulfilled in Shopify with tracking info externalSystem: name: Shopify summary: E-commerce platform url: https://shopify.dev/docs/api/admin-rest/2024-01/resources/fulfillment --- ## Overview The Order Fulfillment flow is the core business process of Forma3D.Connect. It traces the full lifecycle of a customer order from initial placement on Shopify through to final fulfillment. ### Key stages 1. **Ingestion** — Shopify sends an `orders/create` webhook to the Order Service 2. **Stock consumption** — Orchestration checks if items can be fulfilled from pre-built stock (hybrid fulfillment) 3. **Print orchestration** — For remaining units not covered by stock, creates print jobs and dispatches to Print Service 4. **Readiness check** — When all print jobs complete (or all items fulfilled from stock), the Order Service marks the order as ready for fulfillment 5. **Shipping** — Shipping Service generates a label via Sendcloud and creates the shipment 6. **Fulfillment** — Order Service creates a fulfillment record in Shopify with tracking information ### Hybrid fulfillment scenarios | Scenario | Stock consumed | Print jobs created | | --- | --- | --- | | **Full stock** | All units from shelf | None — line item marked complete immediately | | **Partial stock** | Some units from shelf | Remaining units printed on demand | | **No stock** | None (stock = 0 or not managed) | All units printed on demand (original behavior) | ### Communication patterns - **Asynchronous events** (BullMQ): `OrderCreated`, `PrintJobCompleted`, `PrintJobFailed`, `OrderReadyForFulfillment`, `ShipmentCreated` - **Synchronous commands** (Internal HTTP): `CreatePrintJobs`, `CreateShipment` - **Internal** (Prisma transactions): Atomic stock consumption with `CONSUMED` transaction recording ### Error handling - Print job failures trigger the retry queue with exponential backoff - After max retries, the order is flagged for manual intervention - Stock consumption uses optimistic locking — if a race condition occurs, the order falls back to print-to-order - Partial fulfillment is not supported — all items must be printed/fulfilled before shipping --- id: ShippingTracking name: Shipping & Tracking version: 0.0.1 summary: | Shipment tracking flow — monitors shipping status changes from Sendcloud and propagates tracking updates back to the order. badges: - content: Supporting Flow backgroundColor: green textColor: green steps: - id: sendcloud_webhook title: Sendcloud summary: Sendcloud sends tracking status update webhooks externalSystem: name: Sendcloud summary: Shipping automation platform url: https://www.sendcloud.com next_step: id: shipping_service label: Webhook received - id: shipping_service title: Shipping Service summary: Processes Sendcloud webhook and updates shipment status service: id: ShippingService version: 0.0.1 next_steps: - id: shipment_status_changed_event label: Status changed - id: update_tracking_cmd label: Push tracking to Order Service - id: shipment_status_changed_event title: Shipment Status Changed message: id: ShipmentStatusChanged version: 0.0.1 next_step: id: order_service_tracking label: Notify Order Service - id: update_tracking_cmd title: Update Tracking message: id: UpdateTracking version: 0.0.1 next_step: id: order_service_tracking label: Send tracking data - id: order_service_tracking title: Order Service summary: Updates order with latest tracking info and triggers notifications service: id: OrderService version: 0.0.1 next_step: id: shopify_tracking label: Sync tracking to Shopify - id: shopify_tracking title: Shopify summary: Tracking information is synced to the Shopify fulfillment externalSystem: name: Shopify summary: E-commerce platform url: https://shopify.dev/docs/api/admin-rest/2024-01/resources/fulfillment --- ## Overview The Shipping & Tracking flow handles the ongoing monitoring of shipment status after a shipment has been created. Sendcloud pushes tracking updates via webhooks, which are processed by the Shipping Service and propagated to the Order Service. ### Tracking lifecycle 1. **Webhook reception** — Sendcloud sends status update webhooks (e.g., `picked_up`, `in_transit`, `delivered`) 2. **Status processing** — Shipping Service validates and persists the status change 3. **Event emission** — `ShipmentStatusChanged` event is published via BullMQ 4. **Command dispatch** — `UpdateTracking` command is sent to Order Service via Internal HTTP 5. **Shopify sync** — Order Service updates the Shopify fulfillment with the latest tracking URL and status ### Status transitions | Sendcloud Status | Internal Status | Shopify Action | | --- | --- | --- | | `ready_to_send` | `label_created` | — | | `handed_to_carrier` | `picked_up` | Add tracking URL | | `in_transit` | `in_transit` | Update tracking | | `delivered` | `delivered` | Mark fulfilled | | `cancelled` | `cancelled` | — | ### Idempotency Duplicate webhook deliveries from Sendcloud are handled gracefully — the Shipping Service checks for status progression and ignores out-of-order or duplicate updates. --- id: StockReplenishment name: Stock Replenishment version: 0.0.1 summary: | Scheduled stock replenishment flow — evaluates inventory deficits, creates stock print batches, and increments stock when batches complete. badges: - content: Inventory Flow backgroundColor: green textColor: green steps: - id: cron_trigger title: Cron Scheduler summary: Periodic trigger evaluates whether stock replenishment is needed service: id: OrderService version: 0.0.1 next_step: id: evaluate_replenishment label: Check conditions - id: evaluate_replenishment title: Evaluate Replenishment summary: | StockReplenishmentService checks global config, time windows, order queue depth, and queries products where currentStock < minimumStock service: id: OrderService version: 0.0.1 next_steps: - id: create_stock_batch label: Deficit found - id: skip_replenishment label: No action needed - id: skip_replenishment title: Skip summary: No products need replenishment or conditions not met (disabled, busy hours, queue full) service: id: OrderService version: 0.0.1 - id: create_stock_batch title: Create Stock Batches summary: | Creates StockBatch + PrintJob records in the database. Uses an inner loop per product: creates up to replenishmentBatchSize batches, re-checking deficit (accounting for pending batches) after each creation. service: id: OrderService version: 0.0.1 next_step: id: stock_scheduled_event label: Publish per print job - id: stock_scheduled_event title: Stock Replenishment Scheduled message: id: StockReplenishmentScheduled version: 0.0.1 next_step: id: print_service_stock label: Send to Print Service - id: print_service_stock title: Print Service summary: Queues the STOCK-purpose print job on SimplyPrint (same flow as ORDER jobs) service: id: PrintService version: 0.0.1 next_steps: - id: simplyprint_stock label: Submit to SimplyPrint - id: stock_job_completed label: Job completed - id: simplyprint_stock title: SimplyPrint summary: 3D printer farm prints the stock item externalSystem: name: SimplyPrint summary: 3D printer farm management url: https://simplyprint.io next_step: id: print_service_stock label: Webhook callback - id: stock_job_completed title: Print Job Completed message: id: PrintJobCompleted version: 0.0.1 next_step: id: inventory_routing label: Route by purpose - id: inventory_routing title: Event Subscriber summary: | EventSubscriberService checks print job purpose. STOCK jobs route to InventoryService instead of the normal order orchestration. service: id: OrderService version: 0.0.1 next_step: id: batch_progress label: Increment batch progress - id: batch_progress title: Batch Progress Check summary: | InventoryService increments completedJobs on the StockBatch. When completedJobs >= totalJobs, the batch is completed. service: id: OrderService version: 0.0.1 next_step: id: stock_increment label: All jobs done - id: stock_increment title: Stock Increment summary: | Atomically increments ProductMapping.currentStock by 1 and records a PRODUCED InventoryTransaction — all inside a Prisma $transaction. service: id: OrderService version: 0.0.1 --- ## Overview The Stock Replenishment flow enables proactive inventory building. Instead of printing every item on demand, products with `minimumStock > 0` are pre-printed during off-peak hours so they can be immediately shipped when orders arrive. ### Key stages 1. **Evaluation** — A Cron scheduler periodically triggers `StockReplenishmentService.evaluateAndSchedule()`. A mutex (`isRunning`) prevents concurrent executions. 2. **Condition checks** — The service verifies global enable flag, allowed time windows, order queue depth, and concurrent stock job limits 3. **Deficit calculation** — Queries products where `currentStock < minimumStock`, ordered by `replenishmentPriority` 4. **Batch creation** — For each product, creates up to `replenishmentBatchSize` batches in a while loop, re-checking deficit (accounting for pending batches) after each batch. Each `StockBatch` has associated `PrintJob` entries (purpose: `STOCK`) 5. **Print execution** — Print Service queues STOCK jobs in SimplyPrint. ORDER jobs are automatically prioritized ahead of STOCK jobs in the queue (ADR-063) 6. **Completion routing** — When jobs complete, `EventSubscriberService` routes STOCK-purpose jobs to `InventoryService` instead of the normal order flow 7. **Stock increment** — On batch completion, `currentStock` is atomically incremented and a transaction is recorded ### Communication patterns - **Asynchronous events** (BullMQ): `StockReplenishmentScheduled`, `PrintJobCompleted` (with purpose routing) - **Internal** (Prisma transactions): Atomic stock increment + transaction recording ### Safeguards - Replenishment only runs during configured time windows (e.g., overnight) - Order queue takes priority — stock jobs pause when order queue exceeds threshold - **Queue priority** — ORDER-purpose jobs are repositioned before STOCK-purpose jobs in SimplyPrint's queue (FIFO within each group, ADR-063) - Concurrent stock job limit prevents printer farm saturation - `maximumStock` cap prevents over-production - **Batch cancellation** — Operators can cancel IN_PROGRESS batches via API (`POST /api/v1/inventory/batches/:id/cancel`), which cancels QUEUED/ASSIGNED print jobs and marks the batch CANCELLED --- dictionary: - id: CurrentStock name: Current Stock summary: The number of pre-printed units of a product currently on the shelf. description: | Tracked on the ProductMapping as `currentStock`. Atomically incremented when a StockBatch completes and decremented when stock is consumed for order fulfillment. Represents the real-time available inventory for a given product. - id: MinimumStock name: Minimum Stock summary: The threshold that triggers automatic replenishment. description: | The `minimumStock` value on a ProductMapping. When `currentStock` drops below this value, the replenishment scheduler creates a stock batch to print more units. Setting this to zero effectively disables automatic replenishment for that product. - id: MaximumStock name: Maximum Stock summary: An optional cap on how many units the replenishment system will produce. description: | The `maximumStock` value on a ProductMapping. Limits the total stock the system will maintain, preventing overproduction. When omitted, no upper bound is enforced. - id: ReplenishmentPriority name: Replenishment Priority summary: Numeric priority determining which products are restocked first. description: | A numeric `replenishmentPriority` on ProductMapping. Higher values are evaluated first when the replenishment scheduler runs, ensuring critical products are restocked before others. - id: ReplenishmentBatchSize name: Replenishment Batch Size summary: The number of print jobs created per replenishment cycle for a product. description: | The `replenishmentBatchSize` on ProductMapping, specifying how many print jobs to create per replenishment cycle for that product. Controls the volume of each restocking operation. - id: StockBatch name: Stock Batch summary: A group of STOCK-purpose print jobs created by a single replenishment cycle. description: | Tracks `totalJobs`, `completedJobs`, and a lifecycle status. When all jobs complete, `currentStock` is atomically incremented by the number of completed jobs. Can be cancelled by operators via the UI or API. - id: StockBatchStatus name: Stock Batch Status summary: Lifecycle state of a StockBatch. description: | `IN_PROGRESS` (jobs printing), `COMPLETED` (all jobs done, stock updated), `FAILED` (jobs failed), `CANCELLED` (operator cancelled the batch). Transitions are driven by print job completion events. - id: InventoryTransaction name: Inventory Transaction summary: An immutable ledger entry recording a stock change. description: | Each transaction has a `transactionType`, `quantity` (always positive), `direction` (IN or OUT), `referenceType`, and `referenceId`. Provides a complete audit trail of all stock movements. - id: TransactionType name: Transaction Type summary: The reason for a stock change. description: | `PRODUCED` (print job completed → stock in), `CONSUMED` (stock allocated to an order → stock out), `ADJUSTMENT_IN` (manual increase), `ADJUSTMENT_OUT` (manual decrease), `SCRAPPED` (damaged/defective units removed). - id: StockDirection name: Stock Direction summary: Whether a transaction adds to (IN) or removes from (OUT) the available stock. description: | Every InventoryTransaction has a direction — `IN` for stock increases and `OUT` for stock decreases. Combined with the always-positive `quantity`, this enables clear ledger accounting. - id: ReferenceType name: Reference Type summary: Links an InventoryTransaction to its source. description: | `STOCK_BATCH` (replenishment), `ORDER` (order consumption), `LINE_ITEM` (line-level consumption), `MANUAL` (operator adjustment or scrap). Enables tracing any stock change back to the originating business operation. - id: StockAwareFulfillment name: Stock-Aware Fulfillment summary: Orchestration logic that checks available stock before creating print jobs. description: | If sufficient stock exists, units are consumed from the shelf and the order skips printing entirely, going directly to fulfillment. This reduces lead times for products with available inventory. - id: HybridFulfillment name: Hybrid Fulfillment summary: Business model where orders can be fulfilled from pre-printed stock or via print-on-demand. description: | Depending on stock availability at the time of order creation, the system either consumes from existing inventory or creates new print jobs. This flexibility allows balancing production efficiency with order responsiveness. - id: ReplenishmentScheduler name: Replenishment Scheduler summary: A cron-based process that evaluates products and creates stock batches when needed. description: | Periodically evaluates all managed products and creates stock batches for any whose `currentStock < minimumStock`. Protected by a mutex to prevent concurrent evaluations from creating duplicate batches. - id: StockManagementFeatureFlag name: Stock Management Feature Flag summary: System configuration key that controls whether inventory tracking is active. description: | The `feature.stock-management.enabled` configuration key controls whether inventory tracking and replenishment are active for a tenant. When disabled, all orders go through standard print-on-demand fulfillment. - id: ManualAdjustment name: Manual Adjustment summary: An operator-initiated stock change with a required reason. description: | An operator can manually increase or decrease stock levels. Recorded as an `ADJUSTMENT_IN` or `ADJUSTMENT_OUT` transaction with an auditable reason. Used for corrections, physical count reconciliation, and other manual interventions. - id: Scrap name: Scrap summary: Recording that pre-printed units are damaged or defective and should be removed from stock. description: | Creates a `SCRAPPED` transaction with direction `OUT`. Used when physical units are damaged, defective, or otherwise unusable. Permanently reduces the `currentStock` count. --- --- dictionary: - id: PrintJob name: Print Job summary: A unit of work representing a single 3D print. description: | Linked to a LineItem and optionally an AssemblyPart (for ORDER purpose) or a StockBatch (for STOCK purpose). Identified externally by `simplyPrintJobId` and `simplyPrintQueueItemId`. - id: PrintJobStatus name: Print Job Status summary: Lifecycle state of a PrintJob. description: | `QUEUED` (awaiting printer assignment), `ASSIGNED` (printer selected), `PRINTING` (actively printing), `COMPLETED` (finished successfully), `FAILED` (print error), `CANCELLED` (operator or cascade cancellation). - id: PrintJobPurpose name: Print Job Purpose summary: Distinguishes why a print job exists. description: | `ORDER` (fulfilling a customer order) or `STOCK` (pre-printing for inventory replenishment). Drives orchestration and inventory transaction logic. - id: CopyNumber name: Copy Number summary: Tracks which copy of a product a PrintJob represents when quantity is greater than one. description: | When a line item has `quantity > 1`, each PrintJob is assigned a `copyNumber` to track which copy of the product it represents. - id: SimplyPrint name: SimplyPrint summary: Third-party 3D printer management platform. description: | Forma3D.Connect uses the SimplyPrint API for file uploads, job creation, printer monitoring, and status synchronization. Serves as the bridge between the software and physical 3D printers. - id: SimplyPrintQueueItemId name: SimplyPrint Queue Item ID summary: The persistent identifier returned by SimplyPrint when a job is queued. description: | The `created_id` returned by SimplyPrint when a job is added to the queue. Used as the stable reference for job operations, distinct from the transient `simplyPrintJobId`. - id: SimplyPrintJobStatus name: SimplyPrint Job Status summary: The status reported by the SimplyPrint API. description: | Values include `queued`, `assigned`, `preparing`, `printing`, `completed`, `failed`, `cancelled`. Mapped to the internal `PrintJobStatus` enum by the integration layer. - id: SimplyPrintPrinterStatus name: SimplyPrint Printer Status summary: The state of a physical printer as reported by SimplyPrint. description: | Values include `offline`, `operational`, `printing`, `paused`, `error`. Cached locally in the `Printer` entity for quick access without API calls. - id: Printer name: Printer summary: A cached representation of a physical 3D printer from SimplyPrint. description: | Tracks `simplyPrintId`, `isOnline`, `currentStatus`, and `currentJobId`. Provides local visibility into printer fleet status without constant API polling. - id: AssemblyPart name: Assembly Part summary: A single printable component within a multi-part product. description: | Defined by `partName`, `partNumber`, `simplyPrintFileId`, `printProfile`, estimated print time and filament usage, and `quantityPerProduct`. Determines how many print jobs are created per line item. - id: ProductMapping name: Product Mapping summary: The bridge between a Shopify SKU/variant and the printable file(s) in SimplyPrint. description: | Contains a `defaultPrintProfile` and zero or more AssemblyParts. Used by the print service to determine what to print and how for each ordered product. - id: PrintProfile name: Print Profile summary: A JSON configuration specifying slicer/printer settings for a print job. description: | Includes layer height, infill, supports, and other settings used when submitting a job to SimplyPrint. Stored on ProductMapping and AssemblyPart. - id: Gcode name: Gcode summary: Machine instructions generated by slicing an STL file. description: | The format uploaded to SimplyPrint for 3D printing. Contains the exact movements and extrusion commands for the printer to execute. - id: STL name: STL summary: Stereolithography file format representing 3D geometry. description: | Source format for printable models before slicing. Describes the surface geometry of a 3D object as a mesh of triangles. - id: FileUpload name: File Upload summary: The process of sending gcode or STL files to SimplyPrint's file system. description: | Returns a `fileId` used to create print jobs. Files are stored in SimplyPrint's cloud storage and referenced when creating new print jobs. - id: StatusPolling name: Status Polling summary: Periodic synchronization of print job statuses from SimplyPrint. description: | Controlled by `SIMPLYPRINT_POLLING_ENABLED` and `SIMPLYPRINT_POLLING_INTERVAL_MS`. Supplements webhook-based updates to ensure no status changes are missed. - id: Reconciliation name: Reconciliation summary: Comparing local PrintJob records against SimplyPrint's state to detect drift. description: | Corrects discrepancies caused by missed webhooks or polling gaps. Ensures the local database accurately reflects the true state of print jobs in SimplyPrint. - id: CancelReason name: Cancel Reason summary: A numeric code from SimplyPrint indicating why a job was cancelled. description: | Examples include print failed, nozzle clog, and operator cancellation. Provides diagnostic context for failed or cancelled print jobs. - id: Requeue name: Requeue summary: An operator action that moves a terminal print job back to QUEUED. description: | Moves a `FAILED` or `CANCELLED` print job back to `QUEUED`, triggering an order revival in the Orders domain. Allows operators to retry failed prints without creating new orders. --- --- dictionary: - id: Shipment name: Shipment summary: A one-to-one record linked to an Order, representing a parcel to be shipped. description: | Contains carrier details, tracking info, label URL, weight, and dimensions. Identified externally by `sendcloudParcelId`. Each order has exactly one shipment. - id: ShipmentStatus name: Shipment Status summary: Lifecycle state of a Shipment. description: | `PENDING` (awaiting label creation), `LABEL_CREATED` (shipping label generated), `ANNOUNCED` (parcel registered with carrier), `IN_TRANSIT` (picked up and moving), `DELIVERED` (successfully received), `FAILED` (delivery or creation error), `CANCELLED` (shipment voided). - id: Sendcloud name: Sendcloud summary: Third-party shipping aggregation platform used for label generation and parcel tracking. description: | Forma3D.Connect uses the Sendcloud API for label generation, carrier selection, parcel tracking, and webhook-based status updates. Abstracts away the complexity of integrating directly with individual carriers. - id: SendcloudConnection name: Sendcloud Connection summary: Per-tenant API credentials for Sendcloud. description: | Consists of encrypted `publicKey` and `secretKey`, plus defaults for shipping method and sender address. Each tenant manages their own Sendcloud integration independently. - id: SendcloudParcelId name: Sendcloud Parcel ID summary: The external identifier assigned by Sendcloud when a parcel is created. description: | Used for all subsequent API operations on that shipment. Serves as the primary key for interacting with the Sendcloud API after parcel creation. - id: ShippingLabel name: Shipping Label summary: A carrier-specific PDF document containing barcode, routing info, and addresses. description: | Generated by Sendcloud and retrieved via `labelUrl`. Contains all information needed for the carrier to process and deliver the parcel. - id: ShippingMethod name: Shipping Method summary: A carrier service option in Sendcloud. description: | Examples include PostNL Standard and DPD Express. Identified by `shippingMethodId` and `shippingMethodName`. A default shipping method is configured per tenant. - id: Carrier name: Carrier summary: The logistics company performing physical delivery. description: | Examples include PostNL, DPD, and DHL. Identified by `carrierName` on the Shipment. Selected based on the shipping method configured for the tenant. - id: TrackingNumber name: Tracking Number summary: The carrier-assigned identifier for a parcel, enabling package tracking. description: | Mirrored on both the Shipment and the parent Order. Passed to Shopify during fulfillment creation for customer visibility. - id: TrackingUrl name: Tracking URL summary: A carrier-provided URL where the recipient can track their shipment. description: | Passed to Shopify during fulfillment creation so customers can monitor their delivery progress directly from their Shopify order confirmation. - id: SendcloudWebhook name: Sendcloud Webhook summary: An HTTP callback from Sendcloud triggered by status changes. description: | Events like `parcel_status_changed` and `integration_connected` are processed by the `SendcloudWebhookService`. Used to keep shipment statuses synchronized with Sendcloud. - id: SendcloudStatusRanges name: Sendcloud Status Ranges summary: Numeric ID ranges that Sendcloud uses to categorize parcel statuses. description: | Ranges cover label created, announced, in transit, delivered, and exception states. Mapped to internal `ShipmentStatus` values by the integration layer. - id: SenderAddress name: Sender Address summary: The shipper's return address in Sendcloud. description: | Identified by `defaultSenderAddressId`. Used when creating parcels to specify the return address on shipping labels. - id: ServicePoint name: Service Point summary: A pickup location where the recipient collects their package instead of home delivery. description: | Examples include parcel shops and lockers. Captured on the Order with `servicePointId`, `servicePointName`, `servicePointAddress`, and `servicePointPostNumber`. - id: WeightAndDimensions name: Weight & Dimensions summary: Physical parcel attributes required for label generation and carrier rate calculation. description: | `weight` in grams and `dimensions` as JSON with length/width/height in centimeters. Required by Sendcloud to generate accurate shipping labels and calculate carrier rates. --- --- dictionary: - id: Gridfinity name: Gridfinity summary: An open-source modular storage system based on a 42mm grid unit. description: | GridFlock generates parametric Gridfinity-compatible baseplates and plate sets. The system is based on a community-driven open standard for modular desktop organization. - id: GridUnit name: Grid Unit summary: The standard Gridfinity cell size of 42mm. description: | Defined as `GRID_UNIT_MM`. All grid dimensions are multiples of this unit. Determines the fundamental sizing of all generated baseplates and plate sets. - id: Baseplate name: Baseplate summary: A single Gridfinity base grid, generated as an STL from dimensional parameters. description: | Specified as width × depth in grid units. One of two GridFlock job types (`BASEPLATE`). Generated parametrically via OpenSCAD based on the requested dimensions and connector configuration. - id: PlateSet name: Plate Set summary: A multi-plate Gridfinity configuration that exceeds the printer's build area. description: | The system automatically calculates the optimal tiling of plates. The second GridFlock job type (`PLATE_SET`). Parent jobs spawn child jobs for individual plates. - id: GridFlockJob name: GridFlock Job summary: A unit of work for parametric grid generation. description: | Has a `type` (BASEPLATE or PLATE_SET), JSON `parameters`, generated file metadata, and `progress` tracking. Plate set jobs have parent/child relationships where the parent tracks overall completion. - id: GridFlockJobStatus name: GridFlock Job Status summary: Lifecycle state of a GridFlock Job. description: | `QUEUED` (awaiting processing), `PROCESSING` (actively generating/slicing), `COMPLETED` (pipeline finished), `FAILED` (error at any step), `EXPIRED` (cleanup after configurable hours). - id: GridFlockPipeline name: GridFlock Pipeline summary: The end-to-end process from STL generation to product mapping creation. description: | STL generation (OpenSCAD) → slicing (BambuStudio CLI) → upload to SimplyPrint → product mapping creation. Runs buffer-based with sequential plate processing to manage memory. - id: FailedStep name: Failed Step summary: The specific pipeline stage where an error occurred. description: | One of `stl-generation`, `slicing`, `simplyprint-upload`, or `mapping-creation`. Reported in the `grid.pipeline-failed` event to enable targeted debugging and retry. - id: OpenSCAD name: OpenSCAD summary: Open-source script-based CAD engine used for parametric STL generation. description: | Generates Gridfinity STL geometry from grid dimensions, connector types, and magnet configurations. Runs as a native process for automated, on-demand model generation. - id: Slicer name: Slicer / Slicing summary: The process of converting an STL 3D model into gcode machine instructions. description: | Performed by a headless BambuStudio CLI running in a dedicated Docker container (`SlicerContainer`). Takes raw 3D geometry and produces printer-ready instructions. - id: SlicerContainer name: Slicer Container summary: A standalone HTTP service wrapping BambuStudio CLI. description: | Accepts STL buffers via `POST /slice` and returns gcode buffers. Decoupled from the Grid Service for resource isolation, preventing CPU-intensive slicing from affecting other services. - id: ConnectorType name: Connector Type summary: The interlocking mechanism between Gridfinity baseplates. description: | Options include `intersection-puzzle` (IP), `edge-puzzle` (EP), or `none`. Determines the geometry at plate boundaries, enabling plates to physically connect to each other. - id: GridFlockPreset name: GridFlock Preset summary: A saved set of generation parameters for quick reuse. description: | Includes dimensions, connector type, magnet configuration, and other parameters. Can be `isPublic` (shared across tenants), `isSystem` (built-in defaults), or tenant-scoped. - id: MagnetParameters name: Magnet Parameters summary: Configuration for optional magnet holes in the baseplate. description: | Specifies presence, diameter, and depth of magnet holes. Part of the generation parameters. Allows baseplates to securely attach to metal surfaces. - id: SlicerMarginBrim name: Slicer Margin / Brim summary: Parameters controlling the border area added during slicing for adhesion. description: | Affects the printable area calculation for plate sets. Controls how much extra material is added around the base of the print for build-plate adhesion. - id: MaxDimension name: Max Dimension summary: The configurable maximum grid size for GridFlock generation. description: | Configured via `grid.max_dimension_mm`. Orders exceeding this are rejected. A separate `MAX_SYNC_GRID_SIZE` threshold determines whether generation runs synchronously or is queued. - id: PrinterProfile name: Printer Profile summary: The target printer model specification used by the slicer. description: | Examples include "Bambu Lab A1". Used by the slicer to select appropriate settings for the target hardware. Configured per tenant via `grid.print_settings`. - id: GridPricing name: Grid Pricing summary: Per-tenant pricing model for custom grids. description: | Includes `grid.basePrice` (fixed fee), `grid.unitPrice` (per grid unit), `grid.unitSizeMm`, and `grid.currency`. Stored in SystemConfig. Determines the cost charged to customers for custom grid generation. - id: FeatureFlag name: Feature Flag summary: System configuration key that controls whether GridFlock is active for a tenant. description: | The `feature.grid.enabled` key. Exposed via the gateway's `TenantFeaturesService`. When disabled, GridFlock SKUs are treated as regular products. - id: GridCustomSku name: GRID-CUSTOM SKU summary: The Shopify SKU prefix that identifies a line item as a GridFlock custom product. description: | Any SKU starting with `GRID-CUSTOM` triggers the GridFlock pipeline during order orchestration. The parameters for generation are parsed from the SKU or order metadata. --- --- dictionary: - id: Order name: Order summary: A tenant-scoped purchase record ingested from Shopify via webhook. description: | Contains customer details, shipping address, pricing, and tracks fulfillment progress through `totalParts` / `completedParts` counters. Each order has a lifecycle status and is linked to one or more line items. - id: LineItem name: Line Item summary: A single product line within an Order. description: | Identified by `shopifyLineItemId` and `productSku`. Links to a ProductMapping for printing. Carries its own status and part counts independently of the parent Order. - id: ProductMapping name: Product Mapping summary: The link between a Shopify SKU/variant and the printable file(s) in SimplyPrint. description: | Includes a `defaultPrintProfile` and optional assembly part definitions. Shared with the Printing and Inventory domains. Serves as the bridge between e-commerce products and 3D printing workflows. - id: AssemblyPart name: Assembly Part summary: One printable component of a multi-part product. description: | Defined by `partName`, `partNumber`, `quantityPerProduct`, and a `simplyPrintFileId`. A single ProductMapping may have many AssemblyParts, each representing a separate 3D-printed component. - id: Orchestration name: Orchestration summary: Coordination logic that monitors downstream progress and determines fulfillment readiness. description: | Monitors print jobs and GridFlock pipelines, determines when an order is ready for shipping and Shopify fulfillment. Lives in the `OrchestrationService`. - id: Fulfillment name: Fulfillment summary: The act of creating a Shopify fulfillment record with tracking information. description: | Triggered once a shipment is confirmed. Distinct from the physical shipping step — fulfillment is the Shopify API call that marks the order as shipped with tracking details. - id: OrderStatus name: Order Status summary: The lifecycle state of an Order. description: | `PENDING` (received, awaiting processing), `PROCESSING` (print jobs active), `PARTIALLY_COMPLETED` (some jobs done, some failed), `COMPLETED` (all parts fulfilled), `FAILED` (all jobs failed), `CANCELLED` (operator or Shopify cancellation). - id: LineItemStatus name: Line Item Status summary: The lifecycle state of a Line Item. description: | `PENDING`, `PRINTING`, `PARTIALLY_COMPLETED`, `COMPLETED`, `FAILED`. Tracked independently from the parent Order status. - id: OrderRevival name: Order Revival summary: Automatic reversion of a terminal order back to PROCESSING when a print job is requeued. description: | When a `FAILED` or `PARTIALLY_COMPLETED` order has a terminal print job requeued by an operator, the order automatically reverts to `PROCESSING` and part counts are recalculated. - id: ShopifyWebhook name: Shopify Webhook summary: An HTTP callback from Shopify that triggers order ingestion. description: | Events like `orders/create` and `orders/updated` trigger order processing. Deduplicated via `X-Shopify-Webhook-Id` stored in the `ProcessedWebhook` table. - id: ProcessedWebhook name: Processed Webhook summary: An idempotency record that prevents duplicate webhook processing. description: | Keyed by `webhookId` and `webhookType`. Ensures the same Shopify event is not processed more than once, even if Shopify retries delivery. - id: ShopifyShop name: Shopify Shop summary: A tenant's Shopify store connection. description: | Holds the encrypted `accessToken`, granted `scopes`, `webhookSecret`, and install/uninstall timestamps. Represents the OAuth-based connection between a tenant and their Shopify store. - id: EventLog name: Event Log summary: A structured business event record for operational visibility. description: | Contains `eventType`, `severity` (INFO, WARNING, ERROR), and optional links to an Order or PrintJob. Used for operational visibility, not security audit. - id: RetryQueue name: Retry Queue summary: An exponential-backoff retry mechanism for failed downstream operations. description: | Each entry has a `RetryJobType` (FULFILLMENT, PRINT_JOB_CREATION, CANCELLATION, NOTIFICATION, SHIPMENT, GRIDFLOCK_PIPELINE) and a `RetryStatus` (PENDING, PROCESSING, COMPLETED, FAILED). - id: ServicePointDelivery name: Service Point Delivery summary: An alternative delivery mode where the package is sent to a pickup location. description: | Captured by `deliveryType` (`home` vs `service_point`) and Sendcloud service-point metadata on the Order. Allows customers to collect packages from convenient pickup points. - id: GridFlockCustomProduct name: GridFlock Custom Product summary: A line item whose SKU starts with GRID-CUSTOM, requiring parametric grid generation. description: | Indicates the product requires Gridfinity grid generation before printing. Triggers the GridFlock pipeline during orchestration, which generates the STL, slices it, and creates a product mapping. - id: PartCounts name: Part Counts summary: Counters that track fulfillment progress on Orders and Line Items. description: | `totalParts` and `completedParts` tracked on both Order and LineItem. Orchestration uses these to determine when all printable components are done and the order is ready for fulfillment. --- --- dictionary: - id: Tenant name: Tenant summary: An isolated organizational unit within Forma3D.Connect. description: | Identified by a `slug`. Each tenant has its own users, roles, integrations, feature flags, and configuration. The `isDefault` tenant serves as the system default. - id: User name: User summary: An authenticated operator or administrator within a tenant. description: | Assigned one or more Roles that grant Permissions. Authenticated via session-based auth with cookie-based identity. - id: Role name: Role summary: A named set of permissions assigned to users. description: | `admin` (full access), `operator` (day-to-day operations), `viewer` (read-only), `legacy-admin` (API key backward compatibility). Each role has a specific set of permissions attached. - id: Permission name: Permission summary: A granular access right following the resource.action pattern. description: | Examples include `orders.read`, `printJobs.write`, `inventory.write`, `admin.operations`. Assigned to Roles via RolePermission join records. Controls what actions users can perform. - id: SessionBasedAuth name: Session-Based Auth summary: The authentication mechanism using express-session backed by PostgreSQL. description: | No JWTs — sessions are server-side with cookie-based identity. The session store is backed by PostgreSQL for persistence across server restarts. - id: ApiKey name: API Key summary: A legacy authentication mechanism for machine-to-machine access. description: | Validated by the gateway. Used by the `legacy-admin` role for automated integrations and backward compatibility with older API consumers. - id: SystemConfig name: System Config summary: Per-tenant key-value configuration stored as JSON. description: | Controls feature flags, integration settings, pricing, and operational parameters. Examples include `feature.grid.enabled`, `stock.replenishment.enabled`, and grid pricing configuration. - id: FeatureFlag name: Feature Flag summary: A SystemConfig entry that enables or disables functionality per tenant. description: | Examples include `feature.grid.enabled` and `feature.stock-management.enabled`. Exposed via the gateway's `TenantFeaturesService`. Allows gradual rollout and per-tenant customization. - id: Gateway name: Gateway summary: The API gateway application that handles HTTP routing and authentication. description: | Handles HTTP routing, WebSocket proxying, authentication, rate limiting, and Swagger aggregation. Proxies requests to downstream services: order-service, print-service, shipping-service, grid-service, and slicer. - id: BullMQEventBus name: BullMQ Event Bus summary: The cross-service asynchronous messaging infrastructure built on BullMQ. description: | Redis-backed queues where each event type corresponds to a named queue. The canonical event names are defined in `SERVICE_EVENTS`. Provides reliable, persistent message delivery between services. - id: EventEmitter2 name: EventEmitter2 summary: The in-process event system used for intra-service communication. description: | NestJS-based event system. Bridge services translate between local EventEmitter2 events and cross-service BullMQ events. Used for decoupling components within a single service. - id: SocketIO name: Socket.IO summary: The real-time WebSocket transport used to push events to the PWA frontend. description: | Operates on the `/events` namespace with `WsApiKeyGuard` authentication. Emits events like `order:created`, `printjob:status-changed`, etc. Enables live dashboard updates. - id: CorrelationId name: Correlation ID summary: A unique identifier propagated across service boundaries to trace business operations. description: | Used by `BusinessObservabilityService` to trace a single business operation through logs and events across all services. - id: AuditLog name: Audit Log summary: A security-focused record of sensitive actions. description: | Captures `actorUserId`, `targetType`, `targetId`, and `metadata` for actions like `auth.login.success` and role changes. Distinct from the business EventLog used for operational visibility. - id: HealthCheck name: Health Check summary: Aggregated downstream service health verification exposed by the gateway. description: | Checks connectivity to all proxied services and infrastructure dependencies. Provides a single endpoint that reports the overall system health status. - id: PushSubscription name: Push Subscription summary: A Web Push API subscription stored per user for browser push notifications. description: | Each subscription has toggles for order, print, shipment, and system notification categories. Allows users to receive real-time alerts even when the browser tab is closed. - id: IntegrationReinitialization name: Integration Reinitialization summary: Live-reloading third-party API clients when tenant settings change. description: | Allows reloading SimplyPrint and Sendcloud API clients without requiring a service restart. Triggered by `integration.simplyprint-changed` and `integration.sendcloud-changed` events. - id: RetryQueue name: Retry Queue summary: A platform-level exponential-backoff mechanism for failed operations. description: | Supports job types across all domains (FULFILLMENT, PRINT_JOB_CREATION, CANCELLATION, NOTIFICATION, SHIPMENT, GRIDFLOCK_PIPELINE) with statuses PENDING, PROCESSING, COMPLETED, FAILED. - id: BullBoard name: Bull Board summary: A monitoring UI for BullMQ queues. description: | Provides visibility into event bus queue depths, failed jobs, and processing rates. Used by operators and developers to monitor and troubleshoot the messaging infrastructure. - id: Sentry name: Sentry summary: The observability platform integrated for error tracking and performance monitoring. description: | Provides error tracking, performance monitoring, and structured logging across all services. Captures unhandled exceptions, slow transactions, and custom metrics. ---