Record cut layers and marker references

This commit is contained in:
Cauê Faleiros
2026-08-27 10:56:11 -03:00
parent 791246c55e
commit 886969cce0
4 changed files with 35 additions and 5 deletions

View File

@@ -647,7 +647,9 @@ const initDB = async () => {
ADD COLUMN IF NOT EXISTS actual_cut_quantity NUMERIC(14, 4), ADD COLUMN IF NOT EXISTS actual_cut_quantity NUMERIC(14, 4),
ADD COLUMN IF NOT EXISTS rejected_quantity NUMERIC(14, 4) NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS rejected_quantity NUMERIC(14, 4) NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS finished_quantity NUMERIC(14, 4), ADD COLUMN IF NOT EXISTS finished_quantity NUMERIC(14, 4),
ADD COLUMN IF NOT EXISTS quality_notes TEXT; ADD COLUMN IF NOT EXISTS quality_notes TEXT,
ADD COLUMN IF NOT EXISTS cut_layers INTEGER,
ADD COLUMN IF NOT EXISTS marker_reference VARCHAR(160);
`).catch(() => {}); `).catch(() => {});
await pool.query(` await pool.query(`

View File

@@ -322,6 +322,8 @@ const mapProductionOrderRow = (row) => {
rejectedQuantity: Number(row.rejected_quantity || 0), rejectedQuantity: Number(row.rejected_quantity || 0),
finishedQuantity: row.finished_quantity === null ? null : Number(row.finished_quantity), finishedQuantity: row.finished_quantity === null ? null : Number(row.finished_quantity),
qualityNotes: row.quality_notes || '', qualityNotes: row.quality_notes || '',
cutLayers: row.cut_layers === null || row.cut_layers === undefined ? null : Number(row.cut_layers),
markerReference: row.marker_reference || '',
defects: Array.isArray(row.defects) ? row.defects.map(mapDefect) : [], defects: Array.isArray(row.defects) ? row.defects.map(mapDefect) : [],
lotReservations: Array.isArray(row.lot_reservations) ? row.lot_reservations.map(mapLotReservation) : [], lotReservations: Array.isArray(row.lot_reservations) ? row.lot_reservations.map(mapLotReservation) : [],
markers: Array.isArray(row.markers) ? row.markers.filter(Boolean) : [], markers: Array.isArray(row.markers) ? row.markers.filter(Boolean) : [],
@@ -526,6 +528,8 @@ const getOrderById = async (id, client = pool) => {
po.rejected_quantity, po.rejected_quantity,
po.finished_quantity, po.finished_quantity,
po.quality_notes, po.quality_notes,
po.cut_layers,
po.marker_reference,
po.created_at, po.created_at,
po.updated_at, po.updated_at,
COALESCE( COALESCE(
@@ -664,6 +668,8 @@ const baseProductionOrderSelect = `
po.rejected_quantity, po.rejected_quantity,
po.finished_quantity, po.finished_quantity,
po.quality_notes, po.quality_notes,
po.cut_layers,
po.marker_reference,
po.created_at, po.created_at,
po.updated_at, po.updated_at,
COALESCE( COALESCE(
@@ -1842,15 +1848,19 @@ const updateProductionOrderExecution = async (id, payload = {}) => {
const rejectedQuantity = normalizeNonNegativeQuantity(payload.rejectedQuantity); const rejectedQuantity = normalizeNonNegativeQuantity(payload.rejectedQuantity);
const finishedQuantity = normalizeNonNegativeQuantity(payload.finishedQuantity); const finishedQuantity = normalizeNonNegativeQuantity(payload.finishedQuantity);
const qualityNotes = normalizeText(payload.qualityNotes); const qualityNotes = normalizeText(payload.qualityNotes);
const cutLayers = payload.cutLayers === '' || payload.cutLayers === null || payload.cutLayers === undefined
? null
: normalizeInteger(payload.cutLayers);
const markerReference = normalizeText(payload.markerReference);
if (actualCutQuantity === null && rejectedQuantity === null && finishedQuantity === null && !qualityNotes) { if (actualCutQuantity === null && rejectedQuantity === null && finishedQuantity === null && !qualityNotes && cutLayers === null && !markerReference) {
const error = new Error('Informe uma quantidade de execução ou uma observação de qualidade.'); const error = new Error('Informe uma quantidade de execução ou uma observação de qualidade.');
error.statusCode = 400; error.statusCode = 400;
throw error; throw error;
} }
const currentResult = await pool.query(` const currentResult = await pool.query(`
SELECT actual_cut_quantity, rejected_quantity, finished_quantity SELECT actual_cut_quantity, rejected_quantity, finished_quantity, cut_layers, marker_reference
FROM production_orders FROM production_orders
WHERE id = $1; WHERE id = $1;
`, [id]); `, [id]);
@@ -1864,6 +1874,14 @@ const updateProductionOrderExecution = async (id, payload = {}) => {
const effectiveActual = actualCutQuantity === null ? (current.actual_cut_quantity === null ? null : Number(current.actual_cut_quantity)) : actualCutQuantity; const effectiveActual = actualCutQuantity === null ? (current.actual_cut_quantity === null ? null : Number(current.actual_cut_quantity)) : actualCutQuantity;
const effectiveRejected = rejectedQuantity === null ? Number(current.rejected_quantity || 0) : rejectedQuantity; const effectiveRejected = rejectedQuantity === null ? Number(current.rejected_quantity || 0) : rejectedQuantity;
const effectiveFinished = finishedQuantity === null ? (current.finished_quantity === null ? null : Number(current.finished_quantity)) : finishedQuantity; const effectiveFinished = finishedQuantity === null ? (current.finished_quantity === null ? null : Number(current.finished_quantity)) : finishedQuantity;
const effectiveLayers = cutLayers === null ? (current.cut_layers === null ? null : Number(current.cut_layers)) : cutLayers;
const effectiveMarkerReference = markerReference || current.marker_reference || '';
if (effectiveLayers !== null && effectiveLayers <= 0) {
const error = new Error('O número de camadas deve ser maior que zero.');
error.statusCode = 400;
throw error;
}
if (effectiveActual !== null && effectiveRejected > effectiveActual) { if (effectiveActual !== null && effectiveRejected > effectiveActual) {
const error = new Error('As perdas não podem ser maiores que a quantidade cortada.'); const error = new Error('As perdas não podem ser maiores que a quantidade cortada.');
@@ -1882,9 +1900,11 @@ const updateProductionOrderExecution = async (id, payload = {}) => {
rejected_quantity = $2, rejected_quantity = $2,
finished_quantity = $3, finished_quantity = $3,
quality_notes = NULLIF($4, ''), quality_notes = NULLIF($4, ''),
cut_layers = $5,
marker_reference = NULLIF($6, ''),
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
WHERE id = $5; WHERE id = $7;
`, [effectiveActual, effectiveRejected, effectiveFinished, qualityNotes, id]); `, [effectiveActual, effectiveRejected, effectiveFinished, qualityNotes, effectiveLayers, effectiveMarkerReference, id]);
return getOrderById(id); return getOrderById(id);
}; };

View File

@@ -307,6 +307,8 @@ const ProductionOrders = () => {
rejectedQuantity: parseOptionalQuantity('rejectedQuantity'), rejectedQuantity: parseOptionalQuantity('rejectedQuantity'),
finishedQuantity: parseOptionalQuantity('finishedQuantity'), finishedQuantity: parseOptionalQuantity('finishedQuantity'),
qualityNotes: String(form.get('qualityNotes') || '').trim(), qualityNotes: String(form.get('qualityNotes') || '').trim(),
cutLayers: parseOptionalQuantity('cutLayers'),
markerReference: String(form.get('markerReference') || '').trim(),
}); });
const nextSummary = await fetchProductionOrders(dateRange, { search: searchTerm }, { force: true }); const nextSummary = await fetchProductionOrders(dateRange, { search: searchTerm }, { force: true });
setSummary(nextSummary); setSummary(nextSummary);
@@ -419,6 +421,8 @@ const ProductionOrders = () => {
<label className="text-xs font-bold text-dark-muted">Cortado real<input name="actualCutQuantity" inputMode="decimal" defaultValue={order.actualCutQuantity ?? ''} className="mt-1.5 h-11 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" placeholder={formatQuantity(order.quantity)} /></label> <label className="text-xs font-bold text-dark-muted">Cortado real<input name="actualCutQuantity" inputMode="decimal" defaultValue={order.actualCutQuantity ?? ''} className="mt-1.5 h-11 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" placeholder={formatQuantity(order.quantity)} /></label>
<label className="text-xs font-bold text-dark-muted">Perdas / defeitos<input name="rejectedQuantity" inputMode="decimal" defaultValue={order.rejectedQuantity || ''} className="mt-1.5 h-11 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" placeholder="0" /></label> <label className="text-xs font-bold text-dark-muted">Perdas / defeitos<input name="rejectedQuantity" inputMode="decimal" defaultValue={order.rejectedQuantity || ''} className="mt-1.5 h-11 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" placeholder="0" /></label>
<label className="text-xs font-bold text-dark-muted">Aprovado final<input name="finishedQuantity" inputMode="decimal" defaultValue={order.finishedQuantity ?? ''} className="mt-1.5 h-11 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" placeholder="Após revisão" /></label> <label className="text-xs font-bold text-dark-muted">Aprovado final<input name="finishedQuantity" inputMode="decimal" defaultValue={order.finishedQuantity ?? ''} className="mt-1.5 h-11 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" placeholder="Após revisão" /></label>
<label className="text-xs font-bold text-dark-muted">Camadas cortadas<input name="cutLayers" inputMode="numeric" defaultValue={order.cutLayers ?? ''} className="mt-1.5 h-11 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" placeholder="Ex.: 12" /></label>
<label className="text-xs font-bold text-dark-muted sm:col-span-2">Risco / encaixe<input name="markerReference" defaultValue={order.markerReference} className="mt-1.5 h-11 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" placeholder="Código do risco, encaixe ou arquivo" /></label>
<label className="text-xs font-bold text-dark-muted sm:col-span-3">Motivo de perda / observação<textarea name="qualityNotes" defaultValue={order.qualityNotes} className="mt-1.5 min-h-20 w-full rounded-lg border border-dark-border bg-dark-input px-3 py-2.5 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" placeholder="Ex.: 4 peças com mancha de tinta; 2 recuperadas na revisão." /></label> <label className="text-xs font-bold text-dark-muted sm:col-span-3">Motivo de perda / observação<textarea name="qualityNotes" defaultValue={order.qualityNotes} className="mt-1.5 min-h-20 w-full rounded-lg border border-dark-border bg-dark-input px-3 py-2.5 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" placeholder="Ex.: 4 peças com mancha de tinta; 2 recuperadas na revisão." /></label>
</div> </div>
<div className="mt-4 flex flex-wrap items-center justify-between gap-3"><p className="text-xs font-semibold text-dark-muted">Planejado: {formatQuantity(order.quantity)} {order.unit}</p><button type="submit" disabled={busyExecutionOrderId === order.id} className="inline-flex h-10 items-center gap-2 rounded-lg border border-brand-primary/35 bg-brand-primary/10 px-4 text-sm font-bold text-brand-primary transition-colors hover:bg-brand-primary/20 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"><CheckCircle2 className="h-4 w-4" />{busyExecutionOrderId === order.id ? 'Salvando...' : 'Salvar execução'}</button></div> <div className="mt-4 flex flex-wrap items-center justify-between gap-3"><p className="text-xs font-semibold text-dark-muted">Planejado: {formatQuantity(order.quantity)} {order.unit}</p><button type="submit" disabled={busyExecutionOrderId === order.id} className="inline-flex h-10 items-center gap-2 rounded-lg border border-brand-primary/35 bg-brand-primary/10 px-4 text-sm font-bold text-brand-primary transition-colors hover:bg-brand-primary/20 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"><CheckCircle2 className="h-4 w-4" />{busyExecutionOrderId === order.id ? 'Salvando...' : 'Salvar execução'}</button></div>

View File

@@ -159,6 +159,8 @@ export interface ProductionOrderItem {
rejectedQuantity: number; rejectedQuantity: number;
finishedQuantity: number | null; finishedQuantity: number | null;
qualityNotes: string; qualityNotes: string;
cutLayers: number | null;
markerReference: string;
defects: ProductionOrderDefect[]; defects: ProductionOrderDefect[];
lotReservations: ProductionOrderLotReservation[]; lotReservations: ProductionOrderLotReservation[];
markers: ProductionOrderMarker[]; markers: ProductionOrderMarker[];
@@ -285,6 +287,8 @@ export type ProductionOrderExecutionPayload = {
rejectedQuantity?: number | null; rejectedQuantity?: number | null;
finishedQuantity?: number | null; finishedQuantity?: number | null;
qualityNotes?: string; qualityNotes?: string;
cutLayers?: number | null;
markerReference?: string;
}; };
export interface CreateProductionOrdersResult { export interface CreateProductionOrdersResult {