Compare commits

...

3 Commits

Author SHA1 Message Date
Cauê Faleiros
ba136b9d78 fix: keep sessions on forbidden responses
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m32s
2026-08-28 11:07:39 -03:00
Cauê Faleiros
5a23817f4c Show synchronized composition in production orders 2026-08-28 09:39:01 -03:00
Cauê Faleiros
35f3938f1c Widen material transformation form 2026-08-28 09:36:05 -03:00
3 changed files with 95 additions and 8 deletions

View File

@@ -560,8 +560,50 @@ const getOrderById = async (id, client = pool) => {
) )
ORDER BY component.id ORDER BY component.id
) )
FROM production_order_components component FROM (
WHERE component.production_order_id = po.id SELECT
order_component.id,
order_component.component_tiny_id,
order_component.component_sku,
order_component.component_name,
order_component.quantity_per_unit,
order_component.total_quantity,
order_component.unit
FROM production_order_components order_component
WHERE order_component.production_order_id = po.id
UNION ALL
SELECT
-composition_component.id AS id,
composition_component.component_tiny_id,
composition_component.component_sku,
composition_component.component_name,
composition_component.quantity_per_unit,
composition_component.quantity_per_unit * po.quantity AS total_quantity,
composition_component.unit
FROM product_composition_components composition_component
JOIN product_compositions composition
ON composition.id = (
SELECT matched_composition.id
FROM product_compositions matched_composition
WHERE matched_composition.source IN ('manual', 'tiny_olist_v3')
AND (
matched_composition.finished_tiny_product_id = po.product_sku
OR matched_composition.finished_tiny_product_id = po.tiny_id
OR matched_composition.external_source_id = po.product_sku
OR matched_composition.external_source_id = po.tiny_id
OR matched_composition.finished_product_sku = UPPER(po.product_sku)
)
ORDER BY CASE matched_composition.source WHEN 'manual' THEN 0 ELSE 1 END, matched_composition.id
LIMIT 1
)
WHERE NOT EXISTS (
SELECT 1
FROM production_order_components order_component
WHERE order_component.production_order_id = po.id
)
) component
), ),
'[]'::json '[]'::json
) as components, ) as components,
@@ -700,8 +742,50 @@ const baseProductionOrderSelect = `
) )
ORDER BY component.id ORDER BY component.id
) )
FROM production_order_components component FROM (
WHERE component.production_order_id = po.id SELECT
order_component.id,
order_component.component_tiny_id,
order_component.component_sku,
order_component.component_name,
order_component.quantity_per_unit,
order_component.total_quantity,
order_component.unit
FROM production_order_components order_component
WHERE order_component.production_order_id = po.id
UNION ALL
SELECT
-composition_component.id AS id,
composition_component.component_tiny_id,
composition_component.component_sku,
composition_component.component_name,
composition_component.quantity_per_unit,
composition_component.quantity_per_unit * po.quantity AS total_quantity,
composition_component.unit
FROM product_composition_components composition_component
JOIN product_compositions composition
ON composition.id = (
SELECT matched_composition.id
FROM product_compositions matched_composition
WHERE matched_composition.source IN ('manual', 'tiny_olist_v3')
AND (
matched_composition.finished_tiny_product_id = po.product_sku
OR matched_composition.finished_tiny_product_id = po.tiny_id
OR matched_composition.external_source_id = po.product_sku
OR matched_composition.external_source_id = po.tiny_id
OR matched_composition.finished_product_sku = UPPER(po.product_sku)
)
ORDER BY CASE matched_composition.source WHEN 'manual' THEN 0 ELSE 1 END, matched_composition.id
LIMIT 1
)
WHERE NOT EXISTS (
SELECT 1
FROM production_order_components order_component
WHERE order_component.production_order_id = po.id
)
) component
), ),
'[]'::json '[]'::json
) as components, ) as components,

View File

@@ -128,7 +128,7 @@ export const fetchStock = async (): Promise<StockData[]> => {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
} }
}); });
if (response.status === 401 || response.status === 403) { if (response.status === 401) {
logout(); logout();
return []; return [];
} }
@@ -171,7 +171,7 @@ export const fetchData = async (): Promise<OrderData[]> => {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
} }
}); });
if (response.status === 401 || response.status === 403) { if (response.status === 401) {
logout(); logout();
return []; return [];
} }
@@ -193,7 +193,10 @@ const authFetch = async (path: string, options: RequestInit = {}): Promise<Respo
} }
}); });
if (response.status === 401 || response.status === 403) { // A 403 means the signed-in user lacks permission for this specific
// resource. Keep their session intact so protected optional data cannot
// force a logout from an otherwise accessible page.
if (response.status === 401) {
logout(); logout();
} }

View File

@@ -2786,7 +2786,7 @@ const MaterialTransformationsScreen = () => {
<div className={`${panelClassName} p-5`}><p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Saída esperada</p><p className="mt-2 text-3xl font-bold text-emerald-300">{formatNumber(expectedOutputKg)} kg</p><p className="mt-1 text-xs font-semibold text-dark-muted">após a perda prevista</p></div> <div className={`${panelClassName} p-5`}><p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Saída esperada</p><p className="mt-2 text-3xl font-bold text-emerald-300">{formatNumber(expectedOutputKg)} kg</p><p className="mt-1 text-xs font-semibold text-dark-muted">após a perda prevista</p></div>
<div className={`${panelClassName} p-5`}><p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Perda prevista</p><p className="mt-2 text-3xl font-bold text-amber-300">{formatNumber(expectedLossKg)} kg</p><p className="mt-1 text-xs font-semibold text-dark-muted">a confirmar na execução e no recebimento</p></div> <div className={`${panelClassName} p-5`}><p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Perda prevista</p><p className="mt-2 text-3xl font-bold text-amber-300">{formatNumber(expectedLossKg)} kg</p><p className="mt-1 text-xs font-semibold text-dark-muted">a confirmar na execução e no recebimento</p></div>
</div> </div>
<div className="grid grid-cols-1 items-start gap-6 xl:grid-cols-[minmax(0,1fr)_390px]"> <div className="grid grid-cols-1 items-start gap-6 xl:grid-cols-[minmax(0,1fr)_460px]">
<section className={`${panelClassName} overflow-hidden`}> <section className={`${panelClassName} overflow-hidden`}>
<div className="border-b border-dark-border p-5"><h2 className="text-base font-bold text-dark-text">Fila de transformação</h2><p className="mt-1 text-sm font-semibold text-dark-muted">Cada linha registra uma etapa; use o mesmo lote para encadear tecelagem, tinturaria e acabamento.</p></div> <div className="border-b border-dark-border p-5"><h2 className="text-base font-bold text-dark-text">Fila de transformação</h2><p className="mt-1 text-sm font-semibold text-dark-muted">Cada linha registra uma etapa; use o mesmo lote para encadear tecelagem, tinturaria e acabamento.</p></div>
{isLoading ? <div className={emptyStateClassName}><Factory className="h-8 w-8 text-brand-primary" /><h3 className="text-base font-bold text-dark-text">Carregando transformações...</h3></div> {isLoading ? <div className={emptyStateClassName}><Factory className="h-8 w-8 text-brand-primary" /><h3 className="text-base font-bold text-dark-text">Carregando transformações...</h3></div>