import React,{useEffect,useMemo,useState,useRef} from 'react'; import {statementDownload} from './statement-download.js'; import {WorkshopStockOperation} from './workshop-stock-operation.js'; import {minorAmount} from './ledger-report-view.js'; import {checkedStatements,statementCategories,statementStates} from './internal-statements-view.js'; const qs=q=>new URLSearchParams(Object.entries(q).filter(([,v])=>v!==''&&v!==null)).toString(); export function StatementGroups({summary}){const cards=(currency,c,categories)=>
{categories.map(category=>{const g=c.groups[category];return
{statementCategories[category]} · {g.accounts} conta(s)
{g.presentation_minor===null?'Sem conta observada':minorAmount(g.presentation_minor)+' '+currency}
{g.inverted_accounts>0&&{g.inverted_accounts} saldo(s) invertido(s), sem usar valor absoluto.}
;})}
;return

Rubricas internas e pendências

Capital próprio: indeterminado. Modelo legal: não. As rubricas sem conta observada são distintas de um saldo zero observado.

{!Object.keys(summary.currencies).length&&

Sem contas com movimentos na fonte deste intervalo. Não se fabricam rubricas com saldo zero.

}{Object.entries(summary.currencies).map(([currency,c])=>

{currency}

Balanço interno parcial

{cards(currency,c,['asset','liability'])}

Capital e equação patrimonial completa: indeterminados. Balanço completo: não.

Resultados internos parciais

{cards(currency,c,['income','expense'])}

Subtotal das rubricas de resultados classificadas: {minorAmount(c.known_result_subtotal_minor)} {currency}. Não é resultado da empresa.

Resultado completo: {c.complete_result_minor===null?'Indeterminado por classificação pendente':minorAmount(c.complete_result_minor)+' '+currency+' no recorte QA'}.

Contas por classificar

{cards(currency,c,['unclassified'])}

{c.unclassified_accounts} conta(s) por classificar; o saldo conserva o sinal original.

)}
;} export function StatementLines({rows,dates}){return
{rows.map(r=>{['opening_net_minor','debit_minor','credit_minor','closing_net_minor'].map(k=>)})}
Conta / rubricaAbertura líquidaDébitos do períodoCréditos do períodoFecho líquido originalOrigem
{r.code}{r.label} · {statementCategories[r.category]}{minorAmount(String(r[k]))} {r.currency}Abrir razão desta conta e datasConferir diário e origensID da conta:{r.account_id}
;} export default function InternalStatementsWorkspace({api,context}){ const[query,setQuery]=useState({view:'history',page:1,per_page:25}),[data,setData]=useState(null),[form,setForm]=useState(null),[preview,setPreview]=useState(null),[busy,setBusy]=useState(false),[error,setError]=useState(''),[notice,setNotice]=useState(''),[accounts,setAccounts]=useState(null),[optionQuery,setOptionQuery]=useState({view:'accounts',page:1,per_page:25,search:''}),[search,setSearch]=useState(''),[accountId,setAccountId]=useState(''),[category,setCategory]=useState(''),[receiptKey,setReceiptKey]=useState(''),[receipt,setReceipt]=useState(null); const mounted=useRef(true),generation=useRef(context.token);generation.current=context.token;useEffect(()=>{mounted.current=true;return()=>{mounted.current=false;};},[context.token]); const command=useMemo(()=>new WorkshopStockOperation(api),[context.token]),locked=busy||command.uncertain; useEffect(()=>{let active=true;setData(null);setBusy(true);setError('');api('/api/general-ledger/statements?'+qs(query)).then(r=>{if(active)setData(checkedStatements(r,query));}).catch(e=>{if(active)setError(e.message);}).finally(()=>{if(active)setBusy(false);});return()=>{active=false;};},[context.token,query]); useEffect(()=>{if(form?.kind!=='mapping'||form.action!=='prepare')return;let active=true;setAccounts(null);api('/api/general-ledger/statements?'+qs(optionQuery)).then(r=>{if(active)setAccounts(checkedStatements(r,optionQuery).records);}).catch(e=>{if(active)setError(e.message);});return()=>{active=false;};},[context.token,optionQuery,form?.kind,form?.action]); function start(kind,action){setPreview(null);setError('');setReceipt(null);const latest=kind==='mapping'?data.mapping_latest:data.result_latest;setForm({kind,action,version:latest?.version??0,reason:'',data:action==='review'?{decision:''}:kind==='mapping'?{entries:(data.mapping_approved?.data.entries??[]).map(e=>({account_id:e.account_id,category:e.category})),evidence:''}:{mapping_id:'',from_on:'',to_on:'',per_page:25,evidence:''}});setAccountId('');setCategory('');} function addEntry(){const id=Number(accountId);if(!Number.isSafeInteger(id)||!category||!accounts?.data.some(a=>a.id===id))return;setForm(f=>({...f,data:{...f.data,entries:[...f.data.entries.filter(e=>e.account_id!==id),{account_id:id,category}]}}));setAccountId('');setCategory('');} async function prepare(e){e.preventDefault();setBusy(true);setError('');try{const input=structuredClone(form);if(input.kind==='result'&&input.action==='prepare'){input.data.mapping_id=Number(input.data.mapping_id);input.data.per_page=Number(input.data.per_page);}const key=crypto.randomUUID(),out=await api('/api/general-ledger/statements/preview','POST',input,undefined,key);setPreview({...out,key});}catch(e){setError(e.message);}finally{setBusy(false);}} async function confirm(retry=false){setBusy(true);setError('');try{const out=retry?await command.retry():await command.send('/api/general-ledger/statements/confirm',{preview:preview.preview},preview.key);setReceiptKey(preview.key);setNotice(out.kind+' v'+out.version+' · '+statementStates[out.state]+(out.replayed?' · recibo recuperado':''));setForm(null);setPreview(null);setQuery({view:'history',page:1,per_page:query.per_page});}catch(e){setError(e.message);if(!command.uncertain)setPreview(null);}finally{setBusy(false);}} async function recover(){setBusy(true);setError('');try{setReceipt(await api('/api/general-ledger/statements/receipts/'+encodeURIComponent(receiptKey)));}catch(e){setError(e.message);}finally{setBusy(false);}} async function download(){const token=context.token;setBusy(true);setError('');try{const result=await statementDownload({id:query.version_id,token,sha256:data.snapshot.csv_sha256,isCurrent:()=>mounted.current&&generation.current===token});const url=URL.createObjectURL(new Blob([result.bytes],{type:'text/csv;charset=utf-8'}));const a=document.createElement('a');a.href=url;a.download='sgf-demonstracoes-internas-QA.csv';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);setNotice('CSV congelado descarregado e SHA verificado.');}catch(e){if(mounted.current&&generation.current===token)setError(e.message);}finally{if(mounted.current&&generation.current===token)setBusy(false);}} const kindLabel=k=>k==='mapping'?'Correspondências':'Resultado interno'; return

CONTABILIDADE

Demonstrações internas parciais QA

Balanço e resultados por correspondências explícitas revistas. Sem classificação automática de contas, capital fabricado ou modelo legal. Os lançamentos originais não são alterados.

{error&&

{error}

}{notice&&

{notice}

} {data&&<>
{data.capabilities.prepare&&<>}{data.capabilities.review&&['mapping','result'].map(kind=>{const latest=kind==='mapping'?data.mapping_latest:data.result_latest;return latest?.state==='prepared'&&;})}
{query.view==='result'&&data.snapshot?<>

Resultado conservado da preparação v{data.snapshot.result_version}, {data.snapshot.frozen_at}, de{data.snapshot.summary.query.from_on} até{data.snapshot.summary.query.to_on}; correspondências v{data.snapshot.summary.mapping_version}. SHA:{data.snapshot.csv_sha256}.

{data.current_source_matches?'A fonte actual confere com este resultado conservado.':'A fonte/correspondência actual difere; este resultado permanece histórico e não foi recalculado.'}

:
{data.records.data.map(r=>)}
Tipo / versão / estadoAutor / data / fundamentoDados conservados
{kindLabel(r.kind)} v{r.version}{statementStates[r.state]}QA{r.host_actor_id} · {r.created_at}{r.reason}{r.kind==='mapping'?
Ver todas as correspondências desta versão
    {r.data.entries.map(e=>
  • {e.account.code} · {statementCategories[e.category]}
  • )}
:<>

{r.data.summary.query.from_on} a{r.data.summary.query.to_on} · {r.data.summary.accounts} contas da fonte · {r.data.evidence}

{r.state==='approved'&&}}
}

Página{data.records.current_page} de{data.records.last_page} · {data.records.total} linha(s). Totais do resultado conservado abrangem todas as páginas da fonte; a tabela é paginada.

} {form&&

{form.action==='prepare'?'Preparação':'Revisão distinta'} · {kindLabel(form.kind)} · versão observada{form.version}

{form.action==='review'?<>

Confira os dados completos da preparação no histórico antes de escolher a decisão. A prévia apresenta os mesmos dados e o impacto.

:<>{form.kind==='mapping'?<>

Não há rubrica automática para conta nova. As escolhas da revisão anterior, quando existem, são apenas a base explícita desta correcção.

{accounts&&<>}
    {form.data.entries.map(e=>
  • Conta#{e.account_id} · {statementCategories[e.category]}
  • )}
:<>{['from_on','to_on'].map(k=>)}}