MAD Lab
/
Tutor > Módulos visuais > PDV

PDV

Módulos visuais

mad-pdv mad-pdv-action mad-pdv-payment
Compacto (zero PHP, catálogo visual)

mad-pdv com product-picker (sem leitor de código de barras) e só dinheiro/Pix.

Operator:
# Product Qty Price Disc. Total
F2 Product F4 Customer F6 Discount F8 Hold sale F10 Finalize Del Remove item Ctrl+Del Cancel sale
Add tax ID? (Enter skips)
Cancel the current sale? Items will be lost.
Held sales
Change
resources/views/tutor/examples/modulos-pdv/compacto.blade.php
{{-- Zero PHP (MadPdvStandalone) — catálogo visual em vez de bipagem, e só os
     dois métodos de pagamento mais usados. Grava no MESMO livro de vendas do
     demo grande (tutor_pdv_vendas/itens/pagamentos): dois caixas, um catálogo. --}}
<mad-pdv model="TutorDemoPdvProduto" database="business"
         name-field="nome" price-field="preco" barcode-field="ean" code-field="codigo"
         stock-field="estoque" unit-field="unidade" active-field="ativo" active-value="1"
         sale-model="TutorDemoPdvVenda" item-model="TutorDemoPdvVendaItem"
         payment-model="TutorDemoPdvVendaPagamento"
         title="Caixa 02 — Compacto" product-picker :product-picker-page-size="12">
    <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>
Completo (bipagem, fração, ação custom)

Quatro formas de pagamento + mad-pdv-action chamando TutorModulosPdv::onOpenDrawer.

Operator:
# Product Qty Price Disc. Total
F2 Product F4 Customer F6 Discount F8 Hold sale F10 Finalize Del Remove item Ctrl+Del Cancel sale
Add tax ID? (Enter skips)
Cancel the current sale? Items will be lost.
Held sales
Change
resources/views/tutor/examples/modulos-pdv/completo.blade.php
@php
    // Partial de <x-tutor-demo> = escopo Blade ISOLADO — `$that` não
    // atravessa esse aninhamento sozinho. Reabre aqui porque a ação custom da
    // toolbar (mad-pdv-action) chama TutorModulosPdv::onOpenDrawer, que só
    // existe no HOST de verdade.
    $that = \Mad\Component\MadRenderContext::getComponent();
@endphp
<mad-pdv model="TutorDemoPdvProduto" database="business"
         name-field="nome" price-field="preco" barcode-field="ean" code-field="codigo"
         stock-field="estoque" unit-field="unidade" active-field="ativo" active-value="1"
         sale-model="TutorDemoPdvVenda" item-model="TutorDemoPdvVendaItem"
         payment-model="TutorDemoPdvVendaPagamento"
         stock-mode="warn" allow-fraction
         title="Caixa 01 — Completo"
         receipt-header="MAD MERCADO LTDA&#10;CNPJ 00.000.000/0001-00">
    <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_debito" label="Débito" hotkey="3" />
    <mad-pdv-payment method="cartao_credito" label="Crédito" hotkey="4" />
    <mad-pdv-action label="Abrir gaveta" method="onOpenDrawer" icon="archive"
                    confirm="Abrir a gaveta?" />
</mad-pdv>
app/control/Tutor/TutorModulosPdv.php
/** Ação custom da toolbar (<mad-pdv-action method="onOpenDrawer">). */
public function onOpenDrawer(array $data = []): MadResponse
{
    $itens = count($data['cart']['items'] ?? []);

    return (new MadResponse())->toast("Gaveta aberta (carrinho com {$itens} itens).", 'info');
}
Código-fonte completo da página
app/control/Tutor/TutorModulosPdv.php
<?php

namespace App\Control\Tutor;

use App\Models\TutorDemo\TutorDemoPdvProduto;
use Illuminate\Support\Facades\Schema;
use Mad\Http\MadResponse;
use Mad\Pdv\MadPdvComponent;

/**
 * TutorModulosPdv — página "PDV" do MAD Lab (catálogo modulos_pdv). Cobre:
 * mad-pdv, mad-pdv-action, mad-pdv-payment.
 *
 * Ao contrário de org-chart/sheet/reconcile, aqui a config INTEIRA viaja nos
 * atributos da própria tag (molde PdvDevView — que também não declara
 * `$model`/`$nameField`/etc. como propriedade da classe). O demo pequeno
 * (compacto.blade.php) NÃO reabre `$that` — cai num MadPdvStandalone novo por
 * request (zero PHP: sem `onOpenDrawer`, só os pagamentos canônicos). O
 * grande (completo.blade.php) reabre `$that` porque só o HOST de verdade
 * (esta classe) tem o método `onOpenDrawer` que a ação custom da toolbar
 * chama. Os dois gravam no MESMO livro de vendas (tutor_pdv_vendas/itens/
 * pagamentos) — dois caixas, um catálogo, um livro só (cenário real de loja).
 */
class TutorModulosPdv extends MadPdvComponent
{
    protected static string $wrapper = self::INTERNAL;

    public function mount(array $params = []): void
    {
        $this->ensureFixture();
    }

    /** Ação custom da toolbar (<mad-pdv-action method="onOpenDrawer">). */
    public function onOpenDrawer(array $data = []): MadResponse
    {
        $itens = count($data['cart']['items'] ?? []);

        return (new MadResponse())->toast("Gaveta aberta (carrinho com {$itens} itens).", 'info');
    }

    // ── Fixture ──────────────────────────────────────────────────────────

    private function ensureFixture(): void
    {
        $schema = Schema::connection('business');

        if (!$schema->hasTable('tutor_pdv_produtos')) {
            $schema->create('tutor_pdv_produtos', function ($t) {
                $t->increments('id');
                $t->string('nome');
                $t->decimal('preco', 12, 2)->default(0);
                $t->string('ean', 20)->nullable();
                $t->string('codigo', 20)->nullable();
                $t->decimal('estoque', 12, 3)->default(0);
                $t->string('unidade', 5)->default('UN');
                $t->string('ativo', 1)->default('1');
            });
        }
        if (!$schema->hasTable('tutor_pdv_vendas')) {
            $schema->create('tutor_pdv_vendas', function ($t) {
                $t->increments('id');
                $t->dateTime('data_hora')->nullable();
                $t->unsignedInteger('operador_id')->nullable();
                $t->string('status', 20)->nullable();
                $t->decimal('subtotal', 12, 2)->default(0);
                $t->decimal('desconto', 12, 2)->default(0);
                $t->decimal('total', 12, 2)->default(0);
                $t->string('documento', 20)->nullable();
                $t->decimal('troco', 12, 2)->default(0);
                $t->char('client_uuid', 36)->nullable()->unique();
            });
        }
        if (!$schema->hasTable('tutor_pdv_venda_itens')) {
            $schema->create('tutor_pdv_venda_itens', function ($t) {
                $t->increments('id');
                $t->unsignedInteger('venda_id');
                $t->unsignedInteger('produto_id');
                $t->decimal('quantidade', 12, 3)->default(0);
                $t->decimal('preco_unitario', 12, 2)->default(0);
                $t->decimal('desconto', 12, 2)->default(0);
                $t->decimal('total', 12, 2)->default(0);
            });
        }
        if (!$schema->hasTable('tutor_pdv_venda_pagamentos')) {
            $schema->create('tutor_pdv_venda_pagamentos', function ($t) {
                $t->increments('id');
                $t->unsignedInteger('venda_id');
                $t->string('forma', 30);
                $t->decimal('valor', 12, 2)->default(0);
                $t->decimal('valor_recebido', 12, 2)->nullable();
            });
        }

        if (TutorDemoPdvProduto::query()->count() > 0) {
            return;
        }

        TutorDemoPdvProduto::query()->insert([
            ['id' => 1, 'nome' => 'Refrigerante Lata 350ml',    'preco' => 5.50,  'ean' => '7892000000001', 'codigo' => 'T001', 'estoque' => 80, 'unidade' => 'UN', 'ativo' => '1'],
            ['id' => 2, 'nome' => 'Pão Francês (unidade)',      'preco' => 0.80,  'ean' => '7892000000002', 'codigo' => 'T002', 'estoque' => 200, 'unidade' => 'UN', 'ativo' => '1'],
            ['id' => 3, 'nome' => 'Sabonete',                   'preco' => 3.20,  'ean' => '7892000000003', 'codigo' => 'T003', 'estoque' => 40, 'unidade' => 'UN', 'ativo' => '1'],
            ['id' => 4, 'nome' => 'Café em pó 500g',            'preco' => 18.90, 'ean' => '7892000000004', 'codigo' => 'T004', 'estoque' => 25, 'unidade' => 'UN', 'ativo' => '1'],
            ['id' => 5, 'nome' => 'Carne moída kg',             'preco' => 34.90, 'ean' => '7892000000005', 'codigo' => 'T005', 'estoque' => 12.5, 'unidade' => 'KG', 'ativo' => '1'],
            ['id' => 6, 'nome' => 'Produto Descontinuado',      'preco' => 1.00,  'ean' => '7892000000006', 'codigo' => 'T006', 'estoque' => 0,  'unidade' => 'UN', 'ativo' => '0'],
        ]);
    }

    protected function view(): string|array
    {
        return ['tutor.modulos.pdv', []];
    }
}
resources/views/tutor/modulos/pdv.blade.php
<x-tutor-page page="modulos_pdv">
    <x-tutor-demo demo="tutor.examples.modulos-pdv.compacto"
        title="Compacto (zero PHP, catálogo visual)" desc="mad-pdv com product-picker (sem leitor de código de barras) e só dinheiro/Pix." />

    <x-tutor-demo demo="tutor.examples.modulos-pdv.completo"
        title="Completo (bipagem, fração, ação custom)" desc="Quatro formas de pagamento + mad-pdv-action chamando TutorModulosPdv::onOpenDrawer."
        php="TutorModulosPdv::onOpenDrawer" />
</x-tutor-page>