Add editable supply material details
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 49s

This commit is contained in:
Cauê Faleiros
2026-08-10 10:15:01 -03:00
parent 40737faaa5
commit 5ed91fa36f
8 changed files with 261 additions and 26 deletions

View File

@@ -266,6 +266,17 @@ const initDB = async () => {
);
`);
await pool.query(`
CREATE TABLE IF NOT EXISTS supply_material_details (
material_key VARCHAR(255) PRIMARY KEY,
display_name TEXT,
preferred_supplier VARCHAR(255),
notes TEXT,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
`);
await pool.query(`
CREATE TABLE IF NOT EXISTS consumption_references (
id SERIAL PRIMARY KEY,

View File

@@ -10,6 +10,7 @@ const {
deleteReceipt,
getSupplySummary,
listFabricPlans,
saveMaterialDetails,
listLots,
listMovements,
listPurchaseNeeds,
@@ -124,4 +125,12 @@ router.get('/supply/purchase-needs', verifyToken, async (req, res, next) => {
}
});
router.put('/supply/materials/:materialKey', verifyToken, async (req, res, next) => {
try {
res.json(await saveMaterialDetails(req.params.materialKey, req.body || {}));
} catch (error) {
next(error);
}
});
module.exports = router;

View File

@@ -90,6 +90,15 @@ const mapFabricPlan = (row) => ({
updatedAt: row.updated_at
});
const mapMaterialDetails = (row) => ({
materialKey: row.material_key,
displayName: row.display_name || '',
preferredSupplier: row.preferred_supplier || '',
notes: row.notes || '',
createdAt: row.created_at,
updatedAt: row.updated_at
});
const createValidationError = (message) => {
const error = new Error(message);
error.statusCode = 400;
@@ -141,6 +150,39 @@ const listFabricPlans = async () => {
return result.rows.map(mapFabricPlan);
};
const listMaterialDetails = async () => {
const result = await pool.query(`
SELECT material_key, display_name, preferred_supplier, notes, created_at, updated_at
FROM supply_material_details
`);
return result.rows.map(mapMaterialDetails);
};
const saveMaterialDetails = async (materialKey, payload) => {
const normalizedKey = normalizeKey(materialKey);
if (!normalizedKey) throw createValidationError('Material inválido para edição.');
const result = await pool.query(`
INSERT INTO supply_material_details (
material_key, display_name, preferred_supplier, notes, updated_at
)
VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
ON CONFLICT (material_key) DO UPDATE
SET display_name = EXCLUDED.display_name,
preferred_supplier = EXCLUDED.preferred_supplier,
notes = EXCLUDED.notes,
updated_at = CURRENT_TIMESTAMP
RETURNING material_key, display_name, preferred_supplier, notes, created_at, updated_at
`, [
normalizedKey,
normalizeText(payload.displayName) || null,
normalizeText(payload.preferredSupplier) || null,
normalizeText(payload.notes) || null
]);
return mapMaterialDetails(result.rows[0]);
};
const buildPurchaseNeeds = (plans, lots, receipts) => {
const needsByMaterial = new Map();
@@ -150,6 +192,7 @@ const buildPurchaseNeeds = (plans, lots, receipts) => {
const current = needsByMaterial.get(key) || {
material: plan.material,
materialKey: normalizeKey(plan.material),
plannedKg: 0,
stockKg: 0,
pendingKg: 0,
@@ -189,6 +232,7 @@ const buildPurchaseNeeds = (plans, lots, receipts) => {
return {
material: need.material,
materialKey: need.materialKey,
plannedKg: need.plannedKg,
stockKg,
pendingKg: need.pendingKg,
@@ -309,6 +353,7 @@ const listTinyStockRows = async () => {
const mergeNeedLine = (needsByMaterial, key, patch) => {
const current = needsByMaterial.get(key) || {
material: patch.material,
materialKey: patch.materialKey || normalizeKey(patch.material),
plannedKg: 0,
stockKg: 0,
pendingKg: 0,
@@ -397,6 +442,7 @@ const buildProjectPurchaseNeeds = async (lots, receipts) => {
mergeNeedLine(needsByMaterial, key, {
material,
materialKey: normalizeKey(material),
plannedKg: suggestedQuantity * quantityPerUnit,
priority: 'Atenção',
unit,
@@ -557,6 +603,7 @@ const buildProjectPurchaseNeeds = async (lots, receipts) => {
return {
material: need.material,
materialKey: need.materialKey,
plannedKg: need.plannedKg,
stockKg,
pendingKg: need.pendingKg,
@@ -633,6 +680,25 @@ const mergePurchaseNeeds = (manualNeeds, projectNeeds) => {
});
};
const applyMaterialDetails = (purchaseNeeds, materialDetails) => {
const detailsByKey = new Map(materialDetails.map(detail => [detail.materialKey, detail]));
return purchaseNeeds.map(need => {
const detail = detailsByKey.get(normalizeKey(need.materialKey || need.material));
if (!detail) return { ...need, preferredSupplier: '', notes: '' };
return {
...need,
material: detail.displayName || need.material,
suppliers: detail.preferredSupplier
? [detail.preferredSupplier, ...need.suppliers.filter(supplier => supplier !== detail.preferredSupplier)]
: need.suppliers,
preferredSupplier: detail.preferredSupplier,
notes: detail.notes
};
});
};
const buildStats = (receipts, lots, purchaseNeeds) => {
const totalQuantityKg = lots.reduce((total, lot) => (
lot.unit === 'kg' ? total + lot.quantity : total
@@ -649,15 +715,19 @@ const buildStats = (receipts, lots, purchaseNeeds) => {
};
const getSupplySummary = async () => {
const [receipts, lots, movements, fabricPlans] = await Promise.all([
const [receipts, lots, movements, fabricPlans, materialDetails] = await Promise.all([
listReceipts(),
listLots(),
listMovements(),
listFabricPlans()
listFabricPlans(),
listMaterialDetails()
]);
const manualPurchaseNeeds = buildPurchaseNeeds(fabricPlans, lots, receipts);
const projectPurchaseNeeds = await buildProjectPurchaseNeeds(lots, receipts);
const purchaseNeeds = mergePurchaseNeeds(manualPurchaseNeeds, projectPurchaseNeeds);
const purchaseNeeds = applyMaterialDetails(
mergePurchaseNeeds(manualPurchaseNeeds, projectPurchaseNeeds),
materialDetails
);
return {
receipts,
@@ -670,15 +740,19 @@ const getSupplySummary = async () => {
};
const listPurchaseNeeds = async () => {
const [plans, lots, receipts] = await Promise.all([
const [plans, lots, receipts, materialDetails] = await Promise.all([
listFabricPlans(),
listLots(),
listReceipts()
listReceipts(),
listMaterialDetails()
]);
return mergePurchaseNeeds(
buildPurchaseNeeds(plans, lots, receipts),
await buildProjectPurchaseNeeds(lots, receipts)
return applyMaterialDetails(
mergePurchaseNeeds(
buildPurchaseNeeds(plans, lots, receipts),
await buildProjectPurchaseNeeds(lots, receipts)
),
materialDetails
);
};
@@ -967,8 +1041,10 @@ module.exports = {
buildProjectPurchaseNeeds,
buildPurchaseNeeds,
listFabricPlans,
listMaterialDetails,
listLots,
listMovements,
listPurchaseNeeds,
listReceipts
listReceipts,
saveMaterialDetails
};