feat(api,web): peso total (kg) e cubagem (m3) do pedido na impressao
- vw_produtos passa a expor vol_m3 (coluna nova no FIM da view - CREATE OR REPLACE exige; aplicada no dev) - PedidoDetail calcula pesoTotal/m3Total (qtd x peso_liquido/vol_m3), tanto para pedidos SAR quanto ERP - Impressao mostra "Peso total: X kg" e "Volume: Y m3" ao lado dos totais (some quando zerado) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1022,14 +1022,31 @@ export class OrdersService {
|
|||||||
const grupoRows =
|
const grupoRows =
|
||||||
prisma && prodIds.length > 0
|
prisma && prodIds.length > 0
|
||||||
? await prisma.$queryRawUnsafe<
|
? await prisma.$queryRawUnsafe<
|
||||||
{ id_erp: number; cod_grupo: number | null; cod_subgrupo: number | null }[]
|
{
|
||||||
|
id_erp: number;
|
||||||
|
cod_grupo: number | null;
|
||||||
|
cod_subgrupo: number | null;
|
||||||
|
peso_liquido: string | null;
|
||||||
|
vol_m3: string | null;
|
||||||
|
}[]
|
||||||
>(
|
>(
|
||||||
`SELECT id_erp, cod_grupo, cod_subgrupo FROM vw_produtos
|
`SELECT id_erp, cod_grupo, cod_subgrupo, peso_liquido::text, vol_m3::text
|
||||||
|
FROM vw_produtos
|
||||||
WHERE id_empresa = ${idMatriz} AND id_erp IN (${prodIds.join(',')})`,
|
WHERE id_empresa = ${idMatriz} AND id_erp IN (${prodIds.join(',')})`,
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
const grupoMap = new Map(grupoRows.map((g) => [Number(g.id_erp), g]));
|
const grupoMap = new Map(grupoRows.map((g) => [Number(g.id_erp), g]));
|
||||||
|
|
||||||
|
// Peso total (kg) e cubagem (m3) do pedido: soma de qtd x peso/vol do produto
|
||||||
|
let pesoTotal = 0;
|
||||||
|
let m3Total = 0;
|
||||||
|
for (const it of o.itens) {
|
||||||
|
const prod = grupoMap.get(it.idProduto);
|
||||||
|
const qtd = Number(it.qtd);
|
||||||
|
pesoTotal += qtd * Number(prod?.peso_liquido ?? 0);
|
||||||
|
m3Total += qtd * Number(prod?.vol_m3 ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: o.id,
|
id: o.id,
|
||||||
numPedSar: o.numPedSar,
|
numPedSar: o.numPedSar,
|
||||||
@@ -1042,6 +1059,8 @@ export class OrdersService {
|
|||||||
dtPedido: o.dtPedido.toISOString(),
|
dtPedido: o.dtPedido.toISOString(),
|
||||||
idPauta: o.idPauta,
|
idPauta: o.idPauta,
|
||||||
codFormapag: o.codFormapag,
|
codFormapag: o.codFormapag,
|
||||||
|
pesoTotal: pesoTotal.toFixed(3),
|
||||||
|
m3Total: m3Total.toFixed(4),
|
||||||
total: decimalToString(o.total),
|
total: decimalToString(o.total),
|
||||||
descontoPerc: decimalToString(o.descontoPerc),
|
descontoPerc: decimalToString(o.descontoPerc),
|
||||||
obs: o.obs,
|
obs: o.obs,
|
||||||
@@ -1132,6 +1151,8 @@ export class OrdersService {
|
|||||||
preco_unitario: string;
|
preco_unitario: string;
|
||||||
desconto_perc: string;
|
desconto_perc: string;
|
||||||
total: string;
|
total: string;
|
||||||
|
peso_liquido: string | null;
|
||||||
|
vol_m3: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vendedorFilter =
|
const vendedorFilter =
|
||||||
@@ -1164,7 +1185,8 @@ export class OrdersService {
|
|||||||
SELECT ei.ordem, ei.id_produto,
|
SELECT ei.ordem, ei.id_produto,
|
||||||
TRIM(p.codigo) AS codigo, TRIM(p.descricao) AS descricao,
|
TRIM(p.codigo) AS codigo, TRIM(p.descricao) AS descricao,
|
||||||
ei.qtd::text, ei.preco_unitario::text,
|
ei.qtd::text, ei.preco_unitario::text,
|
||||||
ei.desconto_perc::text, ei.total::text
|
ei.desconto_perc::text, ei.total::text,
|
||||||
|
p.peso_liquido::text AS peso_liquido, p.vol_m3::text AS vol_m3
|
||||||
FROM vw_peditens_erp ei
|
FROM vw_peditens_erp ei
|
||||||
LEFT JOIN vw_produtos p
|
LEFT JOIN vw_produtos p
|
||||||
ON p.id_erp = ei.id_produto
|
ON p.id_erp = ei.id_produto
|
||||||
@@ -1177,6 +1199,15 @@ export class OrdersService {
|
|||||||
if (!headerRows[0]) throw new NotFoundException(`Pedido ERP ${idPedido} não encontrado`);
|
if (!headerRows[0]) throw new NotFoundException(`Pedido ERP ${idPedido} não encontrado`);
|
||||||
const h = headerRows[0];
|
const h = headerRows[0];
|
||||||
|
|
||||||
|
const pesoTotalErp = itemRows.reduce(
|
||||||
|
(acc, it) => acc + Number(it.qtd) * Number(it.peso_liquido ?? 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const m3TotalErp = itemRows.reduce(
|
||||||
|
(acc, it) => acc + Number(it.qtd) * Number(it.vol_m3 ?? 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: `erp-${h.id_pedido}`,
|
id: `erp-${h.id_pedido}`,
|
||||||
numPedSar: (h.num_ped_sar ?? '').trim(),
|
numPedSar: (h.num_ped_sar ?? '').trim(),
|
||||||
@@ -1207,6 +1238,8 @@ export class OrdersService {
|
|||||||
aprovadoEm: null,
|
aprovadoEm: null,
|
||||||
motivoRecusa: null,
|
motivoRecusa: null,
|
||||||
idempotencyKey: null,
|
idempotencyKey: null,
|
||||||
|
pesoTotal: pesoTotalErp.toFixed(3),
|
||||||
|
m3Total: m3TotalErp.toFixed(4),
|
||||||
itens: itemRows.map((it) => ({
|
itens: itemRows.map((it) => ({
|
||||||
id: `${idPedido}-${it.ordem}`,
|
id: `${idPedido}-${it.ordem}`,
|
||||||
idProduto: Number(it.id_produto),
|
idProduto: Number(it.id_produto),
|
||||||
|
|||||||
@@ -381,7 +381,30 @@ export function OrderPrintPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ── Totais ──────────────────────────────────────────────────────── */}
|
{/* ── Totais ──────────────────────────────────────────────────────── */}
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end', padding: '14px 28px 4px' }}>
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
gap: 16,
|
||||||
|
padding: '14px 28px 4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Peso e cubagem do pedido (logística/frete) */}
|
||||||
|
<div style={{ fontSize: 10.5, color: MUTED, lineHeight: 1.8 }}>
|
||||||
|
{Number(order.pesoTotal ?? 0) > 0 && (
|
||||||
|
<div>
|
||||||
|
<strong style={{ color: INK }}>Peso total:</strong>{' '}
|
||||||
|
{Number(order.pesoTotal).toLocaleString('pt-BR', { maximumFractionDigits: 2 })} kg
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{Number(order.m3Total ?? 0) > 0 && (
|
||||||
|
<div>
|
||||||
|
<strong style={{ color: INK }}>Volume:</strong>{' '}
|
||||||
|
{Number(order.m3Total).toLocaleString('pt-BR', { maximumFractionDigits: 3 })} m³
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div style={{ width: 300 }}>
|
<div style={{ width: 300 }}>
|
||||||
<TotRow k="Total dos produtos" v={money(order.totalProdutos)} />
|
<TotRow k="Total dos produtos" v={money(order.totalProdutos)} />
|
||||||
{Number(order.totalIpi) > 0 && <TotRow k="IPI" v={money(order.totalIpi)} />}
|
{Number(order.totalIpi) > 0 && <TotRow k="IPI" v={money(order.totalIpi)} />}
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ export const PedidoDetailSchema = PedidoSummarySchema.extend({
|
|||||||
// Pauta/forma de pagamento do pedido SAR — edição do orçamento pré-carrega
|
// Pauta/forma de pagamento do pedido SAR — edição do orçamento pré-carrega
|
||||||
idPauta: z.number().int().nullable().optional(),
|
idPauta: z.number().int().nullable().optional(),
|
||||||
codFormapag: z.number().int().nullable().optional(),
|
codFormapag: z.number().int().nullable().optional(),
|
||||||
|
// Peso total (kg) e cubagem (m3) — qtd x peso_liquido/vol_m3 de vw_produtos
|
||||||
|
pesoTotal: z.string().optional(),
|
||||||
|
m3Total: z.string().optional(),
|
||||||
aprovadoPor: z.number().int().nullable(),
|
aprovadoPor: z.number().int().nullable(),
|
||||||
aprovadoEm: z.iso.datetime().nullable(),
|
aprovadoEm: z.iso.datetime().nullable(),
|
||||||
motivoRecusa: z.string().nullable(),
|
motivoRecusa: z.string().nullable(),
|
||||||
|
|||||||
@@ -289,7 +289,9 @@ SELECT
|
|||||||
COALESCE(p.preco_promocional, 0) AS preco_promocional,
|
COALESCE(p.preco_promocional, 0) AS preco_promocional,
|
||||||
COALESCE(p.tx_desc_lote, 0) AS tx_desc_lote,
|
COALESCE(p.tx_desc_lote, 0) AS tx_desc_lote,
|
||||||
p.lista_pauta,
|
p.lista_pauta,
|
||||||
CASE WHEN p.dt_atual > sub.da THEN p.dt_atual ELSE sub.da END AS dt_atual
|
CASE WHEN p.dt_atual > sub.da THEN p.dt_atual ELSE sub.da END AS dt_atual,
|
||||||
|
-- vol_m3 no FIM: CREATE OR REPLACE VIEW so aceita coluna nova apos as existentes
|
||||||
|
COALESCE(p.vol_m3, 0) AS vol_m3
|
||||||
FROM gestao.produto p
|
FROM gestao.produto p
|
||||||
LEFT JOIN gestao.grupo grp ON grp.codigo = p.cod_grupo AND grp.id_empresa = p.id_empresa
|
LEFT JOIN gestao.grupo grp ON grp.codigo = p.cod_grupo AND grp.id_empresa = p.id_empresa
|
||||||
LEFT JOIN gestao.grupo sub ON sub.codigo = p.cod_subgrupo AND sub.id_empresa = p.id_empresa
|
LEFT JOIN gestao.grupo sub ON sub.codigo = p.cod_subgrupo AND sub.id_empresa = p.id_empresa
|
||||||
|
|||||||
Reference in New Issue
Block a user