Add Tiny production order detail sync
This commit is contained in:
@@ -128,6 +128,13 @@ const initDB = async () => {
|
|||||||
quantity NUMERIC(14, 4) DEFAULT 0,
|
quantity NUMERIC(14, 4) DEFAULT 0,
|
||||||
unit VARCHAR(20) DEFAULT 'UN',
|
unit VARCHAR(20) DEFAULT 'UN',
|
||||||
integration_status VARCHAR(100),
|
integration_status VARCHAR(100),
|
||||||
|
notes TEXT,
|
||||||
|
supplier TEXT,
|
||||||
|
lot_code VARCHAR(120),
|
||||||
|
roll_quantity NUMERIC(14, 4),
|
||||||
|
fabric_kg NUMERIC(14, 4),
|
||||||
|
rib_kg NUMERIC(14, 4),
|
||||||
|
yield_pieces_per_kg NUMERIC(14, 4),
|
||||||
tiny_payload JSONB,
|
tiny_payload JSONB,
|
||||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
@@ -145,6 +152,36 @@ const initDB = async () => {
|
|||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS production_order_components (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
production_order_id INTEGER NOT NULL REFERENCES production_orders(id) ON DELETE CASCADE,
|
||||||
|
component_tiny_id VARCHAR(100),
|
||||||
|
component_sku VARCHAR(255),
|
||||||
|
component_name TEXT NOT NULL,
|
||||||
|
quantity_per_unit NUMERIC(14, 4) DEFAULT 0,
|
||||||
|
total_quantity NUMERIC(14, 4) DEFAULT 0,
|
||||||
|
unit VARCHAR(30),
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS production_order_steps (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
production_order_id INTEGER NOT NULL REFERENCES production_orders(id) ON DELETE CASCADE,
|
||||||
|
step_number INTEGER,
|
||||||
|
name VARCHAR(160) NOT NULL,
|
||||||
|
start_date DATE,
|
||||||
|
end_date DATE,
|
||||||
|
status VARCHAR(80),
|
||||||
|
color VARCHAR(40),
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
await pool.query(`
|
await pool.query(`
|
||||||
CREATE TABLE IF NOT EXISTS cutting_family_rules (
|
CREATE TABLE IF NOT EXISTS cutting_family_rules (
|
||||||
family_key VARCHAR(20) PRIMARY KEY,
|
family_key VARCHAR(20) PRIMARY KEY,
|
||||||
@@ -277,6 +314,33 @@ const initDB = async () => {
|
|||||||
|
|
||||||
await pool.query(`
|
await pool.query(`
|
||||||
ALTER TABLE production_orders
|
ALTER TABLE production_orders
|
||||||
|
ADD COLUMN IF NOT EXISTS notes TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS supplier TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS lot_code VARCHAR(120),
|
||||||
|
ADD COLUMN IF NOT EXISTS roll_quantity NUMERIC(14, 4),
|
||||||
|
ADD COLUMN IF NOT EXISTS fabric_kg NUMERIC(14, 4),
|
||||||
|
ADD COLUMN IF NOT EXISTS rib_kg NUMERIC(14, 4),
|
||||||
|
ADD COLUMN IF NOT EXISTS yield_pieces_per_kg NUMERIC(14, 4);
|
||||||
|
`).catch(() => {});
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
ALTER TABLE production_orders
|
||||||
|
ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'America/Sao_Paulo',
|
||||||
|
ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at AT TIME ZONE 'America/Sao_Paulo',
|
||||||
|
ALTER COLUMN updated_at SET DEFAULT CURRENT_TIMESTAMP;
|
||||||
|
`).catch(() => {});
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
ALTER TABLE production_order_components
|
||||||
|
ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'America/Sao_Paulo',
|
||||||
|
ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at AT TIME ZONE 'America/Sao_Paulo',
|
||||||
|
ALTER COLUMN updated_at SET DEFAULT CURRENT_TIMESTAMP;
|
||||||
|
`).catch(() => {});
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
ALTER TABLE production_order_steps
|
||||||
ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'America/Sao_Paulo',
|
ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at AT TIME ZONE 'America/Sao_Paulo',
|
||||||
ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMP,
|
ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMP,
|
||||||
ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at AT TIME ZONE 'America/Sao_Paulo',
|
ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at AT TIME ZONE 'America/Sao_Paulo',
|
||||||
@@ -425,6 +489,9 @@ const initDB = async () => {
|
|||||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_production_orders_issue_date ON production_orders (issue_date DESC);`);
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_production_orders_issue_date ON production_orders (issue_date DESC);`);
|
||||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_production_orders_expected_date ON production_orders (expected_date DESC);`);
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_production_orders_expected_date ON production_orders (expected_date DESC);`);
|
||||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_production_order_markers_order_id ON production_order_markers (production_order_id);`);
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_production_order_markers_order_id ON production_order_markers (production_order_id);`);
|
||||||
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_production_order_components_order_id ON production_order_components (production_order_id);`);
|
||||||
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_production_order_components_sku ON production_order_components (component_sku);`);
|
||||||
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_production_order_steps_order_id ON production_order_steps (production_order_id);`);
|
||||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_cutting_product_overrides_family_key ON cutting_product_overrides (family_key);`);
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_cutting_product_overrides_family_key ON cutting_product_overrides (family_key);`);
|
||||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_catalog_products_type ON catalog_products (type);`);
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_catalog_products_type ON catalog_products (type);`);
|
||||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_catalog_products_category_id ON catalog_products (category_id);`);
|
await pool.query(`CREATE INDEX IF NOT EXISTS idx_catalog_products_category_id ON catalog_products (category_id);`);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { verifyToken } = require('../auth');
|
const { authenticateAPIKey, verifyToken } = require('../auth');
|
||||||
const { createProductionOrders, listProductionOrders, updateProductionOrderStatus } = require('../services/productionOrderService');
|
const { createProductionOrders, listProductionOrders, updateProductionOrderStatus, upsertTinyProductionOrderDetail } = require('../services/productionOrderService');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -23,6 +23,15 @@ router.post('/production-orders', verifyToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/production-orders/tiny-sync', authenticateAPIKey, async (req, res) => {
|
||||||
|
try {
|
||||||
|
res.status(201).json(await upsertTinyProductionOrderDetail(req.body || {}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error syncing Tiny production order:', error);
|
||||||
|
res.status(error.statusCode || 500).json({ error: error.message || 'Internal Server Error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router.patch('/production-orders/:id/status', verifyToken, async (req, res) => {
|
router.patch('/production-orders/:id/status', verifyToken, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
res.json(await updateProductionOrderStatus(req.params.id, req.body?.status));
|
res.json(await updateProductionOrderStatus(req.params.id, req.body?.status));
|
||||||
|
|||||||
@@ -31,8 +31,16 @@ const sampleSpecs = {
|
|||||||
columns: ['id', 'production_order_id', 'label', 'color', 'created_at'],
|
columns: ['id', 'production_order_id', 'label', 'color', 'created_at'],
|
||||||
orderBy: ['created_at', 'id']
|
orderBy: ['created_at', 'id']
|
||||||
},
|
},
|
||||||
|
production_order_components: {
|
||||||
|
columns: ['id', 'production_order_id', 'component_tiny_id', 'component_sku', 'component_name', 'quantity_per_unit', 'total_quantity', 'unit', 'created_at', 'updated_at'],
|
||||||
|
orderBy: ['updated_at', 'created_at', 'id']
|
||||||
|
},
|
||||||
|
production_order_steps: {
|
||||||
|
columns: ['id', 'production_order_id', 'step_number', 'name', 'start_date', 'end_date', 'status', 'color', 'created_at', 'updated_at'],
|
||||||
|
orderBy: ['updated_at', 'created_at', 'id']
|
||||||
|
},
|
||||||
production_orders: {
|
production_orders: {
|
||||||
columns: ['id', 'tiny_id', 'number', 'status', 'order_reference', 'issue_date', 'expected_date', 'product_sku', 'product_description', 'quantity', 'unit', 'integration_status', 'created_at', 'updated_at'],
|
columns: ['id', 'tiny_id', 'number', 'status', 'order_reference', 'issue_date', 'expected_date', 'product_sku', 'product_description', 'quantity', 'unit', 'integration_status', 'notes', 'supplier', 'lot_code', 'roll_quantity', 'fabric_kg', 'rib_kg', 'yield_pieces_per_kg', 'created_at', 'updated_at'],
|
||||||
orderBy: ['updated_at', 'created_at', 'id']
|
orderBy: ['updated_at', 'created_at', 'id']
|
||||||
},
|
},
|
||||||
stock: {
|
stock: {
|
||||||
|
|||||||
@@ -18,9 +18,22 @@ const normalizeStatus = (status) => {
|
|||||||
|
|
||||||
const normalizeDateParam = (value) => {
|
const normalizeDateParam = (value) => {
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
const date = new Date(`${value}T00:00:00`);
|
const normalizedValue = String(value).trim();
|
||||||
if (Number.isNaN(date.getTime())) return null;
|
const isoMatch = normalizedValue.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/);
|
||||||
return value;
|
if (isoMatch) {
|
||||||
|
const [, year, month, day] = isoMatch;
|
||||||
|
const date = new Date(`${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}T00:00:00`);
|
||||||
|
return Number.isNaN(date.getTime()) ? null : `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const brMatch = normalizedValue.match(/^(\d{1,2})[-/](\d{1,2})[-/](\d{4})/);
|
||||||
|
if (brMatch) {
|
||||||
|
const [, day, month, year] = brMatch;
|
||||||
|
const date = new Date(`${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}T00:00:00`);
|
||||||
|
return Number.isNaN(date.getTime()) ? null : `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (value) => {
|
const formatDate = (value) => {
|
||||||
@@ -34,11 +47,44 @@ const formatDate = (value) => {
|
|||||||
const normalizeText = (value) => String(value || '').trim();
|
const normalizeText = (value) => String(value || '').trim();
|
||||||
|
|
||||||
const normalizeQuantity = (value) => {
|
const normalizeQuantity = (value) => {
|
||||||
const quantity = Number(value);
|
const normalizedValue = typeof value === 'string' && value.includes(',')
|
||||||
|
? value.replace(/\./g, '').replace(',', '.')
|
||||||
|
: value;
|
||||||
|
const quantity = Number(normalizedValue);
|
||||||
if (!Number.isFinite(quantity) || quantity <= 0) return 0;
|
if (!Number.isFinite(quantity) || quantity <= 0) return 0;
|
||||||
return quantity;
|
return quantity;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeNullableQuantity = (value) => {
|
||||||
|
const quantity = normalizeQuantity(value);
|
||||||
|
return quantity || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeInteger = (value) => {
|
||||||
|
const number = Number(value);
|
||||||
|
return Number.isInteger(number) ? number : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapComponent = (component) => ({
|
||||||
|
id: component.id,
|
||||||
|
componentTinyId: component.component_tiny_id || '',
|
||||||
|
componentSku: component.component_sku || '',
|
||||||
|
componentName: component.component_name || '',
|
||||||
|
quantityPerUnit: Number(component.quantity_per_unit || 0),
|
||||||
|
totalQuantity: Number(component.total_quantity || 0),
|
||||||
|
unit: component.unit || ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStep = (step) => ({
|
||||||
|
id: step.id,
|
||||||
|
stepNumber: step.step_number === null ? null : Number(step.step_number),
|
||||||
|
name: step.name || '',
|
||||||
|
startDate: formatDate(step.start_date),
|
||||||
|
endDate: formatDate(step.end_date),
|
||||||
|
status: step.status || '',
|
||||||
|
color: step.color || ''
|
||||||
|
});
|
||||||
|
|
||||||
const mapProductionOrderRow = (row) => {
|
const mapProductionOrderRow = (row) => {
|
||||||
const status = normalizeStatus(row.status);
|
const status = normalizeStatus(row.status);
|
||||||
|
|
||||||
@@ -56,7 +102,16 @@ const mapProductionOrderRow = (row) => {
|
|||||||
quantity: Number(row.quantity || 0),
|
quantity: Number(row.quantity || 0),
|
||||||
unit: row.unit || 'UN',
|
unit: row.unit || 'UN',
|
||||||
integrationStatus: row.integration_status || '',
|
integrationStatus: row.integration_status || '',
|
||||||
|
notes: row.notes || '',
|
||||||
|
supplier: row.supplier || '',
|
||||||
|
lotCode: row.lot_code || '',
|
||||||
|
rollQuantity: row.roll_quantity === null ? null : Number(row.roll_quantity),
|
||||||
|
fabricKg: row.fabric_kg === null ? null : Number(row.fabric_kg),
|
||||||
|
ribKg: row.rib_kg === null ? null : Number(row.rib_kg),
|
||||||
|
yieldPiecesPerKg: row.yield_pieces_per_kg === null ? null : Number(row.yield_pieces_per_kg),
|
||||||
markers: Array.isArray(row.markers) ? row.markers.filter(Boolean) : [],
|
markers: Array.isArray(row.markers) ? row.markers.filter(Boolean) : [],
|
||||||
|
components: Array.isArray(row.components) ? row.components.map(mapComponent) : [],
|
||||||
|
steps: Array.isArray(row.steps) ? row.steps.map(mapStep) : [],
|
||||||
createdAt: row.created_at || null,
|
createdAt: row.created_at || null,
|
||||||
updatedAt: row.updated_at || null
|
updatedAt: row.updated_at || null
|
||||||
};
|
};
|
||||||
@@ -77,27 +132,152 @@ const getOrderById = async (id, client = pool) => {
|
|||||||
po.quantity,
|
po.quantity,
|
||||||
po.unit,
|
po.unit,
|
||||||
po.integration_status,
|
po.integration_status,
|
||||||
|
po.notes,
|
||||||
|
po.supplier,
|
||||||
|
po.lot_code,
|
||||||
|
po.roll_quantity,
|
||||||
|
po.fabric_kg,
|
||||||
|
po.rib_kg,
|
||||||
|
po.yield_pieces_per_kg,
|
||||||
po.created_at,
|
po.created_at,
|
||||||
po.updated_at,
|
po.updated_at,
|
||||||
COALESCE(
|
COALESCE(
|
||||||
JSON_AGG(
|
(
|
||||||
|
SELECT JSON_AGG(
|
||||||
JSON_BUILD_OBJECT(
|
JSON_BUILD_OBJECT(
|
||||||
'label', pom.label,
|
'label', marker.label,
|
||||||
'color', pom.color
|
'color', marker.color
|
||||||
)
|
)
|
||||||
ORDER BY pom.label
|
ORDER BY marker.label
|
||||||
) FILTER (WHERE pom.id IS NOT NULL),
|
)
|
||||||
|
FROM production_order_markers marker
|
||||||
|
WHERE marker.production_order_id = po.id
|
||||||
|
),
|
||||||
'[]'::json
|
'[]'::json
|
||||||
) as markers
|
) as markers,
|
||||||
|
COALESCE(
|
||||||
|
(
|
||||||
|
SELECT JSON_AGG(
|
||||||
|
JSON_BUILD_OBJECT(
|
||||||
|
'id', component.id,
|
||||||
|
'component_tiny_id', component.component_tiny_id,
|
||||||
|
'component_sku', component.component_sku,
|
||||||
|
'component_name', component.component_name,
|
||||||
|
'quantity_per_unit', component.quantity_per_unit,
|
||||||
|
'total_quantity', component.total_quantity,
|
||||||
|
'unit', component.unit
|
||||||
|
)
|
||||||
|
ORDER BY component.id
|
||||||
|
)
|
||||||
|
FROM production_order_components component
|
||||||
|
WHERE component.production_order_id = po.id
|
||||||
|
),
|
||||||
|
'[]'::json
|
||||||
|
) as components,
|
||||||
|
COALESCE(
|
||||||
|
(
|
||||||
|
SELECT JSON_AGG(
|
||||||
|
JSON_BUILD_OBJECT(
|
||||||
|
'id', step.id,
|
||||||
|
'step_number', step.step_number,
|
||||||
|
'name', step.name,
|
||||||
|
'start_date', step.start_date,
|
||||||
|
'end_date', step.end_date,
|
||||||
|
'status', step.status,
|
||||||
|
'color', step.color
|
||||||
|
)
|
||||||
|
ORDER BY step.step_number NULLS LAST, step.id
|
||||||
|
)
|
||||||
|
FROM production_order_steps step
|
||||||
|
WHERE step.production_order_id = po.id
|
||||||
|
),
|
||||||
|
'[]'::json
|
||||||
|
) as steps
|
||||||
FROM production_orders po
|
FROM production_orders po
|
||||||
LEFT JOIN production_order_markers pom ON pom.production_order_id = po.id
|
WHERE po.id = $1;
|
||||||
WHERE po.id = $1
|
|
||||||
GROUP BY po.id;
|
|
||||||
`, [id]);
|
`, [id]);
|
||||||
|
|
||||||
return result.rows[0] ? mapProductionOrderRow(result.rows[0]) : null;
|
return result.rows[0] ? mapProductionOrderRow(result.rows[0]) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const baseProductionOrderSelect = `
|
||||||
|
SELECT
|
||||||
|
po.id,
|
||||||
|
po.tiny_id,
|
||||||
|
po.number,
|
||||||
|
po.status,
|
||||||
|
po.order_reference,
|
||||||
|
po.issue_date,
|
||||||
|
po.expected_date,
|
||||||
|
po.product_sku,
|
||||||
|
po.product_description,
|
||||||
|
po.quantity,
|
||||||
|
po.unit,
|
||||||
|
po.integration_status,
|
||||||
|
po.notes,
|
||||||
|
po.supplier,
|
||||||
|
po.lot_code,
|
||||||
|
po.roll_quantity,
|
||||||
|
po.fabric_kg,
|
||||||
|
po.rib_kg,
|
||||||
|
po.yield_pieces_per_kg,
|
||||||
|
po.created_at,
|
||||||
|
po.updated_at,
|
||||||
|
COALESCE(
|
||||||
|
(
|
||||||
|
SELECT JSON_AGG(
|
||||||
|
JSON_BUILD_OBJECT(
|
||||||
|
'label', marker.label,
|
||||||
|
'color', marker.color
|
||||||
|
)
|
||||||
|
ORDER BY marker.label
|
||||||
|
)
|
||||||
|
FROM production_order_markers marker
|
||||||
|
WHERE marker.production_order_id = po.id
|
||||||
|
),
|
||||||
|
'[]'::json
|
||||||
|
) as markers,
|
||||||
|
COALESCE(
|
||||||
|
(
|
||||||
|
SELECT JSON_AGG(
|
||||||
|
JSON_BUILD_OBJECT(
|
||||||
|
'id', component.id,
|
||||||
|
'component_tiny_id', component.component_tiny_id,
|
||||||
|
'component_sku', component.component_sku,
|
||||||
|
'component_name', component.component_name,
|
||||||
|
'quantity_per_unit', component.quantity_per_unit,
|
||||||
|
'total_quantity', component.total_quantity,
|
||||||
|
'unit', component.unit
|
||||||
|
)
|
||||||
|
ORDER BY component.id
|
||||||
|
)
|
||||||
|
FROM production_order_components component
|
||||||
|
WHERE component.production_order_id = po.id
|
||||||
|
),
|
||||||
|
'[]'::json
|
||||||
|
) as components,
|
||||||
|
COALESCE(
|
||||||
|
(
|
||||||
|
SELECT JSON_AGG(
|
||||||
|
JSON_BUILD_OBJECT(
|
||||||
|
'id', step.id,
|
||||||
|
'step_number', step.step_number,
|
||||||
|
'name', step.name,
|
||||||
|
'start_date', step.start_date,
|
||||||
|
'end_date', step.end_date,
|
||||||
|
'status', step.status,
|
||||||
|
'color', step.color
|
||||||
|
)
|
||||||
|
ORDER BY step.step_number NULLS LAST, step.id
|
||||||
|
)
|
||||||
|
FROM production_order_steps step
|
||||||
|
WHERE step.production_order_id = po.id
|
||||||
|
),
|
||||||
|
'[]'::json
|
||||||
|
) as steps
|
||||||
|
FROM production_orders po
|
||||||
|
`;
|
||||||
|
|
||||||
const listProductionOrders = async (filters = {}) => {
|
const listProductionOrders = async (filters = {}) => {
|
||||||
const params = [];
|
const params = [];
|
||||||
const where = [];
|
const where = [];
|
||||||
@@ -126,35 +306,8 @@ const listProductionOrders = async (filters = {}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await pool.query(`
|
const result = await pool.query(`
|
||||||
SELECT
|
${baseProductionOrderSelect}
|
||||||
po.id,
|
|
||||||
po.tiny_id,
|
|
||||||
po.number,
|
|
||||||
po.status,
|
|
||||||
po.order_reference,
|
|
||||||
po.issue_date,
|
|
||||||
po.expected_date,
|
|
||||||
po.product_sku,
|
|
||||||
po.product_description,
|
|
||||||
po.quantity,
|
|
||||||
po.unit,
|
|
||||||
po.integration_status,
|
|
||||||
po.created_at,
|
|
||||||
po.updated_at,
|
|
||||||
COALESCE(
|
|
||||||
JSON_AGG(
|
|
||||||
JSON_BUILD_OBJECT(
|
|
||||||
'label', pom.label,
|
|
||||||
'color', pom.color
|
|
||||||
)
|
|
||||||
ORDER BY pom.label
|
|
||||||
) FILTER (WHERE pom.id IS NOT NULL),
|
|
||||||
'[]'::json
|
|
||||||
) as markers
|
|
||||||
FROM production_orders po
|
|
||||||
LEFT JOIN production_order_markers pom ON pom.production_order_id = po.id
|
|
||||||
${where.length ? `WHERE ${where.join(' AND ')}` : ''}
|
${where.length ? `WHERE ${where.join(' AND ')}` : ''}
|
||||||
GROUP BY po.id
|
|
||||||
ORDER BY
|
ORDER BY
|
||||||
COALESCE(po.issue_date, po.created_at::date) DESC,
|
COALESCE(po.issue_date, po.created_at::date) DESC,
|
||||||
CASE WHEN po.number ~ '^\\d+$' THEN po.number::bigint ELSE NULL END DESC NULLS LAST,
|
CASE WHEN po.number ~ '^\\d+$' THEN po.number::bigint ELSE NULL END DESC NULLS LAST,
|
||||||
@@ -285,6 +438,222 @@ const createProductionOrders = async (orders = []) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resolveTinyOrderPayload = (payload = {}) => {
|
||||||
|
const order = payload.order && typeof payload.order === 'object' ? payload.order : payload;
|
||||||
|
const quantity = normalizeQuantity(order.quantity);
|
||||||
|
const productDescription = normalizeText(order.productDescription || order.productName || order.produto || order.descricao);
|
||||||
|
|
||||||
|
if (!productDescription || !quantity) {
|
||||||
|
const error = new Error('Produto e quantidade da OP são obrigatórios.');
|
||||||
|
error.statusCode = 400;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tinyId: normalizeText(order.tinyId || order.tiny_id || order.idTiny || order.id),
|
||||||
|
number: normalizeText(order.number || order.numero || order.numeroOp || order.productionOrderNumber),
|
||||||
|
status: normalizeStatus(order.status || order.situacao || 'in_progress'),
|
||||||
|
orderReference: normalizeText(order.orderReference || order.reference || order.plano || order.pedidos),
|
||||||
|
issueDate: normalizeDateParam(order.issueDate || order.date || order.data),
|
||||||
|
expectedDate: normalizeDateParam(order.expectedDate || order.dataPrevista),
|
||||||
|
productSku: normalizeText(order.productSku || order.sku || order.codigo || order.codigoSku),
|
||||||
|
productDescription,
|
||||||
|
quantity,
|
||||||
|
unit: normalizeText(order.unit || order.unidade) || 'UN',
|
||||||
|
integrationStatus: normalizeText(order.integrationStatus) || 'Tiny',
|
||||||
|
notes: normalizeText(order.notes || order.observations || order.observacoes),
|
||||||
|
supplier: normalizeText(order.supplier || order.fornecedor),
|
||||||
|
lotCode: normalizeText(order.lotCode || order.lote),
|
||||||
|
rollQuantity: normalizeNullableQuantity(order.rollQuantity || order.quantidadeRolos),
|
||||||
|
fabricKg: normalizeNullableQuantity(order.fabricKg || order.quilosMalha),
|
||||||
|
ribKg: normalizeNullableQuantity(order.ribKg || order.quilosRibana),
|
||||||
|
yieldPiecesPerKg: normalizeNullableQuantity(order.yieldPiecesPerKg || order.rendimento),
|
||||||
|
rawPayload: payload
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeComponents = (components = [], orderQuantity = 0) => (
|
||||||
|
Array.isArray(components) ? components : []
|
||||||
|
).map(component => {
|
||||||
|
const quantityPerUnit = normalizeQuantity(component.quantityPerUnit || component.quantity || component.quantidade);
|
||||||
|
const totalQuantity = normalizeQuantity(component.totalQuantity || component.quantidadeTotal) || quantityPerUnit * orderQuantity;
|
||||||
|
|
||||||
|
return {
|
||||||
|
componentTinyId: normalizeText(component.componentTinyId || component.idComponente || component.id_componente || component.id),
|
||||||
|
componentSku: normalizeText(component.componentSku || component.sku || component.codigo || component.code),
|
||||||
|
componentName: normalizeText(component.componentName || component.name || component.nome || component.produto),
|
||||||
|
quantityPerUnit,
|
||||||
|
totalQuantity,
|
||||||
|
unit: normalizeText(component.unit || component.unidade)
|
||||||
|
};
|
||||||
|
}).filter(component => component.componentName);
|
||||||
|
|
||||||
|
const normalizeSteps = (steps = []) => (
|
||||||
|
Array.isArray(steps) ? steps : []
|
||||||
|
).map(step => ({
|
||||||
|
stepNumber: normalizeInteger(step.stepNumber || step.number || step.nro || step.numero),
|
||||||
|
name: normalizeText(step.name || step.etapa || step.posto || step.description),
|
||||||
|
startDate: normalizeDateParam(step.startDate || step.dataInicio),
|
||||||
|
endDate: normalizeDateParam(step.endDate || step.dataFim),
|
||||||
|
status: normalizeText(step.status || step.situacao),
|
||||||
|
color: normalizeText(step.color || step.cor)
|
||||||
|
})).filter(step => step.name);
|
||||||
|
|
||||||
|
const upsertTinyProductionOrderDetail = async (payload = {}) => {
|
||||||
|
const order = resolveTinyOrderPayload(payload);
|
||||||
|
const components = normalizeComponents(payload.components || payload.composition || payload.composicao, order.quantity);
|
||||||
|
const steps = normalizeSteps(payload.steps || payload.etapas);
|
||||||
|
const client = await pool.connect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.query('BEGIN');
|
||||||
|
|
||||||
|
const existingResult = await client.query(`
|
||||||
|
SELECT id
|
||||||
|
FROM production_orders
|
||||||
|
WHERE ($1 <> '' AND tiny_id = $1)
|
||||||
|
OR ($2 <> '' AND number = $2)
|
||||||
|
ORDER BY CASE WHEN tiny_id = $1 THEN 0 ELSE 1 END
|
||||||
|
LIMIT 1;
|
||||||
|
`, [order.tinyId, order.number]);
|
||||||
|
|
||||||
|
let orderId = existingResult.rows[0]?.id;
|
||||||
|
|
||||||
|
if (orderId) {
|
||||||
|
await client.query(`
|
||||||
|
UPDATE production_orders
|
||||||
|
SET tiny_id = COALESCE(NULLIF($1, ''), tiny_id),
|
||||||
|
number = COALESCE(NULLIF($2, ''), number),
|
||||||
|
status = $3,
|
||||||
|
order_reference = NULLIF($4, ''),
|
||||||
|
issue_date = $5,
|
||||||
|
expected_date = $6,
|
||||||
|
product_sku = NULLIF($7, ''),
|
||||||
|
product_description = $8,
|
||||||
|
quantity = $9,
|
||||||
|
unit = $10,
|
||||||
|
integration_status = $11,
|
||||||
|
notes = NULLIF($12, ''),
|
||||||
|
supplier = NULLIF($13, ''),
|
||||||
|
lot_code = NULLIF($14, ''),
|
||||||
|
roll_quantity = $15,
|
||||||
|
fabric_kg = $16,
|
||||||
|
rib_kg = $17,
|
||||||
|
yield_pieces_per_kg = $18,
|
||||||
|
tiny_payload = $19::jsonb,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $20;
|
||||||
|
`, [
|
||||||
|
order.tinyId,
|
||||||
|
order.number,
|
||||||
|
order.status,
|
||||||
|
order.orderReference,
|
||||||
|
order.issueDate,
|
||||||
|
order.expectedDate,
|
||||||
|
order.productSku,
|
||||||
|
order.productDescription,
|
||||||
|
order.quantity,
|
||||||
|
order.unit,
|
||||||
|
order.integrationStatus,
|
||||||
|
order.notes,
|
||||||
|
order.supplier,
|
||||||
|
order.lotCode,
|
||||||
|
order.rollQuantity,
|
||||||
|
order.fabricKg,
|
||||||
|
order.ribKg,
|
||||||
|
order.yieldPiecesPerKg,
|
||||||
|
JSON.stringify(order.rawPayload),
|
||||||
|
orderId
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
const insertResult = await client.query(`
|
||||||
|
INSERT INTO production_orders (
|
||||||
|
tiny_id, number, status, order_reference, issue_date, expected_date,
|
||||||
|
product_sku, product_description, quantity, unit, integration_status,
|
||||||
|
notes, supplier, lot_code, roll_quantity, fabric_kg, rib_kg,
|
||||||
|
yield_pieces_per_kg, tiny_payload
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
NULLIF($1, ''), NULLIF($2, ''), $3, NULLIF($4, ''), $5, $6,
|
||||||
|
NULLIF($7, ''), $8, $9, $10, $11, NULLIF($12, ''),
|
||||||
|
NULLIF($13, ''), NULLIF($14, ''), $15, $16, $17, $18, $19::jsonb
|
||||||
|
)
|
||||||
|
RETURNING id;
|
||||||
|
`, [
|
||||||
|
order.tinyId,
|
||||||
|
order.number,
|
||||||
|
order.status,
|
||||||
|
order.orderReference,
|
||||||
|
order.issueDate,
|
||||||
|
order.expectedDate,
|
||||||
|
order.productSku,
|
||||||
|
order.productDescription,
|
||||||
|
order.quantity,
|
||||||
|
order.unit,
|
||||||
|
order.integrationStatus,
|
||||||
|
order.notes,
|
||||||
|
order.supplier,
|
||||||
|
order.lotCode,
|
||||||
|
order.rollQuantity,
|
||||||
|
order.fabricKg,
|
||||||
|
order.ribKg,
|
||||||
|
order.yieldPiecesPerKg,
|
||||||
|
JSON.stringify(order.rawPayload)
|
||||||
|
]);
|
||||||
|
orderId = insertResult.rows[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.query('DELETE FROM production_order_components WHERE production_order_id = $1;', [orderId]);
|
||||||
|
for (const component of components) {
|
||||||
|
await client.query(`
|
||||||
|
INSERT INTO production_order_components (
|
||||||
|
production_order_id, component_tiny_id, component_sku, component_name,
|
||||||
|
quantity_per_unit, total_quantity, unit
|
||||||
|
)
|
||||||
|
VALUES ($1, NULLIF($2, ''), NULLIF($3, ''), $4, $5, $6, NULLIF($7, ''));
|
||||||
|
`, [
|
||||||
|
orderId,
|
||||||
|
component.componentTinyId,
|
||||||
|
component.componentSku,
|
||||||
|
component.componentName,
|
||||||
|
component.quantityPerUnit,
|
||||||
|
component.totalQuantity,
|
||||||
|
component.unit
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.query('DELETE FROM production_order_steps WHERE production_order_id = $1;', [orderId]);
|
||||||
|
for (const step of steps) {
|
||||||
|
await client.query(`
|
||||||
|
INSERT INTO production_order_steps (
|
||||||
|
production_order_id, step_number, name, start_date, end_date, status, color
|
||||||
|
)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, NULLIF($6, ''), NULLIF($7, ''));
|
||||||
|
`, [
|
||||||
|
orderId,
|
||||||
|
step.stepNumber,
|
||||||
|
step.name,
|
||||||
|
step.startDate,
|
||||||
|
step.endDate,
|
||||||
|
step.status,
|
||||||
|
step.color
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.query('COMMIT');
|
||||||
|
return {
|
||||||
|
order: await getOrderById(orderId),
|
||||||
|
componentCount: components.length,
|
||||||
|
stepCount: steps.length
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await client.query('ROLLBACK');
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const updateProductionOrderStatus = async (id, status) => {
|
const updateProductionOrderStatus = async (id, status) => {
|
||||||
const normalizedStatus = normalizeStatus(status);
|
const normalizedStatus = normalizeStatus(status);
|
||||||
|
|
||||||
@@ -314,5 +683,6 @@ module.exports = {
|
|||||||
createProductionOrders,
|
createProductionOrders,
|
||||||
listProductionOrders,
|
listProductionOrders,
|
||||||
normalizeStatus,
|
normalizeStatus,
|
||||||
|
upsertTinyProductionOrderDetail,
|
||||||
updateProductionOrderStatus
|
updateProductionOrderStatus
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type FormEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
import { Fragment, type FormEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { Link, useOutletContext } from 'react-router-dom';
|
import { Link, useOutletContext } from 'react-router-dom';
|
||||||
import { ArrowLeft, CalendarDays, CheckCircle2, ClipboardList, Clock3, Download, PackageCheck, Search } from 'lucide-react';
|
import { ArrowLeft, CalendarDays, CheckCircle2, ClipboardList, Clock3, Download, PackageCheck, Search } from 'lucide-react';
|
||||||
import DateRangePicker from '../components/DateRangePicker';
|
import DateRangePicker from '../components/DateRangePicker';
|
||||||
@@ -63,6 +63,16 @@ const formatQuantity = (value: number) => (
|
|||||||
}).format(value)
|
}).format(value)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const formatOptionalQuantity = (value: number | null) => (
|
||||||
|
value === null ? '-' : formatQuantity(value)
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasProductionOrderDetails = (order: ProductionOrderItem) => (
|
||||||
|
order.components.length > 0 ||
|
||||||
|
order.steps.length > 0 ||
|
||||||
|
Boolean(order.notes || order.supplier || order.lotCode || order.rollQuantity || order.fabricKg || order.ribKg || order.yieldPiecesPerKg)
|
||||||
|
);
|
||||||
|
|
||||||
const getStatusStyle = (status: ProductionOrderStatus, fallbackLabel: string) => (
|
const getStatusStyle = (status: ProductionOrderStatus, fallbackLabel: string) => (
|
||||||
statusStyles[String(status)] || {
|
statusStyles[String(status)] || {
|
||||||
label: fallbackLabel || 'Em aberto',
|
label: fallbackLabel || 'Em aberto',
|
||||||
@@ -500,8 +510,10 @@ const ProductionOrders = () => {
|
|||||||
<tbody className="divide-y divide-dark-border">
|
<tbody className="divide-y divide-dark-border">
|
||||||
{paginatedOrders.map((order: ProductionOrderItem) => {
|
{paginatedOrders.map((order: ProductionOrderItem) => {
|
||||||
const statusStyle = getStatusStyle(order.status, order.statusLabel);
|
const statusStyle = getStatusStyle(order.status, order.statusLabel);
|
||||||
|
const showDetails = hasProductionOrderDetails(order);
|
||||||
return (
|
return (
|
||||||
<tr key={order.id} className="transition-colors hover:bg-dark-input/50">
|
<Fragment key={order.id}>
|
||||||
|
<tr className="transition-colors hover:bg-dark-input/50">
|
||||||
<td className="px-6 py-3 font-mono text-xs font-bold text-dark-text">{order.number || '-'}</td>
|
<td className="px-6 py-3 font-mono text-xs font-bold text-dark-text">{order.number || '-'}</td>
|
||||||
<td className="px-6 py-3 text-xs font-semibold text-dark-muted">{order.orderReference || '-'}</td>
|
<td className="px-6 py-3 text-xs font-semibold text-dark-muted">{order.orderReference || '-'}</td>
|
||||||
<td className="px-6 py-3 text-xs font-semibold text-dark-muted">
|
<td className="px-6 py-3 text-xs font-semibold text-dark-muted">
|
||||||
@@ -557,6 +569,77 @@ const ProductionOrders = () => {
|
|||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{showDetails && (
|
||||||
|
<tr className="bg-dark-input/20">
|
||||||
|
<td colSpan={9} className="px-6 pb-5 pt-1">
|
||||||
|
<div className="grid grid-cols-1 gap-4 rounded-xl border border-dark-border bg-dark-card/80 p-4 xl:grid-cols-[1.4fr_1fr_1fr]">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xs font-bold uppercase tracking-widest text-dark-muted">Composição</h3>
|
||||||
|
{order.components.length ? (
|
||||||
|
<div className="mt-3 overflow-hidden rounded-lg border border-dark-border">
|
||||||
|
<table className="w-full text-xs">
|
||||||
|
<thead className="bg-dark-header text-dark-muted">
|
||||||
|
<tr>
|
||||||
|
<th className="px-3 py-2 text-left font-bold">Produto</th>
|
||||||
|
<th className="px-3 py-2 text-left font-bold">SKU</th>
|
||||||
|
<th className="px-3 py-2 text-right font-bold">Qtd.</th>
|
||||||
|
<th className="px-3 py-2 text-right font-bold">Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-dark-border">
|
||||||
|
{order.components.map(component => (
|
||||||
|
<tr key={component.id}>
|
||||||
|
<td className="px-3 py-2 font-semibold text-dark-text">{component.componentName}</td>
|
||||||
|
<td className="px-3 py-2 font-mono text-dark-muted">{component.componentSku || '-'}</td>
|
||||||
|
<td className="px-3 py-2 text-right font-semibold text-dark-muted">{formatQuantity(component.quantityPerUnit)} {component.unit}</td>
|
||||||
|
<td className="px-3 py-2 text-right font-bold text-dark-text">{formatQuantity(component.totalQuantity)} {component.unit}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="mt-3 text-xs font-semibold text-dark-muted">Sem composição sincronizada.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xs font-bold uppercase tracking-widest text-dark-muted">Etapas</h3>
|
||||||
|
{order.steps.length ? (
|
||||||
|
<div className="mt-3 divide-y divide-dark-border rounded-lg border border-dark-border">
|
||||||
|
{order.steps.map(step => (
|
||||||
|
<div key={step.id} className="grid grid-cols-[32px_1fr_auto] items-center gap-3 px-3 py-2 text-xs">
|
||||||
|
<span className="font-mono font-bold text-dark-muted">{step.stepNumber ?? '-'}</span>
|
||||||
|
<div>
|
||||||
|
<p className="font-bold text-dark-text">{step.name}</p>
|
||||||
|
<p className="mt-0.5 font-semibold text-dark-muted">{formatDate(step.startDate)} - {formatDate(step.endDate)}</p>
|
||||||
|
</div>
|
||||||
|
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: step.color || 'var(--color-brand-primary)' }} title={step.status || 'Sem status'} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="mt-3 text-xs font-semibold text-dark-muted">Sem etapas sincronizadas.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xs font-bold uppercase tracking-widest text-dark-muted">Observações</h3>
|
||||||
|
<dl className="mt-3 grid grid-cols-2 gap-2 text-xs">
|
||||||
|
<div><dt className="font-bold text-dark-muted">Fornecedor</dt><dd className="mt-0.5 font-semibold text-dark-text">{order.supplier || '-'}</dd></div>
|
||||||
|
<div><dt className="font-bold text-dark-muted">Lote</dt><dd className="mt-0.5 font-semibold text-dark-text">{order.lotCode || '-'}</dd></div>
|
||||||
|
<div><dt className="font-bold text-dark-muted">Rolos</dt><dd className="mt-0.5 font-semibold text-dark-text">{formatOptionalQuantity(order.rollQuantity)}</dd></div>
|
||||||
|
<div><dt className="font-bold text-dark-muted">Malha kg</dt><dd className="mt-0.5 font-semibold text-dark-text">{formatOptionalQuantity(order.fabricKg)}</dd></div>
|
||||||
|
<div><dt className="font-bold text-dark-muted">Ribana kg</dt><dd className="mt-0.5 font-semibold text-dark-text">{formatOptionalQuantity(order.ribKg)}</dd></div>
|
||||||
|
<div><dt className="font-bold text-dark-muted">Rendimento</dt><dd className="mt-0.5 font-semibold text-dark-text">{formatOptionalQuantity(order.yieldPiecesPerKg)}</dd></div>
|
||||||
|
</dl>
|
||||||
|
{order.notes && <p className="mt-3 whitespace-pre-wrap rounded-lg border border-dark-border bg-dark-input p-3 text-xs font-semibold text-dark-muted">{order.notes}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
29
src/types.ts
29
src/types.ts
@@ -34,6 +34,26 @@ export interface ProductionOrderMarker {
|
|||||||
color?: string | null;
|
color?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductionOrderComponent {
|
||||||
|
id: number;
|
||||||
|
componentTinyId: string;
|
||||||
|
componentSku: string;
|
||||||
|
componentName: string;
|
||||||
|
quantityPerUnit: number;
|
||||||
|
totalQuantity: number;
|
||||||
|
unit: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductionOrderStep {
|
||||||
|
id: number;
|
||||||
|
stepNumber: number | null;
|
||||||
|
name: string;
|
||||||
|
startDate: string | null;
|
||||||
|
endDate: string | null;
|
||||||
|
status: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductionOrderItem {
|
export interface ProductionOrderItem {
|
||||||
id: number;
|
id: number;
|
||||||
tinyId: string;
|
tinyId: string;
|
||||||
@@ -48,7 +68,16 @@ export interface ProductionOrderItem {
|
|||||||
quantity: number;
|
quantity: number;
|
||||||
unit: string;
|
unit: string;
|
||||||
integrationStatus: string;
|
integrationStatus: string;
|
||||||
|
notes: string;
|
||||||
|
supplier: string;
|
||||||
|
lotCode: string;
|
||||||
|
rollQuantity: number | null;
|
||||||
|
fabricKg: number | null;
|
||||||
|
ribKg: number | null;
|
||||||
|
yieldPiecesPerKg: number | null;
|
||||||
markers: ProductionOrderMarker[];
|
markers: ProductionOrderMarker[];
|
||||||
|
components: ProductionOrderComponent[];
|
||||||
|
steps: ProductionOrderStep[];
|
||||||
createdAt: string | null;
|
createdAt: string | null;
|
||||||
updatedAt: string | null;
|
updatedAt: string | null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user