@soostori/desktop-adapter 0.1.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth-pin.d.ts +16 -0
- package/dist/auth-pin.d.ts.map +1 -0
- package/dist/auth-pin.js +14 -0
- package/dist/auth-pin.js.map +1 -0
- package/dist/business-repository.d.ts +31 -0
- package/dist/business-repository.d.ts.map +1 -0
- package/dist/business-repository.js +61 -0
- package/dist/business-repository.js.map +1 -0
- package/dist/categories-repository.d.ts +15 -0
- package/dist/categories-repository.d.ts.map +1 -0
- package/dist/categories-repository.js +35 -0
- package/dist/categories-repository.js.map +1 -0
- package/dist/customers-repository.d.ts +20 -0
- package/dist/customers-repository.d.ts.map +1 -0
- package/dist/customers-repository.js +95 -0
- package/dist/customers-repository.js.map +1 -0
- package/dist/debts-repository.d.ts +48 -0
- package/dist/debts-repository.d.ts.map +1 -0
- package/dist/debts-repository.js +57 -0
- package/dist/debts-repository.js.map +1 -0
- package/dist/devices-repository.d.ts +29 -0
- package/dist/devices-repository.d.ts.map +1 -0
- package/dist/devices-repository.js +120 -0
- package/dist/devices-repository.js.map +1 -0
- package/dist/domain-repositories.d.ts +12 -0
- package/dist/domain-repositories.d.ts.map +1 -0
- package/dist/domain-repositories.js +12 -0
- package/dist/domain-repositories.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/inventory-repository.d.ts +31 -0
- package/dist/inventory-repository.d.ts.map +1 -0
- package/dist/inventory-repository.js +127 -0
- package/dist/inventory-repository.js.map +1 -0
- package/dist/products-repository-mappers.d.ts +77 -0
- package/dist/products-repository-mappers.d.ts.map +1 -0
- package/dist/products-repository-mappers.js +75 -0
- package/dist/products-repository-mappers.js.map +1 -0
- package/dist/products-repository-read.d.ts +17 -0
- package/dist/products-repository-read.d.ts.map +1 -0
- package/dist/products-repository-read.js +51 -0
- package/dist/products-repository-read.js.map +1 -0
- package/dist/products-repository-stock.d.ts +28 -0
- package/dist/products-repository-stock.d.ts.map +1 -0
- package/dist/products-repository-stock.js +57 -0
- package/dist/products-repository-stock.js.map +1 -0
- package/dist/products-repository-types.d.ts +51 -0
- package/dist/products-repository-types.d.ts.map +1 -0
- package/dist/products-repository-types.js +5 -0
- package/dist/products-repository-types.js.map +1 -0
- package/dist/products-repository-variants.d.ts +19 -0
- package/dist/products-repository-variants.d.ts.map +1 -0
- package/dist/products-repository-variants.js +26 -0
- package/dist/products-repository-variants.js.map +1 -0
- package/dist/products-repository-write.d.ts +14 -0
- package/dist/products-repository-write.d.ts.map +1 -0
- package/dist/products-repository-write.js +99 -0
- package/dist/products-repository-write.js.map +1 -0
- package/dist/products-repository.d.ts +69 -0
- package/dist/products-repository.d.ts.map +1 -0
- package/dist/products-repository.js +48 -0
- package/dist/products-repository.js.map +1 -0
- package/dist/sales-repository.d.ts +95 -0
- package/dist/sales-repository.d.ts.map +1 -0
- package/dist/sales-repository.js +160 -0
- package/dist/sales-repository.js.map +1 -0
- package/dist/sqlite-database.d.ts +16 -0
- package/dist/sqlite-database.d.ts.map +1 -0
- package/dist/sqlite-database.js +24 -0
- package/dist/sqlite-database.js.map +1 -0
- package/dist/sqlite-repository.d.ts +18 -0
- package/dist/sqlite-repository.d.ts.map +1 -0
- package/dist/sqlite-repository.js +61 -0
- package/dist/sqlite-repository.js.map +1 -0
- package/dist/sqlite-transaction.d.ts +13 -0
- package/dist/sqlite-transaction.d.ts.map +1 -0
- package/dist/sqlite-transaction.js +35 -0
- package/dist/sqlite-transaction.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain-repositories.d.ts","sourceRoot":"","sources":["../src/domain-repositories.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAC/D,OAAO,EAAE,sBAAsB,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain repositories index — exports all domain repositories for Phase 9.2 wiring.
|
|
3
|
+
*
|
|
4
|
+
* These repositories expose Desktop's existing SQLite tables through the canonical
|
|
5
|
+
* SDK Repository<T> interface. No existing IPC handlers or table schemas are modified.
|
|
6
|
+
*/
|
|
7
|
+
export { ProductsRepository } from './products-repository.js';
|
|
8
|
+
export { CategoriesRepository } from './categories-repository.js';
|
|
9
|
+
export { CustomersRepository } from './customers-repository.js';
|
|
10
|
+
export { DesktopSalesRepository } from './sales-repository.js';
|
|
11
|
+
export { DebtsRepository } from './debts-repository.js';
|
|
12
|
+
//# sourceMappingURL=domain-repositories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain-repositories.js","sourceRoot":"","sources":["../src/domain-repositories.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAA;AAC/D,OAAO,EAAE,sBAAsB,EAAsB,MAAM,uBAAuB,CAAA;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @soostori/desktop-adapter — Desktop (Electron) platform adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements SDK repository contracts over the existing soostori.db SQLite database.
|
|
5
|
+
* All business logic remains in canonical SDK packages.
|
|
6
|
+
* This adapter is internal to the soostori-sdk workspace — NOT published to NPM.
|
|
7
|
+
*
|
|
8
|
+
* Usage from Electron main process:
|
|
9
|
+
* import { setDatabase } from '@soostori/desktop-adapter'
|
|
10
|
+
* import { getDatabase } from 'soostori-desktop/electron/database'
|
|
11
|
+
* setDatabase(getDatabase())
|
|
12
|
+
*
|
|
13
|
+
* Then IPC handlers can use:
|
|
14
|
+
* import { DesktopDevicesRepository, DesktopBusinessRepository } from '@soostori/desktop-adapter'
|
|
15
|
+
*/
|
|
16
|
+
export { setDatabase, getDatabase, isDatabaseSet } from './sqlite-database.js';
|
|
17
|
+
export { SqliteRepository } from './sqlite-repository.js';
|
|
18
|
+
export { SqliteTransactionHandle } from './sqlite-transaction.js';
|
|
19
|
+
export { DesktopDevicesRepository } from './devices-repository.js';
|
|
20
|
+
export { DesktopBusinessRepository } from './business-repository.js';
|
|
21
|
+
export { ProductsRepository, CategoriesRepository, CustomersRepository, DesktopSalesRepository, type PaymentMethod, } from './domain-repositories.js';
|
|
22
|
+
export { DesktopInventoryRepository } from './inventory-repository.js';
|
|
23
|
+
export { hashPin, verifyPin, ROLE_PERMISSIONS, hasPermission, } from './auth-pin.js';
|
|
24
|
+
export { asUserId, asShopId, asEmployeeId, asDeviceId, asProductId, asCategoryId } from '@soostori/core';
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAA;AACpE,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,KAAK,aAAa,GACnB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAA;AACtE,OAAO,EACL,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,aAAa,GACd,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @soostori/desktop-adapter — Desktop (Electron) platform adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements SDK repository contracts over the existing soostori.db SQLite database.
|
|
5
|
+
* All business logic remains in canonical SDK packages.
|
|
6
|
+
* This adapter is internal to the soostori-sdk workspace — NOT published to NPM.
|
|
7
|
+
*
|
|
8
|
+
* Usage from Electron main process:
|
|
9
|
+
* import { setDatabase } from '@soostori/desktop-adapter'
|
|
10
|
+
* import { getDatabase } from 'soostori-desktop/electron/database'
|
|
11
|
+
* setDatabase(getDatabase())
|
|
12
|
+
*
|
|
13
|
+
* Then IPC handlers can use:
|
|
14
|
+
* import { DesktopDevicesRepository, DesktopBusinessRepository } from '@soostori/desktop-adapter'
|
|
15
|
+
*/
|
|
16
|
+
export { setDatabase, getDatabase, isDatabaseSet } from './sqlite-database.js';
|
|
17
|
+
export { SqliteRepository } from './sqlite-repository.js';
|
|
18
|
+
export { SqliteTransactionHandle } from './sqlite-transaction.js';
|
|
19
|
+
export { DesktopDevicesRepository } from './devices-repository.js';
|
|
20
|
+
export { DesktopBusinessRepository } from './business-repository.js';
|
|
21
|
+
export { ProductsRepository, CategoriesRepository, CustomersRepository, DesktopSalesRepository, } from './domain-repositories.js';
|
|
22
|
+
export { DesktopInventoryRepository } from './inventory-repository.js';
|
|
23
|
+
export { hashPin, verifyPin, ROLE_PERMISSIONS, hasPermission, } from './auth-pin.js';
|
|
24
|
+
export { asUserId, asShopId, asEmployeeId, asDeviceId, asProductId, asCategoryId } from '@soostori/core';
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAA;AACpE,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GAEvB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAA;AACtE,OAAO,EACL,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,aAAa,GACd,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inventory repository — implements @soostori/inventory InventoryRepository over Desktop SQLite.
|
|
3
|
+
*
|
|
4
|
+
* The `inventory_transactions` table IS the canonical ledger (append-only).
|
|
5
|
+
* `products.current_stock` is the cached balance — updated by the repository.
|
|
6
|
+
*
|
|
7
|
+
* Desktop legacy tables (stock_movements, stock_adjustment_log) are NOT used by this
|
|
8
|
+
* adapter — they are deprecated in favor of inventory_transactions.
|
|
9
|
+
*/
|
|
10
|
+
import type { InventoryRepository, MovementFilter } from '@soostori/inventory';
|
|
11
|
+
import type { StockMovement, StockBalance, StockReservation, StockSummary } from '@soostori/inventory';
|
|
12
|
+
import type { UUID } from '@soostori/core';
|
|
13
|
+
export declare class DesktopInventoryRepository implements InventoryRepository {
|
|
14
|
+
getMovement(id: UUID): Promise<StockMovement | null>;
|
|
15
|
+
listMovements(filter?: MovementFilter, pagination?: {
|
|
16
|
+
limit?: number;
|
|
17
|
+
offset?: number;
|
|
18
|
+
}): Promise<StockMovement[]>;
|
|
19
|
+
appendMovement(movement: StockMovement): Promise<void>;
|
|
20
|
+
hasMovementByKey(idempotencyKey: UUID): Promise<boolean>;
|
|
21
|
+
getLatestMovement(productId: UUID, _productVariantId?: UUID | null): Promise<StockMovement | null>;
|
|
22
|
+
getStockSummary(shopId: UUID, productId: UUID): Promise<StockSummary | null>;
|
|
23
|
+
getBalance(productId: UUID, _productVariantId?: UUID | null): Promise<StockBalance | null>;
|
|
24
|
+
upsertBalance(_balance: StockBalance): Promise<void>;
|
|
25
|
+
createReservation(_r: StockReservation): Promise<void>;
|
|
26
|
+
getReservation(_id: UUID): Promise<StockReservation | null>;
|
|
27
|
+
getReservationsBySale(_saleId: UUID): Promise<StockReservation[]>;
|
|
28
|
+
updateReservationStatus(_id: UUID, _s: StockReservation['status']): Promise<void>;
|
|
29
|
+
getActiveReservations(_productId: UUID): Promise<StockReservation[]>;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=inventory-repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory-repository.d.ts","sourceRoot":"","sources":["../src/inventory-repository.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACtG,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,gBAAgB,CAAA;AAgCnD,qBAAa,0BAA2B,YAAW,mBAAmB;IAC9D,WAAW,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAKpD,aAAa,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,UAAU,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAalH,cAAc,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBtD,gBAAgB,CAAC,cAAc,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAOxD,iBAAiB,CAAC,SAAS,EAAE,IAAI,EAAE,iBAAiB,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAOlG,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IA0B5E,UAAU,CAAC,SAAS,EAAE,IAAI,EAAE,iBAAiB,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAgB1F,aAAa,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpD,iBAAiB,CAAC,EAAE,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IACtD,cAAc,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAC3D,qBAAqB,CAAC,OAAO,EAAE,IAAI,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IACjE,uBAAuB,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,gBAAgB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IACjF,qBAAqB,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAC3E"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inventory repository — implements @soostori/inventory InventoryRepository over Desktop SQLite.
|
|
3
|
+
*
|
|
4
|
+
* The `inventory_transactions` table IS the canonical ledger (append-only).
|
|
5
|
+
* `products.current_stock` is the cached balance — updated by the repository.
|
|
6
|
+
*
|
|
7
|
+
* Desktop legacy tables (stock_movements, stock_adjustment_log) are NOT used by this
|
|
8
|
+
* adapter — they are deprecated in favor of inventory_transactions.
|
|
9
|
+
*/
|
|
10
|
+
import { getDatabase } from './sqlite-database.js';
|
|
11
|
+
import { randomUUID } from 'crypto';
|
|
12
|
+
function rowToMovement(row) {
|
|
13
|
+
return {
|
|
14
|
+
id: row.id,
|
|
15
|
+
shopId: row.shop_id,
|
|
16
|
+
productId: row.product_id,
|
|
17
|
+
productVariantId: null,
|
|
18
|
+
type: row.event_type,
|
|
19
|
+
quantity: row.quantity,
|
|
20
|
+
balanceAfter: row.balance_after,
|
|
21
|
+
referenceId: row.payload ? JSON.parse(row.payload).referenceId ?? null : null,
|
|
22
|
+
referenceType: row.payload ? JSON.parse(row.payload).referenceType ?? null : null,
|
|
23
|
+
reason: row.payload ? JSON.parse(row.payload).reason ?? null : null,
|
|
24
|
+
actorType: 'employee',
|
|
25
|
+
actorId: row.user_id,
|
|
26
|
+
deviceId: row.device_id,
|
|
27
|
+
timestamp: row.created_at,
|
|
28
|
+
sequence: row.sequence_number,
|
|
29
|
+
idempotencyKey: (row.idempotency_key ?? randomUUID()),
|
|
30
|
+
syncedAt: null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export class DesktopInventoryRepository {
|
|
34
|
+
async getMovement(id) {
|
|
35
|
+
const row = getDatabase().prepare('SELECT * FROM inventory_transactions WHERE id = ?').get(id);
|
|
36
|
+
return row ? rowToMovement(row) : null;
|
|
37
|
+
}
|
|
38
|
+
async listMovements(filter, pagination) {
|
|
39
|
+
const db = getDatabase();
|
|
40
|
+
let sql = 'SELECT * FROM inventory_transactions WHERE 1=1';
|
|
41
|
+
const params = [];
|
|
42
|
+
if (filter?.productId) {
|
|
43
|
+
sql += ' AND product_id = ?';
|
|
44
|
+
params.push(filter.productId);
|
|
45
|
+
}
|
|
46
|
+
if (filter?.type) {
|
|
47
|
+
sql += ' AND event_type = ?';
|
|
48
|
+
params.push(filter.type);
|
|
49
|
+
}
|
|
50
|
+
sql += ' ORDER BY created_at DESC';
|
|
51
|
+
if (pagination?.limit) {
|
|
52
|
+
sql += ' LIMIT ?';
|
|
53
|
+
params.push(pagination.limit);
|
|
54
|
+
}
|
|
55
|
+
if (pagination?.offset) {
|
|
56
|
+
sql += ' OFFSET ?';
|
|
57
|
+
params.push(pagination.offset);
|
|
58
|
+
}
|
|
59
|
+
const rows = db.prepare(sql).all(...params);
|
|
60
|
+
return rows.map(rowToMovement);
|
|
61
|
+
}
|
|
62
|
+
async appendMovement(movement) {
|
|
63
|
+
const db = getDatabase();
|
|
64
|
+
db.prepare(`
|
|
65
|
+
INSERT INTO inventory_transactions
|
|
66
|
+
(id, shop_id, product_id, device_id, user_id, event_type, quantity, balance_after, status, payload, sequence_number, idempotency_key, created_at)
|
|
67
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
68
|
+
`).run(movement.id, movement.shopId, movement.productId, movement.deviceId, movement.actorId ?? null, movement.type, movement.quantity, movement.balanceAfter, 'confirmed', movement.reason || movement.referenceId ? JSON.stringify({ reason: movement.reason, referenceId: movement.referenceId, referenceType: movement.referenceType }) : null, movement.sequence, movement.idempotencyKey, movement.timestamp);
|
|
69
|
+
// Update cached balance on products
|
|
70
|
+
db.prepare('UPDATE products SET current_stock = ?, updated_at = ? WHERE id = ?')
|
|
71
|
+
.run(movement.balanceAfter, movement.timestamp, movement.productId);
|
|
72
|
+
}
|
|
73
|
+
async hasMovementByKey(idempotencyKey) {
|
|
74
|
+
const row = getDatabase().prepare('SELECT 1 FROM inventory_transactions WHERE idempotency_key = ? LIMIT 1').get(idempotencyKey);
|
|
75
|
+
return row !== undefined;
|
|
76
|
+
}
|
|
77
|
+
async getLatestMovement(productId, _productVariantId) {
|
|
78
|
+
const row = getDatabase().prepare('SELECT * FROM inventory_transactions WHERE product_id = ? ORDER BY sequence_number DESC LIMIT 1').get(productId);
|
|
79
|
+
return row ? rowToMovement(row) : null;
|
|
80
|
+
}
|
|
81
|
+
async getStockSummary(shopId, productId) {
|
|
82
|
+
const db = getDatabase();
|
|
83
|
+
const product = db.prepare('SELECT name FROM products WHERE id = ?').get(productId);
|
|
84
|
+
if (!product)
|
|
85
|
+
return null;
|
|
86
|
+
const last = await this.getLatestMovement(productId);
|
|
87
|
+
const sold = db.prepare("SELECT COALESCE(SUM(quantity), 0) as total FROM inventory_transactions WHERE product_id = ? AND event_type = 'sold'").get(productId);
|
|
88
|
+
const received = db.prepare("SELECT COALESCE(SUM(quantity), 0) as total FROM inventory_transactions WHERE product_id = ? AND event_type = 'received'").get(productId);
|
|
89
|
+
const adjusted = db.prepare("SELECT COALESCE(SUM(quantity), 0) as total FROM inventory_transactions WHERE product_id = ? AND event_type = 'adjusted'").get(productId);
|
|
90
|
+
return {
|
|
91
|
+
productId, shopId,
|
|
92
|
+
productName: product.name,
|
|
93
|
+
currentQuantity: last?.balanceAfter ?? 0,
|
|
94
|
+
totalReceived: received.total,
|
|
95
|
+
totalSold: Math.abs(sold.total),
|
|
96
|
+
totalAdjusted: adjusted.total,
|
|
97
|
+
totalTransferred: 0,
|
|
98
|
+
lastMovementAt: last?.timestamp ?? null,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async getBalance(productId, _productVariantId) {
|
|
102
|
+
const db = getDatabase();
|
|
103
|
+
const product = db.prepare('SELECT current_stock FROM products WHERE id = ?').get(productId);
|
|
104
|
+
if (!product)
|
|
105
|
+
return null;
|
|
106
|
+
const last = await this.getLatestMovement(productId);
|
|
107
|
+
return {
|
|
108
|
+
productId,
|
|
109
|
+
shopId: last?.shopId ?? '',
|
|
110
|
+
productVariantId: null,
|
|
111
|
+
quantity: product.current_stock,
|
|
112
|
+
reservedQuantity: 0,
|
|
113
|
+
lastSequence: last?.sequence ?? 0,
|
|
114
|
+
updatedAt: last?.timestamp ?? new Date().toISOString(),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async upsertBalance(_balance) {
|
|
118
|
+
// Balance is updated by appendMovement — no separate upsert needed
|
|
119
|
+
}
|
|
120
|
+
// Reservation stubs — not used by Desktop in Phase 9.2
|
|
121
|
+
async createReservation(_r) { }
|
|
122
|
+
async getReservation(_id) { return null; }
|
|
123
|
+
async getReservationsBySale(_saleId) { return []; }
|
|
124
|
+
async updateReservationStatus(_id, _s) { }
|
|
125
|
+
async getActiveReservations(_productId) { return []; }
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=inventory-repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory-repository.js","sourceRoot":"","sources":["../src/inventory-repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row mappers — Desktop DB rows → SDK domain types.
|
|
3
|
+
*
|
|
4
|
+
* shopId is read from ProductsRepository._currentSaleMeta at mapp time.
|
|
5
|
+
* The orchestrator calls ProductsRepository.setSaleMeta({ shopId }) before any
|
|
6
|
+
* sale operation; the mapper extracts it here.
|
|
7
|
+
*/
|
|
8
|
+
import type { Product, Category } from '@soostori/core';
|
|
9
|
+
import type { ProductVariant } from './products-repository-types.js';
|
|
10
|
+
export declare function setCurrentMeta(meta: {
|
|
11
|
+
shopId?: string;
|
|
12
|
+
saleId?: string;
|
|
13
|
+
userId?: string;
|
|
14
|
+
deviceId?: string;
|
|
15
|
+
}): void;
|
|
16
|
+
/** Desktop categories row — internal to the adapter, not part of SDK contract. */
|
|
17
|
+
export interface CategoryRow {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
description: string | null;
|
|
21
|
+
icon: string | null;
|
|
22
|
+
color: string;
|
|
23
|
+
display_order: number;
|
|
24
|
+
is_active: number;
|
|
25
|
+
created_at: string;
|
|
26
|
+
updated_at: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ProductRow {
|
|
29
|
+
id: string;
|
|
30
|
+
category_id: string | null;
|
|
31
|
+
name: string;
|
|
32
|
+
sku: string | null;
|
|
33
|
+
barcode: string | null;
|
|
34
|
+
description: string | null;
|
|
35
|
+
image_url: string | null;
|
|
36
|
+
cost_price: number;
|
|
37
|
+
selling_price: number;
|
|
38
|
+
discount_price: number | null;
|
|
39
|
+
unit: string;
|
|
40
|
+
stock_quantity: number;
|
|
41
|
+
current_stock: number;
|
|
42
|
+
low_stock_threshold: number;
|
|
43
|
+
track_inventory: number;
|
|
44
|
+
has_variants: number;
|
|
45
|
+
parent_variant_id: string | null;
|
|
46
|
+
expiry_date: string | null;
|
|
47
|
+
metadata: string | null;
|
|
48
|
+
is_active: number;
|
|
49
|
+
created_at: string;
|
|
50
|
+
updated_at: string;
|
|
51
|
+
deleted_at: string | null;
|
|
52
|
+
distributor_name: string | null;
|
|
53
|
+
distributor_phone: string | null;
|
|
54
|
+
barcode_generated: number;
|
|
55
|
+
allow_single_unit_sale: number;
|
|
56
|
+
units_per_package: number | null;
|
|
57
|
+
box_buying_price: number | null;
|
|
58
|
+
bulk_selling_price: number | null;
|
|
59
|
+
group_prices: string | null;
|
|
60
|
+
}
|
|
61
|
+
export interface VariantRow {
|
|
62
|
+
id: string;
|
|
63
|
+
product_id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
sku: string | null;
|
|
66
|
+
barcode: string | null;
|
|
67
|
+
cost_price: number | null;
|
|
68
|
+
selling_price: number | null;
|
|
69
|
+
stock_quantity: number;
|
|
70
|
+
metadata: string | null;
|
|
71
|
+
created_at: string;
|
|
72
|
+
updated_at: string;
|
|
73
|
+
}
|
|
74
|
+
export declare function rowToProduct(row: ProductRow): Product;
|
|
75
|
+
export declare function rowToCategory(row: CategoryRow): Category;
|
|
76
|
+
export declare function rowToVariant(row: VariantRow): ProductVariant;
|
|
77
|
+
//# sourceMappingURL=products-repository-mappers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"products-repository-mappers.d.ts","sourceRoot":"","sources":["../src/products-repository-mappers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row mappers — Desktop DB rows → SDK domain types.
|
|
3
|
+
*
|
|
4
|
+
* shopId is read from ProductsRepository._currentSaleMeta at mapp time.
|
|
5
|
+
* The orchestrator calls ProductsRepository.setSaleMeta({ shopId }) before any
|
|
6
|
+
* sale operation; the mapper extracts it here.
|
|
7
|
+
*/
|
|
8
|
+
import { asProductId, asCategoryId, asShopId } from '@soostori/core';
|
|
9
|
+
// Shared mutable context — written by ProductsRepository.setSaleMeta, read by mappers.
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
let _currentMeta = {};
|
|
12
|
+
export function setCurrentMeta(meta) {
|
|
13
|
+
_currentMeta = meta;
|
|
14
|
+
}
|
|
15
|
+
function currentShopId() {
|
|
16
|
+
if (!_currentMeta.shopId)
|
|
17
|
+
throw new Error('shopId not set — call setCurrentMeta({ shopId }) before row mapping');
|
|
18
|
+
return asShopId(_currentMeta.shopId);
|
|
19
|
+
}
|
|
20
|
+
export function rowToProduct(row) {
|
|
21
|
+
return {
|
|
22
|
+
id: asProductId(row.id),
|
|
23
|
+
shopId: currentShopId(),
|
|
24
|
+
categoryId: row.category_id ? asCategoryId(row.category_id) : null,
|
|
25
|
+
name: row.name,
|
|
26
|
+
sku: row.sku ?? null,
|
|
27
|
+
barcode: row.barcode ?? null,
|
|
28
|
+
description: row.description ?? null,
|
|
29
|
+
costPrice: (row.cost_price ?? 0),
|
|
30
|
+
sellingPrice: row.selling_price,
|
|
31
|
+
groupPrices: row.group_prices ?? null,
|
|
32
|
+
isGroup: row.has_variants === 1,
|
|
33
|
+
unitsPerPackage: row.units_per_package ?? 1,
|
|
34
|
+
stockQuantity: row.stock_quantity ?? 0,
|
|
35
|
+
currentStock: row.current_stock ?? 0,
|
|
36
|
+
lowStockThreshold: row.low_stock_threshold ?? 5,
|
|
37
|
+
trackInventory: row.track_inventory === 1,
|
|
38
|
+
allowSingleUnitSale: row.allow_single_unit_sale === 1,
|
|
39
|
+
distributorName: row.distributor_name ?? null,
|
|
40
|
+
distributorPhone: row.distributor_phone ?? null,
|
|
41
|
+
image: row.image_url ?? null,
|
|
42
|
+
isActive: row.is_active === 1,
|
|
43
|
+
createdAt: row.created_at,
|
|
44
|
+
updatedAt: row.updated_at,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function rowToCategory(row) {
|
|
48
|
+
return {
|
|
49
|
+
id: asCategoryId(row.id),
|
|
50
|
+
shopId: currentShopId(),
|
|
51
|
+
name: row.name,
|
|
52
|
+
description: row.description ?? null,
|
|
53
|
+
color: row.color,
|
|
54
|
+
isActive: row.is_active === 1,
|
|
55
|
+
createdAt: row.created_at,
|
|
56
|
+
updatedAt: row.updated_at,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function rowToVariant(row) {
|
|
60
|
+
return {
|
|
61
|
+
// Variant IDs use a distinct brand — bypass cast required (was as unknown as before)
|
|
62
|
+
id: asProductId(row.id),
|
|
63
|
+
productId: asProductId(row.product_id),
|
|
64
|
+
name: row.name,
|
|
65
|
+
sku: row.sku ?? null,
|
|
66
|
+
barcode: row.barcode ?? null,
|
|
67
|
+
costPrice: row.cost_price ?? null,
|
|
68
|
+
sellingPrice: row.selling_price ?? null,
|
|
69
|
+
stockQuantity: row.stock_quantity ?? 0,
|
|
70
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : null,
|
|
71
|
+
createdAt: row.created_at,
|
|
72
|
+
updatedAt: row.updated_at,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=products-repository-mappers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"products-repository-mappers.js","sourceRoot":"","sources":["../src/products-repository-mappers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Products read operations — queries only, no side effects.
|
|
3
|
+
*/
|
|
4
|
+
import type { Product, Category } from '@soostori/core';
|
|
5
|
+
import type { ProductId, CategoryId } from './products-repository-types.js';
|
|
6
|
+
export declare class ProductsReadRepository {
|
|
7
|
+
findById(id: ProductId): Promise<Product | null>;
|
|
8
|
+
findMany(filter?: {
|
|
9
|
+
categoryId?: CategoryId;
|
|
10
|
+
search?: string;
|
|
11
|
+
activeOnly?: boolean;
|
|
12
|
+
}): Promise<Product[]>;
|
|
13
|
+
findByBarcode(barcode: string): Promise<Product | null>;
|
|
14
|
+
findCategoryById(id: CategoryId): Promise<Category | null>;
|
|
15
|
+
findCategories(): Promise<Category[]>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=products-repository-read.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"products-repository-read.d.ts","sourceRoot":"","sources":["../src/products-repository-read.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAI3E,qBAAa,sBAAsB;IAC3B,QAAQ,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAOhD,QAAQ,CAAC,MAAM,CAAC,EAAE;QACtB,UAAU,CAAC,EAAE,UAAU,CAAA;QACvB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,UAAU,CAAC,EAAE,OAAO,CAAA;KACrB,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAmBhB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAOvD,gBAAgB,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAO1D,cAAc,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;CAM5C"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Products read operations — queries only, no side effects.
|
|
3
|
+
*/
|
|
4
|
+
import { getDatabase } from './sqlite-database.js';
|
|
5
|
+
import { rowToProduct, rowToCategory } from './products-repository-mappers.js';
|
|
6
|
+
export class ProductsReadRepository {
|
|
7
|
+
async findById(id) {
|
|
8
|
+
const row = getDatabase()
|
|
9
|
+
.prepare('SELECT * FROM products WHERE id = ? AND deleted_at IS NULL')
|
|
10
|
+
.get(id);
|
|
11
|
+
return row ? rowToProduct(row) : null;
|
|
12
|
+
}
|
|
13
|
+
async findMany(filter) {
|
|
14
|
+
const db = getDatabase();
|
|
15
|
+
const conditions = ['deleted_at IS NULL'];
|
|
16
|
+
const values = [];
|
|
17
|
+
if (filter?.categoryId) {
|
|
18
|
+
conditions.push('category_id = ?');
|
|
19
|
+
values.push(filter.categoryId);
|
|
20
|
+
}
|
|
21
|
+
if (filter?.search) {
|
|
22
|
+
conditions.push('(name LIKE ? OR barcode LIKE ? OR sku LIKE ?)');
|
|
23
|
+
const term = `%${filter.search}%`;
|
|
24
|
+
values.push(term, term, term);
|
|
25
|
+
}
|
|
26
|
+
if (filter?.activeOnly)
|
|
27
|
+
conditions.push('is_active = 1');
|
|
28
|
+
const where = `WHERE ${conditions.join(' AND ')}`;
|
|
29
|
+
const rows = db.prepare(`SELECT * FROM products ${where}`).all(...values);
|
|
30
|
+
return rows.map(rowToProduct);
|
|
31
|
+
}
|
|
32
|
+
async findByBarcode(barcode) {
|
|
33
|
+
const row = getDatabase()
|
|
34
|
+
.prepare('SELECT * FROM products WHERE barcode = ? AND deleted_at IS NULL AND is_active = 1')
|
|
35
|
+
.get(barcode);
|
|
36
|
+
return row ? rowToProduct(row) : null;
|
|
37
|
+
}
|
|
38
|
+
async findCategoryById(id) {
|
|
39
|
+
const row = getDatabase()
|
|
40
|
+
.prepare('SELECT * FROM categories WHERE id = ?')
|
|
41
|
+
.get(id);
|
|
42
|
+
return row ? rowToCategory(row) : null;
|
|
43
|
+
}
|
|
44
|
+
async findCategories() {
|
|
45
|
+
const rows = getDatabase()
|
|
46
|
+
.prepare('SELECT * FROM categories ORDER BY display_order, name')
|
|
47
|
+
.all();
|
|
48
|
+
return rows.map(rowToCategory);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=products-repository-read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"products-repository-read.js","sourceRoot":"","sources":["../src/products-repository-read.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAKlD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAE9E,MAAM,OAAO,sBAAsB;IACjC,KAAK,CAAC,QAAQ,CAAC,EAAa;QAC1B,MAAM,GAAG,GAAG,WAAW,EAAE;aACtB,OAAO,CAAC,4DAA4D,CAAC;aACrE,GAAG,CAAC,EAAY,CAA2B,CAAA;QAC9C,OAAO,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACvC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,MAId;QACC,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;QACxB,MAAM,UAAU,GAAG,CAAC,oBAAoB,CAAC,CAAA;QACzC,MAAM,MAAM,GAAwB,EAAE,CAAA;QACtC,IAAI,MAAM,EAAE,UAAU,EAAE,CAAC;YACvB,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;YAClC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAoB,CAAC,CAAA;QAC1C,CAAC;QACD,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;YACnB,UAAU,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAA;YAChE,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,MAAM,GAAG,CAAA;YACjC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QAC/B,CAAC;QACD,IAAI,MAAM,EAAE,UAAU;YAAE,UAAU,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;QACxD,MAAM,KAAK,GAAG,SAAS,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAA;QACjD,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,0BAA0B,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAiB,CAAA;QACzF,OAAO,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IAC/B,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAe;QACjC,MAAM,GAAG,GAAG,WAAW,EAAE;aACtB,OAAO,CAAC,mFAAmF,CAAC;aAC5F,GAAG,CAAC,OAAO,CAA2B,CAAA;QACzC,OAAO,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACvC,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,EAAc;QACnC,MAAM,GAAG,GAAG,WAAW,EAAE;aACtB,OAAO,CAAC,uCAAuC,CAAC;aAChD,GAAG,CAAC,EAAY,CAA4B,CAAA;QAC/C,OAAO,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACxC,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,MAAM,IAAI,GAAG,WAAW,EAAE;aACvB,OAAO,CAAC,uDAAuD,CAAC;aAChE,GAAG,EAAmB,CAAA;QACzB,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IAChC,CAAC;CACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Products stock operations — increment, decrement, set.
|
|
3
|
+
*
|
|
4
|
+
* CRITICAL: These mutations append to the canonical inventory_transactions ledger.
|
|
5
|
+
* The products.current_stock column is the cached balance only.
|
|
6
|
+
*
|
|
7
|
+
* Concurrency safety: decrementStock uses a conditional UPDATE with a WHERE clause
|
|
8
|
+
* (current_stock >= ?) so the stock check is ATOMIC in SQLite — no TOCTOU race.
|
|
9
|
+
* If no rows are updated, insufficient stock.
|
|
10
|
+
*/
|
|
11
|
+
import type { ProductId } from './products-repository-types.js';
|
|
12
|
+
export declare class ProductsStockRepository {
|
|
13
|
+
decrementStock(productId: ProductId, quantity: number, meta?: {
|
|
14
|
+
saleId?: string;
|
|
15
|
+
userId?: string;
|
|
16
|
+
deviceId?: string;
|
|
17
|
+
shopId?: string;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
incrementStock(productId: ProductId, quantity: number, meta?: {
|
|
20
|
+
reason?: string;
|
|
21
|
+
referenceId?: string;
|
|
22
|
+
userId?: string;
|
|
23
|
+
deviceId?: string;
|
|
24
|
+
shopId?: string;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
setStock(productId: ProductId, newQuantity: number): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=products-repository-stock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"products-repository-stock.d.ts","sourceRoot":"","sources":["../src/products-repository-stock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAA;AAE/D,qBAAa,uBAAuB;IAC5B,cAAc,CAClB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAClF,OAAO,CAAC,IAAI,CAAC;IAuCV,cAAc,CAClB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GACxG,OAAO,CAAC,IAAI,CAAC;IA6BV,QAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAOzE"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Products stock operations — increment, decrement, set.
|
|
3
|
+
*
|
|
4
|
+
* CRITICAL: These mutations append to the canonical inventory_transactions ledger.
|
|
5
|
+
* The products.current_stock column is the cached balance only.
|
|
6
|
+
*
|
|
7
|
+
* Concurrency safety: decrementStock uses a conditional UPDATE with a WHERE clause
|
|
8
|
+
* (current_stock >= ?) so the stock check is ATOMIC in SQLite — no TOCTOU race.
|
|
9
|
+
* If no rows are updated, insufficient stock.
|
|
10
|
+
*/
|
|
11
|
+
import { randomUUID } from 'crypto';
|
|
12
|
+
import { getDatabase } from './sqlite-database.js';
|
|
13
|
+
export class ProductsStockRepository {
|
|
14
|
+
async decrementStock(productId, quantity, meta = {}) {
|
|
15
|
+
const db = getDatabase();
|
|
16
|
+
const now = new Date().toISOString();
|
|
17
|
+
// Atomic conditional UPDATE — check AND mutation in ONE SQLite statement.
|
|
18
|
+
// If current_stock < quantity, 0 rows are updated → insufficient stock.
|
|
19
|
+
const info = db.prepare('UPDATE products SET current_stock = current_stock - ?, stock_quantity = stock_quantity - ?, updated_at = ? WHERE id = ? AND current_stock >= ?').run(quantity, quantity, now, productId, quantity);
|
|
20
|
+
if (info.changes === 0) {
|
|
21
|
+
// Either product not found OR insufficient stock — determine which
|
|
22
|
+
const product = db.prepare('SELECT current_stock FROM products WHERE id = ?').get(productId);
|
|
23
|
+
if (!product)
|
|
24
|
+
throw new Error(`Product ${productId} not found`);
|
|
25
|
+
throw new Error(`Insufficient stock: ${product.current_stock} available, ${quantity} requested`);
|
|
26
|
+
}
|
|
27
|
+
// Read the new balance after atomic decrement
|
|
28
|
+
const updated = db.prepare('SELECT current_stock FROM products WHERE id = ?').get(productId);
|
|
29
|
+
// Append canonical inventory ledger entry
|
|
30
|
+
db.prepare(`
|
|
31
|
+
INSERT INTO inventory_transactions
|
|
32
|
+
(id, shop_id, product_id, device_id, user_id, event_type, quantity, balance_after, status, created_at)
|
|
33
|
+
VALUES (?, ?, ?, ?, ?, 'sale', ?, ?, 'confirmed', ?)
|
|
34
|
+
`).run(randomUUID(), meta.shopId ?? null, productId, meta.deviceId ?? null, meta.userId ?? null, -quantity, updated.current_stock, now);
|
|
35
|
+
}
|
|
36
|
+
async incrementStock(productId, quantity, meta = {}) {
|
|
37
|
+
const db = getDatabase();
|
|
38
|
+
const now = new Date().toISOString();
|
|
39
|
+
// Atomic increment — safe because stock can only increase
|
|
40
|
+
db.prepare('UPDATE products SET current_stock = current_stock + ?, stock_quantity = stock_quantity + ?, updated_at = ? WHERE id = ?').run(quantity, quantity, now, productId);
|
|
41
|
+
// Read new balance after increment
|
|
42
|
+
const product = db.prepare('SELECT current_stock FROM products WHERE id = ?').get(productId);
|
|
43
|
+
if (!product)
|
|
44
|
+
throw new Error(`Product ${productId} not found`);
|
|
45
|
+
db.prepare(`
|
|
46
|
+
INSERT INTO inventory_transactions
|
|
47
|
+
(id, shop_id, product_id, device_id, user_id, event_type, quantity, balance_after, status, created_at)
|
|
48
|
+
VALUES (?, ?, ?, ?, ?, 'received', ?, ?, 'confirmed', ?)
|
|
49
|
+
`).run(randomUUID(), meta.shopId ?? null, productId, meta.deviceId ?? null, meta.userId ?? null, quantity, product.current_stock, now);
|
|
50
|
+
}
|
|
51
|
+
async setStock(productId, newQuantity) {
|
|
52
|
+
const db = getDatabase();
|
|
53
|
+
const now = new Date().toISOString();
|
|
54
|
+
db.prepare('UPDATE products SET current_stock = ?, stock_quantity = ?, updated_at = ? WHERE id = ?').run(newQuantity, newQuantity, now, productId);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=products-repository-stock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"products-repository-stock.js","sourceRoot":"","sources":["../src/products-repository-stock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGlD,MAAM,OAAO,uBAAuB;IAClC,KAAK,CAAC,cAAc,CAClB,SAAoB,EACpB,QAAgB,EAChB,OAAiF,EAAE;QAEnF,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;QACxB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QAEpC,0EAA0E;QAC1E,wEAAwE;QACxE,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CACrB,gJAAgJ,CACjJ,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAmB,EAAE,QAAQ,CAAC,CAAA;QAE7D,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;YACvB,mEAAmE;YACnE,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,iDAAiD,CAAC,CAAC,GAAG,CAAC,SAAmB,CAA0C,CAAA;YAC/I,IAAI,CAAC,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,WAAW,SAAS,YAAY,CAAC,CAAA;YAC/D,MAAM,IAAI,KAAK,CACb,uBAAuB,OAAO,CAAC,aAAa,eAAe,QAAQ,YAAY,CAChF,CAAA;QACH,CAAC;QAED,8CAA8C;QAC9C,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,iDAAiD,CAAC,CAAC,GAAG,CAAC,SAAmB,CAA8B,CAAA;QAEnI,0CAA0C;QAC1C,EAAE,CAAC,OAAO,CAAC;;;;KAIV,CAAC,CAAC,GAAG,CACJ,UAAU,EAAE,EACZ,IAAI,CAAC,MAAM,IAAI,IAAI,EACnB,SAAmB,EACnB,IAAI,CAAC,QAAQ,IAAI,IAAI,EACrB,IAAI,CAAC,MAAM,IAAI,IAAI,EACnB,CAAC,QAAQ,EACT,OAAO,CAAC,aAAa,EACrB,GAAG,CACJ,CAAA;IACH,CAAC;IAED,KAAK,CAAC,cAAc,CAClB,SAAoB,EACpB,QAAgB,EAChB,OAAuG,EAAE;QAEzG,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;QACxB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QAEpC,0DAA0D;QAC1D,EAAE,CAAC,OAAO,CACR,yHAAyH,CAC1H,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAmB,CAAC,CAAA;QAEnD,mCAAmC;QACnC,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,iDAAiD,CAAC,CAAC,GAAG,CAAC,SAAmB,CAA0C,CAAA;QAC/I,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,WAAW,SAAS,YAAY,CAAC,CAAA;QAE/D,EAAE,CAAC,OAAO,CAAC;;;;KAIV,CAAC,CAAC,GAAG,CACJ,UAAU,EAAE,EACZ,IAAI,CAAC,MAAM,IAAI,IAAI,EACnB,SAAmB,EACnB,IAAI,CAAC,QAAQ,IAAI,IAAI,EACrB,IAAI,CAAC,MAAM,IAAI,IAAI,EACnB,QAAQ,EACR,OAAO,CAAC,aAAa,EACrB,GAAG,CACJ,CAAA;IACH,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,SAAoB,EAAE,WAAmB;QACtD,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;QACxB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACpC,EAAE,CAAC,OAAO,CACR,wFAAwF,CACzF,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,GAAG,EAAE,SAAmB,CAAC,CAAA;IAC3D,CAAC;CACF"}
|