Skip to main content

backbone_inventory/application/service/
inventory_events.rs

1//! Inventory domain events — the public extension surface (hand-authored, user-owned).
2//!
3//! Semantic events a consumer subscribes to (per the module brief), distinct from generated CRUD
4//! events. Published through an `InventoryEventSink`. Notably `StockDelivered` lets `backbone-selling`
5//! advance its `delivered_qty` watermark, and `StockReceived` lets buying reconcile a PO.
6//!
7//! Every payload's `company_id` is a legacy twin (ADR-0029): filled from the ambient org scope's
8//! company echo for consumers that still read a tenant off the wire. No module statement keys on
9//! it.
10
11use rust_decimal::Decimal;
12use serde::{Deserialize, Serialize};
13use uuid::Uuid;
14
15/// Goods received into stock (Purchase Receipt submitted).
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17pub struct StockReceived {
18    pub receipt_id: Uuid,
19    pub company_id: Uuid,
20    pub warehouse_id: Uuid,
21    pub source_po_id: Option<Uuid>,
22    pub total_value: Decimal,
23}
24
25/// Stock delivered out (Delivery Note submitted) — carries the source SO so selling can advance
26/// its delivered_qty watermark.
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
28pub struct StockDelivered {
29    pub delivery_id: Uuid,
30    pub company_id: Uuid,
31    pub warehouse_id: Uuid,
32    pub source_so_id: Option<Uuid>,
33    pub total_cogs: Decimal,
34}
35
36/// Stock moved between warehouses (Stock Entry submitted) — value-neutral.
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
38pub struct StockMoved {
39    pub entry_id: Uuid,
40    pub company_id: Uuid,
41    pub from_warehouse_id: Option<Uuid>,
42    pub to_warehouse_id: Option<Uuid>,
43}
44
45/// Stock adjusted to a physical count (Stock Reconciliation submitted).
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
47pub struct StockReconciled {
48    pub reconciliation_id: Uuid,
49    pub company_id: Uuid,
50    pub warehouse_id: Uuid,
51    pub net_difference: Decimal,
52}
53
54// ---- stock-move pipeline events (schema/hooks/stock.hook.yaml `events:` block) -------------
55
56/// A stock move hit `confirmed` (spec §1 `_action_confirm`).
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
58pub struct MoveConfirmed {
59    pub move_id: Uuid,
60    pub company_id: Uuid,
61    pub item_id: Uuid,
62    pub demand_qty: Decimal,
63    pub picking_id: Option<Uuid>,
64}
65
66/// A stock move hit `assigned` — fully reserved against quants (spec §1 `_action_assign`).
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
68pub struct MoveAssigned {
69    pub move_id: Uuid,
70    pub company_id: Uuid,
71    pub picking_id: Option<Uuid>,
72}
73
74/// A stock move hit `done` — quants flipped, SLE minted (spec §4 `_action_done`).
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
76pub struct MoveDone {
77    pub move_id: Uuid,
78    pub company_id: Uuid,
79    pub item_id: Uuid,
80    pub quantity: Decimal,
81    pub price_unit: Decimal,
82    pub is_inventory: bool,
83}
84
85/// A stock move hit `cancel` — its reservation was released (spec §1 `_action_cancel`).
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
87pub struct MoveCancelled {
88    pub move_id: Uuid,
89    pub company_id: Uuid,
90    pub released_qty: Decimal,
91}
92
93/// A transfer's projected state changed (the T1 picking projection recompute — the picking has no
94/// state machine of its own; its state is re-derived from its moves on every move change).
95#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
96pub struct TransferProjected {
97    pub transfer_id: Uuid,
98    pub company_id: Uuid,
99    pub state: String,
100    pub previous_state: String,
101}
102
103/// A partial validate minted the backorder move (spec §4 step 4).
104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
105pub struct BackorderCreated {
106    pub backorder_id: Uuid,
107    pub company_id: Uuid,
108    pub origin_id: Uuid,
109}
110
111/// The daily scheduler ordered replenishment on a reordering rule (spec §7 task 1; the T11
112/// orderpoint computes decided the quantity).
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
114pub struct OrderpointTriggered {
115    pub orderpoint_id: Uuid,
116    pub company_id: Uuid,
117    pub item_id: Uuid,
118    pub qty_to_order: Decimal,
119    pub forecast_qty: Decimal,
120}
121
122/// A landed-cost document was validated: its cost lines were allocated over the target
123/// receipt's DONE move lines and the remaining-share portion revalued the bins through the
124/// move engine's adjustment verb.
125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
126pub struct LandedCostValidated {
127    pub lc_id: Uuid,
128    pub company_id: Uuid,
129    pub target_receipt_id: Uuid,
130    /// Σ cost line amounts (negative on a reversal document).
131    pub amount_total: Decimal,
132    /// The remaining-share value actually revalued onto the bins (the effective Σ of the
133    /// minted landed-cost SLE rows — smaller than `amount_total` when part of the target stock
134    /// was already consumed: the retroactive-revaluation asymmetry).
135    pub revalued_value: Decimal,
136}
137
138/// The inventory domain-event union (discriminated) published on the module event bus.
139#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
140#[serde(tag = "type")]
141pub enum InventoryEvent {
142    StockReceived(StockReceived),
143    StockDelivered(StockDelivered),
144    StockMoved(StockMoved),
145    StockReconciled(StockReconciled),
146    MoveConfirmed(MoveConfirmed),
147    MoveAssigned(MoveAssigned),
148    MoveDone(MoveDone),
149    MoveCancelled(MoveCancelled),
150    TransferProjected(TransferProjected),
151    BackorderCreated(BackorderCreated),
152    OrderpointTriggered(OrderpointTriggered),
153    LandedCostValidated(LandedCostValidated),
154}
155
156/// Sink for inventory domain events. Fire-and-forget; a real adapter wires a bus, tests record.
157pub trait InventoryEventSink: Send + Sync {
158    fn publish(&self, event: InventoryEvent);
159}
160
161/// Default sink — emits structured tracing events.
162pub struct LoggingSink;
163
164impl InventoryEventSink for LoggingSink {
165    fn publish(&self, event: InventoryEvent) {
166        tracing::info!(target: "inventory.events", ?event, "inventory domain event");
167    }
168}