Connect production orders to stock movements
This commit is contained in:
@@ -8,6 +8,12 @@ const normalizeNumber = (value) => {
|
||||
return Number.isFinite(number) && number > 0 ? number : null;
|
||||
};
|
||||
|
||||
const normalizeNonNegativeNumber = (value) => {
|
||||
if (value === '' || value === null || value === undefined) return null;
|
||||
const number = Number(String(value).replace(',', '.'));
|
||||
return Number.isFinite(number) && number >= 0 ? number : null;
|
||||
};
|
||||
|
||||
const normalizeKey = (value) => normalizeText(value)
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
@@ -377,8 +383,131 @@ const deleteFabricPlan = async (id) => {
|
||||
`, [id]);
|
||||
};
|
||||
|
||||
const updateLotQuantity = async (client, lotId, quantity) => {
|
||||
const status = quantity > 0 ? 'active' : 'depleted';
|
||||
const result = await client.query(`
|
||||
UPDATE supply_stock_lots
|
||||
SET quantity = $2,
|
||||
status = $3,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1
|
||||
RETURNING id, receipt_id, category, product, quantity, unit, supplier, invoice, status, created_at, updated_at
|
||||
`, [lotId, quantity, status]);
|
||||
|
||||
return mapLot(result.rows[0]);
|
||||
};
|
||||
|
||||
const adjustInventoryLot = async (id, payload) => {
|
||||
const countedQuantity = normalizeNonNegativeNumber(payload.countedQuantity);
|
||||
const reason = normalizeText(payload.reason);
|
||||
|
||||
if (countedQuantity === null) throw createValidationError('Quantidade contada deve ser zero ou maior.');
|
||||
if (!reason) throw createValidationError('Justificativa do ajuste é obrigatória.');
|
||||
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
const lotResult = await client.query(`
|
||||
SELECT id, receipt_id, category, product, quantity, unit, supplier, invoice, status, created_at, updated_at
|
||||
FROM supply_stock_lots
|
||||
WHERE id = $1
|
||||
FOR UPDATE
|
||||
`, [id]);
|
||||
|
||||
if (!lotResult.rowCount) throw createValidationError('Lote não encontrado.');
|
||||
|
||||
const lot = lotResult.rows[0];
|
||||
const currentQuantity = Number(lot.quantity);
|
||||
const difference = countedQuantity - currentQuantity;
|
||||
const updatedLot = await updateLotQuantity(client, lot.id, countedQuantity);
|
||||
|
||||
await client.query(`
|
||||
INSERT INTO supply_movements (
|
||||
lot_id, type, category, product, quantity, unit, reason
|
||||
)
|
||||
VALUES ($1, 'inventory_adjustment', $2, $3, $4, $5, $6)
|
||||
`, [
|
||||
lot.id,
|
||||
lot.category,
|
||||
lot.product,
|
||||
difference,
|
||||
lot.unit,
|
||||
`${reason} · sistema ${currentQuantity} ${lot.unit} · contado ${countedQuantity} ${lot.unit}`
|
||||
]);
|
||||
|
||||
await client.query('COMMIT');
|
||||
return updatedLot;
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
};
|
||||
|
||||
const consumeLotForProduction = async (id, payload) => {
|
||||
const quantity = normalizeNumber(payload.quantity);
|
||||
const reason = normalizeText(payload.reason);
|
||||
const productionOrderNumber = normalizeText(payload.productionOrderNumber);
|
||||
|
||||
if (!quantity) throw createValidationError('Quantidade de saída deve ser maior que zero.');
|
||||
if (!productionOrderNumber && !reason) throw createValidationError('Informe a OP ou uma justificativa para a saída.');
|
||||
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
const lotResult = await client.query(`
|
||||
SELECT id, receipt_id, category, product, quantity, unit, supplier, invoice, status, created_at, updated_at
|
||||
FROM supply_stock_lots
|
||||
WHERE id = $1
|
||||
FOR UPDATE
|
||||
`, [id]);
|
||||
|
||||
if (!lotResult.rowCount) throw createValidationError('Lote não encontrado.');
|
||||
|
||||
const lot = lotResult.rows[0];
|
||||
const currentQuantity = Number(lot.quantity);
|
||||
if (lot.status !== 'active' || currentQuantity <= 0) throw createValidationError('Lote sem saldo disponível.');
|
||||
if (quantity > currentQuantity) throw createValidationError('Quantidade de saída maior que o saldo do lote.');
|
||||
|
||||
const updatedLot = await updateLotQuantity(client, lot.id, currentQuantity - quantity);
|
||||
const movementReason = [
|
||||
productionOrderNumber ? `OP ${productionOrderNumber}` : '',
|
||||
reason
|
||||
].filter(Boolean).join(' · ');
|
||||
|
||||
await client.query(`
|
||||
INSERT INTO supply_movements (
|
||||
lot_id, type, category, product, quantity, unit, reason
|
||||
)
|
||||
VALUES ($1, 'production_exit', $2, $3, $4, $5, $6)
|
||||
`, [
|
||||
lot.id,
|
||||
lot.category,
|
||||
lot.product,
|
||||
-quantity,
|
||||
lot.unit,
|
||||
movementReason || 'Saída para produção'
|
||||
]);
|
||||
|
||||
await client.query('COMMIT');
|
||||
return updatedLot;
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
adjustInventoryLot,
|
||||
approveReceipt,
|
||||
consumeLotForProduction,
|
||||
createFabricPlan,
|
||||
createReceipt,
|
||||
deleteFabricPlan,
|
||||
|
||||
Reference in New Issue
Block a user