mad-pdv
Frente de caixa (PDV): busca/bipagem, carrinho configurável, pagamento composto, venda a prazo e cupom.
Frente de caixa (PDV/POS) completa: busca de produto por bipagem ou nome,
carrinho com colunas configuraveis, desconto por item e por total, pagamento
composto com troco, venda a prazo gerando contas a receber, vendas em espera e
impressao de cupom. Tudo declarado no Blade com <mad-pdv> e sub-tags.
Compilador: Mad\Pdv\MadPdvCompiler (passo 1.11 do MadBlade). Componente:
Mad\Pdv\MadPdvComponent (abstrato, wrapper INTERNAL). Sem host proprio, o
compiler instancia um Mad\Pdv\MadPdvStandalone. VOs de apoio:
Mad\Pdv\PdvPayment, Mad\Pdv\PdvColumn, Mad\Pdv\PdvInstallmentPlan.
Exemplo minimo
<mad-pdv model="Produto" database="business"
name-field="descricao" price-field="preco" barcode-field="ean"
sale-model="Venda" item-model="VendaItem" payment-model="VendaPagamento"
title="Caixa 01">
<mad-pdv-payment method="dinheiro" label="Dinheiro" allow-change entra-no-caixa hotkey="1" />
<mad-pdv-payment method="pix" label="Pix" entra-no-caixa hotkey="2" />
<mad-pdv-payment method="cartao_credito" label="Credito" sacado="adquirente"
gera-titulo prazo-primeira-dias="30" hotkey="3" />
</mad-pdv>
Obrigatorio: model, name-field, price-field, pelo menos um entre
code-field e barcode-field, e os tres *-model de persistencia. Os demais
campos de gravacao ja vem com o esquema canonico
venda / venda_item / venda_pagamento.
Produto (busca e bipagem)
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
model |
string | — | Model do produto. Obrigatorio |
database |
string | MAIN_DATABASE |
Conexao |
name-field |
string | — | Campo da descricao. Obrigatorio |
price-field |
string | — | Campo do preco. Obrigatorio |
code-field |
string | '' |
Codigo interno (um dos dois e obrigatorio) |
barcode-field |
string | '' |
Codigo de barras (um dos dois e obrigatorio) |
stock-field |
string | '' |
Campo de estoque |
unit-field |
string | '' |
Unidade de medida |
image-field |
string | '' |
Imagem do produto |
active-field |
string | '' |
Campo de ativo |
active-value |
string | 1 |
Valor considerado ativo |
order-by |
string | '' |
Ordenacao da busca |
where |
string | '' |
Filtro DSL, ou :where="$closure" |
search-min-length |
int | 2 |
Minimo de caracteres para buscar |
search-limit |
int | 10 |
Itens no dropdown de busca |
product-picker |
bool | false |
Catalogo visual de produtos (para quem nao tem leitor) |
product-picker-page-size |
int | 24 |
Itens por pagina do catalogo |
Persistencia
Todos os campos abaixo tem default; so os *-model sao obrigatorios.
| Prop | Default | Prop | Default |
|---|---|---|---|
sale-model |
— | item-model |
— |
sale-total-field |
total |
item-sale-field |
venda_id |
sale-subtotal-field |
subtotal |
item-product-field |
produto_id |
sale-discount-field |
desconto |
item-qty-field |
quantidade |
sale-datetime-field |
data_hora |
item-price-field |
preco_unitario |
sale-customer-field |
cliente_id |
item-discount-field |
desconto |
sale-operator-field |
operador_id |
item-total-field |
total |
sale-status-field |
status |
payment-model |
— |
sale-status-done |
concluida |
payment-sale-field |
venda_id |
sale-document-field |
documento |
payment-method-field |
forma |
sale-change-field |
troco |
payment-amount-field |
valor |
sale-uuid-field |
client_uuid |
payment-tendered-field |
valor_recebido |
sale-database, item-database e payment-database permitem gravar cada
tabela numa conexao diferente da do produto.
Cliente
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
customer-model |
string | '' |
Model do cliente (vazio = sem cliente) |
customer-key |
string | '' |
Chave (default: PK do model) |
customer-display |
string | {nome} |
Mask de exibicao |
customer-order-by |
string | '' |
Ordenacao (aceita direcao, ex. nome desc) |
customer-where |
string | '' |
Filtro DSL, ou :customer-where="$closure" |
customer-required |
bool | false |
Exige cliente para finalizar |
customer-default-id |
int | — | Cliente pre-selecionado (consumidor final) |
Comportamento
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
discount-mode |
string | both |
none | item | total | both |
max-discount-percent |
float | sem limite | Teto de desconto |
allow-price-override |
bool | false |
Permite editar o preco unitario |
allow-fraction |
bool | false |
Quantidade fracionada |
stock-mode |
string | — | off | warn | block |
stock-decrement |
bool | — | Baixa estoque ao finalizar |
ask-document |
bool | false |
Pergunta CPF/CNPJ na nota |
hold-sales |
bool | true |
Vendas em espera |
hold-limit |
int | 10 |
Maximo de vendas em espera |
print-mode |
string | browser |
off | browser |
receipt-width |
int | 80 |
Largura do cupom em mm: 58 ou 80 |
receipt-header |
string | '' |
Cabecalho do cupom |
receipt-footer |
string | '' |
Rodape do cupom |
auto-print |
bool | false |
Imprime ao finalizar |
on-finalized |
string | '' |
Metodo do host chamado apos a venda |
Visual
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
title |
string | '' |
Titulo da tela |
fullscreen-toggle |
bool | true |
Botao de tela cheia |
currency-symbol |
string | R$ |
Simbolo da moeda |
locale |
string | '' |
Locale de formatacao (vazio = o da app) |
density |
string | normal |
normal | compact |
show-images |
bool | — | Miniatura do produto no carrinho |
<mad-pdv-payment>
Repetivel; a ordem no documento e a ordem dos botoes. Sem nenhuma declarada, o runtime usa o conjunto canonico dinheiro / debito / credito / pix.
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
method |
string | — | Identificador da forma. Obrigatorio |
label |
string | '' |
Rotulo do botao |
icon |
string | por metodo conhecido | Icone lucide |
allow-change |
bool | false |
Aceita valor recebido maior e calcula troco |
hotkey |
string | '' |
Tecla de atalho |
gera-titulo |
bool | false |
Gera parcelas em contas a receber |
sacado |
string | nenhum |
Quem deve: nenhum | cliente | adquirente |
entra-no-caixa |
bool | false |
Conta na conferencia da gaveta |
exige-cliente |
bool | false |
Bloqueia finalizar sem cliente (crediario) |
max-parcelas |
int | 1 |
Limite de parcelas (cap 120) |
prazo-primeira-dias |
int | 0 |
Dias da venda ate o 1o vencimento |
intervalo-dias |
int | 30 |
Intervalo entre parcelas |
ajuste-residuo |
string | ultima |
Onde vai o centavo restante: ultima | primeira |
taxa-percentual |
float | 0 |
Taxa percentual da forma |
taxa-fixa |
float | 0 |
Taxa fixa por transacao |
adquirente |
string | '' |
Rotulo/id do sacado quando sacado="adquirente" |
Com gera-titulo, configure tambem o destino dos titulos na tag raiz:
receivable-model (vazio = feature desligada), mais os campos
receivable-sale-field (venda_id), receivable-customer-field
(cliente_id), receivable-sacado-field (sacado_tipo),
receivable-doc-field (documento), receivable-number-field (parcela),
receivable-count-field (total_parcelas), receivable-amount-field
(valor), receivable-due-field (vencimento), receivable-method-field
(forma), receivable-status-field (status) e receivable-status-open
(aberto).
<mad-pdv-column>
Colunas extras do carrinho. Chave ausente (nenhuma declarada) = conjunto canonico.
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
field |
string | '' |
Campo do produto exibido |
label |
string | '' |
Header |
width |
string | '' |
Largura |
align |
string | left |
left | center | right |
slot |
string | before-total |
after-product | before-total |
transform |
string | '' |
Transformer server-side da celula |
transform-target |
string | display |
display (so a celula) | stored (a saida tambem e gravada) |
affects |
string | '' |
Com stored: price faz a saida virar o preco unitario efetivo |
mode |
string | display |
display | input |
input-type |
string | text |
text | number | date | combo |
options |
string | '' |
Opcoes do combo |
item-field |
string | '' |
Campo do item de venda onde o valor digitado e gravado |
required |
bool | false |
Obrigatoria no modo input |
maxlength |
int | 120 |
Limite do input |
default |
string | '' |
Valor inicial |
merge-ignore |
bool | false |
Impede fundir linhas do mesmo produto com valores diferentes |
receipt |
bool | false |
Sai tambem no cupom |
<mad-pdv-action>
Botao extra na toolbar, repetivel.
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
label |
string | — | Rotulo. Obrigatorio |
method |
string | — | Metodo wire do host. Obrigatorio |
icon |
string | '' |
Icone lucide |
hotkey |
string | '' |
Tecla de atalho |
confirm |
string | '' |
Mensagem de confirmacao antes de executar |
Subclasse (hooks)
use Mad\Pdv\MadPdvComponent;
class CaixaLoja extends MadPdvComponent
{
protected string $model = 'Produto';
protected string $saleModel = 'Venda';
protected function beforeSaveSale(object $venda, array $ctx): void
{
$venda->unit_id = MadSession::unitId();
}
protected function afterSale(object $venda, array $itens, array $pagamentos): void
{
}
}
Gotchas
@ifem volta de sub-tag do PDV nao funciona. O compilador roda antes do Blade, sobre o texto cru, e extrai<mad-pdv-payment>/<mad-pdv-column>por regex: a condicional some no render mas a sub-tag ja entrou na config. O sintoma e confuso — a forma nao aparece na tela e ainda assim dispara a validacao dela. Condicione pelos atributos (:gera-titulo="$cond").- Atributo
mad-*nao aceita{{ }}; expressao PHP entra com dois-pontos. <mad-pdv-receipt>e<mad-pdv-hotkey>sao reservadas de v2: geram warning no log e sao ignoradas, sem quebrar o render.- As colunas do carrinho viajam para o cliente com chave opaca (
c0,c1, ...); o nome do campo nunca sai do servidor. A gravacao itera a lista do servidor, entao chave inventada no payload nunca e gravada. - O transformer de coluna roda sempre no servidor e e contido (captura o erro e loga uma vez por coluna) — um typo nao fecha o caixa.
- O rateio de parcelas e servidor (
PdvInstallmentPlan); o client so tem um espelho para o preview. Invariante: a soma das parcelas e sempre igual ao valor, com o residuo de centavos na ultima (ou na primeira, viaajuste-residuo).