refactor(erp): integração direta com banco ERP — schema sar
Revoga ADR 0006 (BD-por-workspace separado). O SAR agora conecta ao banco PostgreSQL do ERP (módulo SIG) e usa o schema `sar` para tudo. PRISMA - Remove: Client, Product, Order, OrderItem, OrderStatusHistory, RepTarget, RepDiscountLimit, PushSubscription (modelos isolados) - Adiciona: Pedido, PedidoItem, HistoricoPedido, AlcadaDesconto, MetaRepresentante, PushSubscription (mapeados para sar.*) - IDs: id_cliente/cod_vendedor/id_empresa são INTEGER (ERP) - situa: Int (1=Pendente 2=Aprovado 3=Cancelado 4=Faturado) - JWT: workspace_id:string → id_empresa:number - URL: inclui ?schema=sar para Prisma rotear ao schema ERP SERVICES - ClientsService: $queryRawUnsafe contra sar.vw_clientes + sar.pedidos - CatalogService: $queryRawUnsafe contra sar.vw_produtos + sar.vw_estoque - OrdersService: Prisma models Pedido/PedidoItem/HistoricoPedido/AlcadaDesconto - DashboardService: MetaRepresentante + queries raw para inativos - NotificationsService: PushSubscription com codVendedor + idEmpresa CONTRATOS (api-interface) - client.contract: campos ERP (idCliente, nome, cgcpf, cod_vendedor…) - order.contract: PedidoSummary/PedidoDetail/CreatePedido + SITUA_LABEL - product.contract: ProdutoSummary/ProdutoDetail (vw_produtos) - auth.contract: workspaceId:string → idEmpresa:number WEB - Todos os cockpits e queries atualizados para os novos tipos Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { Badge, Input, Select, Space, Table, Tag, Tooltip, Typography } from 'antd';
|
||||
import { Badge, Input, Select, Space, Table, Typography } from 'antd';
|
||||
import type { TableColumnsType } from 'antd';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import type { ActivityStatus, ClientSummary, FinancialStatus } from '@sar/api-interface';
|
||||
import type { ActivityStatus, ClientSummary } from '@sar/api-interface';
|
||||
import { useClientList } from '../../lib/queries/clients';
|
||||
|
||||
const { Title } = Typography;
|
||||
@@ -16,31 +16,27 @@ const ACTIVITY_CONFIG: Record<ActivityStatus, { color: string; label: string }>
|
||||
inactive: { color: 'error', label: 'Inativo' },
|
||||
};
|
||||
|
||||
const FINANCIAL_CONFIG: Record<FinancialStatus, { color: string; label: string }> = {
|
||||
regular: { color: 'success', label: 'Regular' },
|
||||
attention: { color: 'warning', label: 'Atenção' },
|
||||
blocked: { color: 'error', label: 'Bloqueado' },
|
||||
};
|
||||
|
||||
// ─── Columns ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function buildColumns(navigate: ReturnType<typeof useNavigate>): TableColumnsType<ClientSummary> {
|
||||
return [
|
||||
{
|
||||
title: 'Cliente',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (name: string, record: ClientSummary) => (
|
||||
dataIndex: 'nome',
|
||||
key: 'nome',
|
||||
render: (nome: string, record: ClientSummary) => (
|
||||
<Space direction="vertical" size={0}>
|
||||
<Typography.Link
|
||||
strong
|
||||
onClick={() => navigate({ to: '/clientes/$id', params: { id: record.id } })}
|
||||
onClick={() =>
|
||||
navigate({ to: '/clientes/$id', params: { id: String(record.idCliente) } })
|
||||
}
|
||||
>
|
||||
{name}
|
||||
{nome}
|
||||
</Typography.Link>
|
||||
{record.tradeName && (
|
||||
{record.razao && (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{record.tradeName}
|
||||
{record.razao}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Space>
|
||||
@@ -49,12 +45,12 @@ function buildColumns(navigate: ReturnType<typeof useNavigate>): TableColumnsTyp
|
||||
},
|
||||
{
|
||||
title: 'CNPJ / CPF',
|
||||
dataIndex: 'taxId',
|
||||
key: 'taxId',
|
||||
dataIndex: 'cgcpf',
|
||||
key: 'cgcpf',
|
||||
width: 160,
|
||||
render: (v: string) => (
|
||||
render: (v: string | null) => (
|
||||
<Typography.Text className="tabular-nums" style={{ fontSize: 13 }}>
|
||||
{v}
|
||||
{v ?? '—'}
|
||||
</Typography.Text>
|
||||
),
|
||||
},
|
||||
@@ -68,49 +64,20 @@ function buildColumns(navigate: ReturnType<typeof useNavigate>): TableColumnsTyp
|
||||
return <Badge status={cfg.color as 'success' | 'warning' | 'error'} text={cfg.label} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Situação',
|
||||
dataIndex: 'financialStatus',
|
||||
key: 'financialStatus',
|
||||
width: 110,
|
||||
render: (v: FinancialStatus) => {
|
||||
const cfg = FINANCIAL_CONFIG[v];
|
||||
return <Tag color={cfg.color}>{cfg.label}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Última compra',
|
||||
dataIndex: 'lastOrderAt',
|
||||
key: 'lastOrderAt',
|
||||
dataIndex: 'dtUltimaCompra',
|
||||
key: 'dtUltimaCompra',
|
||||
width: 140,
|
||||
render: (v: string | null, record: ClientSummary) => {
|
||||
render: (v: string | null) => {
|
||||
if (!v) return <Typography.Text type="secondary">—</Typography.Text>;
|
||||
const date = new Date(v).toLocaleDateString('pt-BR');
|
||||
const value = record.lastOrderValue
|
||||
? `R$ ${Number(record.lastOrderValue).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}`
|
||||
: '';
|
||||
return (
|
||||
<Tooltip title={value}>
|
||||
<Typography.Text className="tabular-nums">{date}</Typography.Text>
|
||||
</Tooltip>
|
||||
<Typography.Text className="tabular-nums">
|
||||
{new Date(v).toLocaleDateString('pt-BR')}
|
||||
</Typography.Text>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Pedidos abertos',
|
||||
dataIndex: 'openOrdersCount',
|
||||
key: 'openOrdersCount',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
render: (v: number) =>
|
||||
v > 0 ? (
|
||||
<Tag color="processing" className="tabular-nums">
|
||||
{v}
|
||||
</Tag>
|
||||
) : (
|
||||
<Typography.Text type="secondary">—</Typography.Text>
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -141,7 +108,7 @@ export function ClientsPage() {
|
||||
Carteira de Clientes
|
||||
</Title>
|
||||
<Typography.Text type="secondary">
|
||||
{data ? `${data.total} cliente${data.total !== 1 ? 's' : ''} na sua carteira` : ' '}
|
||||
{data ? `${data.total} cliente${data.total !== 1 ? 's' : ''} na sua carteira` : ' '}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
|
||||
@@ -179,7 +146,7 @@ export function ClientsPage() {
|
||||
<Table<ClientSummary>
|
||||
columns={columns}
|
||||
dataSource={data?.data ?? []}
|
||||
rowKey="id"
|
||||
rowKey="idCliente"
|
||||
loading={isLoading || isFetching}
|
||||
pagination={{
|
||||
current: page,
|
||||
@@ -189,11 +156,12 @@ export function ClientsPage() {
|
||||
showTotal: (total) => `${total} clientes`,
|
||||
onChange: (p) => setPage(p),
|
||||
}}
|
||||
scroll={{ x: 900 }}
|
||||
scroll={{ x: 700 }}
|
||||
size="middle"
|
||||
onRow={(record) => ({
|
||||
style: { cursor: 'pointer' },
|
||||
onClick: () => navigate({ to: '/clientes/$id', params: { id: record.id } }),
|
||||
onClick: () =>
|
||||
navigate({ to: '/clientes/$id', params: { id: String(record.idCliente) } }),
|
||||
})}
|
||||
/>
|
||||
</Space>
|
||||
|
||||
Reference in New Issue
Block a user