feat(BD-09): completed feature

This commit is contained in:
chattie
2026-08-18 06:23:37 +02:00
parent 60a8dc01ed
commit 8ee1938af9
92 changed files with 807 additions and 123 deletions

View File

@@ -10,7 +10,7 @@ import {
InsufficientStockError,
InvalidStockQuantityError,
} from '../domain/errors.js';
import type { StockItem } from '../domain/stock.js';
import type { SetAvailableStockCommand, StockItem } from '../domain/stock.js';
import { PgInventoryRepository } from '../infrastructure/pg-inventory-repository.js';
export interface InventoryRoutesDeps {
@@ -25,6 +25,14 @@ const availabilityQuerySchema = z.object({
const stockBodySchema = z.object({ quantity: z.number().int().min(0) });
const stockCommandBodySchema = z.object({ quantity: z.number().int().positive() });
const bulkAdjustItemSchema = z.object({
variantId: z.uuid(),
quantity: z.number().int().min(0),
});
const bulkAdjustBodySchema = z.object({
items: z.array(bulkAdjustItemSchema).min(1).max(100),
});
export async function registerInventoryRoutes(
app: FastifyInstance,
deps: InventoryRoutesDeps,
@@ -89,6 +97,75 @@ export async function registerInventoryRoutes(
throw mapInventoryError(error);
}
});
// POST /inventory/bulk-adjust — atomic bulk stock adjustment
app.post('/inventory/bulk-adjust', async (request, reply) => {
const user = await deps.authenticate(request);
requireRole(user, 'admin');
const { items } = parseJson(bulkAdjustBodySchema, request.body);
const client = await deps.pool.connect();
try {
await client.query('BEGIN');
const results: StockItem[] = [];
for (const item of items) {
const command: SetAvailableStockCommand = {
variantId: item.variantId,
quantity: item.quantity,
};
// Use direct SQL for atomicity within the transaction
const result = await client.query<{
id: string;
variant_id: string;
available: number;
reserved: number;
sold: number;
incoming: number;
created_at: Date;
updated_at: Date;
}>(
`INSERT INTO inventory_stock (variant_id, available)
VALUES ($1, $2)
ON CONFLICT (variant_id) DO UPDATE
SET available = EXCLUDED.available, updated_at = now()
RETURNING *`,
[command.variantId, command.quantity],
);
// Log the movement
await client.query(
`INSERT INTO inventory_movements (variant_id, operation, quantity)
VALUES ($1, $2, $3)`,
[command.variantId, 'bulk_adjust', command.quantity],
);
const row = result.rows[0];
if (!row) {
throw new Error(`Inventory stock not found for variant ${command.variantId}`);
}
results.push({
id: row.id,
variantId: row.variant_id,
available: row.available,
reserved: row.reserved,
sold: row.sold,
incoming: row.incoming,
createdAt: row.created_at,
updatedAt: row.updated_at,
});
}
await client.query('COMMIT');
return reply.code(201).send({ adjusted: results.map(serializeStockItem), errors: [] });
} catch (error) {
await client.query('ROLLBACK');
throw mapInventoryError(error);
} finally {
client.release();
}
});
}
function mapInventoryError(error: unknown): Error {