Add editable supply material details
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 49s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 49s
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user