Compare commits

...

1 Commits

Author SHA1 Message Date
JuruSysadmin f86991419f feat: agrupa resposta do pedido cond venda 7 2026-03-17 18:04:19 -03:00
7 changed files with 561 additions and 141 deletions

View File

@ -0,0 +1,250 @@
# Migracao do Frontend: `/pedidos/condvenda-7/:numped7`
## O que mudou
Antes, a API retornava uma lista achatada de itens:
```ts
PedidoCondVenda7RowDto[]
```
Agora, a API retorna uma lista de grupos logisticos:
```ts
PedidoCondVenda7GroupDto[]
```
Cada grupo ja vem separado no backend pela chave:
```txt
numped|tipoentrega|dtentrega|codfilialretira
```
## Regra para o frontend
- Nao agrupar por conta propria.
- Nao usar `hash` como chave de separacao.
- Nao inferir `tipoentrega` pelo primeiro item de uma lista mista.
- Gerar 1 etiqueta por grupo retornado pela API.
## Novo formato de resposta
```ts
type PedidoCondVenda7RowDto = {
numped: number
cgc: string
razaosocial: string
dtemissao: string
codfilial: number
filialvenda: string
codfilialretira: number
filialretira: string
data: string
numcar: number
codcli: number
cliente: string
codusur: number
vendedor: string
vlfrete: number
valor: number
tipoentrega: string
dtentrega: string | null
codprod: number
descricao: string
qt: number
hash: string
}
type PedidoCondVenda7GroupDto = {
groupKey: string
numped: number
tipoentrega: string
dtentrega: string | null
codfilialretira: number
filialretira: string
cgc: string
razaosocial: string
dtemissao: string
codfilial: number
filialvenda: string
data: string
numcar: number
codcli: number
cliente: string
codusur: number
vendedor: string
vlfrete: number
valor: number
hash: string
items: PedidoCondVenda7RowDto[]
}
```
## Exemplo de resposta
```json
[
{
"groupKey": "157073710|RETIRA POSTERIOR|2026-03-11T00:00:00.000Z|4",
"numped": 157073710,
"tipoentrega": "RETIRA POSTERIOR",
"dtentrega": "2026-03-11T00:00:00.000Z",
"codfilialretira": 4,
"filialretira": "JURUNENSE BR",
"cgc": "13772792000407",
"razaosocial": "JURUNENSE HOME CENTER LTDA",
"dtemissao": "2026-03-17T18:58:51.000Z",
"codfilial": 4,
"filialvenda": "JURUNENSE BR",
"data": "2026-03-04T00:00:00.000Z",
"numcar": 0,
"codcli": 247485,
"cliente": "SORAIA CRISTINA SILVA DA COSTA",
"codusur": 157,
"vendedor": "BR - LOURIVAL EDVALDO COSTA FERREIRA",
"vlfrete": 0,
"valor": 827.83,
"hash": "4368D18F69C36357A1FD52271EF58861",
"items": [
{
"numped": 157073710,
"tipoentrega": "RETIRA POSTERIOR",
"codprod": 12586,
"descricao": "ARG COZ E BANHEIROS 20KG QUARTZ",
"qt": 7,
"hash": "4368D18F69C36357A1FD52271EF58861"
}
]
},
{
"groupKey": "157073714|ENTREGA|2026-03-11T00:00:00.000Z|12",
"numped": 157073714,
"tipoentrega": "ENTREGA",
"dtentrega": "2026-03-11T00:00:00.000Z",
"codfilialretira": 12,
"filialretira": "JURUNENSE DISTRITO",
"cgc": "13772792001217",
"razaosocial": "JURUNENSE HOME CENTER LTDA",
"dtemissao": "2026-03-17T18:58:51.000Z",
"codfilial": 12,
"filialvenda": "JURUNENSE DISTRITO",
"data": "2026-03-04T00:00:00.000Z",
"numcar": 8899569,
"codcli": 247485,
"cliente": "SORAIA CRISTINA SILVA DA COSTA",
"codusur": 157,
"vendedor": "BR - LOURIVAL EDVALDO COSTA FERREIRA",
"vlfrete": 0,
"valor": 0,
"hash": "4368D18F69C36357A1FD52271EF58861",
"items": [
{
"numped": 157073714,
"tipoentrega": "ENTREGA",
"codprod": 54001,
"descricao": "CER 66X66RT MILANO BG GR 2,18M",
"qt": 4,
"hash": "4368D18F69C36357A1FD52271EF58861"
},
{
"numped": 157073714,
"tipoentrega": "ENTREGA",
"codprod": 54067,
"descricao": "CER 45X45 BEGE 45 2,00M",
"qt": 14,
"hash": "4368D18F69C36357A1FD52271EF58861"
}
]
}
]
```
## O que mudar no frontend
### 1. Tipagem
Trocar o tipo esperado da resposta:
```ts
const data: PedidoCondVenda7RowDto[] = await response.json()
```
por:
```ts
const groups: PedidoCondVenda7GroupDto[] = await response.json()
```
### 2. Renderizacao
Antes:
```ts
renderLabel(data)
```
Agora:
```ts
groups.forEach((group) => {
renderLabel(group)
})
```
### 3. Cabecalho da etiqueta
Os dados do cabecalho devem vir do grupo:
```ts
group.numped
group.tipoentrega
group.dtentrega
group.cliente
group.filialretira
group.vendedor
group.hash
group.valor
```
### 4. Itens da etiqueta
A tabela de produtos deve vir de:
```ts
group.items
```
Exemplo:
```ts
group.items.map((item) => ({
codprod: item.codprod,
descricao: item.descricao,
qt: item.qt,
}))
```
## Erro antigo que nao pode voltar
Se existir algo assim:
```ts
const firstItem = data[0]
const tipoEntrega = firstItem.tipoentrega
```
isso precisa sair se `data` representar mais de um pedido misturado.
Agora o correto e:
```ts
const tipoEntrega = group.tipoentrega
const items = group.items
```
## Resumo operacional
- 1 grupo retornado = 1 etiqueta
- cabecalho vem do grupo
- produtos vem de `group.items`
- separacao ja vem pronta do backend

View File

@ -8,7 +8,8 @@ import {
ApiTags, ApiTags,
} from '@nestjs/swagger'; } from '@nestjs/swagger';
import { PedidoSevenService } from '../services/pedido-seven.service'; import { PedidoSevenService } from '../services/pedido-seven.service';
import { PedidoCondVenda7RowDto } from '../dto/pedido-condvenda7-row.dto'; import { PedidoCondVenda7GroupDto } from '../dto/pedido-condvenda7-group.dto';
import { PedidoSevenParamDto } from '../dto/pedido-seven-param.dto';
@ApiTags('Pedidos') @ApiTags('Pedidos')
@Controller('pedidos') @Controller('pedidos')
@ -21,12 +22,13 @@ export class PedidoSevenController {
}) })
@ApiParam({ @ApiParam({
name: 'numped7', name: 'numped7',
type: Number,
example: 123456, example: 123456,
description: 'Valor para PCPEDC.NUMPEDENTFUT', description: 'Valor para PCPEDC.NUMPEDENTFUT',
}) })
@ApiOkResponse({ @ApiOkResponse({
description: 'Linhas retornadas pela consulta (pedido x itens)', description: 'Grupos logisticos retornados pela consulta (pedido x itens)',
type: [PedidoCondVenda7RowDto], type: [PedidoCondVenda7GroupDto],
}) })
@ApiNotFoundResponse({ @ApiNotFoundResponse({
description: 'Pedido nao encontrado para o NUMPEDENTFUT informado', description: 'Pedido nao encontrado para o NUMPEDENTFUT informado',
@ -34,7 +36,9 @@ export class PedidoSevenController {
@ApiServiceUnavailableResponse({ @ApiServiceUnavailableResponse({
description: 'Banco de dados indisponivel (DataSource nao inicializado)', description: 'Banco de dados indisponivel (DataSource nao inicializado)',
}) })
async consultar(@Param('numped7', ParseIntPipe) numped7: number) { async consultar(
@Param('numped7', ParseIntPipe) numped7: PedidoSevenParamDto['numped7'],
) {
return this.pedidoSevenService.consultarPorNumped7(numped7); return this.pedidoSevenService.consultarPorNumped7(numped7);
} }
} }

3
src/dto/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from './pedido-condvenda7-row.dto';
export * from './pedido-condvenda7-row.mapper';
export * from './pedido-seven-param.dto';

View File

@ -0,0 +1,77 @@
import { ApiProperty } from '@nestjs/swagger';
import { PedidoCondVenda7RowDto } from './pedido-condvenda7-row.dto';
export class PedidoCondVenda7GroupDto {
@ApiProperty({
example: '157073710|RETIRA POSTERIOR|2026-03-11T00:00:00.000Z|4',
})
groupKey!: string;
@ApiProperty({ example: 157073710 })
numped!: number;
@ApiProperty({ example: 'ENTREGA' })
tipoentrega!: string;
@ApiProperty({
type: String,
format: 'date-time',
required: false,
nullable: true,
})
dtentrega!: Date | null;
@ApiProperty({ example: 4 })
codfilialretira!: number;
@ApiProperty({ example: 'JURUNENSE BR' })
filialretira!: string;
@ApiProperty({ example: '12345678000199' })
cgc!: string;
@ApiProperty({ example: 'MINHA EMPRESA LTDA' })
razaosocial!: string;
@ApiProperty({ description: 'SYSDATE', type: String, format: 'date-time' })
dtemissao!: Date;
@ApiProperty({ example: 1 })
codfilial!: number;
@ApiProperty({ example: 'FILIAL CENTRO' })
filialvenda!: string;
@ApiProperty({ type: String, format: 'date-time' })
data!: Date;
@ApiProperty({ example: 999 })
numcar!: number;
@ApiProperty({ example: 123 })
codcli!: number;
@ApiProperty({ example: 'CLIENTE TESTE' })
cliente!: string;
@ApiProperty({ example: 10 })
codusur!: number;
@ApiProperty({ example: 'VENDEDOR' })
vendedor!: string;
@ApiProperty({ example: 25.5 })
vlfrete!: number;
@ApiProperty({ example: 199.9 })
valor!: number;
@ApiProperty({ example: 'e10adc3949ba59abbe56e057f20f883e' })
hash!: string;
@ApiProperty({
type: [PedidoCondVenda7RowDto],
description: 'Itens pertencentes ao grupo logistico',
})
items!: PedidoCondVenda7RowDto[];
}

View File

@ -0,0 +1,124 @@
import { InternalServerErrorException } from '@nestjs/common';
import { z, ZodError } from 'zod';
import { PedidoCondVenda7RowDto } from './pedido-condvenda7-row.dto';
function normalizeRowKeys(row: Record<string, unknown>): Record<string, unknown> {
return Object.entries(row).reduce<Record<string, unknown>>((acc, [key, value]) => {
acc[key.replace(/_/g, '').toUpperCase()] = value;
return acc;
}, {});
}
function coerceNumber(field: string) {
return z.preprocess((value) => {
if (typeof value === 'number' && Number.isFinite(value)) return value;
if (typeof value === 'string' && value.trim() !== '') {
const parsed = Number(value);
if (Number.isFinite(parsed)) return parsed;
}
return value;
}, z.number({ invalid_type_error: `Campo ${field} invalido` }));
}
function coerceString(field: string) {
return z.preprocess((value) => {
if (typeof value === 'string') return value;
if (value == null) return '';
if (typeof value === 'number' || typeof value === 'boolean') {
return String(value);
}
return value;
}, z.string({ invalid_type_error: `Campo ${field} invalido` }));
}
function coerceDate(field: string) {
return z.preprocess((value) => {
if (value instanceof Date && !Number.isNaN(value.getTime())) return value;
if (typeof value === 'string' || typeof value === 'number') {
const parsed = new Date(value);
if (!Number.isNaN(parsed.getTime())) return parsed;
}
return value;
}, z.date({ invalid_type_error: `Campo ${field} invalido` }));
}
const pedidoCondVenda7RowSchema = z.object({
NUMPED: coerceNumber('NUMPED'),
CGC: coerceString('CGC'),
RAZAOSOCIAL: coerceString('RAZAOSOCIAL'),
DTEMISSAO: coerceDate('DTEMISSAO'),
CODFILIAL: coerceNumber('CODFILIAL'),
FILIALVENDA: coerceString('FILIALVENDA'),
CODFILIALRETIRA: coerceNumber('CODFILIALRETIRA'),
FILIALRETIRA: coerceString('FILIALRETIRA'),
DATA: coerceDate('DATA'),
NUMCAR: coerceNumber('NUMCAR'),
CODCLI: coerceNumber('CODCLI'),
CLIENTE: coerceString('CLIENTE'),
CODUSUR: coerceNumber('CODUSUR'),
VENDEDOR: coerceString('VENDEDOR'),
VLFRETE: coerceNumber('VLFRETE'),
VALOR: z.preprocess((value) => {
if (value == null) return 0;
if (typeof value === 'number' && Number.isFinite(value)) return value;
if (typeof value === 'string' && value.trim() !== '') {
const parsed = Number(value);
if (Number.isFinite(parsed)) return parsed;
}
return 0;
}, z.number()),
TIPOENTREGA: coerceString('TIPOENTREGA'),
DTENTREGA: z.preprocess((value) => {
if (value == null) return null;
if (value instanceof Date && !Number.isNaN(value.getTime())) return value;
if (typeof value === 'string' || typeof value === 'number') {
const parsed = new Date(value);
if (!Number.isNaN(parsed.getTime())) return parsed;
}
return null;
}, z.date().nullable()),
CODPROD: coerceNumber('CODPROD'),
DESCRICAO: coerceString('DESCRICAO'),
QT: coerceNumber('QT'),
HASH: coerceString('HASH'),
});
export function mapPedidoCondVenda7Row(
row: Record<string, unknown>,
): PedidoCondVenda7RowDto {
try {
const parsed = pedidoCondVenda7RowSchema.parse(normalizeRowKeys(row));
return Object.assign(new PedidoCondVenda7RowDto(), {
numped: parsed.NUMPED,
cgc: parsed.CGC,
razaosocial: parsed.RAZAOSOCIAL,
dtemissao: parsed.DTEMISSAO,
codfilial: parsed.CODFILIAL,
filialvenda: parsed.FILIALVENDA,
codfilialretira: parsed.CODFILIALRETIRA,
filialretira: parsed.FILIALRETIRA,
data: parsed.DATA,
numcar: parsed.NUMCAR,
codcli: parsed.CODCLI,
cliente: parsed.CLIENTE,
codusur: parsed.CODUSUR,
vendedor: parsed.VENDEDOR,
vlfrete: parsed.VLFRETE,
valor: parsed.VALOR,
tipoentrega: parsed.TIPOENTREGA,
dtentrega: parsed.DTENTREGA,
codprod: parsed.CODPROD,
descricao: parsed.DESCRICAO,
qt: parsed.QT,
hash: parsed.HASH,
});
} catch (error) {
if (error instanceof ZodError) {
const message = error.issues[0]?.message ?? 'Linha invalida retornada pelo banco';
throw new InternalServerErrorException(message);
}
throw error;
}
}

View File

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class PedidoSevenParamDto {
@ApiProperty({
example: 123456,
description: 'Valor para PCPEDC.NUMPEDENTFUT',
})
numped7!: number;
}

View File

@ -1,4 +1,5 @@
import { import {
HttpException,
Injectable, Injectable,
InternalServerErrorException, InternalServerErrorException,
NotFoundException, NotFoundException,
@ -6,119 +7,17 @@ import {
ServiceUnavailableException, ServiceUnavailableException,
} from '@nestjs/common'; } from '@nestjs/common';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { PedidoCondVenda7GroupDto } from '../dto/pedido-condvenda7-group.dto';
import { PedidoCondVenda7RowDto } from '../dto/pedido-condvenda7-row.dto'; import { PedidoCondVenda7RowDto } from '../dto/pedido-condvenda7-row.dto';
import { mapPedidoCondVenda7Row } from '../dto/pedido-condvenda7-row.mapper';
@Injectable() @Injectable()
export class PedidoSevenService { export class PedidoSevenService {
constructor(@Optional() private readonly dataSource?: DataSource) {} constructor(@Optional() private readonly dataSource?: DataSource) {}
private static toNumber(value: unknown, field: string): number {
if (typeof value === 'number' && Number.isFinite(value)) return value;
if (typeof value === 'string' && value.trim() !== '') {
const n = Number(value);
if (Number.isFinite(n)) return n;
}
throw new InternalServerErrorException(`Campo ${field} invalido`);
}
private static asString(value: unknown, field: string): string {
if (typeof value === 'string') return value;
if (value == null) return '';
if (typeof value === 'number' || typeof value === 'boolean') {
return String(value);
}
throw new InternalServerErrorException(`Campo ${field} invalido`);
}
private static toDate(value: unknown, field: string): Date {
if (value instanceof Date && !Number.isNaN(value.getTime())) return value;
if (typeof value === 'string' || typeof value === 'number') {
const d = new Date(value);
if (!Number.isNaN(d.getTime())) return d;
}
throw new InternalServerErrorException(`Campo ${field} invalido`);
}
private static toNullableNumber(value: unknown): number {
if (value == null) return 0;
if (typeof value === 'number' && Number.isFinite(value)) return value;
if (typeof value === 'string' && value.trim() !== '') {
const n = Number(value);
if (Number.isFinite(n)) return n;
}
return 0;
}
private static toNullableDate(value: unknown): Date | null {
if (value == null) return null;
if (value instanceof Date && !Number.isNaN(value.getTime())) return value;
if (typeof value === 'string' || typeof value === 'number') {
const d = new Date(value);
if (!Number.isNaN(d.getTime())) return d;
}
return null;
}
private static pick(row: Record<string, unknown>, key: string): unknown {
return (
row[key] ??
row[key.toUpperCase()] ??
row[key.toLowerCase()] ??
row[key.replace(/_/g, '')] ??
row[key.toUpperCase().replace(/_/g, '')]
);
}
private static mapRow(row: Record<string, unknown>): PedidoCondVenda7RowDto {
const dto = new PedidoCondVenda7RowDto();
dto.numped = this.toNumber(this.pick(row, 'NUMPED'), 'NUMPED');
dto.cgc = this.asString(this.pick(row, 'CGC'), 'CGC');
dto.razaosocial = this.asString(
this.pick(row, 'RAZAOSOCIAL'),
'RAZAOSOCIAL',
);
dto.dtemissao = this.toDate(this.pick(row, 'DTEMISSAO'), 'DTEMISSAO');
dto.codfilial = this.toNumber(this.pick(row, 'CODFILIAL'), 'CODFILIAL');
dto.filialvenda = this.asString(
this.pick(row, 'FILIALVENDA'),
'FILIALVENDA',
);
dto.codfilialretira = this.toNumber(
this.pick(row, 'CODFILIALRETIRA'),
'CODFILIALRETIRA',
);
dto.filialretira = this.asString(
this.pick(row, 'FILIALRETIRA'),
'FILIALRETIRA',
);
dto.data = this.toDate(this.pick(row, 'DATA'), 'DATA');
dto.numcar = this.toNumber(this.pick(row, 'NUMCAR'), 'NUMCAR');
dto.codcli = this.toNumber(this.pick(row, 'CODCLI'), 'CODCLI');
dto.cliente = this.asString(this.pick(row, 'CLIENTE'), 'CLIENTE');
dto.codusur = this.toNumber(this.pick(row, 'CODUSUR'), 'CODUSUR');
dto.vendedor = this.asString(this.pick(row, 'VENDEDOR'), 'VENDEDOR');
dto.vlfrete = this.toNumber(this.pick(row, 'VLFRETE'), 'VLFRETE');
dto.valor = this.toNullableNumber(this.pick(row, 'VALOR'));
dto.tipoentrega = this.asString(
this.pick(row, 'TIPOENTREGA'),
'TIPOENTREGA',
);
dto.dtentrega = this.toNullableDate(this.pick(row, 'DTENTREGA'));
dto.codprod = this.toNumber(this.pick(row, 'CODPROD'), 'CODPROD');
dto.descricao = this.asString(this.pick(row, 'DESCRICAO'), 'DESCRICAO');
dto.qt = this.toNumber(this.pick(row, 'QT'), 'QT');
dto.hash = this.asString(this.pick(row, 'HASH'), 'HASH');
return dto;
}
async consultarPorNumped7( async consultarPorNumped7(
numped7: number, numped7: number,
): Promise<PedidoCondVenda7RowDto[]> { ): Promise<PedidoCondVenda7GroupDto[]> {
if (!this.dataSource) { if (!this.dataSource) {
throw new ServiceUnavailableException('DataSource nao disponivel'); throw new ServiceUnavailableException('DataSource nao disponivel');
} }
@ -142,36 +41,31 @@ SELECT
PCPEDC.DATA, PCPEDC.DATA,
PCPEDC.NUMCAR, PCPEDC.NUMCAR,
PCPEDC.CODCLI, PCPEDC.CODCLI,
PCCLIENT.CLIENTE CLIENTE, PCCLIENT.CLIENTE,
PCNFSAID.CODUSUR, PCNFSAID.CODUSUR,
PCUSUARI.NOME VENDEDOR, PCUSUARI.NOME VENDEDOR,
PCPEDC.VLFRETE, PCPEDC.VLFRETE,
CASE CASE
WHEN ROW_NUMBER() OVER ( WHEN ROW_NUMBER() OVER (
PARTITION BY PCPEDC.NUMPEDENTFUT PARTITION BY PCPEDC.NUMPED
ORDER BY PCPEDC.NUMPED, PCPEDI.ROWID ORDER BY PCPEDI.ROWID
) = 1 ) = 1
THEN CASE THEN SUM(PCPEDI.QT * PCPEDI.PVENDA) OVER (PARTITION BY PCPEDC.NUMPED)
WHEN NVL(PCNFSAID.VLTOTGER, 0) = 0 THEN PCNFSAID.VLTOTAL
ELSE PCNFSAID.VLTOTGER
END
ELSE NULL ELSE NULL
END VALOR, END AS VALOR,
DECODE( CASE PCPEDI.TIPOENTREGA
PCPEDI.TIPOENTREGA, WHEN 'EN' THEN 'ENTREGA'
'EN', 'ENTREGA', WHEN 'EF' THEN 'ENCOMENDA'
'EF', 'ENCOMENDA', WHEN 'RP' THEN 'RETIRA POSTERIOR'
'RP', 'RETIRA POSTERIOR', WHEN 'RI' THEN 'RETIRA IMEDIATA'
'RI', 'RETIRA IMEDIATA', ELSE 'RETIRA IMEDIATA'
'RETIRA IMEDIATA' END AS TIPOENTREGA,
) TIPOENTREGA,
PCPEDC.DTENTREGA, PCPEDC.DTENTREGA,
PCPEDI.CODPROD, PCPEDI.CODPROD,
PCPRODUT.DESCRICAO, PCPRODUT.DESCRICAO,
PCPEDI.QT, PCPEDI.QT,
MD5(PCNFSAID.NUMPED || '@Juru2025$') HASH MD5(PCNFSAID.NUMPED || '@Juru2025$') HASH
FROM FROM PCPEDC,
PCPEDC,
PCPEDI, PCPEDI,
PCNFSAID, PCNFSAID,
PCCLIENT, PCCLIENT,
@ -179,8 +73,7 @@ FROM
PCPRODUT, PCPRODUT,
PCFILIAL, PCFILIAL,
PCFILIAL RETIRA PCFILIAL RETIRA
WHERE WHERE PCNFSAID.NUMPED = PCPEDC.NUMPEDENTFUT
PCNFSAID.NUMPED = PCPEDC.NUMPEDENTFUT
AND PCPEDC.CODFILIAL = PCFILIAL.CODIGO AND PCPEDC.CODFILIAL = PCFILIAL.CODIGO
AND PCPEDI.CODFILIALRETIRA = RETIRA.CODIGO AND PCPEDI.CODFILIALRETIRA = RETIRA.CODIGO
AND PCPEDI.CODPROD = PCPRODUT.CODPROD AND PCPEDI.CODPROD = PCPRODUT.CODPROD
@ -190,9 +83,7 @@ WHERE
AND PCNFSAID.DTCANCEL IS NULL AND PCNFSAID.DTCANCEL IS NULL
AND PCNFSAID.CONDVENDA = 7 AND PCNFSAID.CONDVENDA = 7
AND PCPEDC.NUMPEDENTFUT = :1 AND PCPEDC.NUMPEDENTFUT = :1
ORDER BY ORDER BY PCPEDC.NUMPED, PCPEDI.CODPROD
PCPEDC.NUMPED,
PCPEDI.CODPROD
`; `;
try { try {
@ -207,18 +98,80 @@ ORDER BY
); );
} }
return rows.map((r) => { const mappedRows = rows.map((r) => {
if (r && typeof r === 'object' && !Array.isArray(r)) { if (r && typeof r === 'object' && !Array.isArray(r)) {
return PedidoSevenService.mapRow(r as Record<string, unknown>); return mapPedidoCondVenda7Row(r as Record<string, unknown>);
} }
throw new InternalServerErrorException( throw new InternalServerErrorException(
'Linha invalida retornada pelo banco', 'Linha invalida retornada pelo banco',
); );
}); });
return this.groupRows(mappedRows);
} catch (err) { } catch (err) {
if (err instanceof HttpException) {
throw err;
}
throw new InternalServerErrorException( throw new InternalServerErrorException(
`Erro ao executar a consulta: ${err instanceof Error ? err.message : String(err)}`, `Erro ao executar a consulta: ${err instanceof Error ? err.message : String(err)}`,
); );
} }
} }
private groupRows(rows: PedidoCondVenda7RowDto[]): PedidoCondVenda7GroupDto[] {
const groups = new Map<string, PedidoCondVenda7GroupDto>();
for (const row of rows) {
const groupKey = this.buildGroupKey(row);
const existingGroup = groups.get(groupKey);
if (existingGroup) {
existingGroup.items.push(row);
existingGroup.valor += row.valor ?? 0;
continue;
}
groups.set(
groupKey,
Object.assign(new PedidoCondVenda7GroupDto(), {
groupKey,
numped: row.numped,
tipoentrega: row.tipoentrega,
dtentrega: row.dtentrega,
codfilialretira: row.codfilialretira,
filialretira: row.filialretira,
cgc: row.cgc,
razaosocial: row.razaosocial,
dtemissao: row.dtemissao,
codfilial: row.codfilial,
filialvenda: row.filialvenda,
data: row.data,
numcar: row.numcar,
codcli: row.codcli,
cliente: row.cliente,
codusur: row.codusur,
vendedor: row.vendedor,
vlfrete: row.vlfrete,
valor: row.valor ?? 0,
hash: row.hash,
items: [row],
}),
);
}
return Array.from(groups.values());
}
private buildGroupKey(row: PedidoCondVenda7RowDto): string {
const dtentrega =
row.dtentrega instanceof Date ? row.dtentrega.toISOString() : 'null';
return [
row.numped,
row.tipoentrega,
dtentrega,
row.codfilialretira,
].join('|');
}
} }