feat(web+api): cockpit rep — novo pedido v2, endereço de entrega e cancelamento de orçamento

- NewOrderPage: etapas 1-5 — merge de cards, busca de produto melhorada
  (autocomplete com estoque + modal catálogo), responsividade mobile (tabela →
  cards, footer compacto), histórico de compras do cliente e campo de
  endereço de entrega (cadastrado vs livre)
- endEntrega: campo TEXT adicionado em sar.pedidos; contrato CreatePedidoSchema
  e PedidoDetailSchema atualizados; aparece no detalhe (OrderDetailPage,
  OrderDetailModal) e no PDF (OrderPrintPage)
- Cancelamento de orçamento: PATCH /orders/:id/cancel — rep cancela seu
  próprio orçamento (situa 0); histórico registrado; UI com Modal.confirm
  substituindo alert() em OrderActionsMenu
- Fix: useClientDetail chamado para cliente selecionado manualmente, garantindo
  que clientEndStr seja preenchido mesmo sem clientIdParam na URL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 14:25:24 +00:00
parent 5bda00009f
commit a6cb1df2aa
10 changed files with 1181 additions and 202 deletions

View File

@@ -439,6 +439,7 @@ export class ClientsService {
interface Row {
id_produto: number;
codigo: string;
descricao: string;
unidade: string | null;
qtd_total: string;
@@ -449,37 +450,39 @@ export class ClientsService {
const rows = await prisma.$queryRawUnsafe<Row[]>(`
SELECT
i.produ AS id_produto,
i.id_produto,
TRIM(p.codigo) AS codigo,
TRIM(p.descricao) AS descricao,
TRIM(p.unidade) AS unidade,
SUM(i.qtd)::numeric(15,3)::text AS qtd_total,
COUNT(DISTINCT ped.id_pedido)::text AS num_pedidos,
MAX(ped.data) AS ultima_compra,
MAX(ped.dt_pedido) AS ultima_compra,
(
SELECT i2.pruni::numeric(15,2)::text
FROM sig.peditens i2
JOIN sig.pedidos p2 ON p2.id_pedido = i2.id_pedido
WHERE i2.produ = i.produ
AND p2.clien = ped.clien
AND p2.id_empresa = ${idEmpresa}
AND p2.situa NOT IN (5)
ORDER BY p2.data DESC, p2.id_pedido DESC
SELECT i2.preco_unitario::numeric(15,2)::text
FROM sar.vw_peditens_erp i2
JOIN sar.vw_pedidos_erp p2 ON p2.id_pedido = i2.id_pedido
WHERE i2.id_produto = i.id_produto
AND p2.id_cliente = ped.id_cliente
AND p2.id_empresa = ${idEmpresa}
AND p2.situa NOT IN (5)
ORDER BY p2.dt_pedido DESC, p2.id_pedido DESC
LIMIT 1
) AS ultimo_preco
FROM sig.pedidos ped
JOIN sig.peditens i ON i.id_pedido = ped.id_pedido
JOIN gestao.produto p ON p.id_erp = i.produ
AND p.id_empresa = ${idEmpresaMatriz}
WHERE ped.clien = ${idCliente}
AND ped.id_empresa = ${idEmpresa}
AND ped.situa NOT IN (5)
GROUP BY i.produ, p.descricao, p.unidade, ped.clien
FROM sar.vw_pedidos_erp ped
JOIN sar.vw_peditens_erp i ON i.id_pedido = ped.id_pedido
JOIN sar.vw_produtos p ON p.id_erp = i.id_produto
AND p.id_empresa = ${idEmpresaMatriz}
WHERE ped.id_cliente = ${idCliente}
AND ped.id_empresa = ${idEmpresa}
AND ped.situa NOT IN (5)
GROUP BY i.id_produto, p.descricao, p.unidade, ped.id_cliente
ORDER BY SUM(i.qtd) DESC
LIMIT ${limit}
`);
return rows.map((r) => ({
idProduto: Number(r.id_produto),
codProduto: r.codigo ?? '',
descricao: r.descricao,
unidade: r.unidade,
qtdTotal: parseFloat(r.qtd_total ?? '0'),
@@ -646,12 +649,12 @@ export class ClientsService {
nf.dt_emissao,
nf.vl_nf::text AS vl_nf,
TRIM(nf.chave_acesso_nfe) AS chave_acesso_nfe
FROM gestao.nf nf
JOIN sig.pedidos p
FROM sar.vw_nf nf
JOIN sar.vw_pedidos_erp p
ON p.numero = nf.num_entrega
AND p.tipo = 'E'
AND p.id_empresa IN (1, 9001)
WHERE p.clien = $1
WHERE p.id_cliente = $1
AND nf.id_empresa = 1
AND nf.status = 'E'
AND TRIM(COALESCE(nf.chave_acesso_nfe, '')) != ''