commit de compras

This commit is contained in:
2026-11-04 09:37:20 +02:00
parent fcf31e8d48
commit 0b05a1192c
741 changed files with 114127 additions and 116 deletions
+7 -4
View File
@@ -3,11 +3,13 @@
<style>body{font:16px system-ui;background:#f3f6f5;color:#142b28;margin:0}main{max-width:1100px;margin:auto;padding:24px}header,section{background:white;padding:24px;margin:18px 0;border:1px solid #d5e0dc;border-radius:12px}h1{font-size:28px}h2{font-size:20px}.notice{padding:16px;background:#fff2cc}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px}label{display:block;margin:12px 0}select,textarea,button{font:inherit;padding:10px;max-width:100%}textarea{display:block;width:90%}button{background:#125a4b;color:white;border:0;border-radius:6px;cursor:pointer}button:disabled{opacity:.5}table{border-collapse:collapse;width:100%}td,th{text-align:left;padding:12px;border-bottom:1px solid #ddd}.scroll{overflow:auto}.amount{font-size:26px;font-weight:bold}pre{white-space:pre-wrap;overflow-wrap:anywhere}a{color:#125a4b}</style></head><body><main>
<header><?php if(session()->get('payables_demo_entry')): ?><p><a href="/demo">Trocar função ou sair da demonstração</a></p><?php endif ?><a href="<?= $e($settings['return_url']??'/') ?>">← <?= $e($settings['return_label']??'Voltar à aplicação') ?></a><p><?= $e($settings['host']) ?> · <?= $e($settings['actor']) ?></p><h1>Obrigações a fornecedores</h1><p><?= $e($settings['organisation']) ?></p><p class="notice">ENSAIO SINTÉTICO — SEM UTILIZAÇÃO REAL. Documento, dívida, pagamento e contabilidade são factos distintos. Este percurso reconhece o principal e, quando activado, preserva o histórico existente.</p><p>Fornecedor, montantes e conferência são reutilizados da factura. Vencimento desconhecido não é substituído por uma data ou zero dias de atraso.</p></header>
<?php if(isset($data['history_receipt_notice'])): ?><p class="notice"><?= $e($data['history_receipt_notice']) ?></p><?php endif ?>
<?php $pageNav=function(string $key)use($data,$settings,$e){if(!isset($data['pagination'][$key]))return;$p=$data['pagination'][$key];$param=['documents'=>'page','proposals'=>'proposal_page','events'=>'event_page'][$key];echo '<nav aria-label="Paginação '.$e($key).'"><p>Página '.$e($p['page']).' de '.$e($p['last_page']).' · '.$e($p['total']).' registos</p>';foreach([-1=>'Anterior',1=>'Seguinte'] as $delta=>$label){$target=$p['page']+$delta;if($target<1||$target>$p['last_page'])continue;$query=array_replace($data['listing_query'],[$param=>$target]);if($key==='documents'){$query['proposal_page']=1;$query['event_page']=1;}$url=($settings['page_url']??'/supplier-payables').'?'.http_build_query($query,'','&',PHP_QUERY_RFC3986);echo '<a href="'.$e($url).'">'.$label.'</a> ';}echo '</nav>';}; ?>
<?php if(isset($data['pagination'])): ?><section><h2>Filtrar documentos</h2><form method="get" action="<?= $e($settings['page_url']??'/supplier-payables') ?>"><label>Número ou fornecedor<input name="q" maxlength="100" value="<?= $e($data['listing_query']['q']) ?>"></label><label>Reconhecimento<select name="recognition"><?php foreach(['all'=>'Todos','recognised'=>'Reconhecidos','unrecognised'=>'Por reconhecer'] as $v=>$label): ?><option value="<?= $e($v) ?>" <?= $data['listing_query']['recognition']===$v?'selected':'' ?>><?= $e($label) ?></option><?php endforeach ?></select></label><label>Documentos por página<select name="per_page"><?php foreach([25,100] as $size): ?><option value="<?= $size ?>" <?= $data['listing_query']['per_page']===$size?'selected':'' ?>><?= $size ?></option><?php endforeach ?></select></label><button>Aplicar filtros</button></form><p>Obrigações, propostas e eventos abaixo pertencem aos documentos desta página. A pesquisa e o estado são aplicados antes da paginação.</p></section><?php endif ?>
<?php if($data['payments_available']??false): ?><p><a href="/supplier-payments">Pagamentos e afectações sintéticas — sem execução bancária</a></p><?php endif ?><p id="status" role="status"></p><p id="error" role="alert"></p><button id="reload">Actualizar consulta</button>
<?php if(isset($settings['report_url'])): ?><p><a href="<?= $e($settings['report_url'].'?'.http_build_query(array_intersect_key($data['listing_query'],array_flip(['q','source'])),'','&',PHP_QUERY_RFC3986)) ?>">Relatório das obrigações reconhecidas abrangidas pelo filtro</a></p><?php endif ?>
<?php if(isset($data['listing_query']['source'])): ?><p>Conferência da origem #<?= $e($data['listing_query']['source']) ?>. <a href="<?= $e($settings['page_url']??'/supplier-payables') ?>">Consultar todas as origens</a></p><?php endif ?>
<?php $pageNav=function(string $key)use($data,$settings,$e){if(!isset($data['pagination'][$key]))return;$p=$data['pagination'][$key];$param=['documents'=>'page','proposals'=>'proposal_page','events'=>'event_page'][$key];$label=['documents'=>'documentos','proposals'=>'propostas','events'=>'histórico'][$key];echo '<nav aria-label="Paginação '.$e($label).'"><p>Página '.$e($p['page']).' de '.$e($p['last_page']).' · '.$e($p['total']).' registos</p>';foreach([-1=>'Anterior',1=>'Seguinte'] as $delta=>$label){$target=$p['page']+$delta;if($target<1||$target>$p['last_page'])continue;$query=array_replace($data['listing_query'],[$param=>$target]);if($key==='documents'){$query['proposal_page']=1;$query['event_page']=1;}$url=($settings['page_url']??'/supplier-payables').'?'.http_build_query($query,'','&',PHP_QUERY_RFC3986);echo '<a href="'.$e($url).'">'.$label.'</a> ';}echo '</nav>';}; ?>
<?php if(isset($data['pagination'])): ?><section><h2>Filtrar documentos</h2><form method="get" action="<?= $e($settings['page_url']??'/supplier-payables') ?>"><?php foreach(['invoice','source'] as $focus): if(isset($data['listing_query'][$focus])): ?><input type="hidden" name="<?= $focus ?>" value="<?= $e($data['listing_query'][$focus]) ?>"><?php endif; endforeach ?><label>Número ou fornecedor<input name="q" maxlength="100" value="<?= $e($data['listing_query']['q']) ?>"></label><label>Reconhecimento<select name="recognition"><?php foreach(['all'=>'Todos','recognised'=>'Reconhecidos','unrecognised'=>'Por reconhecer'] as $v=>$label): ?><option value="<?= $e($v) ?>" <?= $data['listing_query']['recognition']===$v?'selected':'' ?>><?= $e($label) ?></option><?php endforeach ?></select></label><label>Documentos por página<select name="per_page"><?php foreach([25,100] as $size): ?><option value="<?= $size ?>" <?= $data['listing_query']['per_page']===$size?'selected':'' ?>><?= $size ?></option><?php endforeach ?></select></label><button>Aplicar filtros</button></form><p>Obrigações, propostas e eventos abaixo pertencem aos documentos desta página. A pesquisa e o estado são aplicados antes da paginação.</p></section><?php endif ?>
<?php if($data['payments_available']??false): ?><p><a href="<?= $e('/supplier-payments'.(isset($data['listing_query']['source'])?'?'.http_build_query(['source'=>$data['listing_query']['source']],'','&',PHP_QUERY_RFC3986):'')) ?>">Pagamentos e afectações sintéticas — sem execução bancária</a></p><?php endif ?><p id="status" role="status"></p><p id="error" role="alert"></p><button id="reload">Actualizar consulta</button>
<section><h2>Facturas de origem</h2><div class="scroll"><table><thead><tr><th>Documento</th><th>Fornecedor</th><th>Base</th><th>Imposto documental</th><th>Total</th><th>Conferência / revisão</th></tr></thead><tbody>
<?php foreach($data['documents'] as $d): ?><tr><td><?= $e($d['number']??'Indeterminado') ?></td><td><?= $e($d['supplier_name']??'Indeterminado') ?></td><td><?= isset($d['subtotal_minor'])?$money($d['subtotal_minor']):'Indeterminado' ?></td><td><?= isset($d['tax_minor'])?$money($d['tax_minor']):'Indeterminado' ?></td><td><?= isset($d['total_minor'])?$money($d['total_minor']):'Indeterminado' ?></td><td><?= $e($d['status']) ?><details><summary>Origem e pendências</summary><pre><?= $e(json_encode($d,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)) ?></pre></details></td></tr><?php endforeach ?>
<?php foreach($data['documents'] as $d): ?><tr><td><?= $e($d['number']??'Indeterminado') ?><?php if(($settings['document_filter']??null)==='source'): ?><p><a href="<?= $e(($settings['page_url']??'/supplier-payables').'?'.http_build_query(['source'=>$d['local_id']],'','&',PHP_QUERY_RFC3986)) ?>">Conferir evidências desta origem</a></p><?php endif ?></td><td><?= $e($d['supplier_name']??'Indeterminado') ?></td><td><?= isset($d['subtotal_minor'])?$money($d['subtotal_minor']):'Indeterminado' ?></td><td><?= isset($d['tax_minor'])?$money($d['tax_minor']):'Indeterminado' ?></td><td><?= isset($d['total_minor'])?$money($d['total_minor']):'Indeterminado' ?></td><td><?= $e($d['status']) ?><details><summary>Origem e pendências</summary><pre><?= $e(json_encode($d,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)) ?></pre></details></td></tr><?php endforeach ?>
</tbody></table></div><?php $pageNav('documents'); ?></section>
<?php if($data['permissions']['prepare']): ?><section><h2>Preparar a obrigação</h2><form data-action="prepare"><label>Factura existente<select name="id"><?php foreach($data['documents'] as $d): ?><option value="<?= $e($d['local_id']) ?>"><?= $e($d['number']) ?> · <?= $money($d['total_minor']) ?></option><?php endforeach ?></select></label><label>Fundamento<textarea name="reason" required minlength="3" maxlength="2000">Proposta sintética com base na factura conferida.</textarea></label><p>Sem edição de fornecedor, valor ou imposto. Vencimento mantém-se desconhecido neste recorte.</p><button>Preparar da origem</button></form></section><?php endif ?>
<?php $historyDocuments=array_filter($data['documents'],fn($d)=>($d['payment_count']??0)>0); if(($data['history_import_available']??false)&&$data['permissions']['prepare']&&$historyDocuments): ?><section><h2>Incorporar factura com pagamentos anteriores</h2><p>Preserva o principal integral e liga os pagamentos originais uma única vez. Só os confirmados reduzem o saldo; pendentes e rejeitados mantêm o seu estado. Não executa nem confirma pagamentos.</p><form data-action="prepare_history"><label>Factura com histórico<select name="id"><?php foreach($historyDocuments as $d): ?><option value="<?= $e($d['local_id']) ?>"><?= $e($d['number']) ?> · <?= $money($d['total_minor']) ?></option><?php endforeach ?></select></label><label>Fundamento<textarea name="reason" required minlength="3" maxlength="2000">Passagem de responsabilidade com preservação dos pagamentos históricos.</textarea></label><button>Preparar passagem com histórico</button></form></section><?php endif ?>
@@ -21,6 +23,7 @@
<p>Sem execução de novos pagamentos, cobrança, efeitos de stock, novo consumo orçamental ou lançamento automático. A passagem de histórico não confirma pagamentos pendentes.</p>
</main><script>
const settings=<?= json_encode($settings,JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT|JSON_THROW_ON_ERROR) ?>;
if(settings.ledger_flow_url){const button=document.createElement('button');button.textContent='Conferir percurso até ao lançamento';document.querySelector('#status').before(button);button.onclick=async()=>{button.disabled=true;try{const r=await fetch('/api/context',{method:'POST',credentials:'same-origin',headers:{Accept:'application/json','Content-Type':'application/json','X-CSRF-TOKEN':settings.csrf},body:JSON.stringify({...settings.ledger_flow_scope,module_id:'accounting'})});const data=await r.json();if(!r.ok)throw Error(data.message||'Módulo indisponível.');location.assign(settings.ledger_flow_url);}catch(e){document.querySelector('#error').textContent=e.message;button.disabled=false;}};}
document.querySelector('#reload').onclick=()=>location.reload();
for(const form of document.querySelectorAll('form[data-action]'))form.onsubmit=async event=>{event.preventDefault();const body=Object.fromEntries(new FormData(form));body.id=Number(body.id);const signature=JSON.stringify(body);if(form.dataset.signature!==signature){form.dataset.signature=signature;form.dataset.key=crypto.randomUUID();}const button=form.querySelector('button');button.disabled=true;document.querySelector('#error').textContent='';try{const r=await fetch(settings.api+'/'+form.dataset.action,{method:'POST',credentials:'same-origin',headers:{Accept:'application/json','Content-Type':'application/json','X-CSRF-TOKEN':settings.csrf,'X-SGF-Context':settings.context??'','Idempotency-Key':form.dataset.key},body:JSON.stringify(body)});const d=await r.json();if(!r.ok)throw new Error(d.message??Object.values(d.errors??{}).flat().join(' '));if(d.state==='conflict'){document.querySelector('#error').textContent=d.reason+' Consulte o histórico.';return;}location.reload();}catch(e){document.querySelector('#error').textContent=e.message;}finally{button.disabled=false;}};
</script></body></html>