mad-reconcile
Conciliação em dois painéis: auto-match por regras (MatchEngine), match manual N:M e desfazer.
Tela de conciliacao em dois paineis: de um lado as linhas nao conciliadas de um model (extrato bancario), do outro as de outro model (lancamentos contabeis). Auto-match por regras, match manual N:M com tolerancia e desfazer. Uma tag declarativa sem filhos.
Compilador: Mad\Reconcile\MadReconcileCompiler (passo 1.09 do MadBlade).
Componente: Mad\Reconcile\MadReconcile. Motor de sugestao:
Mad\Reconcile\MatchEngine. Sem host proprio, o compiler instancia um
Mad\Reconcile\MadReconcileStandalone.
Tag
<mad-reconcile left-model="BankStatementLine" right-model="LedgerEntry"
database="business"
left-amount="valor" right-amount="valor"
left-date="data" right-date="data_lancamento"
left-doc="documento" right-doc="documento"
left-title="Extrato" right-title="Razao"
match-on="amount,date:3,document"
tolerance="0.01" max-rows="500"
context="banco-conta1" />
Aceita forma self-closing ou par de tags; nao tem filhos.
Props
| Prop | Tipo | Default | Descricao |
|---|---|---|---|
left-model |
string | — | Model do painel esquerdo. Obrigatorio |
right-model |
string | — | Model do painel direito. Obrigatorio |
database |
string | MAIN_DATABASE |
Conexao |
match-on |
string | amount |
Regras do auto-match: amount, date:N, document (separados por virgula) |
left-amount |
string | valor |
Campo de valor do lado esquerdo |
right-amount |
string | valor |
Campo de valor do lado direito |
left-date |
string | '' |
Campo de data (esquerdo) |
right-date |
string | '' |
Campo de data (direito) |
left-doc |
string | '' |
Campo de documento (esquerdo) |
right-doc |
string | '' |
Campo de documento (direito) |
left-label |
string | '' |
Campo de descricao exibido (esquerdo) |
right-label |
string | '' |
Campo de descricao exibido (direito) |
left-title |
string | nome do model | Titulo do painel esquerdo |
right-title |
string | nome do model | Titulo do painel direito |
tolerance |
float | 0.0 |
Tolerancia de diferenca de soma no match manual |
max-rows |
int | 500 |
Cap de linhas carregadas por painel |
context |
string | '' |
Namespace da conciliacao — separa grupos de conciliacoes diferentes |
Fluxo
- Paineis com as linhas nao conciliadas (data, documento, descricao, valor), filtro client-side e contador por painel.
- Auto-match (
onAutoMatch) roda oMatchEnginee grava grupos com statussuggested. Cada sugestao vira um card com score, somas dos dois lados e botoes Confirmar / Rejeitar (mais "Confirmar tudo" na toolbar). - Match manual — o usuario clica linhas nos dois paineis; a toolbar mostra
NxMe a diferenca ao vivo. "Conciliar selecao" so habilita com|somaL - somaR| <= tolerancee grava direto comoconfirmed(onManualMatch, aceita N:M). - Conciliados — secao com os grupos confirmados e Desfazer (
onUnmatch), que apaga o grupo e devolve os registros aos paineis. Rejeitar sugestao eonRejectGroup.
MatchEngine
Tokens de match-on: amount (implicito, sempre ativo), date:N (tolerancia
de N dias) e document (exige igualdade de documento no passe exato).
| Passe | Criterio | Score |
|---|---|---|
| 1 | Valor exato + mesmo documento (ou mesma data, sem document) |
100 |
| 2 | Valor exato + data dentro da tolerancia (a mais proxima) | 95 - 5 x dias (min 70) |
| 3 | N:1 por soma (ate 3 itens de um lado somando 1 do outro, nos dois sentidos) | 70 |
Os passes sao greedy: cada registro entra em no maximo um grupo. O passe 3 e
subset-sum e esta capado em 20 mil combinacoes por rodada — ao estourar, o
usuario recebe um toast pedindo para rodar de novo depois de confirmar (flag
truncated).
Persistencia e escopo
Grupos vivem em mad_reconcile_group (context, status suggested|confirmed,
score, matched_by, matched_at) e itens em mad_reconcile_item
(group_id, side L|R, record_table, record_id e snapshot do valor).
use Mad\Reconcile\MadReconcile;
class ConciliacaoBancaria extends MadReconcile
{
protected static string $wrapper = self::INTERNAL;
protected string $leftModel = 'BankStatementLine';
protected string $rightModel = 'LedgerEntry';
protected string $matchOn = 'amount,date:3,document';
protected string $context = 'banco-conta1';
protected function leftQuery(\Illuminate\Database\Eloquent\Builder $q): void
{
$q->where('unit_id', MadSession::unitId());
}
protected function rightQuery(\Illuminate\Database\Eloquent\Builder $q): void
{
$q->where('unit_id', MadSession::unitId());
}
/** Depois de confirmar: baixar titulos, marcar flags, etc. */
protected function afterConfirm(array $groupIds): void
{
}
}
Gotchas
- Sem subclasse nao ha escopo.
leftQuery/rightQuerysao os hooks anti-IDOR: registro fora do escopo nao aparece no painel e e recusado noonManualMatch(owhereInroda sobre a query escopada e ja exclui os conciliados). Em multi-tenant/multi-unidade, use subclasse sempre. - As operacoes de grupo (
onConfirmGroup,onRejectGroup,onUnmatch,onConfirmAll) sao travadas pelocontext— grupo de outra conciliacao e ignorado. Duas telas de conciliacao na mesma base precisam decontextdistintos. - A validacao de soma do match manual roda no servidor; o client so pre-habilita o botao.
- Valores negativos so casam com valores de mesmo sinal.
- Atributo
mad-*nao aceita{{ }}— use o prefixo dois-pontos para expressao PHP (:tolerance="$that->tol()").