/* O Tal Picolé — Sistema de comprovantes (impressão / PDF) Suporta 3 formatos: A4, 80mm térmica, 58mm térmica. Abre uma nova janela com o conteúdo formatado e chama print(). */ function _money(v){ return "R$ " + (v||0).toLocaleString("pt-BR",{minimumFractionDigits:2,maximumFractionDigits:2}); } function _date(d){ const dt = d instanceof Date ? d : new Date(d); return dt.toLocaleDateString("pt-BR"); } function _now(){ const d = new Date(); return d.toLocaleDateString("pt-BR") + " " + d.toLocaleTimeString("pt-BR",{hour:"2-digit",minute:"2-digit"}); } function _docNum(prefix, id){ const n = (id||"").replace(/[^a-z0-9]/gi,"").slice(-6).toUpperCase().padStart(6,"0"); return (prefix||"DOC") + "-" + n; } /* Carrega logo customizada (data URL) salva pela fábrica, se houver. */ function _getLogoDataUrl(){ try { return localStorage.getItem("otp.logo") || ""; } catch(e){ return ""; } } /* Cores da marca alinhadas ao design system */ const BRAND_COLORS = { red: "#D4310B", redDark: "#B3270A", redSoft: "#FFF1EA", redSofter:"#FDE6DC", ink: "#1A1A1A", inkSoft: "#666666", cream: "#FAFAFB", }; /* Renderiza HTML do cabeçalho da empresa — minimalista e profissional. */ function _headerHtml(company, fmt){ const logoUrl = _getLogoDataUrl(); if (fmt === "a4") { const logoSize = 64; const logoBlock = logoUrl ? `` : `
P
`; return `
${logoBlock}
${company.name||""}
${company.cnpj ? "CNPJ "+company.cnpj : ""} ${company.cnpj && company.ie ? " · " : ""} ${company.ie ? "IE "+company.ie : ""} ${(company.cnpj || company.ie) ? "
" : ""} ${company.address ? company.address+"
" : ""} ${[company.phone, company.email].filter(Boolean).join(" · ")}
`; } const logoSize = fmt === "mm80" ? 48 : 38; const logoBlock = logoUrl ? `` : `
P
`; return `
${logoBlock}
${company.name||""}
${company.cnpj ? "CNPJ "+company.cnpj+"
" : ""} ${company.address||""}
${[company.phone, company.email].filter(Boolean).join(" · ")}
`; } /* Estilo base para janela de impressão por formato */ function _baseCss(fmt){ if (fmt === "a4") { return ` @page { size: A4; margin: 12mm 10mm; } * { box-sizing: border-box; } html, body { margin:0; padding:0; } body { font-family: Helvetica, Arial, sans-serif; color:#222; font-size:11px; line-height:1.5; } .doc { max-width: 760px; margin: 0 auto; padding: 0; } @media print { .doc { max-width: none; width: 100%; margin: 0; padding: 0; box-shadow: none !important; } body { background: white !important; } } table { table-layout: auto; word-wrap: break-word; } .brand-bar { height: 3px; background: ${BRAND_COLORS.red}; margin-bottom: 8px; } h1.doc-title { font-size:14px; text-transform:uppercase; letter-spacing:.1em; font-weight:700; color:#111; margin:0 0 4px; } h1 { font-size:14px; text-transform:uppercase; letter-spacing:.1em; font-weight:700; color:#111; margin:0 0 4px; } .doc-meta { font-size:10px; color:#666; font-family: 'Courier New', monospace; letter-spacing:.04em; margin-bottom:14px; padding-bottom:10px; border-bottom:1px solid #ddd; display:flex; justify-content:space-between; align-items:center; gap:14px; } .via-tag { display:inline-block; font-size:8.5px; font-weight:700; letter-spacing:.12em; color:${BRAND_COLORS.red}; text-transform:uppercase; border:1px solid ${BRAND_COLORS.red}; padding:2px 8px; } .section-title, .ttl { font-size:9.5px; font-weight:700; text-transform:uppercase; letter-spacing:.12em; color:#111; margin:18px 0 6px; padding-bottom:3px; border-bottom:1px solid #aaa; } .meta, .meta-grid { display:grid; grid-template-columns:1fr 1fr; gap:10px 28px; margin:0 0 16px; padding:0; background:transparent; border:none; } .meta > div, .meta-grid .item { font-size:11px; color:#222; } .meta strong, .meta-grid .item strong.lbl { display:block; font-size:8.5px; font-weight:700; color:#888; text-transform:uppercase; letter-spacing:.1em; margin-bottom:3px; } table { width:100%; border-collapse: collapse; } th { text-align:left; padding:7px 6px; font-size:9px; font-weight:700; color:#555; text-transform:uppercase; letter-spacing:.08em; border-bottom:1.5px solid #333; background:transparent; } td { padding:6px; font-size:10.5px; border-bottom:1px solid #eee; color:#222; vertical-align:top; } tbody tr:nth-child(even) { background:transparent; } tbody tr:last-child td { border-bottom:none; } .num { text-align:right; font-variant-numeric: tabular-nums; } .totals { margin-left:auto; width:300px; margin-top:14px; background:transparent; border:none; padding:0; border-radius:0; } .totals .row { display:flex; justify-content:space-between; padding:5px 0; font-size:11px; border-bottom:1px solid #eee; } .totals .row:last-of-type { border-bottom:none; } .totals .row.big { border-top:1.5px solid #333; border-bottom:3px double #333; padding:10px 0; margin-top:6px; font-size:13px; font-weight:700; color:#111; text-transform:uppercase; letter-spacing:.04em; } .totals .row.profit { background:#FAFAFA; padding:8px 10px; margin-top:6px; font-size:12px; font-weight:700; color:${BRAND_COLORS.red}; border-bottom:none; } .sigs, .signatures { display:grid; grid-template-columns:1fr 1fr; gap:60px; margin-top:54px; } .sig, .signature { text-align:center; font-size:9.5px; color:#555; } .sig .line, .signature .line { border-top:1px solid #222; padding-top:4px; font-size:9.5px; color:#555; } .footer { margin-top:30px; padding-top:10px; border-top:1px solid #ddd; font-size:9px; color:#888; text-align:center; line-height:1.6; } .footer .doc-no { color:#222; font-weight:700; font-family:'Courier New', monospace; letter-spacing:.05em; } .badge-status { display:inline-block; font-size:9px; font-weight:700; padding:2px 7px; border:1px solid; letter-spacing:.05em; text-transform:uppercase; border-radius:0; } .badge-pago { color:#15803D; border-color:#15803D; background:transparent; } .badge-pendente { color:#B45309; border-color:#B45309; background:transparent; } .badge-parcial { color:#1E40AF; border-color:#1E40AF; background:transparent; } .terms-box, .terms { margin-top:18px; padding:10px 12px; border:1px solid #ddd; border-left:3px solid ${BRAND_COLORS.red}; border-radius:0; background:transparent; font-size:9.5px; line-height:1.55; color:#444; } .terms strong, .terms-box strong { color:#111; } .via-divider { border:none; border-top:1.5px dashed #aaa; margin:36px 0 26px; position:relative; height:1.5px; } .via-divider span { position:absolute; left:50%; top:-9px; transform:translateX(-50%); background:white; padding:0 14px; color:#666; font-size:9px; font-weight:700; letter-spacing:.12em; text-transform:uppercase; } .page-break { page-break-after: always; } `; } // térmica const w = fmt === "mm80" ? "76mm" : "54mm"; const fz = fmt === "mm80" ? "11px" : "10px"; const fzS = fmt === "mm80" ? "9.5px" : "9px"; return ` @page { size: ${w} auto; margin: 2mm; } body { font-family: 'Courier New', ui-monospace, monospace; color:#000; margin:0; padding:0; font-size:${fz}; line-height:1.35; } .doc { width: ${w}; padding: 2mm 1mm; } h1 { font-size:12px; margin:0; text-transform:uppercase; letter-spacing:.05em; } table { width:100%; border-collapse: collapse; } td { padding:1px 0; font-size:${fz}; vertical-align:top; } .num { text-align:right; font-variant-numeric: tabular-nums; } .sep { border-top:1px dashed #333; margin:5px 0; } .meta { font-size:${fzS}; margin:4px 0; } .row { display:flex; justify-content:space-between; font-size:${fz}; padding:1px 0; } .row.big { font-weight:700; font-size:12px; border-top:1px dashed #333; padding-top:3px; margin-top:3px; } .row.profit { font-weight:700; font-size:12px; border:1px solid #333; padding:3px 4px; margin-top:3px; } .center { text-align:center; } .small { font-size:${fzS}; } .ttl { font-weight:700; font-size:${fz}; text-transform:uppercase; margin:4px 0 2px; letter-spacing:.06em; } .via-divider { border:none; border-top:1px dashed #333; margin: 8px 0; text-align:center; font-size:${fzS}; padding-top:4px; } `; } /* Abre nova janela já com botões de imprimir/baixar/fechar */ function _openPrintWindow(title, css, bodyHtml, autoPrint){ const win = window.open("", "_blank", "width=900,height=900"); if (!win) { alert("O bloqueador de pop-ups impediu a impressão. Habilite pop-ups para este site."); return; } const html = ` ${title}

${title}

${bodyHtml}
${autoPrint ? `` : ""} `; win.document.open(); win.document.write(html); win.document.close(); } /* ============================================================ COMPROVANTE DE VENDA (Distribuidor) Suporta 2 vias: "empresa" (cópia interna) e "distribuidor" (cliente). vias: "both" | "empresa" | "distribuidor" ============================================================ */ function _saleBodyA4({ company, sale, items, products, viaLabel, viaDestinatario }) { const head = _headerHtml(company, "a4"); const docNum = _docNum("VEN", sale.id || Date.now()+""); const rows = (items.length === 0 ? [`(Sem detalhamento de itens nesta reimpressão)`] : items.map(it => { const p = products.find(x => x.id === it.productId); const boxes = it.boxes || 0; const loose = it.loose || 0; const productBox = it.boxSize || p?.boxSize || sale.perBox || 60; const units = boxes * productBox + loose; const unitPrice = it.unitPrice != null ? it.unitPrice : (p?.price || 0) * 0.55; const subtotal = it.subtotal != null ? it.subtotal : units * unitPrice; const qtyDesc = [boxes > 0 ? `${boxes} cx (${productBox}un)` : "", loose > 0 ? `${loose} av.` : ""].filter(Boolean).join(" + ") || "—"; return ` ${p?.flavor||"—"} · ${p?.line||""} ${qtyDesc} ${units} ${_money(unitPrice)} ${_money(subtotal)} `; }) ).join(""); const paid = sale.paid != null ? sale.paid : (sale.status === "a_receber" ? 0 : sale.total); const pending = Math.max(0, sale.total - paid); const statusHtml = pending === 0 ? `Pago` : paid > 0 ? `Parcial · falta ${_money(pending)}` : `Pendente · ${_money(pending)}`; return `
${head}
${viaLabel}

Comprovante de Venda

${statusHtml}
Nº ${docNum} Emitido em ${_now()}
${viaDestinatario === "empresa" ? "Cliente (Distribuidor)" : "Emitente"}${viaDestinatario === "empresa" ? (sale.client?.name || "—") : (company.name||"—")}
${viaDestinatario === "empresa" ? (sale.client?.doc||"") : (company.cnpj||"")}
${viaDestinatario === "empresa" ? "Cidade" : "Adquirente"}${viaDestinatario === "empresa" ? (sale.client?.city || "—") : (sale.client?.name||"—")}
Forma de pagamento${sale.payment||"—"}
Data da operação${_date(sale.date||new Date())}${sale.time ? " · "+sale.time : ""}
Discriminação dos itens
${rows}
ProdutoQuantidadeUn.Vl. unitárioSubtotal
Subtotal${_money(sale.subtotal||sale.total)}
${sale.discount>0 ? `
Desconto− ${_money(sale.discount)}
` : ""}
Total${_money(sale.total)}
${paid > 0 && paid < sale.total ? `
Valor pago${_money(paid)}
Saldo pendente${_money(pending)}
` : ""}
${pending > 0 ? `
Venda a prazo: O cliente compromete-se a quitar o saldo pendente de ${_money(pending)} conforme condições negociadas. Pagamentos parciais serão registrados sequencialmente até a quitação integral.
` : ""}
${viaDestinatario === "empresa" ? "Cliente / Distribuidor" : "Adquirente — Recebi os produtos"}
${viaDestinatario === "empresa" ? "Vendedor / Conferente" : "Emitente — "+(company.name||"")}
`; } function _saleBodyThermal({ company, sale, items, products, fmt, viaLabel }) { const head = _headerHtml(company, fmt); const docNum = _docNum("VEN", sale.id || Date.now()+""); const rows = items.map(it => { const p = products.find(x => x.id === it.productId); const boxes = it.boxes || 0; const loose = it.loose || 0; const productBox = it.boxSize || p?.boxSize || sale.perBox || 60; const units = boxes * productBox + loose; const unitPrice = it.unitPrice != null ? it.unitPrice : (p?.price || 0) * 0.55; const subtotal = it.subtotal != null ? it.subtotal : units * unitPrice; const name = (p?.flavor||"—").slice(0, fmt==="mm80"?26:18); const qtyTxt = [boxes > 0 ? `${boxes}cx×${productBox}un` : "", loose > 0 ? `${loose} av.` : ""].filter(Boolean).join(" + "); return `${name} ${qtyTxt} × ${_money(unitPrice)}${_money(subtotal)}`; }).join(""); const paid = sale.paid != null ? sale.paid : (sale.status === "a_receber" ? 0 : sale.total); const pending = Math.max(0, sale.total - paid); return `
${head}
COMPROVANTE DE VENDA
${viaLabel}
Nº ${docNum}
Cliente: ${sale.client?.name||"—"}
${sale.client?.doc ? "CNPJ: "+sale.client.doc+"
" : ""} Data: ${_date(sale.date||new Date())}${sale.time ? " · "+sale.time : ""}
Pagto: ${sale.payment||"—"}
Itens
${rows}
Subtotal${_money(sale.subtotal||sale.total)}
${sale.discount>0 ? `
Desconto-${_money(sale.discount)}
` : ""}
TOTAL${_money(sale.total)}
${pending > 0 ? `
PENDENTE${_money(pending)}
` : ""}
_________________________
Assinatura
Documento sem valor fiscal
${_now()}
${company.name||""}
`; } function printSaleReceipt({ company, sale, items, products, fmt, autoPrint, vias }){ const docNum = _docNum("VEN", sale.id || Date.now()+""); const css = _baseCss(fmt); const viaList = vias === "empresa" ? [{ key:"empresa", label:"Via da Empresa" }] : vias === "distribuidor" ? [{ key:"distribuidor", label:"Via do Distribuidor" }] : [{ key:"empresa", label:"Via da Empresa" }, { key:"distribuidor", label:"Via do Distribuidor" }]; let body = ""; if (fmt === "a4") { body = viaList.map((v, idx) => { const block = _saleBodyA4({ company, sale, items, products, viaLabel: v.label, viaDestinatario: v.key, }); const wrapper = idx < viaList.length - 1 ? `
${block}
` : block; return wrapper; }).join(""); } else { body = viaList.map((v) => _saleBodyThermal({ company, sale, items, products, fmt, viaLabel: v.label, })).join('
--- corte ---
'); } const title = "Comprovante de Venda — " + docNum + (vias && vias !== "both" ? " ("+(vias==="empresa"?"Empresa":"Distribuidor")+")" : ""); _openPrintWindow(title, css, body, autoPrint); } /* ============================================================ COMPROVANTE DE SAÍDA (Picolezeiro · consignação) ============================================================ */ function printSaidaReceipt({ company, trip, picolezeiro, beaches, products, fmt, autoPrint }){ const head = _headerHtml(company, fmt); const docNum = _docNum("SAI", trip.id || Date.now()+""); const css = _baseCss(fmt); const beachNames = (trip.beachesPlanned||[]).map(bid => beaches.find(b=>b.id===bid)?.name).filter(Boolean); const totalUnits = trip.items.reduce((s,i)=>s+i.qty,0); const expectedValue = trip.items.reduce((s,i)=>{ const p = products.find(x=>x.id===i.productId); return s + i.qty * (p?.price||0); },0); let body = ""; if (fmt === "a4") { const rows = trip.items.map(it => { const p = products.find(x=>x.id===it.productId); return ` ${p?.flavor||"—"} · ${p?.line||""} ${it.qty} ${_money(p?.price||0)} ${_money((p?.price||0) * it.qty)} `; }).join(""); body = `
${head}

Comprovante de Saída · Consignação

Nº ${docNum} Emitido em ${_now()}
Picolezeiro${picolezeiro?.name||"—"}
${picolezeiro?.cpf||""} · ${picolezeiro?.phone||""}
Data de saída${_date(trip.date||new Date())}
Praias planejadas${beachNames.join(", ")||"—"}
Comissão acordada${picolezeiro?.commission||0}% sobre valor vendido
Produtos retirados (${totalUnits} un)
${rows}
ProdutoQuantidadeVl. unitárioValor máx.
Total de unidades${totalUnits} un
Valor máximo previsto${_money(expectedValue)}
Termos da consignação: O picolezeiro recebe os produtos relacionados em consignação. No retorno deverá prestar contas dos valores efetivamente vendidos. Sobras retornam ao estoque da fábrica. A comissão é calculada sobre o faturamento líquido confirmado no retorno.
Picolezeiro — Recebi os produtos
Conferente / Fábrica
`; } else { const rows = trip.items.map(it => { const p = products.find(x=>x.id===it.productId); const name = (p?.flavor||"—").slice(0, fmt==="mm80"?26:18); return `${name} ${it.qty} un × ${_money(p?.price||0)}${_money((p?.price||0)*it.qty)}`; }).join(""); body = `
${head}
COMPROVANTE DE SAÍDA
CONSIGNAÇÃO · Nº ${docNum}
Picolezeiro: ${picolezeiro?.name||"—"}
CPF: ${picolezeiro?.cpf||"—"}
Data: ${_date(trip.date||new Date())}
Praias: ${beachNames.join(", ")||"—"}
Comissão: ${picolezeiro?.commission||0}%
Itens retirados
${rows}
Unidades${totalUnits}
VALOR MÁX${_money(expectedValue)}
_____________________________
Picolezeiro
_____________________________
Conferente
${_now()}
${company.name||""}
`; } _openPrintWindow("Comprovante de Saída — " + docNum, css, body, autoPrint); } /* ============================================================ COMPROVANTE DE RETORNO (Picolezeiro) ============================================================ */ function printRetornoReceipt({ company, trip, picolezeiro, beaches, products, fmt, autoPrint }){ const head = _headerHtml(company, fmt); const docNum = _docNum("RET", trip.id || Date.now()+""); const css = _baseCss(fmt); const beachNames = (trip.beachesActual||trip.beachesPlanned||[]).map(bid => beaches.find(b=>b.id===bid)?.name).filter(Boolean); const totalSold = (trip.sold||[]).reduce((s,x)=>s+x.qty,0); const totalRet = (trip.returned||[]).reduce((s,x)=>s+x.qty,0); const faturado = trip.faturado || 0; const comissao = trip.comissao || 0; const recebido = trip.recebido || 0; const liquido = faturado - comissao; const diff = recebido - faturado; let body = ""; if (fmt === "a4") { const rows = trip.items.map(it => { const p = products.find(x=>x.id===it.productId); const sold = (trip.sold||[]).find(x=>x.productId===it.productId)?.qty || 0; const ret = (trip.returned||[]).find(x=>x.productId===it.productId)?.qty ?? (it.qty - sold); return ` ${p?.flavor||"—"} · ${p?.line||""} ${it.qty} ${sold} ${ret} ${_money((p?.price||0)*sold)} `; }).join(""); body = `
${head}

Comprovante de Retorno · Consignação

Nº ${docNum} Emitido em ${_now()}
Picolezeiro${picolezeiro?.name||"—"}
${picolezeiro?.cpf||""}
Data do retorno${_date(trip.date||new Date())}
Praias atendidas${beachNames.join(", ")||"—"}
Comissão aplicada${picolezeiro?.commission||0}% sobre valor vendido
Prestação de contas
${rows}
ProdutoRetiradoVendidoSobraFaturado
Total vendido${totalSold} un
Sobra (estoque)${totalRet} un
Faturado${_money(faturado)}
Comissão picolezeiro− ${_money(comissao)}
Líquido p/ empresa${_money(liquido)}
Valor recebido em dinheiro${_money(recebido)}
${diff !== 0 ? `
Divergência${diff>0?'+':'−'} ${_money(Math.abs(diff))}
` : ""}
Picolezeiro — Prestou contas
Conferente / Fábrica
`; } else { const rows = trip.items.map(it => { const p = products.find(x=>x.id===it.productId); const sold = (trip.sold||[]).find(x=>x.productId===it.productId)?.qty || 0; const ret = (trip.returned||[]).find(x=>x.productId===it.productId)?.qty ?? (it.qty - sold); const name = (p?.flavor||"—").slice(0, fmt==="mm80"?22:14); return `${name} Ret:${it.qty} Vd:${sold} Sb:${ret}${_money((p?.price||0)*sold)}`; }).join(""); body = `
${head}
COMPROVANTE DE RETORNO
Nº ${docNum}
Picolezeiro: ${picolezeiro?.name||"—"}
Data: ${_date(trip.date||new Date())}
Praias: ${beachNames.join(", ")||"—"}
Comissão: ${picolezeiro?.commission||0}%
Itens
${rows}
Vendidos${totalSold} un
Sobra${totalRet} un
Faturado${_money(faturado)}
Comissão-${_money(comissao)}
LÍQUIDO${_money(liquido)}
Recebido${_money(recebido)}
${diff !== 0 ? `
Diferença${diff>0?'+':'-'}${_money(Math.abs(diff))}
` : ""}
_____________________________
Picolezeiro
_____________________________
Conferente
${_now()}
${company.name||""}
`; } _openPrintWindow("Comprovante de Retorno — " + docNum, css, body, autoPrint); } /* ============================================================ Modal de seleção de formato (UI compartilhada) ============================================================ */ function ReceiptFormatModal({ title, subtitle, onClose, onPrint }) { const [fmt, setFmt] = useState("a4"); const options = [ { value:"a4", label:"Papel A4", hint:"Comprovante completo (impressora comum)", icon:"📄" }, { value:"mm80", label:"Térmica 80mm", hint:"Cupom largo (impressora de cupom)", icon:"🧾" }, { value:"mm58", label:"Térmica 58mm", hint:"Cupom estreito (mini-impressora)", icon:"🎫" }, ]; return ( } >

Selecione o formato de impressão:

{options.map(o => ( ))}
); } /* ============================================================ COMPROVANTE DE COMISSÃO (Picolezeiro · LUCRO) Recibo formal de pagamento da comissão devida ao picolezeiro. Pode ser por 1 retorno OU por período (várias trips). props: - company - picolezeiro - trips: array de trips fechados (1 ou mais) - period: { label, start, end } opcional - paymentMethod: forma de pagamento da comissão - paymentDate: data do pagamento (default hoje) - fmt: "a4" | "mm80" | "mm58" ============================================================ */ function printCommissionReceipt({ company, picolezeiro, trips, beaches, period, paymentMethod, paymentDate, fmt, autoPrint }){ const tripsArr = Array.isArray(trips) ? trips : [trips]; const beachesArr = beaches || []; const docNum = _docNum("COM", (tripsArr[0]?.id || "") + (tripsArr.length > 1 ? "-" + tripsArr.length : "") + Date.now().toString(36)); const css = _baseCss(fmt); const faturadoTotal = tripsArr.reduce((s, t) => s + (t.faturado || 0), 0); const comissaoTotal = tripsArr.reduce((s, t) => s + (t.comissao || 0), 0); const liquidoEmpresa = faturadoTotal - comissaoTotal; const totalSold = tripsArr.reduce((s, t) => s + ((t.sold||[]).reduce((a, x) => a + x.qty, 0)), 0); /* Valor da comissão por extenso (simples). */ const valorPorExtenso = (v) => { const reais = Math.floor(v); const cent = Math.round((v - reais) * 100); return `${reais} reais${cent > 0 ? " e " + cent + " centavos" : ""}`; }; const dataPgto = paymentDate || new Date().toISOString().slice(0,10); const periodLabel = period?.label || (tripsArr.length === 1 ? "Retorno de " + _date(tripsArr[0].date) : tripsArr.length + " retornos"); let body = ""; if (fmt === "a4") { const tripRows = tripsArr.map(t => { const tripFat = t.faturado || 0; const tripCom = t.comissao || 0; const tripSold = (t.sold||[]).reduce((s,x)=>s+x.qty,0); const tripBeaches = (t.beachesActual || t.beachesPlanned || []) .map(bid => beachesArr.find(b => b.id === bid)?.name) .filter(Boolean) .join(", ") || "—"; return ` ${_date(t.date)} ${tripBeaches} ${tripSold} ${_money(tripFat)} ${_money(tripCom)} `; }).join(""); body = `
${_headerHtml(company, "a4")}
Recibo de Pagamento

Recibo de Comissão · Picolezeiro

Pago
Nº ${docNum} Emitido em ${_now()}
Valor recebido
${_money(comissaoTotal)}
(${valorPorExtenso(comissaoTotal)})

Eu, ${picolezeiro?.name||"—"}${picolezeiro?.cpf ? ", portador(a) do CPF nº " + picolezeiro.cpf : ""}, declaro ter recebido da empresa ${company.name||"—"}${company.cnpj ? ", inscrita no CNPJ sob nº " + company.cnpj : ""}, a importância de ${_money(comissaoTotal)} (${valorPorExtenso(comissaoTotal)}), referente à comissão de ${picolezeiro?.commission||0}% sobre o faturamento de ${_money(faturadoTotal)} apurado em ${periodLabel}, conforme detalhamento abaixo. Para clareza, firmo o presente recibo, dando plena e geral quitação da obrigação acima referida.

Beneficiário${picolezeiro?.name||"—"}
CPF ${picolezeiro?.cpf||"—"}${picolezeiro?.phone ? " · " + picolezeiro.phone : ""}
Pagador${company.name||"—"}
CNPJ ${company.cnpj||"—"}
Período de apuração${periodLabel}
Data do pagamento${_date(dataPgto)}
Forma de pagamento${paymentMethod || "Dinheiro"}
Comissão acordada${picolezeiro?.commission||0}% sobre valor vendido
Detalhamento por retorno
${tripRows}
Data Praias Un. vendidas Faturado Comissão (${picolezeiro?.commission||0}%)
Totais ${totalSold} un ${_money(faturadoTotal)} ${_money(comissaoTotal)}
Faturamento total${_money(faturadoTotal)}
Alíquota de comissão${picolezeiro?.commission||0}%
Líquido retido pela empresa${_money(liquidoEmpresa)}
LUCRO DO PICOLEZEIRO${_money(comissaoTotal)}
Dou plena, geral e irrevogável quitação à empresa pagadora quanto ao valor acima descrito, referente exclusivamente ao período/retornos discriminados. Nada mais tendo a reclamar a qualquer título.
${picolezeiro?.name||""}
CPF ${picolezeiro?.cpf||"—"}
${company.name||""}
CNPJ ${company.cnpj||"—"}
`; } else { // Térmica const trRows = tripsArr.map(t => `
${_date(t.date)}${_money(t.comissao||0)}
Fat ${_money(t.faturado||0)}
`).join(""); body = `
${_headerHtml(company, fmt)}
RECIBO DE COMISSÃO
Picolezeiro · Nº ${docNum}
${picolezeiro?.name||"—"}
CPF ${picolezeiro?.cpf||"—"}
Data pgto: ${_date(dataPgto)}
Período: ${periodLabel}
Comissão: ${picolezeiro?.commission||0}%
Pgto: ${paymentMethod||"Dinheiro"}
Retornos no período
${trRows}
Faturado total${_money(faturadoTotal)}
Un. vendidas${totalSold}
LUCRO RECEBIDO${_money(comissaoTotal)}
Recebi da empresa ${company.name||""} a importância acima referente à comissão da consignação. Dou plena quitação.
_________________________
${picolezeiro?.name||""}
_________________________
Pagador
${_now()}
${company.name||""}
`; } _openPrintWindow("Recibo de Comissão — " + (picolezeiro?.name||"") + " — " + docNum, css, body, autoPrint); } Object.assign(window, { printSaleReceipt, printSaidaReceipt, printRetornoReceipt, printCommissionReceipt, ReceiptFormatModal, });