import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { ClsService } from 'nestjs-cls';
import { Prisma } from '@prisma/client';
import type {
  AprovarPedido,
  CancelPedido,
  CreatePedido,
  UpdatePedido,
  PedidoDetail,
  PedidoErpConsultaItem,
  PedidoErpConsultaQuery,
  PedidoErpConsultaResponse,
  PedidoListQuery,
  PedidoListResponse,
  PedidoSummary,
  RecusarPedido,
} from '@sar/api-interface';
import type { WorkspaceClsStore } from '../workspace/workspace.types';
import { getTeamCodes } from '../workspace/team-scope.util';
import { NotificationsService } from '../notifications/notifications.service';

// Situa SAR: 0=Orçamento, 1=Ag.Aprovação, 2=Confirmado, 3=Cancelado, 4=Faturado
// Situa SIG: 1=Pendente,    2=Liberado,  5=Cancelado, 4=Faturado
const SITUA_ORCAMENTO = 0;
const SITUA_PENDENTE = 1;
const SITUA_APROVADO = 2;
const SITUA_CANCELADO = 3;

// Mapeia situa SIG → situa SAR para exibição correta no frontend.
// SIG usa 5 para Cancelado; SAR usa 3. Demais valores coincidem.
function sigToSar(sigSitua: number): number {
  return sigSitua === 5 ? 3 : sigSitua;
}

function decimalToString(v: Prisma.Decimal | null | undefined): string {
  return v ? v.toString() : '0';
}

@Injectable()
export class OrdersService {
  constructor(
    private readonly cls: ClsService<WorkspaceClsStore>,
    private readonly notifications: NotificationsService,
  ) {}

  async list(query: PedidoListQuery): Promise<PedidoListResponse> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const role = this.cls.get('role');
    const userId = this.cls.get('userId');
    const codVendedor = userId ? parseInt(userId, 10) : 0;

    const { idCliente, situa, numPedSar, from, to, page, limit } = query;
    const offset = (page - 1) * limit;

    // Rep vê só os próprios pedidos; supervisor vê os da sua equipe
    // (cod_supervisor em vw_representantes); gerente/admin veem tudo.
    const team = role === 'supervisor' ? await getTeamCodes(prisma, codVendedor) : [];
    const where: Prisma.PedidoWhereInput = {
      idEmpresa,
      ...(role === 'rep' ? { codVendedor } : {}),
      ...(role === 'supervisor' ? { codVendedor: { in: team } } : {}),
      ...(idCliente != null ? { idCliente } : {}),
      ...(situa != null ? { situa } : {}),
      ...(numPedSar ? { numPedSar: { contains: numPedSar, mode: 'insensitive' as const } } : {}),
      ...(from || to
        ? {
            dtPedido: {
              ...(from ? { gte: new Date(from) } : {}),
              ...(to ? { lte: new Date(to) } : {}),
            },
          }
        : {}),
    };

    const [pedidos, total] = await Promise.all([
      prisma.pedido.findMany({ where, orderBy: { dtPedido: 'desc' }, skip: offset, take: limit }),
      prisma.pedido.count({ where }),
    ]);

    const cliIds = [...new Set(pedidos.map((p) => p.idCliente))];
    const repCods = [...new Set(pedidos.map((p) => p.codVendedor))];
    const [cliNameRows, repNameRows] = await Promise.all([
      cliIds.length
        ? prisma.$queryRawUnsafe<
            { id_cliente: number; nome: string | null; razao: string | null }[]
          >(
            `SELECT id_cliente, nome, razao FROM vw_clientes WHERE id_cliente IN (${cliIds.join(',')})`,
          )
        : Promise.resolve([]),
      repCods.length
        ? prisma.$queryRawUnsafe<{ codigo: number; nome: string | null }[]>(
            `SELECT codigo, nome FROM vw_representantes WHERE codigo IN (${repCods.join(',')})`,
          )
        : Promise.resolve([]),
    ]);
    const cliMap = new Map(cliNameRows.map((c) => [Number(c.id_cliente), c]));
    const repMap = new Map(repNameRows.map((r) => [Number(r.codigo), r.nome]));

    const data: PedidoSummary[] = pedidos.map((p) => ({
      id: p.id,
      numPedSar: p.numPedSar,
      idCliente: p.idCliente,
      nomeCliente: cliMap.get(p.idCliente)?.nome ?? null,
      razaoCliente: cliMap.get(p.idCliente)?.razao ?? null,
      codVendedor: p.codVendedor,
      nomeVendedor: repMap.get(p.codVendedor) ?? null,
      situa: p.situa,
      dtPedido: p.dtPedido.toISOString(),
      total: decimalToString(p.total),
      descontoPerc: decimalToString(p.descontoPerc),
      obs: p.obs,
      createdAt: p.createdAt.toISOString(),
      fonte: 'sar' as const,
    }));

    return { data, total, page, limit };
  }

  async listErpConsulta(query: PedidoErpConsultaQuery): Promise<PedidoErpConsultaResponse> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const role = this.cls.get('role');
    const userId = this.cls.get('userId');
    const codVendedor = userId ? parseInt(userId, 10) : 0;
    const idMatriz = idEmpresa > 9000 ? idEmpresa - 9000 : idEmpresa;

    const { search, from, to, page, limit } = query;
    const offset = (page - 1) * limit;

    const dataHistorico = new Date();
    dataHistorico.setDate(dataHistorico.getDate() - 90);
    const dataHistoricoStr = dataHistorico.toISOString().split('T')[0];

    // Defesa em profundidade: o contrato já exige YYYY-MM-DD, mas como as datas
    // são interpoladas no SQL abaixo, revalidamos antes de montar a query.
    const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
    if ((from && !DATE_RE.test(from)) || (to && !DATE_RE.test(to))) {
      throw new BadRequestException('Datas devem estar no formato YYYY-MM-DD');
    }

    const vendedorFilter =
      role === 'rep'
        ? `AND a.cod_vendedor = ${codVendedor}`
        : role === 'supervisor'
          ? `AND a.cod_vendedor IN (${(await getTeamCodes(prisma, codVendedor)).join(',')})`
          : '';
    const fromFilterE = from ? `AND COALESCE(a.dt_pedido, b.dt_pedido) >= '${from}'` : '';
    const toFilterE = to ? `AND COALESCE(a.dt_pedido, b.dt_pedido) <= '${to}'` : '';
    const fromFilterP = from ? `AND a.dt_pedido >= '${from}'` : '';
    const toFilterP = to ? `AND a.dt_pedido <= '${to}'` : '';
    const safe = (s: string) => s.replace(/'/g, "''");
    const searchFilter = search
      ? `AND (r.numero_pedido::text ILIKE '%${safe(search)}%' OR LOWER(cl.nome) LIKE LOWER('%${safe(search)}%') OR LOWER(cl.razao) LIKE LOWER('%${safe(search)}%'))`
      : '';

    interface ErpConsultaRow {
      numero_pedido: number;
      data: Date;
      dt_prevent: Date | null;
      data_emissao: Date | null;
      situa: number;
      status_descr: string | null;
      id_cliente: number;
      nome_cliente: string | null;
      razao_cliente: string | null;
      cod_vendedor: number;
      forma_pagamento: string | null;
      total: string;
      obs: string | null;
      tipo: 'P' | 'E';
      num_ped_sar: string | null;
      numero_entrega: number | null;
      numero_nf: number | null;
      chave_acesso_nfe: string | null;
      sit_ped: number | null;
      id_pedido: number | null;
    }

    const cte = `
      WITH all_rows AS (
        SELECT
          b.numero              AS numero_pedido,
          COALESCE(b.dt_pedido, a.dt_pedido) AS data,
          a.dt_prevent,
          a.dt_emissao          AS data_emissao,
          a.situa,
          NULL::text            AS status_descr,
          a.id_cliente,
          a.cod_vendedor,
          a.forma_pagamento,
          a.total::text,
          a.obs,
          'E'::varchar(1)       AS tipo,
          TRIM(b.num_ped_sar)   AS num_ped_sar,
          a.numero              AS numero_entrega,
          d.numero              AS numero_nf,
          d.chave_acesso_nfe,
          b.situa               AS sit_ped,
          a.id_pedido
        FROM sar.vw_pedidos_erp a
        LEFT JOIN sar.vw_pedidos_erp b
          ON a.numer_ped_vinc = b.numero
         AND a.id_empresa = b.id_empresa
         AND b.tipo = 'P'
        LEFT JOIN sar.vw_nf d
          ON a.numero = d.num_entrega
         AND d.id_empresa = ${idMatriz}
        WHERE a.id_empresa = ${idEmpresa}
          AND a.tipo = 'E'
          ${vendedorFilter}
          AND b.id_pedido IS NOT NULL
          AND COALESCE(a.dt_pedido, b.dt_pedido) >= '${dataHistoricoStr}'
          ${fromFilterE} ${toFilterE}

        UNION ALL

        SELECT
          a.numero              AS numero_pedido,
          a.dt_pedido           AS data,
          a.dt_prevent,
          a.dt_emissao          AS data_emissao,
          a.situa,
          c.descricao           AS status_descr,
          a.id_cliente,
          a.cod_vendedor,
          a.forma_pagamento,
          a.total::text,
          a.obs,
          'P'::varchar(1)       AS tipo,
          TRIM(a.num_ped_sar)   AS num_ped_sar,
          NULL::integer         AS numero_entrega,
          NULL::integer         AS numero_nf,
          NULL::varchar         AS chave_acesso_nfe,
          NULL::integer         AS sit_ped,
          a.id_pedido
        FROM sar.vw_pedidos_erp a
        LEFT JOIN vw_sitpedido c
          ON c.id_sitpedido = a.situa
        WHERE a.id_empresa = ${idEmpresa}
          AND a.tipo = 'P'
          ${vendedorFilter}
          AND a.situa NOT IN (3, 4, 5, 6)
          ${fromFilterP} ${toFilterP}
      ),
      named AS (
        SELECT r.*, cl.nome AS nome_cliente, cl.razao AS razao_cliente
        FROM all_rows r
        LEFT JOIN vw_clientes cl ON cl.id_cliente = r.id_cliente
        WHERE 1=1 ${searchFilter}
      )
    `;

    const [countRows, dataRows] = await Promise.all([
      prisma.$queryRawUnsafe<[{ count: string }]>(
        `${cte} SELECT COUNT(*)::text AS count FROM named`,
      ),
      prisma.$queryRawUnsafe<ErpConsultaRow[]>(
        `${cte} SELECT * FROM named ORDER BY data DESC LIMIT ${limit} OFFSET ${offset}`,
      ),
    ]);

    const total = Number(countRows[0]?.count ?? 0);

    const data: PedidoErpConsultaItem[] = dataRows.map((r) => ({
      numeroPedido: Number(r.numero_pedido),
      data: r.data ? new Date(r.data).toISOString() : new Date().toISOString(),
      dtPrevent: r.dt_prevent ? new Date(r.dt_prevent).toISOString() : null,
      dataEmissao: r.data_emissao ? new Date(r.data_emissao).toISOString() : null,
      situa: Number(r.situa),
      statusDescr: r.status_descr ?? null,
      idCliente: Number(r.id_cliente),
      nomeCliente: r.nome_cliente ?? null,
      razaoCliente: r.razao_cliente ?? null,
      codVendedor: Number(r.cod_vendedor),
      formaPagamento: r.forma_pagamento ?? null,
      total: r.total ?? '0',
      obs: r.obs ?? null,
      tipo: r.tipo as 'P' | 'E',
      numPedSar: r.num_ped_sar ?? null,
      numeroEntrega: r.numero_entrega != null ? Number(r.numero_entrega) : null,
      numeroNf: r.numero_nf != null ? Number(r.numero_nf) : null,
      chaveAcessoNfe: r.chave_acesso_nfe ?? null,
      sitPed: r.sit_ped != null ? Number(r.sit_ped) : null,
      idPedido: r.id_pedido != null ? Number(r.id_pedido) : null,
    }));

    return { data, total, page, limit };
  }

  // Supervisor só age sobre pedidos da própria equipe; gerente/admin passam
  // direto. NotFound (não Forbidden) para não revelar pedidos de outras equipes.
  private async assertPedidoDaEquipe(codVendedorPedido: number, idPedido: string): Promise<void> {
    const role = this.cls.get('role');
    if (role !== 'supervisor') return;
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const userId = this.cls.get('userId') ?? '0';
    const team = await getTeamCodes(prisma, parseInt(userId, 10));
    if (!team.includes(codVendedorPedido)) {
      throw new NotFoundException(`Pedido ${idPedido} não encontrado`);
    }
  }

  async findOne(id: string): Promise<PedidoDetail> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const role = this.cls.get('role');
    const userId = this.cls.get('userId');
    const codVendedor = userId ? parseInt(userId, 10) : 0;

    const repFilter =
      role === 'rep'
        ? { codVendedor }
        : role === 'supervisor'
          ? { codVendedor: { in: await getTeamCodes(prisma, codVendedor) } }
          : {};

    const o = await prisma.pedido.findFirst({
      where: { id, idEmpresa, ...repFilter },
      include: {
        itens: { orderBy: { ordem: 'asc' } },
        historico: { orderBy: { changedAt: 'asc' } },
      },
    });

    if (!o) throw new NotFoundException(`Pedido ${id} não encontrado`);

    return this.mapDetail(o);
  }

  // Valida a alçada de um payload de criação/edição (mesma regra do transmit):
  // desconto global e por item contra alcada + promoção/regra do dono do pedido.
  private async assertAlcadaDto(
    codVendedor: number,
    dto: {
      descontoPerc: number;
      idPauta?: number;
      itens: {
        ordem: number;
        idProduto: number;
        codProduto?: string;
        precoUnitario: number;
        descontoPerc: number;
      }[];
    },
  ): Promise<void> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');

    const limitRows = await prisma.alcadaDesconto.findMany({ where: { codVendedor, idEmpresa } });
    const limitMap = new Map(limitRows.map((r) => [r.codGrupo, Number(r.limitePerc)]));
    const limiteDefault = limitMap.get(0) ?? 5;
    if (dto.descontoPerc > limiteDefault) {
      throw new BadRequestException(
        `Desconto de ${dto.descontoPerc}% acima do máximo permitido para você (${limiteDefault}%). Reduza o desconto para continuar.`,
      );
    }
    await this.assertAlcadaItens(
      {
        codVendedor,
        descontoPerc: dto.descontoPerc,
        idPauta: dto.idPauta ?? null,
        itens: dto.itens.map((it) => ({
          ordem: it.ordem,
          idProduto: it.idProduto,
          codProduto: it.codProduto ?? null,
          precoUnitario: it.precoUnitario,
          descontoPerc: it.descontoPerc,
        })),
      },
      limitMap,
      limiteDefault,
    );
  }

  // Cria novo pedido SAR como ORÇAMENTO (situa 0). A alçada de desconto valida
  // JÁ AQUI (bloqueia o Concluir com a mensagem do que passou) e de novo no
  // transmit() — defesa em profundidade, pois aprovação/ERP podem mudar valores.
  // A notificação ao supervisor continua só no transmit().
  // Idempotency-Key: retorna pedido existente se já processado (FR-4.3).
  async create(dto: CreatePedido): Promise<PedidoDetail> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const role = this.cls.get('role');
    const userId = this.cls.get('userId') ?? '0';
    const codVendedor = parseInt(userId, 10);

    // PGD-AUTHZ: rep só lança pedido para cliente da própria carteira.
    if (role === 'rep') {
      const cliRows = await prisma.$queryRawUnsafe<{ cod_vendedor: number }[]>(
        `SELECT cod_vendedor FROM vw_clientes WHERE id_cliente = $1 LIMIT 1`,
        dto.idCliente,
      );
      if (!cliRows[0] || Number(cliRows[0].cod_vendedor) !== codVendedor) {
        throw new NotFoundException(`Cliente ${dto.idCliente} não encontrado`);
      }
    }

    // Idempotency-Key: retorna pedido existente sem re-processar.
    // Escopado a empresa + vendedor — chave de terceiro não vaza pedido alheio.
    if (dto.idempotencyKey) {
      const existing = await prisma.pedido.findFirst({
        where: { idempotencyKey: dto.idempotencyKey, idEmpresa, codVendedor },
        include: {
          itens: { orderBy: { ordem: 'asc' } },
          historico: { orderBy: { changedAt: 'asc' } },
        },
      });
      if (existing) return this.mapDetail(existing);
    }

    // Alçada: bloqueia a criação do orçamento se algum desconto passa do teto
    await this.assertAlcadaDto(codVendedor, dto);

    const itemsData = dto.itens.map((it) => {
      const descontoValor =
        Math.round(it.qtd * it.precoUnitario * (it.descontoPerc / 100) * 100) / 100;
      const total = Math.round(it.qtd * it.precoUnitario * (1 - it.descontoPerc / 100) * 100) / 100;
      return {
        ordem: it.ordem,
        idProduto: it.idProduto,
        codProduto: it.codProduto ?? null,
        descProduto: it.descProduto,
        qtd: it.qtd,
        precoUnitario: it.precoUnitario,
        descontoPerc: it.descontoPerc,
        descontoValor,
        total,
      };
    });

    const totalProdutos = itemsData.reduce((acc, it) => acc + it.total, 0);
    const descontoValorGlobal = Math.round(totalProdutos * (dto.descontoPerc / 100) * 100) / 100;
    const total = Math.round(totalProdutos * (1 - dto.descontoPerc / 100) * 100) / 100;

    const situa = SITUA_ORCAMENTO;

    // Gera número sequencial GLOBAL: SAR-NNNNN (numPedSar é unique entre empresas).
    const lastOrder = await prisma.pedido.findFirst({
      orderBy: { numPedSar: 'desc' },
      select: { numPedSar: true },
    });
    const seq = lastOrder ? parseInt(lastOrder.numPedSar.replace('SAR-', ''), 10) + 1 : 1;
    const numPedSar = `SAR-${String(seq).padStart(5, '0')}`;

    const now = new Date();
    const pedido = await prisma.pedido.create({
      data: {
        idEmpresa,
        numPedSar,
        idCliente: dto.idCliente,
        codVendedor,
        situa,
        dtPedido: now,
        idPauta: dto.idPauta ?? null,
        codFormapag: dto.codFormapag ?? null,
        totalProdutos,
        total,
        descontoPerc: dto.descontoPerc,
        descontoValor: descontoValorGlobal,
        obs: dto.obs ?? null,
        endEntrega: dto.endEntrega ?? null,
        idempotencyKey: dto.idempotencyKey ?? null,
        itens: { create: itemsData },
        historico: {
          create: [
            {
              situaAnterior: null,
              situaNova: situa,
              changedBy: codVendedor,
              changedAt: now,
            },
          ],
        },
      },
      include: {
        itens: { orderBy: { ordem: 'asc' } },
        historico: { orderBy: { changedAt: 'asc' } },
      },
    });

    return this.mapDetail(pedido);
  }

  // Transmite um Orçamento (situa 0) → Transmitido (situa 2).
  // Alçada de desconto (codGrupo=0 = default) é BLOQUEIO DURO: desconto acima do
  // máximo do rep barra a transmissão com mensagem — não há fila de aprovação.
  async transmit(id: string): Promise<PedidoDetail> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const role = this.cls.get('role');
    const userId = this.cls.get('userId') ?? '0';
    const codVendedor = parseInt(userId, 10);

    // Rep só transmite o próprio orçamento
    const repFilter = role === 'rep' ? { codVendedor } : {};
    const pedido = await prisma.pedido.findFirst({
      where: { id, idEmpresa, ...repFilter },
      include: { itens: { orderBy: { ordem: 'asc' } } },
    });
    if (!pedido) throw new NotFoundException(`Pedido ${id} não encontrado`);
    if (pedido.situa !== SITUA_ORCAMENTO)
      throw new BadRequestException(
        `Pedido não é um orçamento (situa: ${pedido.situa}) — só orçamentos podem ser transmitidos`,
      );

    // Alçada do rep (codGrupo=0 = default; fallback 5%) — bloqueia se desconto acima.
    const limitRows = await prisma.alcadaDesconto.findMany({ where: { codVendedor, idEmpresa } });
    const limitMap = new Map(limitRows.map((r) => [r.codGrupo, Number(r.limitePerc)]));
    const limiteMax = limitMap.get(0) ?? 5;
    const desconto = Number(pedido.descontoPerc);
    if (desconto > limiteMax) {
      throw new BadRequestException(
        `Desconto de ${desconto}% acima do máximo permitido para você (${limiteMax}%). Reduza o desconto para transmitir o pedido.`,
      );
    }

    await this.assertAlcadaItens(pedido, limitMap, limiteMax);

    const now = new Date();
    await prisma.pedido.update({ where: { id }, data: { situa: SITUA_APROVADO } });
    await prisma.historicoPedido.create({
      data: {
        idPedido: id,
        situaAnterior: SITUA_ORCAMENTO,
        situaNova: SITUA_APROVADO,
        changedBy: codVendedor,
        changedAt: now,
        nota: 'Transmitido',
      },
    });

    const final = await prisma.pedido.findUniqueOrThrow({
      where: { id },
      include: {
        itens: { orderBy: { ordem: 'asc' } },
        historico: { orderBy: { changedAt: 'asc' } },
      },
    });
    return this.mapDetail(final);
  }

  // Alçada por ITEM (complementa o gate global do transmit): o desconto EFETIVO
  // de cada item — preço cobrado vs preço de tabela, combinado com o desconto
  // global — deve caber no maior valor entre a alçada do grupo do produto
  // (alcada_desconto por codGrupo; 0 = default), a promoção ativa
  // (produto/grupo) e a regra de desconto vigente (grupo/subgrupo/rep) — teto,
  // não soma. Cobre o desconto lançado no item E desconto disfarçado de preço
  // unitário reduzido. Preço de tabela: pauta do pedido > promocional > preço
  // base; produto sem referência valida só pelo campo de desconto.
  private async assertAlcadaItens(
    pedido: {
      codVendedor: number;
      descontoPerc: Prisma.Decimal | number;
      idPauta: number | null;
      itens: {
        ordem: number;
        idProduto: number;
        codProduto: string | null;
        precoUnitario: Prisma.Decimal | number;
        descontoPerc: Prisma.Decimal | number;
      }[];
    },
    limitMap: Map<number, number>,
    limiteDefault: number,
  ): Promise<void> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const idMatriz = idEmpresa > 9000 ? idEmpresa - 9000 : idEmpresa;

    if (pedido.itens.length === 0) return;
    const prodIds = [...new Set(pedido.itens.map((i) => i.idProduto))];

    interface ProdRow {
      id_erp: number;
      codigo: string | null;
      cod_grupo: number | null;
      cod_subgrupo: number | null;
      vl_preco1: string | null;
      preco_promocional: string | null;
      preco_pauta: string | null;
    }
    // prodIds/idPauta vêm do banco (Int), não do payload — interpolação segura.
    const prodRows = await prisma.$queryRawUnsafe<ProdRow[]>(`
      SELECT p.id_erp, TRIM(p.codigo) AS codigo, p.cod_grupo, p.cod_subgrupo,
             p.vl_preco1::text, p.preco_promocional::text,
             ${
               pedido.idPauta != null
                 ? `(SELECT pp.preco1::text FROM vw_pauta_produtos pp
                      WHERE pp.id_pauta = ${Number(pedido.idPauta)}
                        AND pp.id_produto = p.id_erp LIMIT 1)`
                 : 'NULL'
             } AS preco_pauta
      FROM vw_produtos p
      WHERE p.id_empresa = ${idMatriz}
        AND p.id_erp IN (${prodIds.join(',')})
    `);
    const prodMap = new Map(prodRows.map((p) => [Number(p.id_erp), p]));

    const hoje = new Date();
    const promos = await prisma.promocao.findMany({
      where: {
        idEmpresa: { in: [idEmpresa, idMatriz] },
        ativa: true,
        dataInicio: { lte: hoje },
        dataFim: { gte: hoje },
      },
    });

    // Regras de desconto vigentes que alcançam o rep dono do pedido
    // (cod_vendedor nulo = todos os reps).
    const regras = await prisma.regraDesconto.findMany({
      where: {
        idEmpresa: { in: [idEmpresa, idMatriz] },
        ativa: true,
        dataInicio: { lte: hoje },
        dataFim: { gte: hoje },
        OR: [{ codVendedor: null }, { codVendedor: pedido.codVendedor }],
      },
    });

    const descGlobal = Number(pedido.descontoPerc);
    const problemas: string[] = [];

    for (const it of pedido.itens) {
      const prod = prodMap.get(it.idProduto);
      const codGrupo = prod?.cod_grupo != null ? Number(prod.cod_grupo) : null;
      const codigo = prod?.codigo?.trim() || it.codProduto || String(it.idProduto);

      const promoPct = promos
        .filter(
          (p) =>
            (p.codProduto && prod?.codigo && p.codProduto.trim() === prod.codigo.trim()) ||
            (p.grpProd && codGrupo != null && p.grpProd.trim() === String(codGrupo)),
        )
        .reduce((max, p) => Math.max(max, Number(p.descPct)), 0);

      // Regra sem grupo/subgrupo vale para qualquer produto; com grupo e/ou
      // subgrupo, o produto precisa casar com o que a regra especifica.
      const codSubgrupo = prod?.cod_subgrupo != null ? Number(prod.cod_subgrupo) : null;
      const regraPct = regras
        .filter(
          (r) =>
            (r.codGrupo == null || (codGrupo != null && r.codGrupo === codGrupo)) &&
            (r.codSubgrupo == null || (codSubgrupo != null && r.codSubgrupo === codSubgrupo)),
        )
        .reduce((max, r) => Math.max(max, Number(r.descPct)), 0);

      // Promoção/regra definem TETO próprio, não somam à alçada: com alçada 5%
      // e regra de 15%, o máximo do item é 15% (não 20%). Vale o maior entre a
      // alçada do grupo e a melhor condição vigente que alcança o produto.
      const alcadaBase =
        codGrupo != null ? (limitMap.get(codGrupo) ?? limiteDefault) : limiteDefault;
      const limiteItem = Math.max(alcadaBase, promoPct, regraPct);

      // Preço de tabela: pauta do pedido > promocional (se menor) > preço base
      const precoPauta = prod?.preco_pauta != null ? Number(prod.preco_pauta) : 0;
      const precoBase = precoPauta > 0 ? precoPauta : Number(prod?.vl_preco1 ?? 0);
      const precoPromo = Number(prod?.preco_promocional ?? 0);
      const tabela = precoPromo > 0 && precoPromo < precoBase ? precoPromo : precoBase;

      const descItem = Number(it.descontoPerc);
      const precoLiquido = Number(it.precoUnitario) * (1 - descItem / 100) * (1 - descGlobal / 100);

      // Desconto efetivo: vs tabela quando há referência; senão só os percentuais
      const descEfetivo =
        tabela > 0
          ? (1 - precoLiquido / tabela) * 100
          : (1 - (1 - descItem / 100) * (1 - descGlobal / 100)) * 100;

      if (descEfetivo > limiteItem + 0.01) {
        problemas.push(
          `item ${it.ordem} (${codigo}) com desconto efetivo de ${descEfetivo.toFixed(1)}% — máximo permitido ${limiteItem}%`,
        );
      }
    }

    if (problemas.length > 0) {
      throw new BadRequestException(
        `Desconto acima da sua alçada: ${problemas.join('; ')}. Ajuste preço/desconto para continuar.`,
      );
    }
  }

  async approve(id: string, dto: AprovarPedido): Promise<PedidoDetail> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const userId = this.cls.get('userId') ?? '0';
    const codVendedor = parseInt(userId, 10);

    const pedido = await prisma.pedido.findFirst({ where: { id, idEmpresa } });
    if (!pedido) throw new NotFoundException(`Pedido ${id} não encontrado`);
    if (pedido.situa !== SITUA_PENDENTE)
      throw new BadRequestException(
        `Pedido não está aguardando aprovação (situa: ${pedido.situa})`,
      );
    await this.assertPedidoDaEquipe(pedido.codVendedor, id);

    const now = new Date();
    const newDescontoPerc = dto.descontoPerc ?? Number(pedido.descontoPerc);
    const newTotal =
      Math.round(Number(pedido.totalProdutos) * (1 - newDescontoPerc / 100) * 100) / 100;

    await prisma.pedido.update({
      where: { id },
      data: {
        situa: SITUA_APROVADO,
        descontoPerc: newDescontoPerc,
        total: newTotal,
        aprovadoPor: codVendedor,
        aprovadoEm: now,
      },
    });

    await prisma.historicoPedido.create({
      data: {
        idPedido: id,
        situaAnterior: SITUA_PENDENTE,
        situaNova: SITUA_APROVADO,
        changedBy: codVendedor,
        changedAt: now,
        nota: dto.nota ?? null,
      },
    });

    const final = await prisma.pedido.findUniqueOrThrow({
      where: { id },
      include: {
        itens: { orderBy: { ordem: 'asc' } },
        historico: { orderBy: { changedAt: 'asc' } },
      },
    });

    void this.notifications.notifyUser(String(pedido.codVendedor), {
      title: 'Pedido aprovado',
      body: `${final.numPedSar} aprovado${dto.descontoPerc !== undefined ? ` com ${newDescontoPerc}% de desconto` : ''}`,
      url: `/pedidos/${id}`,
    });

    return this.mapDetail(final);
  }

  async reject(id: string, dto: RecusarPedido): Promise<PedidoDetail> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const userId = this.cls.get('userId') ?? '0';
    const codVendedor = parseInt(userId, 10);

    const pedido = await prisma.pedido.findFirst({ where: { id, idEmpresa } });
    if (!pedido) throw new NotFoundException(`Pedido ${id} não encontrado`);
    if (pedido.situa !== SITUA_PENDENTE)
      throw new BadRequestException(
        `Pedido não está aguardando aprovação (situa: ${pedido.situa})`,
      );
    await this.assertPedidoDaEquipe(pedido.codVendedor, id);

    const now = new Date();

    await prisma.pedido.update({
      where: { id },
      data: { situa: SITUA_CANCELADO, motivoRecusa: dto.motivo },
    });

    await prisma.historicoPedido.create({
      data: {
        idPedido: id,
        situaAnterior: SITUA_PENDENTE,
        situaNova: SITUA_CANCELADO,
        changedBy: codVendedor,
        changedAt: now,
        nota: dto.motivo,
      },
    });

    const final = await prisma.pedido.findUniqueOrThrow({
      where: { id },
      include: {
        itens: { orderBy: { ordem: 'asc' } },
        historico: { orderBy: { changedAt: 'asc' } },
      },
    });

    void this.notifications.notifyUser(String(pedido.codVendedor), {
      title: 'Pedido recusado',
      body: `${final.numPedSar}: ${dto.motivo}`,
      url: `/pedidos/${id}`,
    });

    return this.mapDetail(final);
  }

  // Edita um orçamento (situa 0). Depois de transmitido não é mais editável.
  // Substitui itens e recalcula totais; alçada valida contra o dono do pedido.
  async update(id: string, dto: UpdatePedido): Promise<PedidoDetail> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const role = this.cls.get('role');
    const userId = this.cls.get('userId') ?? '0';
    const codVendedor = parseInt(userId, 10);

    const repFilter =
      role === 'rep'
        ? { codVendedor }
        : role === 'supervisor'
          ? { codVendedor: { in: await getTeamCodes(prisma, codVendedor) } }
          : {};
    const pedido = await prisma.pedido.findFirst({ where: { id, idEmpresa, ...repFilter } });
    if (!pedido) throw new NotFoundException(`Pedido ${id} não encontrado`);
    if (pedido.situa !== SITUA_ORCAMENTO)
      throw new BadRequestException(
        'Apenas orçamentos podem ser editados — este pedido já foi transmitido',
      );

    // PGD-AUTHZ: rep só aponta o pedido para cliente da própria carteira.
    if (role === 'rep' && dto.idCliente !== pedido.idCliente) {
      const cliRows = await prisma.$queryRawUnsafe<{ cod_vendedor: number }[]>(
        `SELECT cod_vendedor FROM vw_clientes WHERE id_cliente = $1 LIMIT 1`,
        dto.idCliente,
      );
      if (!cliRows[0] || Number(cliRows[0].cod_vendedor) !== codVendedor) {
        throw new NotFoundException(`Cliente ${dto.idCliente} não encontrado`);
      }
    }

    // Alçada do DONO do pedido (supervisor editando usa o limite do rep)
    await this.assertAlcadaDto(pedido.codVendedor, dto);

    const itemsData = dto.itens.map((it) => {
      const descontoValor =
        Math.round(it.qtd * it.precoUnitario * (it.descontoPerc / 100) * 100) / 100;
      const total = Math.round(it.qtd * it.precoUnitario * (1 - it.descontoPerc / 100) * 100) / 100;
      return {
        ordem: it.ordem,
        idProduto: it.idProduto,
        codProduto: it.codProduto ?? null,
        descProduto: it.descProduto,
        qtd: it.qtd,
        precoUnitario: it.precoUnitario,
        descontoPerc: it.descontoPerc,
        descontoValor,
        total,
      };
    });
    const totalProdutos = itemsData.reduce((acc, it) => acc + it.total, 0);
    const descontoValorGlobal = Math.round(totalProdutos * (dto.descontoPerc / 100) * 100) / 100;
    const total = Math.round(totalProdutos * (1 - dto.descontoPerc / 100) * 100) / 100;

    const final = await prisma.pedido.update({
      where: { id },
      data: {
        idCliente: dto.idCliente,
        idPauta: dto.idPauta ?? null,
        codFormapag: dto.codFormapag ?? null,
        totalProdutos,
        total,
        descontoPerc: dto.descontoPerc,
        descontoValor: descontoValorGlobal,
        obs: dto.obs ?? null,
        endEntrega: dto.endEntrega ?? null,
        itens: { deleteMany: {}, create: itemsData },
        historico: {
          create: [
            {
              situaAnterior: SITUA_ORCAMENTO,
              situaNova: SITUA_ORCAMENTO,
              changedBy: codVendedor,
              changedAt: new Date(),
              nota: 'Orçamento editado',
            },
          ],
        },
      },
      include: {
        itens: { orderBy: { ordem: 'asc' } },
        historico: { orderBy: { changedAt: 'asc' } },
      },
    });

    return this.mapDetail(final);
  }

  async cancel(id: string, dto: CancelPedido): Promise<PedidoDetail> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const userId = this.cls.get('userId') ?? '0';
    const codVendedor = parseInt(userId, 10);

    // Rep só pode cancelar seu próprio orçamento (situa 0).
    const pedido = await prisma.pedido.findFirst({ where: { id, idEmpresa, codVendedor } });
    if (!pedido) throw new NotFoundException(`Pedido ${id} não encontrado`);
    if (pedido.situa !== SITUA_ORCAMENTO)
      throw new BadRequestException('Apenas orçamentos podem ser cancelados pelo representante');

    const now = new Date();
    // ATENCAO: banco ERP e LATIN1 - sem em-dash/caracteres fora do LATIN1 aqui
    const motivoCompleto = dto.descricao?.trim()
      ? `${dto.motivo} - ${dto.descricao.trim()}`
      : dto.motivo;

    await prisma.pedido.update({
      where: { id },
      data: { situa: SITUA_CANCELADO, motivoRecusa: motivoCompleto },
    });
    await prisma.historicoPedido.create({
      data: {
        idPedido: id,
        situaAnterior: SITUA_ORCAMENTO,
        situaNova: SITUA_CANCELADO,
        changedBy: codVendedor,
        changedAt: now,
        nota: `Cancelado pelo representante - ${motivoCompleto}`,
      },
    });

    const final = await prisma.pedido.findUniqueOrThrow({
      where: { id },
      include: {
        itens: { orderBy: { ordem: 'asc' } },
        historico: { orderBy: { changedAt: 'asc' } },
      },
    });

    return this.mapDetail(final);
  }

  // Resolve nome do cliente (nome + razão) e nome do representante a partir dos
  // códigos, lendo das views do ERP. Usado no detalhe de pedidos SAR-nativos.
  private async lookupNames(
    idCliente: number,
    codVendedor: number,
  ): Promise<{
    nomeCliente: string | null;
    razaoCliente: string | null;
    nomeVendedor: string | null;
  }> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');

    // Cliente e representante são cadastros globais (sem id_empresa).
    const [cliRows, repRows] = await Promise.all([
      prisma.$queryRawUnsafe<{ nome: string | null; razao: string | null }[]>(
        `SELECT nome, razao FROM vw_clientes WHERE id_cliente = ${idCliente} LIMIT 1`,
      ),
      prisma.$queryRawUnsafe<{ nome: string | null }[]>(
        `SELECT nome FROM vw_representantes WHERE codigo = ${codVendedor} LIMIT 1`,
      ),
    ]);

    return {
      nomeCliente: cliRows[0]?.nome ?? null,
      razaoCliente: cliRows[0]?.razao ?? null,
      nomeVendedor: repRows[0]?.nome ?? null,
    };
  }

  private async mapDetail(o: {
    id: string;
    numPedSar: string;
    idCliente: number;
    codVendedor: number;
    situa: number;
    dtPedido: Date;
    idPauta: number | null;
    codFormapag: number | null;
    total: Prisma.Decimal;
    descontoPerc: Prisma.Decimal;
    descontoValor: Prisma.Decimal;
    totalProdutos: Prisma.Decimal;
    totalIpi: Prisma.Decimal;
    totalIcmsst: Prisma.Decimal;
    acrescimo: Prisma.Decimal;
    comissao: Prisma.Decimal;
    pedFlex: Prisma.Decimal;
    aprovadoPor: number | null;
    aprovadoEm: Date | null;
    motivoRecusa: string | null;
    obs: string | null;
    endEntrega: string | null;
    idempotencyKey: string | null;
    createdAt: Date;
    updatedAt: Date;
    itens: {
      id: string;
      idProduto: number;
      codProduto: string | null;
      descProduto: string | null;
      ordem: number;
      qtd: Prisma.Decimal;
      precoUnitario: Prisma.Decimal;
      descontoPerc: Prisma.Decimal;
      total: Prisma.Decimal;
    }[];
    historico: {
      id: string;
      situaAnterior: number | null;
      situaNova: number;
      changedBy: number;
      nota: string | null;
      changedAt: Date;
    }[];
  }): Promise<PedidoDetail> {
    const names = await this.lookupNames(o.idCliente, o.codVendedor);

    // Grupo/subgrupo dos produtos (vw_produtos, empresa matriz) — a edição do
    // orçamento usa para calcular o teto de desconto por item no carrinho.
    const prisma = this.cls.get('prisma');
    const idEmpresa = this.cls.get('idEmpresa');
    const idMatriz = idEmpresa > 9000 ? idEmpresa - 9000 : idEmpresa;
    const prodIds = [...new Set(o.itens.map((i) => i.idProduto))];
    const grupoRows =
      prisma && prodIds.length > 0
        ? await prisma.$queryRawUnsafe<
            {
              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, peso_liquido::text, vol_m3::text
               FROM vw_produtos
              WHERE id_empresa = ${idMatriz} AND id_erp IN (${prodIds.join(',')})`,
          )
        : [];
    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 {
      id: o.id,
      numPedSar: o.numPedSar,
      idCliente: o.idCliente,
      nomeCliente: names.nomeCliente,
      razaoCliente: names.razaoCliente,
      codVendedor: o.codVendedor,
      nomeVendedor: names.nomeVendedor,
      situa: o.situa,
      dtPedido: o.dtPedido.toISOString(),
      idPauta: o.idPauta,
      codFormapag: o.codFormapag,
      pesoTotal: pesoTotal.toFixed(3),
      m3Total: m3Total.toFixed(4),
      total: decimalToString(o.total),
      descontoPerc: decimalToString(o.descontoPerc),
      obs: o.obs,
      endEntrega: o.endEntrega,
      createdAt: o.createdAt.toISOString(),
      totalProdutos: decimalToString(o.totalProdutos),
      totalIpi: decimalToString(o.totalIpi),
      totalIcmsst: decimalToString(o.totalIcmsst),
      descontoValor: decimalToString(o.descontoValor),
      acrescimo: decimalToString(o.acrescimo),
      comissao: decimalToString(o.comissao),
      pedFlex: decimalToString(o.pedFlex),
      fonte: 'sar' as const,
      aprovadoPor: o.aprovadoPor,
      aprovadoEm: o.aprovadoEm?.toISOString() ?? null,
      motivoRecusa: o.motivoRecusa,
      idempotencyKey: o.idempotencyKey,
      updatedAt: o.updatedAt.toISOString(),
      itens: o.itens.map((it) => ({
        id: it.id,
        idProduto: it.idProduto,
        codProduto: it.codProduto,
        descProduto: it.descProduto,
        ordem: it.ordem,
        qtd: decimalToString(it.qtd),
        precoUnitario: decimalToString(it.precoUnitario),
        descontoPerc: decimalToString(it.descontoPerc),
        total: decimalToString(it.total),
        codGrupo:
          grupoMap.get(it.idProduto)?.cod_grupo != null
            ? Number(grupoMap.get(it.idProduto)?.cod_grupo)
            : null,
        codSubgrupo:
          grupoMap.get(it.idProduto)?.cod_subgrupo != null
            ? Number(grupoMap.get(it.idProduto)?.cod_subgrupo)
            : null,
      })),
      historico: o.historico.map((h) => ({
        id: h.id,
        situaAnterior: h.situaAnterior,
        situaNova: h.situaNova,
        changedBy: h.changedBy,
        nota: h.nota,
        changedAt: h.changedAt.toISOString(),
      })),
    };
  }

  async findOneErp(idPedido: number): Promise<PedidoDetail> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const role = this.cls.get('role');
    const userId = this.cls.get('userId') ?? '0';
    const codVendedor = parseInt(userId, 10);

    interface ErpHeader {
      id_pedido: number;
      num_ped_sar: string;
      numero: number;
      id_cliente: number;
      cod_vendedor: number;
      situa: number;
      status_descr: string;
      dt_pedido: Date;
      total_produtos: string;
      total_ipi: string;
      total_icmsst: string;
      total: string;
      desconto_perc: string;
      desconto_valor: string;
      acrescimo: string;
      comissao: string;
      ped_flex: string;
      obs: string | null;
      forma_pagamento: string | null;
      nome_cliente: string | null;
      razao_cliente: string | null;
      nome_vendedor: string | null;
    }

    interface ErpItem {
      ordem: number;
      id_produto: number;
      codigo: string | null;
      descricao: string | null;
      qtd: string;
      preco_unitario: string;
      desconto_perc: string;
      total: string;
      peso_liquido: string | null;
      vol_m3: string | null;
    }

    const vendedorFilter =
      role === 'rep'
        ? `AND e.cod_vendedor = ${codVendedor}`
        : role === 'supervisor'
          ? `AND e.cod_vendedor IN (${(await getTeamCodes(prisma, codVendedor)).join(',')})`
          : '';
    const idMatriz = idEmpresa > 9000 ? idEmpresa - 9000 : idEmpresa;

    const [headerRows, itemRows] = await Promise.all([
      prisma.$queryRawUnsafe<ErpHeader[]>(`
        SELECT e.id_pedido, e.num_ped_sar, e.numero, e.id_cliente, e.cod_vendedor,
               e.situa, e.status_descr, e.dt_pedido,
               e.total_produtos::text, e.total_ipi::text, e.total_icmsst::text,
               e.total::text, e.desconto_perc::text, e.desconto_valor::text,
               e.acrescimo::text, e.comissao::text, e.ped_flex::text, e.obs,
               TRIM(e.forma_pagamento) AS forma_pagamento,
               c.nome AS nome_cliente, c.razao AS razao_cliente,
               (SELECT r.nome FROM vw_representantes r
                  WHERE r.codigo = e.cod_vendedor LIMIT 1) AS nome_vendedor
        FROM vw_pedidos_erp e
        LEFT JOIN vw_clientes c ON c.id_cliente = e.id_cliente
        WHERE e.id_empresa = ${idEmpresa}
          AND e.id_pedido  = ${idPedido}
          ${vendedorFilter}
        LIMIT 1
      `),
      prisma.$queryRawUnsafe<ErpItem[]>(`
        SELECT ei.ordem, ei.id_produto,
               TRIM(p.codigo) AS codigo, TRIM(p.descricao) AS descricao,
               ei.qtd::text, ei.preco_unitario::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
        LEFT JOIN vw_produtos p
               ON p.id_erp = ei.id_produto
              AND p.id_empresa = ${idMatriz}
        WHERE ei.id_pedido = ${idPedido}
        ORDER BY ei.ordem
      `),
    ]);

    if (!headerRows[0]) throw new NotFoundException(`Pedido ERP ${idPedido} não encontrado`);
    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 {
      id: `erp-${h.id_pedido}`,
      numPedSar: (h.num_ped_sar ?? '').trim(),
      numero: Number(h.numero),
      idCliente: Number(h.id_cliente),
      nomeCliente: h.nome_cliente ?? null,
      razaoCliente: h.razao_cliente ?? null,
      codVendedor: Number(h.cod_vendedor),
      nomeVendedor: h.nome_vendedor ?? null,
      situa: sigToSar(Number(h.situa)),
      statusDescr: h.status_descr,
      dtPedido: new Date(h.dt_pedido).toISOString(),
      total: h.total ?? '0',
      descontoPerc: h.desconto_perc ?? '0',
      obs: h.obs?.trim() || null,
      createdAt: new Date(h.dt_pedido).toISOString(),
      updatedAt: new Date(h.dt_pedido).toISOString(),
      fonte: 'erp' as const,
      formaPagamento: h.forma_pagamento || null,
      totalProdutos: h.total_produtos ?? '0',
      totalIpi: h.total_ipi ?? '0',
      totalIcmsst: h.total_icmsst ?? '0',
      descontoValor: h.desconto_valor ?? '0',
      acrescimo: h.acrescimo ?? '0',
      comissao: h.comissao ?? '0',
      pedFlex: h.ped_flex ?? '0',
      aprovadoPor: null,
      aprovadoEm: null,
      motivoRecusa: null,
      idempotencyKey: null,
      pesoTotal: pesoTotalErp.toFixed(3),
      m3Total: m3TotalErp.toFixed(4),
      itens: itemRows.map((it) => ({
        id: `${idPedido}-${it.ordem}`,
        idProduto: Number(it.id_produto),
        codProduto: it.codigo ?? null,
        descProduto: it.descricao ?? null,
        ordem: Number(it.ordem),
        qtd: it.qtd,
        precoUnitario: it.preco_unitario,
        descontoPerc: it.desconto_perc,
        total: it.total,
      })),
      historico: [],
    };
  }
}
