chore: absorbe nakui (ERP matemático) en modules/nakui
- crates/modules/nakui/core/: el crate nakui-core (4 bins, tests).
Deps directas (serde, rhai, surrealdb, petgraph, sha2, uuid, tokio,
thiserror v1) — no convertidas a workspace = true en esta pasada.
- crates/modules/nakui/modules/{inventory,sales,treasury}/: datos
declarativos del dominio (nsmc.json, schema.k, morphisms/) que el
crate consume — no son crates.
cargo check -p nakui-core: 0 errores.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
// recibir_stock
|
||||
// Inflow: external supplier delivers `cantidad` units into a Stock record.
|
||||
// NOT conservation-bound: the units enter the system from outside.
|
||||
//
|
||||
// states.stock: the current Stock record.
|
||||
// ids.stock: its UUID (string).
|
||||
// params: { cantidad:i64, timestamp:str, movimiento_id:str }
|
||||
|
||||
let cantidad = input.params.cantidad;
|
||||
if cantidad <= 0 {
|
||||
throw "cantidad debe ser positiva (la dirección la fija el morfismo)"
|
||||
}
|
||||
let mov_id = input.params.movimiento_id;
|
||||
if type_of(mov_id) == "()" {
|
||||
throw "params.movimiento_id es obligatorio (idempotencia)"
|
||||
}
|
||||
|
||||
[
|
||||
#{
|
||||
op: "set",
|
||||
path: #{ entity: "Stock", id: input.ids.stock, field: "cantidad" },
|
||||
value: input.states.stock.cantidad + cantidad,
|
||||
},
|
||||
#{
|
||||
op: "create",
|
||||
entity: "MovimientoStock",
|
||||
id: mov_id,
|
||||
data: #{
|
||||
id: mov_id,
|
||||
stock_id: input.ids.stock,
|
||||
delta: cantidad,
|
||||
razon: "recepcion",
|
||||
timestamp: input.params.timestamp,
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,50 @@
|
||||
// transferir_stock
|
||||
// Two-input morphism. Conservation rule (Stock.cantidad grouped by sku_id)
|
||||
// is enforced by the kernel against the produced ops. Same-SKU is also
|
||||
// asserted in-script for an explicit error message; without that check
|
||||
// the kernel would still catch the violation via per-sku grouping.
|
||||
//
|
||||
// states.source / states.dest: the two Stock records.
|
||||
// ids.source / ids.dest: their UUIDs.
|
||||
// params: { cantidad:i64, timestamp:str, transfer_id:str }
|
||||
|
||||
let cantidad = input.params.cantidad;
|
||||
let source = input.states.source;
|
||||
let dest = input.states.dest;
|
||||
|
||||
if cantidad <= 0 {
|
||||
throw "cantidad debe ser positiva"
|
||||
}
|
||||
if source.sku_id != dest.sku_id {
|
||||
throw "transferencia exige mismo SKU; source=" + source.sku_id + " dest=" + dest.sku_id
|
||||
}
|
||||
let xfr_id = input.params.transfer_id;
|
||||
if type_of(xfr_id) == "()" {
|
||||
throw "params.transfer_id es obligatorio (idempotencia)"
|
||||
}
|
||||
|
||||
[
|
||||
#{
|
||||
op: "set",
|
||||
path: #{ entity: "Stock", id: input.ids.source, field: "cantidad" },
|
||||
value: source.cantidad - cantidad,
|
||||
},
|
||||
#{
|
||||
op: "set",
|
||||
path: #{ entity: "Stock", id: input.ids.dest, field: "cantidad" },
|
||||
value: dest.cantidad + cantidad,
|
||||
},
|
||||
#{
|
||||
op: "create",
|
||||
entity: "TransferenciaStock",
|
||||
id: xfr_id,
|
||||
data: #{
|
||||
id: xfr_id,
|
||||
source_stock_id: input.ids.source,
|
||||
dest_stock_id: input.ids.dest,
|
||||
sku_id: source.sku_id,
|
||||
cantidad: cantidad,
|
||||
timestamp: input.params.timestamp,
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"module": "inventory",
|
||||
"morphisms": [
|
||||
{
|
||||
"name": "recibir_stock",
|
||||
"inputs": [
|
||||
{ "role": "stock", "entity": "Stock" }
|
||||
],
|
||||
"reads": ["stock.cantidad", "stock.sku_id"],
|
||||
"writes": ["stock.cantidad", "MovimientoStock"],
|
||||
"depends_on": [],
|
||||
"script": "morphisms/recibir_stock.rhai"
|
||||
},
|
||||
{
|
||||
"name": "transferir_stock",
|
||||
"inputs": [
|
||||
{ "role": "source", "entity": "Stock" },
|
||||
{ "role": "dest", "entity": "Stock" }
|
||||
],
|
||||
"reads": ["source.cantidad", "source.sku_id", "dest.cantidad", "dest.sku_id"],
|
||||
"writes": ["source.cantidad", "dest.cantidad", "TransferenciaStock"],
|
||||
"invariants": {
|
||||
"conserve": [
|
||||
{ "entity": "Stock", "field": "cantidad", "group_by": "sku_id" }
|
||||
]
|
||||
},
|
||||
"depends_on": [],
|
||||
"script": "morphisms/transferir_stock.rhai"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
schema Stock:
|
||||
id: str
|
||||
sku_id: str
|
||||
ubicacion: str
|
||||
cantidad: int
|
||||
|
||||
check:
|
||||
cantidad >= 0, "stock no puede ser negativo"
|
||||
len(ubicacion) > 0, "ubicacion requerida"
|
||||
len(sku_id) > 0, "sku_id requerido"
|
||||
|
||||
schema MovimientoStock:
|
||||
id: str
|
||||
stock_id: str
|
||||
delta: int
|
||||
razon: str
|
||||
timestamp: str
|
||||
|
||||
check:
|
||||
razon in ["recepcion", "despacho", "ajuste"], "razon invalida"
|
||||
delta != 0, "delta no puede ser cero"
|
||||
|
||||
schema TransferenciaStock:
|
||||
id: str
|
||||
source_stock_id: str
|
||||
dest_stock_id: str
|
||||
sku_id: str
|
||||
cantidad: int
|
||||
timestamp: str
|
||||
|
||||
check:
|
||||
cantidad > 0, "cantidad debe ser positiva"
|
||||
source_stock_id != dest_stock_id, "source y dest no pueden ser el mismo stock"
|
||||
len(sku_id) > 0, "sku_id requerido"
|
||||
@@ -0,0 +1,52 @@
|
||||
// vender
|
||||
// Cross-module morphism: decreases Stock (inventory) and increases Caja
|
||||
// (treasury). NOT conservation-bound — a sale is asymmetric: units leave
|
||||
// the system, money enters. The kernel validates each entity against its
|
||||
// own schema (Stock from inventory, Caja from treasury, Venta from sales).
|
||||
//
|
||||
// states.stock: the Stock record (inventory module).
|
||||
// states.caja: the Caja record (treasury module).
|
||||
// params: { cantidad:i64, precio_unitario:i64 (en centavos),
|
||||
// timestamp:str, venta_id:str }
|
||||
|
||||
let cantidad = input.params.cantidad;
|
||||
let precio = input.params.precio_unitario;
|
||||
let venta_id = input.params.venta_id;
|
||||
|
||||
if cantidad <= 0 { throw "cantidad debe ser positiva" }
|
||||
if precio <= 0 { throw "precio_unitario debe ser positivo" }
|
||||
if type_of(venta_id) == "()" { throw "params.venta_id es obligatorio (idempotencia)" }
|
||||
|
||||
let stock = input.states.stock;
|
||||
let caja = input.states.caja;
|
||||
|
||||
let total = cantidad * precio;
|
||||
|
||||
[
|
||||
#{
|
||||
op: "set",
|
||||
path: #{ entity: "Stock", id: input.ids.stock, field: "cantidad" },
|
||||
value: stock.cantidad - cantidad,
|
||||
},
|
||||
#{
|
||||
op: "set",
|
||||
path: #{ entity: "Caja", id: input.ids.caja, field: "saldo" },
|
||||
value: caja.saldo + total,
|
||||
},
|
||||
#{
|
||||
op: "create",
|
||||
entity: "Venta",
|
||||
id: venta_id,
|
||||
data: #{
|
||||
id: venta_id,
|
||||
stock_id: input.ids.stock,
|
||||
caja_id: input.ids.caja,
|
||||
sku_id: stock.sku_id,
|
||||
cantidad: cantidad,
|
||||
precio_unitario: precio,
|
||||
currency: caja.currency,
|
||||
total: total,
|
||||
timestamp: input.params.timestamp,
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"module": "sales",
|
||||
"schemas": [
|
||||
"schema.k",
|
||||
"../treasury/schema.k",
|
||||
"../inventory/schema.k"
|
||||
],
|
||||
"morphisms": [
|
||||
{
|
||||
"name": "vender",
|
||||
"inputs": [
|
||||
{ "role": "stock", "entity": "Stock" },
|
||||
{ "role": "caja", "entity": "Caja" }
|
||||
],
|
||||
"reads": ["stock.cantidad", "stock.sku_id", "caja.saldo", "caja.currency"],
|
||||
"writes": ["stock.cantidad", "caja.saldo", "Venta"],
|
||||
"depends_on": [],
|
||||
"script": "morphisms/vender.rhai"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
schema Venta:
|
||||
id: str
|
||||
stock_id: str
|
||||
caja_id: str
|
||||
sku_id: str
|
||||
cantidad: int
|
||||
precio_unitario: int
|
||||
currency: str
|
||||
total: int
|
||||
timestamp: str
|
||||
|
||||
check:
|
||||
cantidad > 0, "cantidad positiva"
|
||||
precio_unitario > 0, "precio_unitario positivo"
|
||||
len(currency) == 3, "currency ISO 4217"
|
||||
total == cantidad * precio_unitario, "total debe ser cantidad * precio_unitario"
|
||||
@@ -0,0 +1,46 @@
|
||||
// register_cash_move
|
||||
// Pure transition. Receives `input` as { states, ids, params }; returns
|
||||
// an array of FieldOp objects. No clock, no random, no IO.
|
||||
//
|
||||
// states.caja: the current Caja record loaded from the store.
|
||||
// ids.caja: the Caja's UUID (string form).
|
||||
// params: { monto:i64, tipo:"in"|"out", timestamp:str, memo:str, movimiento_id:str }
|
||||
|
||||
let saldo = input.states.caja.saldo;
|
||||
let monto = input.params.monto;
|
||||
let tipo = input.params.tipo;
|
||||
let caja_id = input.ids.caja;
|
||||
|
||||
let nuevo_saldo = if tipo == "in" {
|
||||
saldo + monto
|
||||
} else if tipo == "out" {
|
||||
saldo - monto
|
||||
} else {
|
||||
throw "tipo desconocido: " + tipo
|
||||
};
|
||||
|
||||
let mov_id = input.params.movimiento_id;
|
||||
if type_of(mov_id) == "()" {
|
||||
throw "params.movimiento_id es obligatorio (idempotencia)"
|
||||
}
|
||||
|
||||
[
|
||||
#{
|
||||
op: "set",
|
||||
path: #{ entity: "Caja", id: caja_id, field: "saldo" },
|
||||
value: nuevo_saldo,
|
||||
},
|
||||
#{
|
||||
op: "create",
|
||||
entity: "Movimiento",
|
||||
id: mov_id,
|
||||
data: #{
|
||||
id: mov_id,
|
||||
caja_id: caja_id,
|
||||
monto: monto,
|
||||
tipo: tipo,
|
||||
timestamp: input.params.timestamp,
|
||||
memo: input.params.memo,
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,53 @@
|
||||
// transfer_between_cajas
|
||||
// Two-input morphism. Conservation rule (Caja.saldo grouped by currency)
|
||||
// is checked by the kernel against the produced ops; the script just emits
|
||||
// the deltas.
|
||||
//
|
||||
// states.source / states.dest: the two Caja records.
|
||||
// ids.source / ids.dest: their UUIDs (string form).
|
||||
// params: { monto:i64, timestamp:str, memo:str, transfer_id:str }
|
||||
|
||||
let source = input.states.source;
|
||||
let dest = input.states.dest;
|
||||
let monto = input.params.monto;
|
||||
|
||||
if monto <= 0 {
|
||||
throw "monto debe ser positivo"
|
||||
}
|
||||
if source.currency != dest.currency {
|
||||
throw "transferencia exige misma moneda; source=" + source.currency + " dest=" + dest.currency
|
||||
}
|
||||
let transfer_id = input.params.transfer_id;
|
||||
if type_of(transfer_id) == "()" {
|
||||
throw "params.transfer_id es obligatorio (idempotencia)"
|
||||
}
|
||||
|
||||
let new_source_saldo = source.saldo - monto;
|
||||
let new_dest_saldo = dest.saldo + monto;
|
||||
|
||||
[
|
||||
#{
|
||||
op: "set",
|
||||
path: #{ entity: "Caja", id: input.ids.source, field: "saldo" },
|
||||
value: new_source_saldo,
|
||||
},
|
||||
#{
|
||||
op: "set",
|
||||
path: #{ entity: "Caja", id: input.ids.dest, field: "saldo" },
|
||||
value: new_dest_saldo,
|
||||
},
|
||||
#{
|
||||
op: "create",
|
||||
entity: "Transferencia",
|
||||
id: transfer_id,
|
||||
data: #{
|
||||
id: transfer_id,
|
||||
source_caja_id: input.ids.source,
|
||||
dest_caja_id: input.ids.dest,
|
||||
monto: monto,
|
||||
currency: source.currency,
|
||||
timestamp: input.params.timestamp,
|
||||
memo: input.params.memo,
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"module": "treasury",
|
||||
"morphisms": [
|
||||
{
|
||||
"name": "register_cash_move",
|
||||
"inputs": [
|
||||
{ "role": "caja", "entity": "Caja" }
|
||||
],
|
||||
"reads": ["caja.saldo", "caja.currency"],
|
||||
"writes": ["caja.saldo", "Movimiento"],
|
||||
"depends_on": [],
|
||||
"script": "morphisms/register_cash_move.rhai"
|
||||
},
|
||||
{
|
||||
"name": "transfer_between_cajas",
|
||||
"inputs": [
|
||||
{ "role": "source", "entity": "Caja" },
|
||||
{ "role": "dest", "entity": "Caja" }
|
||||
],
|
||||
"reads": ["source.saldo", "source.currency", "dest.saldo", "dest.currency"],
|
||||
"writes": ["source.saldo", "dest.saldo", "Transferencia"],
|
||||
"invariants": {
|
||||
"conserve": [
|
||||
{ "entity": "Caja", "field": "saldo", "group_by": "currency" }
|
||||
]
|
||||
},
|
||||
"depends_on": [],
|
||||
"script": "morphisms/transfer_between_cajas.rhai"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
schema Caja:
|
||||
id: str
|
||||
name: str
|
||||
saldo: int
|
||||
currency: str
|
||||
|
||||
check:
|
||||
saldo >= 0, "saldo de caja no puede ser negativo"
|
||||
len(currency) == 3, "currency debe ser ISO 4217 (3 letras)"
|
||||
|
||||
schema Movimiento:
|
||||
id: str
|
||||
caja_id: str
|
||||
monto: int
|
||||
tipo: str
|
||||
timestamp: str
|
||||
memo?: str
|
||||
|
||||
check:
|
||||
monto > 0, "monto debe ser positivo (la direccion la fija el tipo)"
|
||||
tipo in ["in", "out"], "tipo debe ser 'in' u 'out'"
|
||||
|
||||
schema Transferencia:
|
||||
id: str
|
||||
source_caja_id: str
|
||||
dest_caja_id: str
|
||||
monto: int
|
||||
currency: str
|
||||
timestamp: str
|
||||
memo?: str
|
||||
|
||||
check:
|
||||
monto > 0, "monto debe ser positivo"
|
||||
len(currency) == 3, "currency ISO 4217"
|
||||
source_caja_id != dest_caja_id, "source y dest no pueden ser la misma caja"
|
||||
Reference in New Issue
Block a user