diff --git a/AGENTS.md b/AGENTS.md index b23a9a4..3a1bf36 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,30 @@ # SGF Tempo — instruções locais +- Entrega11/11: facturação multilinha QA na mesma8085, docs/SGF_MULTI_LINE_INVOICE_DELIVERY.md/provaJSON. Editor1–25serviços/acrescido-incluído/cálculo comum vinculado/revisão temporal distinta/PDF, doisTEST-MULTI-2026/1–2 de1160/base1000/imposto160, doisrecebíveis abertos total2320, doisGL29→31/24contas intactas, mesmo writer. Zero novosdireitos/grants/users/contas. PropriedadeComercial preservada, dependências financeiras qualificadas; APIComercial exige móduloComercial.143dirigidas+122preservação/96conjuntos/trêsbindings; concorrência5,09s empresaFORUPDATE/transactionid503→409apóslibertação/replay200/umdocumento/contador1, sóregressão(fixturesemrecebívelGLidentificada). Falhasparciais rascunho/emissão rollbackrecuperaçãoúnica.22comandos8085+2resultado guardadosprivados: NÃO repetir. Resultado parcialv5/v6mesmamappingv4/31GL/subtotal2286,21/dezporclassificar; antigos19/27e23/29CSVidênticos. Períodoreadonly44factos/0pendentes/débitoscréditosiguais/aberto. Caixa950/activo1200-100-1100/RH/Stock/IVA4/seisregistos/cacheintactos. UI21simuladas/PDF25linhas5páginasinspeccionadasnãoénavegador; vigência/datacorrente/zeroisento/misturasdescontos/POS/Excel/fiscalidadelegalnãoincluídos. Sem demos/SGC/publicação/candidata/checkpoint/aceitação; matrizv30incompleta, visual/carga/implantação separados. Propostasanterioresporautorizar sãohistóricas. + + +- Autorização expressa11/11: «Autorizar a facturação multilinha QA proposta», docs/SGF_MULTI_LINE_INVOICE_SCOPE.md/JSON. Implementar bloco completo UI/cálculo comum→emissãoQA/PDF→recebíveis→diário, dois documentos1160 cada/acrescido e incluído/regraQA16% existente, nova sérieTEST-MULTI2026 pelos mecanismos actuais;2recebíveis total2320/2GL29→31/24contas intactas/nenhum direito ou acesso novo. Direitos22nomes existentes SELECT-only verificados. Retira apenas a exclusão de emissão/dívida/GL deste recorte seleccionado, sem fiscalidade/operações reais/AT/envio/pagamentos/Stock/Caixa/RH/reabertura/publicação/checkpoint. Mesma8085/regressão, seisregistos/cache/todosartefactos anteriores preservados. Não pedir por campo/etapa; propostas anteriores por autorizar substituídas. Antes de instalar, provar todo bloco e preservar ramos antigos. + + +- Próxima fronteira concreta11/11: docs/SGF_MULTI_LINE_INVOICE_SCOPE.md/JSON, POR AUTORIZAR/NÃO IMPLEMENTADA. ServiceCalculation/e emissão ainda bloqueiam váriaslinhas e preço incluído; âmbito anterior SGF_MULTI_LINE_INCLUDED_SCOPE autoriza apenas cálculo e exclui expressamente factura/dívida/GL. Proposta agrupada editor/cabeçalho/grelha/totais/cálculo→emissão QA/PDF→recebíveis→diário;2documentos1160 cada(400+600 acrescido,464+696 incluído), regraQA16% existente/vigente, série novaTEST-MULTI2026,2recebíveis total2320,2GL novos29→31/mantém24contas/sem dinheiro. Nenhum direito/concessão novo; SELECT-only22nomes já presentes nos dois livros. Usar cliente/serviços QA existentes autorizados sem seisregistos; novaspreparações pelos writersactuais. Não retirar guardas nem emitir/contabilizar antes da decisão. Sem novasdemos/SGC/PUBLIC/operações reais/AT/envio/publicação/Stock/Caixa/RH/fecho/checkpoint. Cadaartefacto antigo permanece imutável, visual/carga/implantação separados. + + +- Entrega11/11: SGF-FIXED-ASSETS-INTEGRATED-QA-v1 na mesma8085, docs/SGF_FIXED_ASSETS_DELIVERY.md/prova JSON. Cadastro/plano/quota revistos, novoQA-ACTIVO-01 custo1200/residual0/vida12/admissão01Oct/quota31Oct100/líquido1100. Seis versões/recibos administrativos e doisGL novos(27→29), cinco contas(19→24); originais íntegros. Extensão tipada do MESMO writer/guardas SQL/pins autorizada, sem segundo diário. Correcção100→120/par atómico e abate120+1080; abate100+1100/fecho do recorte com harness financeiro isolado em rollback na regressão. Concorrênciaempresa FORUPDATE/transactionid5,24s503→409 pós-libertação/replay200/sem duplicação.141dirigidas+87preservação/78conjuntos/3bindings,13aritméticas reutilizadas. Cinco concessões QA por livro10total, nenhum perfil/user/PGrole/PUBLIC novo. Quatro comandos de correspondência/resultado novo:24contas catálogo/23com movimento/29GL/dez por classificar, corte01Jan–11Nov/subtotalconhecido286,21; antigo19/27CSV idêntico histórico.8085conferência read-only40factos/0pendentes/balanced; período aberto. Progresso/chaves privados assets-http-progress-private.json e assets-statements-http-progress-private.json: NÃO repetir12+4comandos nem origens antigas. Fonte financeira estável separada da revisão administrativa que invalida capturas pendentes. SSRlegado não terminou/não contado; novo15UI+HTTP/buildSHA aprovados, sem revisão visual. Matrizv29produto incompleto; legal/carga/implantação separados. Seis registos/cache/Caixa950/RH/Stock preservados, sem demos/SGC/publicação/candidata/checkpoint/aceitação. + + +- Referências visuais11/11 recebidas:15capturas em DOCs/Ecrans, docs/SGF_BILLING_SCREEN_REFERENCES.md. Primeiras12 relevantes(menu/listas/cadastros/grelha facturação/totais);13–15 aplicações alheias e duplicados não são requisitos adicionais. Aproveitar campos/percurso, não transcrever dados pessoais/NUIT/contactos/valores nem adoptar taxas comolei. Preservar menu horizontal/dropdown, fontes/estados/direitos; não inferir POS/Excel/campos novos implementados. É orientação durante o ciclo de activos, não cancelamento/substituição da tarefa nem aceitação visual do SGF. + +- Autorização expressa11/11: «Autorizar o ciclo integrado de activos QA», docs/SGF_FIXED_ASSETS_SCOPE.md/JSON. Implementar todo o ciclo agrupado com extensão tipada do MESMO núcleo/pins, não segundo writer.3direitos/5grants por livro10total aos15/16/17existentes,5contas artificiais por livro(19→24),2GL novos8085(27→29) mantendooriginais,4comandos de correspondências/resultado parcial novo preservandoold19/27snapshot. Cadastro/custódia/plano/depreciação/relatórios/conferência; abate/correcção técnicaquota e fecho rollback sóregressão. QAactivo1200/residual0/vida12/Outubro100/líquido1100. Sem novasdemos/SGC/contas reais/dinheiro/dívida/submissão/fechoreal/publicação/checkpoint; preservar6registos/cache/Caixa950/HP16/RM6/trabalhoalheio. Indicações anteriores por autorizar são históricas; não pedir por campo/operação/teste. + +- Continuação11/11: painel fiscal reconciliado viaFiscalCoveragev3, docs/SGF_COVERAGE_RECONCILIATION.md. RH/cálculosinternosQA reconhecidos, legalIRPS-INSS continua pendente, demonstraçõesparciais existentes/activos ausentes explícitos.7metadata+8HTTP,66conjuntos/3bindings intactos/HP16/RM6/GL27, zero grants/comandos negócios/cache/demos. Próxima decisão concreta docs/SGF_FIXED_ASSETS_SCOPE.md/JSON, **por autorizar/não implementada**: activosQA1200/residual0/vida12, quotaOutubro100/líquido1100, admissão01Oct/quota31Oct no mesmo núcleo/períodoQA2026aberto.3direitos fixed_assets.view15/16/17 prepare15 review16:5grants por livro10total, sem novas identidades;5contas artificiais/livro(19→24),2GL novos8085(27→29) preservando27originais, quatro comandos adicionais de correspondência/resultado parcial com antigo snapshot preservado/histórico. Extensão TIPADA do escritor/pins originais exige esta autorização explícita, não contornar com segundo writer/INSERT. Custódia/cadastro/plano/revisão/relatórios; abate/correcção técnica/fecho rollback na regressão. Nova origemQA por classificar; sem capital/dívida presumidos ou duplicaçãoStock/custos. AssetDepreciation puro13checks ainda não instalado; direitos ausentes SELECT-only ambos livros. Não conceder/aplicar antes da decisão. Preservarseis registos/cache/Caixa950/fontes; sem demos/SGC/pagamentos/fiscalidadelegal/publicação/aceitação/checkpoint, visual/carga/implantação separados. Matrizv28incompleta. + + +- Resumos legíveis RH entregues11/11, docs/SGF_HUMAN_RESOURCES_READABLE_DELIVERY.md, mesma8085 RH→pessoal→versão revista→Descarregar resumo legível desta versão. Derivação somente da versão congelada, CSV PT/UTF8BOM, formato v1/SHA próprio e referência ao original; originais preservados. Auth/contexto/área revalidados, tokens só header, cliente valida origem/formato/digest/contexto antes de guardar. Sem novos direitos/escritas de negócio8085/schema/perfis.58verificações dirigidas(17reg+11descarga+3handler simulado+27HTTP),66conjuntos+3bindings intactos/HP16/RM6/GL27/seis registos/cache preservados. Auditoria FUTURA de pessoal corrigida para contracts/attendance/leave e area; etiqueta anterior herdada de remunerações nos16eventos8085 não foi reescrita, histórico pleno continuaHPversions. Não repetir operações para testar rótulos: um evento de contrato apenas na regressão e revertido. Matrizv27incompleta; visual/carga/legal/implantação separados; sem demos/SGC/publicação/candidata/aceitação/checkpoint. + + +- Contratos/assiduidade/férias entregues11/11, docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md, mesma8085 RH autónomo→Contratos, assiduidade e férias.9direitos/15grants por livro existente30total, mesmos3perfis/pins sem novas identidades. QA-CONTRATO-01/QA-COLABORADOR-01, planos09/10Nov480min, declaração09=480/10explicitamentevaziaapósgozo, orçamento fictício960/reserva→gozo480/disponível480.6origens16versões/recibos; não repetir comandos (hp-http-progress-private.json privado).233dirigidas+56compat afectada+78preservação, concorrência5,09s503 hostempresa FOR UPDATE/transactionid pg_blocking_pids→409 apóslibertação/nova preparação409 por insuficiência/replay200/um pedido aprovado.11versões suporte só na regressão.62conjuntos originais egrants antigos iguais+15exactos,3bindings/GL27/contas19/Caixa950/RMsixversões-recibos-CSV intactos/seis registos/cache.27fixturespaginação+26saldos+foreignunit/falha parcial/contrato salarial/recusa revertidos. Saldo actual separado do resultado congelado, leitura conjunta4SQL25/100 sem medição carga; recusa de proposta inviável permite NOVA conferência explícita com fontes/saldoactuais, nunca executa proposta. Ligação contratual opcional só em novaspreparações RM, prova regressão, sem retroatribuir contrato nem gerar novo salário8085. UI/MIME/SHA confirmados sem navegador/visual; flags root off/privadaQA on, sem demos/SGC/publicação/candidata/checkpoint/aceitação. Matrizv26produto amplo incompleto: legislação laboral/fiscal, IRPS/INSS/modelos/turnos gerais/benefícios/efeitos financeiros ainda pendentes; visual/carga/armazenamento/implantação separados. Propostas por autorizar anteriores são históricas. + + - Autorização expressa11/11: «Autorizar o bloco e as30 concessões QA apresentadas», docs/SGF_HUMAN_RESOURCES_PERSONNEL_SCOPE.md/JSON. Implementar todo o bloco contratos/assiduidade/férias/conferência e ligação contratual explícita às novas preparações salariais apenas em regressão.9direitos/15grants por livro existente/30total, mesmos3perfisRH, sem novas identidades/pins. Preservar6versões/recibos salariais8085,seis registos/cache/GL27/contas19 e exemplos. Sem demos/SGC/efeitos financeiros/modelos legais/publicação/checkpoint. Indicações anteriores por autorizar substituídas; não pedir por campo/operação/teste. - Pedido11/11 para contratos/assiduidade/férias no RH autónomo. Âmbito completo preparado `docs/SGF_HUMAN_RESOURCES_PERSONNEL_SCOPE.md`/JSON: contrato QA10000, planos09/10Nov480min, presença09, orçamento fictício960min/pedido-gozo10de480/saldo480, conferência parcial por pessoa e ligação contratual explícita só em futuras preparações salariais (prova regressão), preservando6versões/recibos salariais8085. Novos9direitos human_resources.contracts/attendance/leave × view/prepare/review; consultas3perfis, preparehr_prepare/reviewhr_review,15grants/livro30total nos perfis existentes, **por autorizar/não concedidos**; SELECT-only confirma ausência nos dois livros. Zero novas identidades/roles/pins alterados. PersonnelTime puro local22checks intervalos/desconhecidos/conflitos/saldo; não instalado, sem writers/schema/dados alterados. Não declarar funções novas entregues. Regra de novos poderes requer decisão antes de conceder; pedido de implementar não contém lista nova aprovada. Preservar8085/seis registos/cache/provas/GL27/contas19/Caixa950/fontes; sem demos/SGC/dívida/pagamento/GL/salário/contrato real/modelos legais/publicação/checkpoint. Não presumir férias legais960min nem classificar faltas automaticamente. Após aprovação, implementar todo bloco sem pedir por campo/rubrica/teste. diff --git a/app/Modules/Accounting/AssetDepreciation.php b/app/Modules/Accounting/AssetDepreciation.php new file mode 100644 index 0000000..2ffb935 --- /dev/null +++ b/app/Modules/Accounting/AssetDepreciation.php @@ -0,0 +1,13 @@ +=1&&$month<=600,422);foreach([$cost,$residual]as$value)if($value!==null)abort_unless(preg_match('/^[0-9]{1,12}\.[0-9]{2}$/D',$value)===1,422,'Valor documental deve ter duas casas.');if($months!==null)abort_unless($months>=1&&$months<=600&&$month<=$months,422); + if($cost===null||$residual===null||$months===null)return['complete'=>false,'quota_cents'=>null,'accumulated_cents'=>null,'carrying_cents'=>null,'exact_quota_cents'=>null,'difference_cents'=>null,'legal_tax_treatment'=>null]; + $c=BigInteger::of(str_replace('.','',$cost));$r=BigInteger::of(str_replace('.','',$residual));abort_if($r->isGreaterThan($c),422,'Residual excede o custo.');$base=$c->minus($r);$standard=$base->quotient($months);$quota=$month===$months?$base->minus($standard->multipliedBy($months-1)):$standard;$accumulated=$month===$months?$base:$standard->multipliedBy($month);$exact=BigRational::of($base)->dividedBy($months)->simplified();return['complete'=>true,'cost_cents'=>(string)$c,'residual_cents'=>(string)$r,'life_months'=>$months,'month'=>$month,'quota_cents'=>(string)$quota,'accumulated_cents'=>(string)$accumulated,'carrying_cents'=>(string)$c->minus($accumulated),'exact_quota_cents'=>(string)$exact,'difference_cents'=>(string)$exact->minus($quota)->simplified(),'policy'=>'QA-straight-line-floor-months-final-residual-adjustment-v1','legal_tax_treatment'=>null]; + } +} diff --git a/app/Modules/Accounting/ComposedLedger.php b/app/Modules/Accounting/ComposedLedger.php index e82ce06..0b0680f 100644 --- a/app/Modules/Accounting/ComposedLedger.php +++ b/app/Modules/Accounting/ComposedLedger.php @@ -61,16 +61,17 @@ final class ComposedLedger $authorize('view'); $isolation=fn()=>app(ComposedMaterialIsolation::class)->check($host,$native,$ledgerScope); $stock=new StockValuation($native,$ledgerScope,$actor->id,$person,$authorize,fn()=>$authorize('view'),$isolation); - $additional=function($kind,$id)use($host,$hostActor,$stock,$isolation){ + $additional=function($kind,$id)use($host,$hostActor,$stock,$isolation,$native,$ledgerScope){ if(str_starts_with($kind,'stock.'))return $stock->source($kind,$id); if(str_starts_with($kind,'workshop.')||str_starts_with($kind,'industry.'))$isolation(); - return $this->withHost($host,$hostActor,function()use($hostActor,$kind,$id){ + return $this->withHost($host,$hostActor,function()use($hostActor,$kind,$id,$native,$ledgerScope){ + if(str_starts_with($kind,'assets.'))return app(FixedAssetLedgerSource::class)->source($hostActor,$kind,$id,$native,$ledgerScope); if(str_starts_with($kind,'cash.'))return app(CashLedgerSource::class)->source($hostActor,$kind,$id);if(str_starts_with($kind,'workshop.'))return $kind==='workshop.direct_cost.validated'?app(CostLedgerSource::class)->source($hostActor,$id):app(CostLedgerSource::class)->correctionSource($hostActor,$kind,$id); if(str_starts_with($kind,'industry.'))return $kind==='industry.cost.validated'?app(IndustrialLedgerSource::class)->source($hostActor,$id):app(IndustrialLedgerSource::class)->correctionSource($hostActor,$kind,$id); return app(SalesLedgerSource::class)->source($hostActor,$kind,$id); }); }; - $period=function($p)use($host,$hostActor,$stock){$extra=$this->withHost($host,$hostActor,function()use($p,$hostActor){$facts=0;$pending=0;$h=hash_init('sha256');foreach([SalesLedgerSource::class,CostLedgerSource::class,IndustrialLedgerSource::class,CashLedgerSource::class] as $class){$v=app($class)->period($hostActor,$p);$facts+=$v['facts'];$pending+=$v['pending'];hash_update($h,$v['hash']);}return ['facts'=>$facts,'pending'=>$pending,'hash'=>hash_final($h)];});$physical=$stock->period($p);return ['facts'=>$extra['facts']+$physical['facts'],'pending'=>$extra['pending']+$physical['pending'],'hash'=>hash('sha256',$extra['hash'].$physical['hash'])];}; + $period=function($p)use($host,$hostActor,$stock,$native,$ledgerScope){$extra=$this->withHost($host,$hostActor,function()use($p,$hostActor,$native,$ledgerScope){$facts=0;$pending=0;$h=hash_init('sha256');foreach([SalesLedgerSource::class,CostLedgerSource::class,IndustrialLedgerSource::class,CashLedgerSource::class] as $class){$v=app($class)->period($hostActor,$p);$facts+=$v['facts'];$pending+=$v['pending'];hash_update($h,$v['hash']);}if(config('fixed_assets.enabled')===true){$v=app(FixedAssetLedgerSource::class)->period($hostActor,$p,$native,$ledgerScope);$facts+=$v['facts'];$pending+=$v['pending'];hash_update($h,$v['hash']);}return ['facts'=>$facts,'pending'=>$pending,'hash'=>hash_final($h)];});$physical=$stock->period($p);return ['facts'=>$extra['facts']+$physical['facts'],'pending'=>$extra['pending']+$physical['pending'],'hash'=>hash('sha256',$extra['hash'].$physical['hash'])];}; $engine=new PayableJournal($native,$ledgerScope,$actor->id,$person,$authorize,fn()=>$authorize('view'),fn(int $id)=>$this->source($actor,$binding,$financialScope,$id),null,$additional,$period,$financialScope); return $work($engine,$actor,$binding,$origin+['sgf_host_user'=>$hostUser,'sgf_host_scope'=>$scope,'binding_revision'=>$revision],$ledgerScope,$stock,fn(\Closure $read)=>$this->withHost($host,$hostActor,$read)); }); diff --git a/app/Modules/Accounting/ComposedLedgerCandidates.php b/app/Modules/Accounting/ComposedLedgerCandidates.php index 7e0e41d..d10be19 100644 --- a/app/Modules/Accounting/ComposedLedgerCandidates.php +++ b/app/Modules/Accounting/ComposedLedgerCandidates.php @@ -16,8 +16,8 @@ final class ComposedLedgerCandidates $append(DB::table('pa_obligations as o')->join('pa_sources as s','s.id','=','o.source_id')->join('invoices as i','i.id','=','s.local_source_id')->where(['o.tenant_id'=>$actor->tenant_id,'o.company_id'=>0,'o.unit_id'=>0])->orderBy('o.id'),'supplier_payable.recognised',['o.id','o.principal_minor','i.number'],fn($o)=>['id'=>$o->id,'label'=>'Compra '.$o->number,'amount'=>$o->principal_minor]); foreach(['ps_allocations'=>'allocation.confirmed','ps_reversals'=>'allocation.reversed'] as $table=>$type)$append(DB::table($table)->where($financial)->orderBy('id'),$type,['id','amount_minor'],fn($o)=>['id'=>$o->id,'label'=>($type==='allocation.confirmed'?'Afectação':'Reversão').' fornecedor #'.$o->id,'amount'=>$o->amount_minor]); $hostRead(function()use($scope,$append){ - $append(ModuleRecords::query('si_invoices')->where($scope)->orderBy('id'),'sales.invoice',['id','series_code','number'],fn($i)=>['id'=>$i->id,'label'=>'Venda '.$i->series_code.'/'.$i->number,'amount'=>null]); - foreach(['allocate'=>'receivable.allocated','reverse_allocation'=>'receivable.reversed','reduction'=>'receivable.reduced','reverse_reduction'=>'receivable.reduction_reversed'] as $event=>$type)$append(DB::table('sr_effects as e')->join('sr_records as r','r.id','=','e.record_id')->where(['r.tenant_id'=>$scope['tenant_id'],'r.company_id'=>$scope['company_id'],'r.unit_id'=>$scope['unit_id']])->whereIn('r.invoice_id',ModuleRecords::query('si_invoices')->select('id'))->where('e.kind',$event)->orderBy('e.id'),$type,['e.id'],fn($e)=>['id'=>$e->id,'label'=>'Afectação/reversão cliente #'.$e->id,'amount'=>null]); + $append(\App\Modules\Services\QualifiedInvoiceSource::query()->where($scope)->orderBy('id'),'sales.invoice',['id','series_code','number'],fn($i)=>['id'=>$i->id,'label'=>'Venda '.$i->series_code.'/'.$i->number,'amount'=>null]); + foreach(['allocate'=>'receivable.allocated','reverse_allocation'=>'receivable.reversed','reduction'=>'receivable.reduced','reverse_reduction'=>'receivable.reduction_reversed'] as $event=>$type)$append(DB::table('sr_effects as e')->join('sr_records as r','r.id','=','e.record_id')->where(['r.tenant_id'=>$scope['tenant_id'],'r.company_id'=>$scope['company_id'],'r.unit_id'=>$scope['unit_id']])->whereIn('r.invoice_id',\App\Modules\Services\QualifiedInvoiceSource::query()->select('id'))->where('e.kind',$event)->orderBy('e.id'),$type,['e.id'],fn($e)=>['id'=>$e->id,'label'=>'Afectação/reversão cliente #'.$e->id,'amount'=>null]); if(config('cash_qa.enabled')===true)foreach(['receipt'=>'cash.receipt_classified','expense'=>'cash.expense_confirmed'] as $kind=>$type)$append(DB::table('cash_events')->where($scope)->where(['action'=>'review','state'=>'approved','kind'=>$kind])->orderBy('id'),$type,['id'],fn($r)=>['id'=>$r->id,'label'=>'Caixa QA #'.$r->id,'amount'=>null]); }); foreach(['gl_cost_sources'=>['order_id','workshop.direct_cost.validated','Custo de oficina'],'gl_industrial_sources'=>['lot_id','industry.cost.validated','Custo industrial']] as $table=>[$column,$type,$label])$append(DB::table($table)->where($scope)->orderBy('id'),$type,[$column],fn($o)=>['id'=>$o->$column,'label'=>$label.' #'.$o->$column,'amount'=>null]); diff --git a/app/Modules/Accounting/ComposedLedgerController.php b/app/Modules/Accounting/ComposedLedgerController.php index 8feb28d..4847959 100644 --- a/app/Modules/Accounting/ComposedLedgerController.php +++ b/app/Modules/Accounting/ComposedLedgerController.php @@ -36,7 +36,7 @@ final class ComposedLedgerController { $d=$r->validate(['page'=>'sometimes|integer|min:1|max:100000','accounts_page'=>'sometimes|integer|min:1|max:100000','accounts_per_page'=>'sometimes|integer|in:25,100','account_q'=>'sometimes|nullable|string|max:80','source_page'=>'sometimes|integer|min:1|max:100000','source_per_page'=>'sometimes|integer|in:25,100','source_kind'=>'sometimes|in:'.implode(',',ComposedLedgerCandidates::KINDS),'period_key'=>'sometimes|string|max:80','account_id'=>'sometimes|nullable|integer|min:1','obligation_id'=>'sometimes|integer|min:1','sales_invoice_id'=>'sometimes|integer|min:1','cost_order_id'=>'sometimes|integer|min:1','industrial_lot_id'=>'sometimes|integer|min:1']);abort_if(array_diff(array_keys($r->query()),array_keys($d)),422);if(($d['account_id']??null)===null)unset($d['account_id']);$rights=$this->rights(); return $ledger->run('view',function($engine,$actor,$binding,$origin,$scope,$stock,$hostRead)use($r,$d,$rights){ - if(isset($d['sales_invoice_id']))abort_unless($hostRead(fn()=>\App\Shared\Context\ModuleRecords::query('si_invoices')->where($scope)->where('id',$d['sales_invoice_id'])->exists()),403,'Documento fora do contexto e módulo.'); + if(isset($d['sales_invoice_id']))abort_unless($hostRead(fn()=>\App\Modules\Services\QualifiedInvoiceSource::query()->where($scope)->where('id',$d['sales_invoice_id'])->exists()),403,'Documento fora do contexto e módulo.'); $data=$engine->listing(array_diff_key($d,['source_page'=>true,'source_per_page'=>true,'source_kind'=>true,'accounts_page'=>true,'accounts_per_page'=>true,'account_q'=>true]));$data['cycle']=$engine->cycleListing((int)($d['page']??1));$data['permissions']=$rights;$data['current_actor']=$actor->id;$data['sgf_scope']=$origin['sgf_host_scope']; $data=array_merge($data,app(ComposedLedgerCandidates::class)->read($d,$actor,$scope,$stock,$hostRead)); $accounts=DB::table('gl_accounts')->where($scope);if(($d['account_q']??'')!==''){$needle='%'.str_replace(['!','%','_'],['!!','!%','!_'],$d['account_q']).'%';$accounts->where(fn($q)=>$q->whereRaw("code ILIKE ? ESCAPE '!'",[$needle])->orWhereRaw("label ILIKE ? ESCAPE '!'",[$needle]));} diff --git a/app/Modules/Accounting/FixedAssetJournalController.php b/app/Modules/Accounting/FixedAssetJournalController.php new file mode 100644 index 0000000..9084689 --- /dev/null +++ b/app/Modules/Accounting/FixedAssetJournalController.php @@ -0,0 +1,28 @@ +'required|in:effect-prepare,validate,post,correct-prepare,correct-validate,correct-post','data'=>'required|array'])->validate();} + private function run(Request $r,string $right,\Closure $fn){$actor=$r->user();app(FixedAssets::class)->scope($actor->id);try{return app(ComposedLedger::class)->run($right,fn($e,$a,$b,$o,$s,$stock,$hostRead)=>$fn($e,DB::connection(),$s,$hostRead));}catch(\DomainException $e){abort(409,$e->getMessage());}catch(\InvalidArgumentException $e){abort(422,$e->getMessage());}} + private function right(string $action):string{return $action==='effect-prepare'?'prepare':str_replace('-','_',$action);} + private function qualified(object $db,array $s,array $d):void{ + $v=$d['data'];if($d['action']==='effect-prepare'){abort_unless(in_array($v['effect_type']??null,['assets.admitted','assets.depreciation','assets.writeoff'],true),422);return;} + if(in_array($d['action'],['validate','post'],true)){$p=$db->table('gl_proposals')->where($s)->where('id',$v['proposal_id']??0)->first();abort_unless($p&&str_starts_with(json_decode($p->snapshot,true)['source']['effect_type'],'assets.'),403);return;} + if($d['action']==='correct-prepare')$id=$v['journal_id']??0;else{$g=$db->table('gl_corrections')->where($s)->where('id',$v['correction_id']??0)->first();abort_unless($g,403);$id=$g->original_journal_id;}$j=$db->table('gl_journals')->where($s)->where('id',$id)->first();abort_unless($j&&json_decode($j->snapshot,true)['source']['effect_type']==='assets.depreciation',403); + } + private function impact(object $db,array $s,array $result):array{ + $ids=[];if(isset($result['proposal_id']))$ids[]=$result['proposal_id'];if(isset($result['correction_id'])){$g=$db->table('gl_corrections')->where($s)->where('id',$result['correction_id'])->first();$ids=[$g->reverse_proposal_id,$g->replacement_proposal_id];} + $snap=[];foreach($ids as$id)$snap[]=json_decode($db->table('gl_proposals')->where($s)->where('id',$id)->value('snapshot'),true);return ['state'=>$result['state'],'snapshots'=>$snap,'journal_count'=>isset($result['journal_id'])?1:(isset($result['reverse_journal_id'])?2:0),'payment_created'=>false]; + } + private function simulate($e,object $db,array $s,array $d,string $key):array{ + $this->qualified($db,$s,$d);$db->beginTransaction();try{$result=$e->command($d['action'],$d['data'],$key);return$this->impact($db,$s,$result);}finally{$db->rollBack();} + } + public function preview(Request $r){abort_if(strlen(json_encode($r->all()))>16384,422);$d=$this->validate($r->all());$key=strtolower((string)$r->header('Idempotency-Key'));abort_unless(preg_match('/^[a-f0-9-]{36}$/D',$key),422);return$this->run($r,$this->right($d['action']),function($e,$db,$s)use($r,$d,$key){$impact=$this->simulate($e,$db,$s,$d,$key);$c=['actor'=>$r->user()->id,'scope'=>$s,'generation'=>$r->session()->get('company_context.token'),'observed'=>time(),'input'=>$d,'key'=>$key,'impact'=>$impact];return response()->json(['preview'=>Crypt::encryptString(Payables::canonical($c)),'impact'=>$impact])->header('Cache-Control','no-store');});} + public function confirm(Request $r){abort_if(array_diff(array_keys($r->all()),['preview']),422);$r->validate(['preview'=>'required|string|max:131072']);try{$c=json_decode(Crypt::decryptString($r->input('preview')),true,64,JSON_THROW_ON_ERROR);}catch(\Throwable){abort(409,'Prévia inválida.');}$d=$this->validate($c['input']??[]);return$this->run($r,$this->right($d['action']),function($e,$db,$s)use($r,$c,$d){abort_unless($c['actor']===$r->user()->id&&$this->hash($c['scope'])===$this->hash($s)&&$c['generation']===$r->session()->get('company_context.token')&&$c['observed']>=time()-600&&$c['observed']<=time(),409,'Pessoa/contexto/prévia alterados.');$this->qualified($db,$s,$d);$old=$e->receipt($c['key']);if($old){abort_unless(hash_equals($old['request_hash'],$this->hash([$d['action'],$d['data']])),409);return response()->json($old['result']+['replayed'=>true])->header('Cache-Control','no-store');}$impact=$this->simulate($e,$db,$s,$d,$c['key']);abort_unless($this->hash($impact)===$this->hash($c['impact']),409,'Impacto ou origem mudou; confira novamente.');$out=$e->command($d['action'],$d['data'],$c['key']);return response()->json($out)->header('Cache-Control','no-store');});} + public function index(Request $r){$v=$r->validate(['asset_key'=>'required|uuid','page'=>'sometimes|integer|min:1|max:100000','per_page'=>'sometimes|integer|in:25,100']);abort_if(array_diff(array_keys($r->query()),array_keys($v)),422);$hostActor=$r->user();return$this->run($r,'view',function($e,$db,$s,$hostRead)use($r,$v,$hostActor){$key=$v['asset_key'];$hostRead(fn()=>app(FixedAssets::class)->approved($s,$key,'asset'));$p=$db->table('gl_proposals')->where($s)->where('snapshot->source->asset_key',$key)->orderByDesc('id')->paginate($v['per_page']??25,['*'],'page',$v['page']??1);$ids=$p->pluck('id');$reviews=$db->table('gl_reviews')->where($s)->whereIn('proposal_id',$ids)->get()->keyBy('proposal_id');$journals=$db->table('gl_journals')->where($s)->whereIn('proposal_id',$ids)->get()->keyBy('proposal_id');$items=[];foreach($p as$row)$items[]=['id'=>$row->id,'revision'=>$row->revision,'prepared_by'=>$row->prepared_by,'snapshot'=>json_decode($row->snapshot,true),'review'=>$reviews->get($row->id),'journal'=>$journals->get($row->id)];$p->setCollection(collect($items));$groups=$db->table('gl_corrections')->where($s)->where(fn($q)=>$q->whereIn('reverse_proposal_id',$ids)->orWhereIn('replacement_proposal_id',$ids))->orderByDesc('id')->get()->map(function($g)use($db,$s){$g->applied=$db->table('gl_correction_results')->where($s)->where('correction_id',$g->id)->exists();$g->approved=$db->table('gl_reviews')->where($s)->whereIn('proposal_id',[$g->reverse_proposal_id,$g->replacement_proposal_id])->where('decision','approved')->count()===2;return$g;});$balance=$hostRead(fn()=>app(FixedAssets::class)->balance($s,$key,$db));return response()->json(['records'=>$p->toArray(),'balance'=>$balance,'corrections'=>$groups])->header('Cache-Control','no-store');});} +} diff --git a/app/Modules/Accounting/FixedAssetLedgerSource.php b/app/Modules/Accounting/FixedAssetLedgerSource.php new file mode 100644 index 0000000..a5615bf --- /dev/null +++ b/app/Modules/Accounting/FixedAssetLedgerSource.php @@ -0,0 +1,41 @@ +scope($actor->id)===$scope,403); + if(in_array($kind,['assets.depreciation.reversal','assets.depreciation.replacement'],true)){ + $j=$native->table('gl_journals')->where($scope)->where('id',$id)->first()??abort(403);$original=json_decode($j->snapshot,true);abort_unless($original['source']['effect_type']==='assets.depreciation',409,'Só a quota original admite esta correcção.'); + $base=$this->source($actor,'assets.depreciation',(int)$original['source']['source_id'],$native,$scope);abort_unless($base['recognition_revision']!==$original['source']['recognition_revision'],409,'Não existe nova quota revista.');return array_replace($base,['effect_type'=>$kind,'source_id'=>$id,'amount_minor'=>$kind==='assets.depreciation.reversal'?(int)$j->debit_minor:$base['amount_minor'],'replacement_source_revision'=>$base['recognition_revision'],'recognition_revision'=>$this->hash(['current'=>$base,'original'=>(array)$j,'kind'=>$kind]),'original_journal_id'=>$id,'original_journal_on'=>$j->journal_on,'original_journal_sha256'=>$this->hash((array)$j)]); + } + abort_unless(in_array($kind,['assets.admitted','assets.depreciation','assets.writeoff'],true),422);$initial=$service->byId($scope,$id);abort_unless($initial&&$initial['state']==='approved',403);$expected=match($kind){'assets.admitted'=>'asset','assets.depreciation'=>'charge','assets.writeoff'=>'disposal'};abort_unless($initial['kind']===$expected,409);$current=$service->approved($scope,$initial['record_key'],$expected);$asset=$expected==='asset'?$current:$service->approved($scope,$current['data']['input']['asset_key'],'asset');$a=$asset['data']['input'];abort_unless($a['cost']!==null&&$a['residual']!==null&&$a['available_on']!==null,409,'Dados financeiros do activo indeterminados.'); + $balance=$service->balance($scope,$asset['record_key'],$native);$amount=(int)str_replace('.','',$a['cost']);$date=$a['available_on'];$facts=['asset'=>FixedAssets::financial($asset)];$extra=[]; + if($expected==='charge'){ + abort_unless($balance['cost_minor']===$amount&&($historical||!$balance['disposed']),409,'Admissão actual contabilizada obrigatória.');$p=$service->approved($scope,$current['data']['input']['plan_key'],'plan');abort_unless(($historical||$p['data']['status']==='active')&&$p['data']['input']['asset_key']===$asset['record_key'],409,'Plano suspenso ou incompatível.');abort_unless($current['data']['dependencies']['plan']['data']['input']['life_months']===$p['data']['input']['life_months']&&FixedAssets::financial($current['data']['dependencies']['asset'])===FixedAssets::financial($asset),409,'Plano/dados alterados; confira a quota.'); + $c=$current['data']['calculation'];abort_unless($c['complete'],409);$amount=(int)$c['quota_cents'];$month=$current['data']['input']['month'];$date=(new \DateTimeImmutable($month.'-01'))->modify('last day of this month')->format('Y-m-d');for($n=1;$n<$c['month'];$n++){$previous=(new \DateTimeImmutable(substr($a['available_on'],0,7).'-01'))->modify('+'.($n-1).' months')->format('Y-m');abort_unless(isset($balance['charges'][$previous]),409,'Quota anterior não contabilizada.');} + if(!$historical&&!isset($balance['charges'][$month]))abort_unless($balance['accumulated_minor']===(int)$c['accumulated_cents']-$amount&&$balance['accumulated_minor']+$amount<=(int)$c['cost_cents']-(int)$c['residual_cents'],409,'Acumulado não corresponde às quotas anteriores revistas; exige conferência/correcção.'); + $facts+=['charge_version'=>$current['id'],'plan'=>$current['data']['dependencies']['plan']['id'],'calculation'=>$c];$extra=['charge_key'=>$current['record_key'],'charge_month'=>$month]; + } + if($expected==='disposal'){ + $pending=DB::table('fa_versions as v')->where(array_combine(array_map(fn($k)=>'v.'.$k,array_keys($scope)),array_values($scope)))->whereIn('v.kind',['plan','charge'])->whereRaw("v.data->'input'->>'asset_key'=?",[$asset['record_key']])->where('v.state','prepared')->whereNotExists(function($q){$q->selectRaw('1')->from('fa_versions as n')->whereColumn('n.tenant_id','v.tenant_id')->whereColumn('n.company_id','v.company_id')->whereColumn('n.unit_id','v.unit_id')->whereColumn('n.record_key','v.record_key')->whereColumn('n.version','>','v.version');})->exists();abort_if($pending,409,'Plano ou quota aguarda decisão; confira antes do abate.'); + foreach($native->table('gl_journals')->where($scope)->where('snapshot->source->asset_key',$asset['record_key'])->where('snapshot->source->effect_type','assets.depreciation')->orderBy('id')->get()as$q){$snap=json_decode($q->snapshot,true)['source'];$currentSource=$this->source($actor,'assets.depreciation',(int)$snap['source_id'],$native,$scope);if($snap['recognition_revision']!==$currentSource['recognition_revision']){$pair=$native->table('gl_correction_results')->where($scope)->where('original_journal_id',$q->id)->first();$replacement=$pair?$native->table('gl_journals')->where($scope)->where('id',$pair->replacement_journal_id)->first():null;abort_unless($replacement&&(json_decode($replacement->snapshot,true)['source']['replacement_source_revision']??null)===$currentSource['recognition_revision'],409,'Quota exige correcção aplicada antes do abate.');}} + + abort_unless($balance['cost_minor']===$amount&&!$balance['disposed'],409,'Activo sem custo aplicado ou já abatido.');$frozen=$current['data']['calculation'];abort_unless($this->hash($balance)===$this->hash($frozen),409,'Saldo alterado; confira novo abate.');$date=$current['data']['input']['effect_on'];$last=$native->table('gl_journals')->where($scope)->where('snapshot->source->asset_key',$asset['record_key'])->max('journal_on');abort_unless($last===null||$last<=$date,409,'Abate anterior a movimentos.');$facts+=['disposal_version'=>$current['id'],'balance'=>$balance];$extra=['cost_minor'=>$balance['cost_minor'],'accumulated_minor'=>$balance['accumulated_minor'],'carrying_minor'=>$balance['carrying_minor']]; + } + abort_unless($amount>0,409,'Efeito nulo não cria lançamento.');$observedKeys=array_values(array_unique(array_filter([$asset['record_key'],$current['record_key'],$current['data']['input']['plan_key']??null])));$observed=DB::table('fa_versions')->where($scope)->whereIn('record_key',$observedKeys)->select('record_key')->selectRaw('max(version) as version')->groupBy('record_key')->orderBy('record_key')->get()->map(fn($r)=>(array)$r)->all();$extra['administrative_revision']=$this->hash($observed);$first=DB::table('fa_versions')->where($scope)->where(['record_key'=>$current['record_key'],'state'=>'approved'])->orderBy('version')->value('id');return $extra+['effect_type'=>$kind,'source_id'=>(int)$first,'obligation_id'=>(int)$first,'asset_key'=>$asset['record_key'],'origin_key'=>$this->hash(['scope'=>$scope,'asset'=>$asset['record_key'],'kind'=>$kind,'month'=>$extra['charge_month']??null]),'host_database'=>DB::selectOne('SELECT current_database() d')->d,'permitted_period_key'=>'QA-2026-INTEGRADO','effect_on'=>$date,'recognition_revision'=>$this->hash($facts),'document_revision'=>$this->hash($facts),'amount_minor'=>$amount,'currency'=>'MZN','tax_minor'=>0,'compatible'=>true,'history_imported'=>false]; + } + public function period(User $actor,array $period,object $native,array $s):array{ + app(FixedAssets::class)->scope($actor->id);$h=hash_init('sha256');$facts=0;$pending=0;$rows=DB::table('fa_versions as v')->where(array_combine(array_map(fn($k)=>'v.'.$k,array_keys($s)),array_values($s)))->where('v.state','approved')->whereIn('v.kind',['asset','charge','disposal'])->whereNotExists(function($q){$q->selectRaw('1')->from('fa_versions as n')->whereColumn('n.record_key','v.record_key')->whereColumn('n.tenant_id','v.tenant_id')->whereColumn('n.company_id','v.company_id')->whereColumn('n.unit_id','v.unit_id')->where('n.state','approved')->whereColumn('n.version','>','v.version');}); + foreach($rows->orderBy('v.id')->cursor()as$r){$d=json_decode($r->data,true);if($d['status']==='cancelled')continue;$on=match($r->kind){'asset'=>$d['input']['available_on'],'charge'=>$d['input']['month'].'-01','disposal'=>$d['input']['effect_on']};if($on!==null&&($on<$period['starts_on']||$on>$period['ends_on']))continue;$facts++;hash_update($h,$r->data);$kind=match($r->kind){'asset'=>'assets.admitted','charge'=>'assets.depreciation','disposal'=>'assets.writeoff'}; + $j=$native->table('gl_journals')->where($s)->where('snapshot->source->effect_type',$kind)->where('snapshot->source->asset_key',$r->kind==='asset'?$r->record_key:$d['input']['asset_key']);if($r->kind==='charge')$j->where('snapshot->source->charge_key',$r->record_key);$j=$j->first(); + if($r->kind==='disposal'&&$j)continue; + try{$source=$this->source($actor,$kind,(int)$r->id,$native,$s,true);if(!$j){$pending++;continue;}$snap=json_decode($j->snapshot,true)['source'];if($snap['recognition_revision']!==$source['recognition_revision']){$pair=$native->table('gl_correction_results')->where($s)->where('original_journal_id',$j->id)->first();$replacement=$pair?$native->table('gl_journals')->where($s)->where('id',$pair->replacement_journal_id)->first():null;if(!$replacement||(json_decode($replacement->snapshot,true)['source']['replacement_source_revision']??null)!==$source['recognition_revision'])$pending++;}} + catch(\DomainException|\Symfony\Component\HttpKernel\Exception\HttpException $e){$pending++;} + }return ['facts'=>$facts,'pending'=>$pending,'hash'=>hash_final($h)]; + } +} diff --git a/app/Modules/Accounting/FixedAssets.php b/app/Modules/Accounting/FixedAssets.php new file mode 100644 index 0000000..2b82a57 --- /dev/null +++ b/app/Modules/Accounting/FixedAssets.php @@ -0,0 +1,93 @@ +json($a));} + private function exact(array $a,array $rules):array{abort_if(array_diff(array_keys($a),array_keys($rules)),422);return Validator::make($a,$rules)->validate();} + public function scope(int $actor,string $action='view'):array{ + abort_unless(config('fixed_assets.enabled')===true,404);CompositionRuntime::assert();$s=$this->context->identifiers()+['unit_id'=>$this->context->unitId()];abort_unless($actor===$this->context->userId()&&$s['unit_id']&&ModuleRecords::active()==='accounting',403);$this->context->selectFor($actor,$s['tenant_id'],$s['company_id'],$s['unit_id']); + foreach(array_unique(['view',$action])as$r)$this->permissions->require($actor,$s['tenant_id'],$s['company_id'],'fixed_assets.'.$r,$s['unit_id']); + + $m=DB::table('fa_meta')->where('policy',self::POLICY)->first();abort_unless($m&&$m->composition===CompositionRuntime::physical()['composition']&&hash_equals($m->source_hash,(string)config('fixed_assets.schema_sha256')),503,'Schema de activos não conferido.'); + $boot=config('database.connections.receipt_native');if($boot===null){$ready=app(ComposedLedger::class)->run('view',fn()=>true);abort_unless($ready===true,503,'Livro ocupado; tente novamente com a mesma operação.');}$native=DB::connection('receipt_native');\Tempo\Finance\GeneralLedger\LedgerSchema::assertSynthetic($native);$m=$native->table('gl_asset_extension_versions')->where('version',1)->first();abort_unless($m&&hash_equals($m->source_hash,(string)config('fixed_assets.native_schema_sha256'))&&$native->table('gl_asset_books')->where($s)->where('host_database',DB::connection()->getDatabaseName())->exists(),503,'Origem contabilística de activos não conferida.');return$s; + } + private function identity(int $actor,array $s):string{$u=DB::table('users')->where('id',$actor)->first(['id','name','email']);abort_unless($u&&DB::table('company_memberships')->where(['tenant_id'=>$s['tenant_id'],'company_id'=>$s['company_id'],'user_id'=>$actor])->exists(),403);return$this->hash((array)$u);} + private function lock(int $actor,array $s):void{DB::statement("SET LOCAL lock_timeout='5s'");DB::statement("SET LOCAL statement_timeout='20s'");DB::table('companies')->where(['tenant_id'=>$s['tenant_id'],'id'=>$s['company_id']])->lockForUpdate()->firstOrFail();DB::table('users')->where('id',$actor)->lockForUpdate()->firstOrFail();DB::table('company_memberships')->where(['tenant_id'=>$s['tenant_id'],'company_id'=>$s['company_id'],'user_id'=>$actor])->lockForUpdate()->first();DB::table('permission_grants')->where(['tenant_id'=>$s['tenant_id'],'company_id'=>$s['company_id']])->orderBy('id')->lockForUpdate()->get();} + private function rows(array $s){return DB::table('fa_versions')->where($s);} + private function row(?object $r):?array{if(!$r)return null;$a=(array)$r;foreach(['data','impact']as$k)$a[$k]=json_decode($a[$k],true,64,JSON_THROW_ON_ERROR);return$a;} + private function latest(array $s,string $key,bool $approved=false):?array{$q=$this->rows($s)->where('record_key',$key);if($approved)$q->where('state','approved');return$this->row($q->orderByDesc('version')->first());} + public function effective(array $s,string $key):?array{return $this->latest($s,$key,true);} + public function byId(array $s,int $id):?array{return $this->row($this->rows($s)->where('id',$id)->first());} + public function approved(array $s,string $key,string $kind):array{$a=$this->effective($s,$key);abort_unless($a&&$a['kind']===$kind&&$a['data']['status']!=='cancelled',409,'Origem revista activa obrigatória.');return $a;} + private function input(array $v):array{ + if(is_string($v['reason']??null))$v['reason']=trim($v['reason']);$d=$this->exact($v,['action'=>'required|in:prepare,review','kind'=>'required|in:asset,plan,charge,disposal','record_key'=>'required|uuid','version'=>'required|integer|min:0','reason'=>'required|string|min:10|max:2000','data'=>'present|array']);$d['version']=(int)$d['version'];$d['record_key']=strtolower($d['record_key']); + if($d['action']==='review'){$d['data']=$this->exact($d['data'],['decision'=>'required|in:approved,refused']);return$d;} + $money='present|nullable|string|regex:/^[0-9]{1,12}\\.[0-9]{2}$/D'; + $rules=match($d['kind']){ + 'asset'=>['code'=>'required|string|regex:/^QA-[A-Z0-9-]{2,60}$/D','description'=>'required|string|min:5|max:300','origin_reference'=>'required|string|regex:/^QA-[A-Z0-9-]{2,80}$/D','cost'=>$money,'residual'=>$money,'available_on'=>'present|nullable|date_format:Y-m-d','location'=>'required|string|max:100','custodian'=>'required|string|max:100'], + 'plan'=>['asset_key'=>'required|uuid','life_months'=>'present|nullable|integer|min:1|max:600','status'=>'required|in:active,suspended'], + 'charge'=>['asset_key'=>'required|uuid','plan_key'=>'required|uuid','month'=>'required|date_format:Y-m'], + 'disposal'=>['asset_key'=>'required|uuid','effect_on'=>'required|date_format:Y-m-d']}; + $d['data']=$this->exact($d['data'],['operation'=>'required|in:establish,amend,cancel,correct_plan','evidence'=>'required|string|min:10|max:1000']+$rules);foreach(['asset_key','plan_key']as$k)if(isset($d['data'][$k]))$d['data'][$k]=strtolower($d['data'][$k]);if(isset($d['data']['life_months']))$d['data']['life_months']=(int)$d['data']['life_months'];return$d; + } + private function basis(int $actor,array $s,array $d):array{ + $last=$this->latest($s,$d['record_key']);$data=$d['action']==='prepare'?$d['data']:($last['data']['input']??[]);$deps=[]; + if($d['kind']!=='asset'){$deps['asset']=$this->approved($s,$data['asset_key']??'','asset');$deps['ledger']=$this->balance($s,$data['asset_key']);} + else $deps['ledger']=$this->balance($s,$d['record_key']); + if($d['kind']==='charge')$deps['plan']=$this->approved($s,$data['plan_key']??'','plan'); + return['last'=>$last,'effective'=>$this->effective($s,$d['record_key']),'dependencies'=>$deps,'actor_identity'=>$this->identity($actor,$s)]; + } + public function balance(array $s,string $key,?object $db=null):array{ + $db??=DB::connection('receipt_native');$q=$db->table('gl_journals')->where($s)->where('snapshot->source->asset_key',$key);$rows=(clone$q)->orderBy('id')->get(['id','snapshot','debit_minor']);$cost=0;$acc=0;$disposed=false;$charges=[]; + foreach($rows as$j){$v=json_decode($j->snapshot,true);$kind=$v['source']['effect_type'];if($kind==='assets.admitted')$cost+=(int)$j->debit_minor;if(in_array($kind,['assets.depreciation','assets.depreciation.replacement'],true))$acc+=(int)$j->debit_minor;if($kind==='assets.depreciation.reversal')$acc-=(int)$j->debit_minor;if($kind==='assets.writeoff')$disposed=true;if($kind==='assets.depreciation')$charges[$v['source']['charge_month']]=$j->id;} + return['cost_minor'=>$cost,'accumulated_minor'=>$acc,'carrying_minor'=>$disposed?0:$cost-$acc,'disposed'=>$disposed,'charges'=>$charges,'journal_ids'=>$rows->pluck('id')->all()]; + } + public static function financial(array $a):array{return array_intersect_key($a['data']['input'],array_flip(['code','origin_reference','cost','residual','available_on']));} + private function plan(int $actor,array $s,array $d,array $b):array{ + $last=$b['last'];$effective=$b['effective'];abort_unless(($last['version']??0)===$d['version']&&(!$last||$last['kind']===$d['kind']),409,'Versão/tipo alterados. Confira novamente.');$old=$effective['data']??null; + if($d['action']==='prepare'){$input=$d['data'];$prepared=$actor;$identity=$this->identity($actor,$s);}else{abort_unless($last&&$last['state']==='prepared'&&$last['prepared_by']!==$actor,403,'Preparação alheia actual obrigatória.');$prepared=$last['prepared_by'];$identity=$last['preparer_identity'];abort_unless(hash_equals($identity,$this->identity($prepared,$s)),409,'Identidade do preparador alterada.');$this->permissions->require($prepared,$s['tenant_id'],$s['company_id'],'fixed_assets.prepare',$s['unit_id']);$input=$last['data']['input'];abort_unless($this->hash($last['data']['dependencies'])===$this->hash($b['dependencies']),409,'Dependências mudaram. Prepare novamente.');} + $op=$input['operation'];if($op==='establish')abort_if($effective,409,'Registo já aprovado. Use correcção.');else abort_unless($old&&$old['status']!=='cancelled',409,'Exige registo activo já aprovado.'); + $kind=$d['kind'];$dep=$b['dependencies'];$balance=$dep['ledger'];$status=$op==='cancel'?'cancelled':'active';$calculation=null; + if($op==='correct_plan')abort_unless($kind==='plan'&&$old,422); + if($kind==='asset'){ + if($old)foreach(['code','origin_reference']as$k)abort_unless($input[$k]===$old['input'][$k],409,'Código/origem imutáveis.'); + abort_if($this->rows($s)->where('kind','asset')->where('record_key','<>',$d['record_key'])->where(fn($q)=>$q->whereRaw("data->'input'->>'code'=?",[$input['code']])->orWhereRaw("data->'input'->>'origin_reference'=?",[$input['origin_reference']]))->exists(),409,'Código ou origem já usados.'); + if($balance['journal_ids']){abort_if($op==='cancel',409,'Activo contabilizado não se cancela; use abate.');foreach(['cost','residual','available_on']as$k)abort_unless($old['input'][$k]===$input[$k],409,'Custo/residual/data contabilizados imutáveis.');} + if($input['cost']!==null&&$input['residual']!==null)AssetDepreciation::quota($input['cost'],$input['residual'],1,1); + if($op==='cancel')abort_if($this->rows($s)->where('kind','<>','asset')->whereRaw("data->'input'->>'asset_key'=?",[$d['record_key']])->exists(),409,'Activo com dependências.'); + }else{ + abort_if($balance['disposed'],409,'Activo já abatido.');if($old)abort_unless($input['asset_key']===$old['input']['asset_key'],409,'Activo imutável.'); + if(in_array($kind,['plan','disposal'],true))abort_if($this->rows($s)->where('kind',$kind)->where('record_key','<>',$d['record_key'])->whereRaw("data->'input'->>'asset_key'=?",[$input['asset_key']])->where('state','approved')->exists(),409,'Reutilize o plano ou abate existente.'); + if($kind==='plan'){$status=$input['status'];abort_if($op==='cancel',422,'Suspenda explicitamente o plano.');if($old&&$balance['charges']&&$input['life_months']!==$old['input']['life_months']){abort_unless($op==='correct_plan',409,'Quotas aplicadas: exige plano de correcção explícito.');abort_unless(count($balance['charges'])===1,409,'Várias quotas aplicadas exigem tratamento coordenado fora deste recorte.');}} + if($kind==='charge'){ + $a=$dep['asset']['data']['input'];$p=$dep['plan']['data'];abort_unless($p['input']['asset_key']===$input['asset_key']&&$p['status']==='active',409,'Plano suspenso ou de outro activo.');abort_unless($a['available_on']!==null,409,'Entrada em uso desconhecida.');$begin=$input['month'].'-01';$available=substr($a['available_on'],0,7).'-01';$delta=(int)substr($begin,0,4)*12+(int)substr($begin,5,2)-((int)substr($available,0,4)*12+(int)substr($available,5,2))+1;abort_unless($delta>=1,409,'Competência anterior à entrada em uso.');$calculation=AssetDepreciation::quota($a['cost'],$a['residual'],$p['input']['life_months'],$delta);if($old)foreach(['month','plan_key']as$k)abort_unless($input[$k]===$old['input'][$k],409,'Competência/plano imutáveis.'); + abort_if($this->rows($s)->where('kind','charge')->where('record_key','<>',$d['record_key'])->whereRaw("data->'input'->>'asset_key'=? AND data->'input'->>'month'=?",[$input['asset_key'],$input['month']])->exists(),409,'Competência já registada.');abort_if($op==='cancel'&&isset($balance['charges'][$input['month']]),409,'Quota aplicada exige par de correcção.'); + if($d['action']==='review'&&$d['data']['decision']==='approved'&&$op!=='cancel')abort_unless($calculation['complete'],409,'Custo/residual/vida desconhecidos.'); + } + if($kind==='disposal'){abort_unless($balance['cost_minor']>0,409,'Admissão contabilizada obrigatória.');abort_unless($input['effect_on']>=$dep['asset']['data']['input']['available_on'],409);$calculation=$balance;} + } + $data=['input'=>$input,'status'=>$status,'dependencies'=>$dep,'calculation'=>$calculation,'synthetic_only'=>true];$state=$d['action']==='prepare'?'prepared':$d['data']['decision'];$version=$d['version']+1; + $impact=['before'=>['version'=>$last['version']??0,'state'=>$last['state']??null,'effective'=>$old],'after'=>['version'=>$version,'state'=>$state,'proposal'=>$data,'effective'=>$state==='approved'?$data:$old],'payments'=>0,'journals'=>0,'accounting_requires_separate_decisions'=>true,'legal_tax_treatment'=>null];return['kind'=>$kind,'record_key'=>$d['record_key'],'version'=>$version,'previous_id'=>$last['id']??null,'action'=>$d['action'],'state'=>$state,'prepared_by'=>$prepared,'preparer_identity'=>$identity,'data'=>$data,'impact'=>$impact]; + } + public function preview(int $actor,array $input,string $key):array{$d=$this->input($input);$this->exact(['key'=>$key],['key'=>'required|uuid']);$s=$this->scope($actor,$d['action']);return DB::transaction(function()use($actor,$s,$d,$key){$this->lock($actor,$s);$this->scope($actor,$d['action']);$b=$this->basis($actor,$s,$d);$out=$this->plan($actor,$s,$d,$b);$c=['actor'=>$actor,'scope'=>$s,'generation'=>request()->session()->get('company_context.token'),'input'=>$d,'key'=>strtolower($key),'basis'=>$this->hash($b),'impact'=>$out['impact'],'observed'=>time()];return['preview'=>Crypt::encryptString($this->json($c)),'impact'=>$out['impact']];});} + public function confirm(int $actor,string $token):array{ + try{$c=json_decode(Crypt::decryptString($token),true,64,JSON_THROW_ON_ERROR);}catch(\Throwable){abort(409,'Prévia inválida.');}abort_unless(is_array($c)&&is_array($c['input']??null),409);$d=$this->input($c['input']);$s=$this->scope($actor,$d['action']);return DB::transaction(function()use($actor,$s,$d,$c){$this->lock($actor,$s);$this->scope($actor,$d['action']);abort_unless($c['actor']===$actor&&$this->hash($c['scope'])===$this->hash($s)&&$c['generation']===request()->session()->get('company_context.token')&&$c['observed']>=time()-600&&$c['observed']<=time(),409,'Pessoa/contexto/prévia alterados.');$q=$s+['actor_id'=>$actor,'command_key'=>$c['key']];$old=DB::table('fa_commands')->where($q)->first();if($old){abort_unless(hash_equals($old->request_hash,$this->hash($d)),409,'Chave usada com outros dados.');return json_decode($old->result,true)+['replayed'=>true];}$b=$this->basis($actor,$s,$d);abort_unless(hash_equals($c['basis'],$this->hash($b)),409,'Origem/versão alterada. Confira novamente.');$out=$this->plan($actor,$s,$d,$b);abort_unless($this->hash($out['impact'])===$this->hash($c['impact']),409,'Impacto mudou.');$row=$out;unset($row['impact'],$row['data']);$id=DB::table('fa_versions')->insertGetId($s+$row+['data'=>$this->json($out['data']),'impact'=>$this->json($out['impact']),'source_basis'=>$this->hash($b['dependencies']),'actor_id'=>$actor,'reason'=>$d['reason'],'created_at'=>now()->utc()]);$result=['id'=>$id,'record_key'=>$d['record_key'],'version'=>$out['version'],'state'=>$out['state'],'impact'=>$out['impact']];DB::table('fa_commands')->insert($q+['request_hash'=>$this->hash($d),'input'=>$this->json($d),'result'=>$this->json($result),'created_at'=>now()->utc()]);Audit::record('fixed_assets.'.$d['action'],$actor,$s['tenant_id'],$s['company_id'],$s['unit_id'],details:['record_id'=>$id,'version'=>$out['version'],'reason'=>$d['reason']]);return$result+['replayed'=>false];}); + } + public function receipt(int $actor,string $key):array{$this->exact(['key'=>$key],['key'=>'required|uuid']);$s=$this->scope($actor);$old=DB::table('fa_commands')->where($s+['actor_id'=>$actor,'command_key'=>strtolower($key)])->first();abort_unless($old,404);return['result'=>json_decode($old->result,true),'created_at'=>$old->created_at,'reason'=>json_decode($old->input,true)['reason'],'historical_receipt_only'=>true,'operation_executed'=>false];} + public function listing(int $actor,array $input):array{ + $s=$this->scope($actor);$d=$this->exact($input,['kind'=>'required|in:asset,plan,charge,disposal','record_key'=>'sometimes|uuid','month'=>'sometimes|date_format:Y-m','state'=>'sometimes|in:prepared,approved,refused','search'=>'sometimes|string|max:100','page'=>'sometimes|integer|min:1|max:100000','per_page'=>'sometimes|integer|in:25,100']); + return DB::transaction(function()use($actor,$s,$d){if(DB::transactionLevel()===1)DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');$q=$this->rows($s)->where('kind',$d['kind']);if(isset($d['record_key'])){$q->where('record_key',$d['record_key']);abort_unless((clone$q)->exists(),403);}else$q->whereNotExists(function($n){$n->selectRaw('1')->from('fa_versions as next');foreach(['tenant_id','company_id','unit_id','record_key']as$k)$n->whereColumn('next.'.$k,'fa_versions.'.$k);$n->whereColumn('next.version','>','fa_versions.version');}); + if(isset($d['month']))$q->whereRaw("data->'input'->>'month'=?",[$d['month']]);if(isset($d['state']))$q->where('state',$d['state']);if(isset($d['search'])){$needle='%'.str_replace(['!','%','_'],['!!','!%','!_'],$d['search']).'%';$q->whereRaw("concat_ws(' ',data->'input'->>'code',data->'input'->>'description',data->'input'->>'asset_key',data->'input'->>'month') ILIKE ? ESCAPE '!'",[$needle]);} + $page=$q->orderByDesc('id')->paginate($d['per_page']??25,['*'],'page',$d['page']??1);$keys=$page->pluck('record_key')->unique()->all();$a=$this->rows($s)->whereIn('record_key',$keys)->where('state','approved')->whereNotExists(function($n){$n->selectRaw('1')->from('fa_versions as e');foreach(['tenant_id','company_id','unit_id','record_key']as$k)$n->whereColumn('e.'.$k,'fa_versions.'.$k);$n->where('e.state','approved')->whereColumn('e.version','>','fa_versions.version');})->get()->keyBy('record_key'); + $items=[];foreach($page->items()as$r){$row=$this->row($r);$row['effective']=$this->row($a->get($r->record_key));$items[]=$row;}$page->setCollection(collect($items));return['policy'=>self::POLICY,'kind'=>$d['kind'],'records'=>$page->toArray(),'capabilities'=>['prepare'=>$this->permissions->has($actor,$s['tenant_id'],$s['company_id'],'fixed_assets.prepare',$s['unit_id']),'review'=>$this->permissions->has($actor,$s['tenant_id'],$s['company_id'],'fixed_assets.review',$s['unit_id'])],'synthetic_only'=>true,'legal_tax_treatment'=>null];}); + } +} diff --git a/app/Modules/Accounting/FixedAssetsController.php b/app/Modules/Accounting/FixedAssetsController.php new file mode 100644 index 0000000..2aa8f55 --- /dev/null +++ b/app/Modules/Accounting/FixedAssetsController.php @@ -0,0 +1,11 @@ +json($fn())->header('Cache-Control','no-store, private');}catch(\Illuminate\Database\QueryException$e){if(!in_array($e->errorInfo[0]??null,['55P03','57014'],true))throw$e;return response()->json(['message'=>'Processamento de activos ocupado. Recupere a mesma confirmação.'],503)->header('Retry-After','5')->header('Cache-Control','no-store');}} + public function index(Request $r,FixedAssets $f){return$this->response(fn()=>$f->listing($r->user()->id,$r->query()));} + public function preview(Request $r,FixedAssets $f){abort_if(strlen(json_encode($r->all()))>16384,422);return$this->response(fn()=>$f->preview($r->user()->id,$r->all(),strtolower((string)$r->header('Idempotency-Key'))));} + public function confirm(Request $r,FixedAssets $f){$d=$r->validate(['preview'=>'required|string|max:131072']);abort_if(array_diff(array_keys($r->all()),['preview']),422);return$this->response(fn()=>$f->confirm($r->user()->id,$d['preview']));} + public function receipt(Request $r,FixedAssets $f,string $key){abort_if($r->query()!==[],422);return$this->response(fn()=>$f->receipt($r->user()->id,strtolower($key)));} +} diff --git a/app/Modules/Accounting/LedgerComposition.php b/app/Modules/Accounting/LedgerComposition.php index bfe5bff..3fb102f 100644 --- a/app/Modules/Accounting/LedgerComposition.php +++ b/app/Modules/Accounting/LedgerComposition.php @@ -20,7 +20,7 @@ final class LedgerComposition return DB::transaction(function()use($user,$options,$own){ if($own)DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');$ledger=app(GeneralLedger::class);$book=$ledger->listing($user,['page'=>(int)($options['journals_page']??1),'period_key'=>$options['period_key']??'QA-2026-10'],false);$ctx=app(CompanyContext::class);$scope=$ctx->identifiers()+['unit_id'=>$ctx->unitId()];$permissions=app(Permissions::class);$can=fn($right)=>$permissions->has($user->id,$scope['tenant_id'],$scope['company_id'],$right,$scope['unit_id']);$panels=['sales'=>['allowed'=>false],'costs'=>['allowed'=>false],'suppliers'=>['allowed'=>false]]; if($can('service_invoices.view')&&$can('service_receivables.view')){ - $this->requireTables(['si_invoices','sr_records','sr_effects','gl_sales_capability']);$p=ModuleRecords::query('si_invoices')->where($scope)->orderBy('id')->paginate(25,['id','series_code','number','issued_at'],'invoices_page',(int)($options['invoices_page']??1));$ids=array_column($p->items(),'id');$posted=$this->postings($scope,'sales.invoice','invoice_id',$ids);$records=DB::table('sr_records')->where($scope)->whereIn('invoice_id',$ids)->where('kind','obligation')->get(['id','invoice_id'])->keyBy('invoice_id');$opened=DB::table('sr_effects')->whereIn('record_id',$records->pluck('id')->all())->where('kind','open')->pluck('record_id')->all();$rows=[];foreach($p->items() as $i){$ob=$records->get($i->id);$rows[]=['invoice_id'=>$i->id,'number'=>$i->series_code.'/'.$i->number,'issued_at'=>$i->issued_at,'confirmed_opening'=>$ob&&in_array($ob->id,$opened,true),'original_journal_id'=>$posted[$i->id]??null];}$panels['sales']=$this->page($p,$rows); + $this->requireTables(['si_invoices','sr_records','sr_effects','gl_sales_capability']);$p=\App\Modules\Services\QualifiedInvoiceSource::query()->where($scope)->orderBy('id')->paginate(25,['id','series_code','number','issued_at'],'invoices_page',(int)($options['invoices_page']??1));$ids=array_column($p->items(),'id');$posted=$this->postings($scope,'sales.invoice','invoice_id',$ids);$records=DB::table('sr_records')->where($scope)->whereIn('invoice_id',$ids)->where('kind','obligation')->get(['id','invoice_id'])->keyBy('invoice_id');$opened=DB::table('sr_effects')->whereIn('record_id',$records->pluck('id')->all())->where('kind','open')->pluck('record_id')->all();$rows=[];foreach($p->items() as $i){$ob=$records->get($i->id);$rows[]=['invoice_id'=>$i->id,'number'=>$i->series_code.'/'.$i->number,'issued_at'=>$i->issued_at,'confirmed_opening'=>$ob&&in_array($ob->id,$opened,true),'original_journal_id'=>$posted[$i->id]??null];}$panels['sales']=$this->page($p,$rows); } if($can('workshop.view')&&$can('direct_costs.view')){ $this->requireTables(['gl_cost_sources','wo_orders','dc_calculations','dc_decisions']);$p=DB::table('gl_cost_sources')->where($scope)->orderBy('order_id')->paginate(25,['order_id','period_key'],'costs_page',(int)($options['costs_page']??1));$ids=array_column($p->items(),'order_id');$calculations=DB::table('dc_calculations as c')->where(array_combine(array_map(fn($k)=>'c.'.$k,array_keys($scope)),array_values($scope)))->whereIn('c.order_id',$ids)->whereNotExists(fn($q)=>$q->selectRaw('1')->from('dc_calculations as n')->whereColumn('n.order_id','c.order_id')->whereColumn('n.version','>','c.version'))->get(['c.id','c.order_id','c.version'])->keyBy('order_id');$decisions=DB::table('dc_decisions')->whereIn('calculation_id',$calculations->pluck('id')->all())->get(['calculation_id','decision'])->keyBy('calculation_id');$posted=$this->postings($scope,'workshop.direct_cost.validated','cost_order_id',$ids);$results=DB::table('gl_correction_results')->where($scope)->whereIn('original_journal_id',array_values($posted))->get(['id','original_journal_id'])->keyBy('original_journal_id');$rows=[];foreach($p->items() as $m){$calc=$calculations->get($m->order_id);$j=$posted[$m->order_id]??null;$rows[]=['order_id'=>$m->order_id,'period_key'=>$m->period_key,'calculation_id'=>$calc?->id,'calculation_version'=>$calc?->version,'recorded_decision'=>$calc?$decisions->get($calc->id)?->decision:null,'original_journal_id'=>$j,'recorded_correction_result_id'=>$j?$results->get($j)?->id:null,'current_dependency_conference_required'=>true];}$panels['costs']=$this->page($p,$rows); diff --git a/app/Modules/Accounting/SalesLedgerSource.php b/app/Modules/Accounting/SalesLedgerSource.php index 142cdf6..03ef78a 100644 --- a/app/Modules/Accounting/SalesLedgerSource.php +++ b/app/Modules/Accounting/SalesLedgerSource.php @@ -30,9 +30,9 @@ final class SalesLedgerSource if(!$ob||!$open)throw new \DomainException('Conferência financeira de abertura pendente; não presumir dívida aprovada.'); $origin=json_decode($ob->data,true)['origin']??[]; if(($origin['invoice_id']??null)!==$id||!hash_equals($origin['snapshot_sha256']??'',hash('sha256',$row->snapshot))||(int)$open->actor_id===(int)$ob->actor_id)throw new \DomainException('Abertura ou fotografia documental divergente.'); - $calc=$snapshot['calculation'];$amount=$this->minor($calc['total_document_proposed']); + $calc=$snapshot['calculation'];$amount=$this->minor($calc['total_document_proposed']);$documentBase=$calc['base_exact'];if(($snapshot['calculation_policy']??null)===\App\Modules\Services\MultiLineInvoiceCalculation::POLICY){$values=\App\Modules\Services\MultiLineInvoiceCalculation::financialAmounts($snapshot);$documentBase=$values['base'];} if($this->minor($ob->amount)!==$amount||$this->minor($open->amount)!==$amount)throw new \DomainException('Principal não coincide com o documento.'); - return ['effect_type'=>'sales.invoice','origin_key'=>$this->hash(['scope'=>$scope,'invoice'=>$id,'series'=>$row->series_code,'number'=>$row->number]),'source_id'=>$id,'invoice_id'=>$id,'obligation_id'=>(int)$ob->id,'recognition_revision'=>$this->hash(['invoice_snapshot'=>$snapshot,'pdf_sha256'=>$row->pdf_sha256,'opening'=>(array)$open,'obligation'=>(array)$ob]),'principal_minor'=>$amount,'amount_minor'=>$amount,'base_minor'=>$this->minor($calc['base_exact']),'tax_minor'=>$this->minor($calc['vat_document_proposed']),'currency'=>'MZN','compatible'=>true,'history_imported'=>false,'due_on'=>json_decode($ob->data,true)['due_date']??null,'document_revision'=>hash('sha256',$row->snapshot),'document_snapshot'=>$snapshot,'opening_effect_id'=>$open->id]; + return ['effect_type'=>'sales.invoice','origin_key'=>$this->hash(['scope'=>$scope,'invoice'=>$id,'series'=>$row->series_code,'number'=>$row->number]),'source_id'=>$id,'invoice_id'=>$id,'obligation_id'=>(int)$ob->id,'recognition_revision'=>$this->hash(['invoice_snapshot'=>$snapshot,'pdf_sha256'=>$row->pdf_sha256,'opening'=>(array)$open,'obligation'=>(array)$ob]),'principal_minor'=>$amount,'amount_minor'=>$amount,'base_minor'=>$this->minor($documentBase),'tax_minor'=>$this->minor($calc['vat_document_proposed']),'currency'=>'MZN','compatible'=>true,'history_imported'=>false,'due_on'=>json_decode($ob->data,true)['due_date']??null,'document_revision'=>hash('sha256',$row->snapshot),'document_snapshot'=>$snapshot,'opening_effect_id'=>$open->id]; } public function source(User $u,string $kind,int $id):array { @@ -75,7 +75,7 @@ final class SalesLedgerSource } public function workspace(User $u,?int $invoice,int $page=1):array { - $scope=$this->scope($u);$p=\App\Shared\Context\ModuleRecords::query('si_invoices')->where($scope)->orderByDesc('id')->paginate(25,['id','series_code','number','issued_at'],'page',$page); + $scope=$this->scope($u);$p=\App\Modules\Services\QualifiedInvoiceSource::query()->where($scope)->orderByDesc('id')->paginate(25,['id','series_code','number','issued_at'],'page',$page); $data=['scope'=>$scope,'invoices'=>$p->items(),'pagination'=>['page'=>$page,'last_page'=>$p->lastPage(),'total'=>$p->total()],'invoice'=>null];if($invoice===null)return $data; $data['invoice']=app(ServiceInvoicing::class)->show($invoice,$u->id); $row=DB::table('si_invoices')->where($scope)->where('id',$invoice)->first()??abort(403); diff --git a/app/Modules/HumanResources/Http/PersonnelController.php b/app/Modules/HumanResources/Http/PersonnelController.php index 027b708..8fd9c3c 100644 --- a/app/Modules/HumanResources/Http/PersonnelController.php +++ b/app/Modules/HumanResources/Http/PersonnelController.php @@ -9,4 +9,5 @@ final class PersonnelController public function confirm(Request $r,Personnel $f){$d=$r->validate(['preview'=>'required|string|max:131072']);abort_if(array_diff(array_keys($r->all()),['preview']),422);return$this->response(fn()=>$f->confirm($r->user()->id,$d['preview']));} public function receipt(Request $r,Personnel $f,string $key){abort_if($r->query()!==[],422);return$this->response(fn()=>$f->receipt($r->user()->id,strtolower($key)));} public function export(Request $r,Personnel $f,int $id){abort_if($r->query()!==[],422);$out=$f->export($r->user()->id,$id);return response($out['bytes'])->header('Content-Type','text/csv; charset=utf-8')->header('Content-Disposition','attachment; filename="sgf-rh-pessoal-interno-QA.csv"')->header('X-Content-SHA256',$out['sha256'])->header('Cache-Control','no-store, private');} + public function readable(Request $r,Personnel $f,int $id){abort_if($r->query()!==[],422);$out=$f->readable($r->user()->id,$id);return response($out['bytes'])->header('Content-Type','text/csv; charset=utf-8')->header('Content-Disposition','attachment; filename="sgf-rh-resumo-legivel-QA.csv"')->header('X-Content-SHA256',$out['sha256'])->header('X-Source-SHA256',$out['source_sha256'])->header('X-SGF-Read-Format',$out['format'])->header('Cache-Control','no-store, private');} } diff --git a/app/Modules/HumanResources/Personnel.php b/app/Modules/HumanResources/Personnel.php index f1e7d6e..e0447ce 100644 --- a/app/Modules/HumanResources/Personnel.php +++ b/app/Modules/HumanResources/Personnel.php @@ -54,7 +54,7 @@ final class Personnel private function plan(int $actor,array $s,array $d,array $b):array{ $last=$b['last'];$eff=$b['effective'];abort_unless(($last['version']??0)===$d['version']&&(!$last||$last['kind']===$d['kind']),409,'Versão/tipo alterados. Confira novamente.');$old=$eff['data']??null; - if($d['action']==='prepare'){$in=$d['data'];$prepared=$actor;$identity=$this->identity($actor,$s);}else{abort_unless($last&&$last['state']==='prepared'&&$last['prepared_by']!==$actor,403,'Preparação alheia actual obrigatória.');$prepared=$last['prepared_by'];$identity=$last['preparer_identity'];HumanResourcesProfiles::assertActor($prepared,$s,'prepare');abort_unless(hash_equals($identity,$this->identity($prepared,$s)),409,'Identidade do preparador mudou.');foreach(['view','prepare']as$a)$this->permissions->require($prepared,$s['tenant_id'],$s['company_id'],'human_resources.'.$this->area($d['kind']).'.'.$a,$s['unit_id']);$in=$last['data']['input'];abort_unless($this->hash($last['data']['dependencies'])===$this->hash($b['dependencies']),409,'Fontes mudaram desde a preparação. Prepare novamente.');} + if($d['action']==='prepare'){$in=$d['data'];$prepared=$actor;$identity=$this->identity($actor,$s);}else{abort_unless($last&&$last['state']==='prepared'&&$last['prepared_by']!==$actor,403,'Preparação alheia actual obrigatória.');$prepared=$last['prepared_by'];$identity=$last['preparer_identity'];HumanResourcesProfiles::assertActor($prepared,$s,'prepare');abort_unless(hash_equals($identity,$this->identity($prepared,$s)),409,'Identidade do preparador mudou.');foreach(['view','prepare']as$a)$this->permissions->require($prepared,$s['tenant_id'],$s['company_id'],'human_resources.'.$this->area($d['kind']).'.'.$a,$s['unit_id']);$in=$last['data']['input'];if($d['data']['decision']==='refused')return$this->refusal($s,$d,$b,$prepared,$identity);abort_unless($this->hash($last['data']['dependencies'])===$this->hash($b['dependencies']),409,'Fontes mudaram desde a preparação. Prepare novamente.');} $op=$in['operation'];$kind=$d['kind'];$worker=$in['worker_key'];if($op==='establish')abort_if($eff,409,'Já revisto. Use correcção neste registo.');else abort_unless($old&&$old['status']!=='cancelled',409,'Operação exige origem efectiva não cancelada.');if($last)abort_unless($worker===$last['data']['input']['worker_key'],409,'Pessoa imutável neste registo.');if($op!=='cancel')abort_unless($b['dependencies']['worker']['status']==='active',409,'Cadastro suspenso.'); if(in_array($op,['cancel','take'],true)){foreach($old['input']as$k=>$v)if(!in_array($k,['operation','evidence','reference_on'],true))abort_unless($this->hash(['v'=>$in[$k]??null])===$this->hash(['v'=>$v]),409,'Esta decisão conserva a origem e os valores revistos.');} $status=$op==='cancel'?'cancelled':'active';$result=[]; @@ -86,6 +86,10 @@ final class Personnel $data=['input'=>$in,'status'=>$status,'dependencies'=>$b['dependencies'],'result'=>$result,'synthetic_only'=>true];$state=$d['action']==='prepare'?'prepared':$d['data']['decision'];$version=$d['version']+1;if($state==='approved'){$csv=$this->csv($kind,$d['record_key'],$version,$data);$data['artifact_csv']=$csv;$data['artifact_sha256']=hash('sha256',$csv);} $impact=['before'=>['version'=>$last['version']??0,'state'=>$last['state']??null,'effective'=>$old],'after'=>['version'=>$version,'state'=>$state,'proposal'=>$data,'effective'=>$state==='approved'?$data:$old],'payments'=>0,'debt_created'=>false,'journals'=>0,'legal_payroll'=>false];return['kind'=>$kind,'record_key'=>$d['record_key'],'version'=>$version,'previous_id'=>$last['id']??null,'action'=>$d['action'],'state'=>$state,'prepared_by'=>$prepared,'preparer_identity'=>$identity,'data'=>$data,'impact'=>$impact]; } + + private function refusal(array $s,array $d,array $b,int $prepared,string $identity):array{ + $last=$b['last'];$old=$b['effective']['data']??null;$data=$last['data'];unset($data['artifact_csv'],$data['artifact_sha256']);$currentBalance=null;if($d['kind']==='leave'){try{$currentBalance=$this->balance($s,$data['input']['entitlement_key']);}catch(\Symfony\Component\HttpKernel\Exception\HttpExceptionInterface$e){if($e->getStatusCode()!==409)throw$e;}}$data['refusal_conference']=['current_balance'=>$currentBalance,'observed_dependencies'=>$b['dependencies'],'original_source_changed'=>$this->hash($last['data']['dependencies'])!==$this->hash($b['dependencies']),'proposal_applied'=>false];$version=$d['version']+1;$impact=['before'=>['version'=>$last['version'],'state'=>$last['state'],'effective'=>$old],'after'=>['version'=>$version,'state'=>'refused','proposal'=>$data,'effective'=>$old],'payments'=>0,'debt_created'=>false,'journals'=>0,'legal_payroll'=>false];return['kind'=>$d['kind'],'record_key'=>$d['record_key'],'version'=>$version,'previous_id'=>$last['id'],'action'=>'review','state'=>'refused','prepared_by'=>$prepared,'preparer_identity'=>$identity,'data'=>$data,'impact'=>$impact]; + } public function period(int $actor,array $s,string $worker,string $period):array{ $this->scope($actor,'report');$from=$period.'-01';$to=(new \DateTimeImmutable($from))->modify('last day of this month')->format('Y-m-d');$q=$this->personQuery($this->effective($s,'day'),$worker)->whereRaw("data->>'status'<>'cancelled' AND data->'input'->>'date'>=? AND data->'input'->>'date'<=?",[$from,$to])->orderByRaw("data->'input'->>'date'");$days=[];$known=0;$unknown=0;$unaccounted=0;$contracts=[];$entitlements=[]; foreach($q->cursor()as$r){abort_if(count($days)>=31,409,'Mais de um registo efectivo por dia exige conferência.');$row=$this->row($r);$i=$row['data']['input'];$leaves=$this->leaveForDay($s,$worker,$i['date']);$calc=PersonnelTime::day($i['date'],$i['planned'],$i['actual'],$i['leave_conferred']?$leaves:null);$days[]=['record_key'=>$r->record_key,'version'=>$r->version,'contract_key'=>$i['contract_key'],'calculation'=>$calc];if($calc['unaccounted_minutes']===null)$unknown++;else{$known++;$unaccounted+=$calc['unaccounted_minutes'];}$contracts[$i['contract_key']]=true;} @@ -95,17 +99,20 @@ final class Personnel return['period'=>$period,'worker'=>$this->worker($s,$worker),'days'=>$days,'contracts'=>$contractSources,'leave_balances'=>$entitlements,'payroll'=>$payroll,'known_days'=>count($days),'known_conferred_days'=>$known,'days_with_unknown_conference'=>$unknown,'other_calendar_days_unknown'=>(int)substr($to,-2)-count($days),'unaccounted_known_minutes'=>$unaccounted,'complete_month_proven'=>false,'basis'=>'explicit_days_only_not_legal_calendar','salary_effect'=>false]; } private function csv(string $kind,string $key,int $version,array $data):string{$rows=[['SGF QA - RH INTERNO - NAO E DOCUMENTO LEGAL'],['Tipo',$kind,'Registo',$key,'Versao',$version],['Pessoa ficticia',$data['dependencies']['worker']['code']],['Estado',$data['status']],['Sem salario/pagamento/lancamento automaticos'],['Input',$this->json($data['input'])],['Resultado',$this->json($data['result'])],['Fontes',$this->json($data['dependencies'])]];return implode("\r\n",array_map(fn($r)=>implode(';',array_map(fn($v)=>'"'.str_replace('"','""',"'".(string)$v).'"',$r)),$rows))."\r\n";} - public function export(int $actor,int $id):array{$s=$this->context->identifiers()+['unit_id'=>$this->context->unitId()];$r=$this->row($this->rows($s)->where(['id'=>$id,'state'=>'approved'])->first());abort_unless($r,403);$this->scope($actor,$r['kind']);$d=$r['data'];abort_unless(hash_equals($d['artifact_sha256'],hash('sha256',$d['artifact_csv'])),409,'Exportação conservada não confere.');return['bytes'=>$d['artifact_csv'],'sha256'=>$d['artifact_sha256']];} + private function approvedExportRecord(int $actor,int $id):array{$s=$this->context->identifiers()+['unit_id'=>$this->context->unitId()];$r=$this->row($this->rows($s)->where(['id'=>$id,'state'=>'approved'])->first());abort_unless($r,403);$this->scope($actor,$r['kind']);$d=$r['data'];abort_unless(hash_equals($d['artifact_sha256'],hash('sha256',$d['artifact_csv'])),409,'Exportação conservada não confere.');return$r;} + public function export(int $actor,int $id):array{$r=$this->approvedExportRecord($actor,$id);return['bytes'=>$r['data']['artifact_csv'],'sha256'=>$r['data']['artifact_sha256']];} + public function readable(int $actor,int $id):array{$r=$this->approvedExportRecord($actor,$id);$bytes=PersonnelReadableCsv::render($r);return['bytes'=>$bytes,'sha256'=>hash('sha256',$bytes),'source_sha256'=>$r['data']['artifact_sha256'],'format'=>PersonnelReadableCsv::VERSION];} public function payrollContract(int $actor,string $key,string $worker,string $period,string $gross):array{$s=$this->scope($actor,'contract');$r=$this->source($s,$key,'contract',$worker);$from=$period.'-01';$to=(new \DateTimeImmutable($from))->modify('last day of this month')->format('Y-m-d');abort_unless($this->covers($r,$from,$to)&&$r['data']['input']['gross']===$gross,409,'Contrato revisto não cobre o mês/bruto escolhidos.');return['record_key'=>$key,'version'=>$r['version'],'id'=>$r['id'],'latest_id'=>$this->latest($s,$key)['id'],'reference'=>$r['data']['input']['reference'],'gross'=>$gross,'valid_from'=>$r['data']['input']['valid_from'],'valid_to'=>$r['data']['input']['valid_to']];} public function listing(int $actor,array $input):array{ $d=$this->exact($input,['kind'=>'required|in:contract,day,entitlement,leave,report','worker_key'=>'sometimes|uuid','record_key'=>'sometimes|uuid','period'=>'sometimes|date_format:Y-m','date'=>'sometimes|date_format:Y-m-d','state'=>'sometimes|in:prepared,approved,refused','search'=>'sometimes|string|max:100','page'=>'sometimes|integer|min:1|max:100000','per_page'=>'sometimes|integer|in:25,100']);$s=$this->scope($actor,$d['kind']); return DB::transaction(function()use($actor,$s,$d){if(DB::transactionLevel()===1)DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');$worker=isset($d['worker_key'])?$this->worker($s,strtolower($d['worker_key'])):null;$q=$this->rows($s)->where('kind',$d['kind']);if($worker)$q=$this->personQuery($q,$worker['record_key']);if(isset($d['record_key'])){$q->where('record_key',$d['record_key']);abort_unless((clone$q)->exists(),403);}else$q->whereNotExists(function($n){$n->selectRaw('1')->from('hp_versions as next');foreach(['tenant_id','company_id','unit_id','record_key']as$k)$n->whereColumn('next.'.$k,'hp_versions.'.$k);$n->whereColumn('next.version','>','hp_versions.version');}); if(isset($d['period']))$q->whereRaw("coalesce(data->'input'->>'period',substring(data->'input'->>'date' from 1 for 7))=?",[$d['period']]);if(isset($d['date']))$q->whereRaw("data->'input'->>'date'=?",[$d['date']]);if(isset($d['state']))$q->where('state',$d['state']);if(isset($d['search'])){$needle='%'.str_replace(['!','%','_'],['!!','!%','!_'],$d['search']).'%';$q->whereRaw("(coalesce(data->'input'->>'reference','')||' '||coalesce(data->'dependencies'->'worker'->>'code','')) ILIKE ? ESCAPE '!'",[$needle]);} - $page=$q->orderByDesc('id')->paginate($d['per_page']??25,['*'],'page',$d['page']??1);$keys=$page->pluck('record_key')->all();$effective=$this->effective($s,$d['kind'])->whereIn('record_key',$keys)->get()->keyBy('record_key');$items=[];foreach($page->items()as$r){$a=$this->row($r);$a['effective']=$this->row($effective->get($r->record_key));$items[]=$a;}$page->setCollection(collect($items));$area=$this->area($d['kind']);return['policy'=>self::POLICY,'kind'=>$d['kind'],'worker'=>$worker,'records'=>$page->toArray(),'capabilities'=>['prepare'=>$this->permissions->has($actor,$s['tenant_id'],$s['company_id'],'human_resources.'.$area.'.prepare',$s['unit_id']),'review'=>$this->permissions->has($actor,$s['tenant_id'],$s['company_id'],'human_resources.'.$area.'.review',$s['unit_id'])],'synthetic_only'=>true,'salary_effect'=>false,'legal_framework_validated'=>false];}); + $page=$q->orderByDesc('id')->paginate($d['per_page']??25,['*'],'page',$d['page']??1);$keys=$page->pluck('record_key')->all();$effective=$this->effective($s,$d['kind'])->whereIn('record_key',$keys)->get()->keyBy('record_key');$balances=[];if($d['kind']==='entitlement'&&$keys!==[]){$totals=$this->effective($s,'leave')->whereIn(DB::raw("data->'input'->>'entitlement_key'"),$keys)->selectRaw("data->'input'->>'entitlement_key' balance_key,coalesce(sum((data->'result'->>'minutes')::bigint) FILTER(WHERE data->>'status'='reserved'),0) reserved,coalesce(sum((data->'result'->>'minutes')::bigint) FILTER(WHERE data->>'status'='taken'),0) taken")->groupByRaw("data->'input'->>'entitlement_key'")->get()->keyBy('balance_key');foreach($effective as$key=>$row){$e=$this->row($row);if($e['data']['status']==='cancelled')continue;$sum=$totals->get($key);$initial=$e['data']['input']['minutes'];$reserved=(int)($sum?->reserved??0);$taken=(int)($sum?->taken??0);$balances[$key]=['effective_entitlement_version'=>$e['version'],'entitlement_minutes'=>$initial,'reserved_minutes'=>$reserved,'taken_minutes'=>$taken,'available_minutes'=>$initial===null?null:$initial-$reserved-$taken,'basis'=>'current_effective_requests_at_read'];}} + $items=[];foreach($page->items()as$r){$a=$this->row($r);$a['effective']=$this->row($effective->get($r->record_key));$a['current_balance']=$balances[$r->record_key]??null;$items[]=$a;}$page->setCollection(collect($items));$area=$this->area($d['kind']);return['policy'=>self::POLICY,'kind'=>$d['kind'],'worker'=>$worker,'records'=>$page->toArray(),'capabilities'=>['prepare'=>$this->permissions->has($actor,$s['tenant_id'],$s['company_id'],'human_resources.'.$area.'.prepare',$s['unit_id']),'review'=>$this->permissions->has($actor,$s['tenant_id'],$s['company_id'],'human_resources.'.$area.'.review',$s['unit_id'])],'synthetic_only'=>true,'salary_effect'=>false,'legal_framework_validated'=>false];}); } public function preview(int $actor,array $input,string $key):array{$d=$this->input($input);$this->exact(['key'=>$key],['key'=>'required|uuid']);$s=$this->scope($actor,$d['kind'],$d['action']);return DB::transaction(function()use($actor,$s,$d,$key){$this->lock($actor,$s);$this->scope($actor,$d['kind'],$d['action']);$b=$this->basis($actor,$s,$d);$out=$this->plan($actor,$s,$d,$b);$c=['actor'=>$actor,'scope'=>$s,'generation'=>request()->session()->get('company_context.token'),'input'=>$d,'key'=>strtolower($key),'basis'=>$this->hash($b),'impact'=>$out['impact'],'observed'=>time()];return['preview'=>Crypt::encryptString($this->json($c)),'impact'=>$out['impact']];});} public function confirm(int $actor,string $token):array{ - try{$c=json_decode(Crypt::decryptString($token),true,64,JSON_THROW_ON_ERROR);}catch(\Throwable){abort(409,'Prévia inválida.');}abort_unless(is_array($c)&&is_array($c['input']??null),409);$d=$this->input($c['input']);$s=$this->scope($actor,$d['kind'],$d['action']);return DB::transaction(function()use($actor,$s,$d,$c){$this->lock($actor,$s);$this->scope($actor,$d['kind'],$d['action']);abort_unless($c['actor']===$actor&&$this->hash($c['scope'])===$this->hash($s)&&$c['generation']===request()->session()->get('company_context.token')&&$c['observed']>=time()-600&&$c['observed']<=time(),409,'Pessoa/contexto/prévia alterados.');$q=$s+['actor_id'=>$actor,'command_key'=>$c['key']];$old=DB::table('hp_commands')->where($q)->first();if($old){abort_unless(hash_equals($old->request_hash,$this->hash($d)),409,'Chave usada com outros dados.');return json_decode($old->result,true)+['replayed'=>true];}$b=$this->basis($actor,$s,$d);abort_unless(hash_equals($c['basis'],$this->hash($b)),409,'Origem/versão alterada. Confira novamente.');$out=$this->plan($actor,$s,$d,$b);abort_unless($this->hash($out['impact'])===$this->hash($c['impact']),409,'Impacto mudou.');$row=$out;unset($row['impact'],$row['data']);$id=DB::table('hp_versions')->insertGetId($s+$row+['data'=>$this->json($out['data']),'impact'=>$this->json($out['impact']),'source_basis'=>$this->hash($b['dependencies']),'actor_id'=>$actor,'reason'=>$d['reason'],'created_at'=>now()->utc()]);$result=['id'=>$id,'record_key'=>$d['record_key'],'version'=>$out['version'],'state'=>$out['state'],'impact'=>$out['impact']];DB::table('hp_commands')->insert($q+['request_hash'=>$this->hash($d),'input'=>$this->json($d),'result'=>$this->json($result),'created_at'=>now()->utc()]);Audit::record('human_resources.remunerations.'.$d['action'],$actor,$s['tenant_id'],$s['company_id'],$s['unit_id'],details:['record_id'=>$id,'version'=>$out['version'],'reason'=>$d['reason']]);return$result+['replayed'=>false];}); + try{$c=json_decode(Crypt::decryptString($token),true,64,JSON_THROW_ON_ERROR);}catch(\Throwable){abort(409,'Prévia inválida.');}abort_unless(is_array($c)&&is_array($c['input']??null),409);$d=$this->input($c['input']);$s=$this->scope($actor,$d['kind'],$d['action']);return DB::transaction(function()use($actor,$s,$d,$c){$this->lock($actor,$s);$this->scope($actor,$d['kind'],$d['action']);abort_unless($c['actor']===$actor&&$this->hash($c['scope'])===$this->hash($s)&&$c['generation']===request()->session()->get('company_context.token')&&$c['observed']>=time()-600&&$c['observed']<=time(),409,'Pessoa/contexto/prévia alterados.');$q=$s+['actor_id'=>$actor,'command_key'=>$c['key']];$old=DB::table('hp_commands')->where($q)->first();if($old){abort_unless(hash_equals($old->request_hash,$this->hash($d)),409,'Chave usada com outros dados.');return json_decode($old->result,true)+['replayed'=>true];}$b=$this->basis($actor,$s,$d);abort_unless(hash_equals($c['basis'],$this->hash($b)),409,'Origem/versão alterada. Confira novamente.');$out=$this->plan($actor,$s,$d,$b);abort_unless($this->hash($out['impact'])===$this->hash($c['impact']),409,'Impacto mudou.');$row=$out;unset($row['impact'],$row['data']);$id=DB::table('hp_versions')->insertGetId($s+$row+['data'=>$this->json($out['data']),'impact'=>$this->json($out['impact']),'source_basis'=>$this->hash($b['dependencies']),'actor_id'=>$actor,'reason'=>$d['reason'],'created_at'=>now()->utc()]);$result=['id'=>$id,'record_key'=>$d['record_key'],'version'=>$out['version'],'state'=>$out['state'],'impact'=>$out['impact']];DB::table('hp_commands')->insert($q+['request_hash'=>$this->hash($d),'input'=>$this->json($d),'result'=>$this->json($result),'created_at'=>now()->utc()]);Audit::record('human_resources.'.$this->area($d['kind']).'.'.$d['action'],$actor,$s['tenant_id'],$s['company_id'],$s['unit_id'],details:['record_id'=>$id,'version'=>$out['version'],'reason'=>$d['reason'],'area'=>$this->area($d['kind'])]);return$result+['replayed'=>false];}); } public function receipt(int $actor,string $key):array{$this->exact(['key'=>$key],['key'=>'required|uuid']);$s=$this->scope($actor,'report');$old=DB::table('hp_commands')->where($s+['actor_id'=>$actor,'command_key'=>strtolower($key)])->first();abort_unless($old,404);return['result'=>json_decode($old->result,true),'created_at'=>$old->created_at,'reason'=>json_decode($old->input,true)['reason'],'historical_receipt_only'=>true,'operation_executed'=>false];} diff --git a/app/Modules/HumanResources/PersonnelReadableCsv.php b/app/Modules/HumanResources/PersonnelReadableCsv.php new file mode 100644 index 0000000..1adcaa1 --- /dev/null +++ b/app/Modules/HumanResources/PersonnelReadableCsv.php @@ -0,0 +1,20 @@ +'Activo','terminated'=>'Terminado','cancelled'=>'Cancelado','reserved'=>'Reservado','taken'=>'Gozado'];$titles=['contract'=>'Contrato interno QA','day'=>'Plano e assiduidade QA','entitlement'=>'Condição de férias QA','leave'=>'Pedido e gozo de férias QA','report'=>'Conferência do período QA']; + $lines=[['SGF — RECURSOS HUMANOS — RESUMO INTERNO QA'],['Documento',$titles[$row['kind']]],['Natureza','Vista legível derivada da versão conservada; não é documento legal nem nova decisão.'],['Colaborador fictício',$d['dependencies']['worker']['code']],['Registo',$row['record_key']],['Versão revista',$row['version']],['Estado nesta versão',$states[$d['status']]??$d['status']],['Preparação','QA'.$row['prepared_by']],['Autor desta revisão','QA'.$row['actor_id']],['Data da revisão (origem UTC)',$row['created_at']],['Fundamento',$row['reason']],['Referência da evidência',$i['evidence']],['Formato de leitura',self::VERSION],['SHA do CSV original conservado',$d['artifact_sha256']],[]]; + if($row['kind']==='contract'){$lines=[...$lines,['CONDIÇÕES DO CONTRATO'],['Referência',$i['reference']],['Modalidade interna',['fixed'=>'Prazo explícito','open'=>'Sem termo declarado','unknown_end'=>'Fim desconhecido'][$i['mode']]],['Início',$i['valid_from']],['Fim',$i['valid_to']??'Não confirmado'],['Bruto mensal declarado',$i['gross'].' MZN'],['Registos dependentes observados nesta decisão',$r['dependent_records']],['Validação jurídica','Não demonstrada']];} + if($row['kind']==='day'){$lines=[...$lines,['ASSIDUIDADE DO DIA'],['Dia local',$i['date']],['Zona',$r['zone']],['Referência da declaração',$i['reference_on']],['Tempo previsto',self::time($r['planned_minutes'])],['Tempo declarado',self::time($r['declared_minutes'])],['Férias conferidas',self::time($r['leave_minutes'])],['Tempo por conferir',self::time($r['unaccounted_minutes'])],['Fora do plano (sem autorização de horas extra)',self::time($r['outside_plan_minutes'])]];foreach(['planned'=>'Intervalos previstos','actual'=>'Intervalos declarados']as$key=>$title){$lines[]=[$title];if($i[$key]===null)$lines[]=['Informação','Desconhecida'];elseif($i[$key]===[])$lines[]=['Informação','Conjunto explicitamente vazio'];else foreach($i[$key]as$range)$lines[]=['Desde / até (hora local)',sprintf('%02d:%02d',intdiv($range['from'],60),$range['from']%60),sprintf('%02d:%02d',intdiv($range['to'],60),$range['to']%60)];}} + if(in_array($row['kind'],['entitlement','leave'],true)){$b=$r['balance_after']??$r;$lines[]=['FÉRIAS — VALORES CONSERVADOS NESTA VERSÃO'];if($row['kind']==='entitlement')$lines[]=['Vigência',$i['valid_from'].' a '.$i['valid_to']];else{$lines[]=['Dia',$i['date']];$lines[]=['Pedido',self::time($r['minutes'])];}$lines=[...$lines,['Orçamento explícito',self::time($b['entitlement_minutes']??null)],['Reservado',self::time($b['reserved_minutes'])],['Gozado',self::time($b['taken_minutes'])],['Disponível',self::time($b['available_minutes'])],['Limite','Orçamento fictício; não comprova direito legal anual. Não é saldo actual se decisões posteriores existirem.']];} + if($row['kind']==='report'){$lines=[...$lines,['CONFERÊNCIA DO PERÍODO'],['Período',$r['period']],['Dias com registos',$r['known_days']],['Dias conferidos',$r['known_conferred_days']],['Dias com conferência desconhecida',$r['days_with_unknown_conference']],['Outros dias sem cobertura comprovada',$r['other_calendar_days_unknown']],['Mês completo','Não demonstrado; dias sem registos não são faltas presumidas.'],[],['Dia local','Versão do dia','Previsto','Declarado','Férias conferidas','Por conferir']];foreach($r['days']as$day){$c=$day['calculation'];$lines[]=[$c['date'],$day['version'],self::time($c['planned_minutes']),self::time($c['declared_minutes']),self::time($c['leave_minutes']),self::time($c['unaccounted_minutes'])];}$lines[]=[];$lines[]=['SALDOS DE FÉRIAS NESTA CONFERÊNCIA','Versão da condição','Orçamento','Reservado','Gozado','Disponível'];foreach($r['leave_balances']as$b)$lines[]=['Condição QA',$b['entitlement_version'],self::time($b['entitlement_minutes']),self::time($b['reserved_minutes']),self::time($b['taken_minutes']),self::time($b['available_minutes'])];$lines[]=[];$lines[]=['PROCESSAMENTOS OBSERVADOS','Versão','Líquido interno (centavos)','Ligação contratual comprovada'];foreach($r['payroll']as$p)$lines[]=['Processamento QA',$p['version'],$p['net_cents']??'Indeterminado',$p['contract_link_proven']?'Sim, escolhida na origem':'Não; sem atribuição retroactiva'];} + $lines=[...$lines,[],['Limites','Sem salário, dívida, pagamento, lançamento ou submissão automáticos. Sem conformidade legal presumida.'],['Fonte','Apenas esta versão revista. Não consulta nem recalcula o estado actual.']]; + // Apostrophe protects all free text, including formula prefixes after whitespace. BOM supports local Excel. + return "\xEF\xBB\xBF".implode("\r\n",array_map(fn($line)=>implode(';',array_map(fn($cell)=>'"'.str_replace('"','""',"'".(string)$cell).'"',$line)),$lines))."\r\n"; + } +} diff --git a/app/Modules/Identity/Services/IdentityWorkflow.php b/app/Modules/Identity/Services/IdentityWorkflow.php index 9fd6e52..6622439 100644 --- a/app/Modules/Identity/Services/IdentityWorkflow.php +++ b/app/Modules/Identity/Services/IdentityWorkflow.php @@ -51,6 +51,8 @@ final class IdentityWorkflow 'analysis_qa_enabled'=>app()->environment('budget-testing')&&config('analysis_qa.enabled')===true, 'bank_qa_enabled'=>app()->environment('budget-testing')&&config('bank_qa.enabled')===true, 'fiscal_rules_qa_enabled'=>app()->environment('budget-testing')&&config('fiscal_rules_qa.enabled')===true, + 'multiline_invoices_enabled'=>app()->environment('budget-testing')&&config('multiline_invoices.enabled')===true, + 'fixed_assets_enabled'=>app()->environment('budget-testing')&&config('fixed_assets.enabled')===true, 'internal_statements_enabled'=>app()->environment('budget-testing')&&config('internal_statements.enabled')===true, 'hr_personnel_enabled'=>app()->environment('budget-testing')&&config('hr_personnel.enabled')===true, 'remunerations_enabled'=>app()->environment('budget-testing')&&config('remunerations.enabled')===true, diff --git a/app/Modules/Services/FiscalCoverage.php b/app/Modules/Services/FiscalCoverage.php index 7f23d18..27f7d2f 100644 --- a/app/Modules/Services/FiscalCoverage.php +++ b/app/Modules/Services/FiscalCoverage.php @@ -7,22 +7,23 @@ final class FiscalCoverage public static function describe(): array { return [ - 'version'=>'SGF-FISCAL-COVERAGE-v2', + 'version'=>'SGF-FISCAL-COVERAGE-v4', 'automatic_knowledge_import'=>false, 'legal_validation'=>false, 'notice'=>'Conhecimento em documentos ou conversas não passa automaticamente para a aplicação. Cada regra exige fonte, versão, âmbito, provas e revisão antes de utilização.', 'taxes'=>[ ['id'=>'iva','label'=>'IVA','state'=>'partial','available'=>'Cálculo delimitado de serviço, MZN, uma linha e imposto acrescido; perfis versionados, revisão distinta e decisão temporal. Documentos QA e diário integrado; livro/mapa interno da origem seleccionada revistos distintamente. Núcleo comum com versões/vigência e cálculo interno QA de1–25linhas, preço acrescido/incluído, exactos e diferenças preservados.','pending'=>'Emissão documental por múltiplas linhas/tratamentos, livros gerais de apuramento/dedução, regularizações e modelos oficiais.','legal_use'=>false], - ['id'=>'irps','label'=>'IRPS','state'=>'pending','available'=>null,'pending'=>'Motor operacional de rendimentos/retenções, remunerações, apuramento e modelos ligados às operações do SGF.','legal_use'=>false], + ['id'=>'irps','label'=>'IRPS','state'=>'partial','available'=>'RH autónomo com cadastro, contratos/tempos/férias e processamento interno QA revisto; retenção artificial explicada e mapa conservado.','pending'=>'Cálculo legal de IRPS por regime/rendimento, retenções/apuramento e modelos oficiais; o ensaio interno não valida o imposto.','legal_use'=>false], ['id'=>'irpc','label'=>'IRPC','state'=>'pending','available'=>null,'pending'=>'Ponte do resultado contabilístico para ajustamentos/base/colecta, retenções e modelos no SGF.','legal_use'=>false], ['id'=>'ispc','label'=>'ISPC','state'=>'pending','available'=>null,'pending'=>'Enquadramento, cálculo, obrigações e modelos operacionais no SGF.','legal_use'=>false], ['id'=>'ice','label'=>'ICE','state'=>'pending','available'=>null,'pending'=>'Classificação dos bens, parâmetros com vigência, liquidação e obrigações operacionais no SGF.','legal_use'=>false], - ['id'=>'inss','label'=>'INSS','state'=>'pending','available'=>null,'pending'=>'Cadastro/processamento de remunerações, contribuições e modelos no SGF.','legal_use'=>false], + ['id'=>'inss','label'=>'INSS','state'=>'partial','available'=>'Cadastro e processamento interno QA em RH, com desconto do trabalhador e encargo patronal artificiais separados e mapa revisto.','pending'=>'Regimes/bases/contribuições legais INSS, reconciliação e modelos oficiais; percentagens do exercício não são parâmetros legais activados.','legal_use'=>false], ['id'=>'other','label'=>'Outros impostos e obrigações','state'=>'pending','available'=>null,'pending'=>'Identificação dos tributos aplicáveis, calendário legal, multas/juros e modelos; sem taxas ou prazos presumidos.','legal_use'=>false], ], 'management'=>[ + ['id'=>'human_resources','label'=>'Recursos Humanos','state'=>'partial','available'=>'Módulo autónomo e perfis próprios: cadastro, contratos internos, plano/assiduidade, condições/pedidos/gozo de férias, conferência de período e remunerações internas QA; histórico e resumos legíveis.','pending'=>'Aplicabilidade laboral real, calendário/turnos gerais, benefícios, cálculo legal IRPS/INSS e efeitos salariais financeiros/contabilísticos.'], ['id'=>'finance','label'=>'Gestão financeira','state'=>'partial','available'=>'Percursos QA de orçamento de Compras, obrigações/recebíveis, afectações/reversões e Caixa com fecho; conferência individual de extracto artificial contra duas linhas existentes.','pending'=>'Operação geral real, bancos e reconciliação bancária geral.'], - ['id'=>'accounting','label'=>'Contabilidade','state'=>'partial','available'=>'Diário, plano, razão, balancete e conferência/fecho QA com origens distintas.','pending'=>'Demonstrações financeiras legais, encerramento anual real e integração dos apuramentos fiscais.'], + ['id'=>'accounting','label'=>'Contabilidade','state'=>'partial','available'=>'Diário, plano, razão, balancete e conferência/fecho QA; demonstrações internas parciais revistas/conservadas, com contas não classificadas explícitas. Cadastro/custódia, plano mensal, quota e diário de activos QA integrados, com correcção e abate no recorte sintético.','pending'=>'Operação geral de activos e tratamento fiscal de depreciações, demonstrações financeiras legais, encerramento anual real e integração dos apuramentos fiscais.'], ['id'=>'analysis','label'=>'Análise financeira','state'=>'partial','available'=>'Custos directos/industriais, orçamento versus realizado, diário/saldos e dois critérios/indicadores QA revistos com fontes explícitas e relatórios.','pending'=>'Rácios de liquidez/rentabilidade/endividamento e tendências conciliadas, com plano de contas e critérios aprovados.'], ], 'rule_lifecycle'=>['Fonte identificada e versão preservada','Aplicabilidade e vigência explícitas','Cálculo exacto com pressupostos/pendências','Provas e revisão por pessoa distinta','Activação específica e histórico sem reescrever operações anteriores'], diff --git a/app/Modules/Services/FiscalRules.php b/app/Modules/Services/FiscalRules.php index be5c84a..7ff844c 100644 --- a/app/Modules/Services/FiscalRules.php +++ b/app/Modules/Services/FiscalRules.php @@ -94,6 +94,15 @@ final class FiscalRules $gate=$this->evaluate($s,['base'=>'0','currency'=>$in['currency'],'date'=>$in['date']],$selected);if($gate['state']!=='determined_qa'){unset($gate['base']);return $gate+['policy'=>FiscalDocumentMath::POLICY,'price_mode'=>$in['price_mode'],'base_document'=>null,'lines'=>[]];} return FiscalDocumentMath::calculate($in['lines'],$in['price_mode'],$selected['definition']['parameters']['rate'])+['currency'=>$in['currency'],'rule'=>$gate['rule']]; } + /** Explicit read-only QA bridge; does not change the module required by rule administration. */ + public function invoiceDocument(int $actor,array $scope,int $event,array $input,?int $boundCalculation=null):array + { + abort_unless(config('multiline_invoices.enabled')===true&&config('fiscal_rules_qa.enabled')===true,404);CompositionRuntime::assert();$actual=$this->context->identifiers()+['unit_id'=>$this->context->unitId()];abort_unless($actor===$this->context->userId()&&$actual===$scope&&ModuleRecords::active()==='commercial',403);if(!$this->permissions->has($actor,$scope['tenant_id'],$scope['company_id'],'fiscal_rules.view',$scope['unit_id'])){ + // Invoice readers cannot enumerate/administer the rule catalogue. Only revalidate + // a calculation already captured for this exact draft/input by a fiscal preparer. + $this->permissions->require($actor,$scope['tenant_id'],$scope['company_id'],'billing_drafts.view',$scope['unit_id']);$row=$boundCalculation?DB::table('mi_calculations')->where($scope)->where('id',$boundCalculation)->first():null;abort_unless($row,403,'Regra exige captura documental previamente autorizada.');$frozen=json_decode($row->snapshot,true,64,JSON_THROW_ON_ERROR);$expected=$frozen['common']['input'];unset($expected['rule_event_id']);abort_unless((int)$frozen['common']['rule']['id']===$event&&$this->hash($expected)===$this->hash($input),409,'Entrada/data da captura fiscal mudou.'); + }$input['rule_event_id']=$event;$in=$this->documentInput($input);$row=$this->events($scope)->where('id',$event)->first();abort_unless($row,403,'Regra fora do contexto.');$selected=$this->row($row);$this->fixture();return ['rule'=>$selected,'result'=>$this->evaluateDocument($scope,$in,$selected),'input'=>$in,'rule_stream'=>['count'=>$this->events($scope)->count(),'last_id'=>$this->events($scope)->max('id')]]; + } public function documentReport(int $actor,int $id):array { $detail=$this->detail($actor,$id,'calculation');$r=$detail['result'];abort_unless(isset($r['price_mode'],$r['lines']),422,'Este registo não é cálculo por linhas.');$fp=fopen('php://temp','w+');$rule=$r['rule']??[];$rows=[['MAPA INTERNO POR LINHAS QA — SEM VALIDADE FISCAL'],['Politica',$r['policy']],['Modo',$r['price_mode']],['Data',$detail['input']['date']],['Moeda',$detail['input']['currency']],['Regra escolhida',(string)($detail['input']['rule_event_id']??'INDETERMINADA')],['Versao regra',(string)($rule['version']??'INDETERMINADA')],['Fonte',$rule['source_reference']??'INDETERMINADA'],['SHA fonte',$rule['source_sha256']??'INDETERMINADO'],['Estado historico',$r['state']],['Estado actual',$detail['current_evaluation']['state']],['Linha','Referencia','Base exacta fraccao','Imposto exacto fraccao','Base documental','Imposto documental','Total','Diferenca base fraccao','Diferenca imposto fraccao']];foreach($r['lines']as$line){$ref="'".$line['reference'];$rows[]=[(string)$line['line'],$ref,$line['base_exact_fraction'],$line['tax_exact_fraction'],$line['base_document']??'INDETERMINADO',$line['tax_document'],$line['total_document']??'INDETERMINADO',$line['base_difference_fraction']??'INDETERMINADO',$line['tax_difference_fraction']];}$rows[]=['Totais documentais','','','',$r['base_document']??'INDETERMINADO',$r['tax_document']??'INDETERMINADO',$r['total']??'INDETERMINADO'];foreach($rows as$row)fputcsv($fp,$row,';','"','',"\r\n");rewind($fp);$bytes=stream_get_contents($fp);$s=$this->scope($actor);Audit::record('fiscal_rule.document_report',$actor,$s['tenant_id'],$s['company_id'],$s['unit_id'],details:['record_id'=>$id,'version'=>0,'review_hash'=>hash('sha256',$bytes)]);return ['bytes'=>$bytes,'sha256'=>hash('sha256',$bytes)]; diff --git a/app/Modules/Services/Http/MultiLineInvoiceController.php b/app/Modules/Services/Http/MultiLineInvoiceController.php new file mode 100644 index 0000000..481692e --- /dev/null +++ b/app/Modules/Services/Http/MultiLineInvoiceController.php @@ -0,0 +1,31 @@ +json($value)->header('Cache-Control','no-store, private');}catch(\Illuminate\Database\QueryException$e){if(!in_array($e->errorInfo[0]??null,['55P03','57014'],true))throw$e;return response()->json(['message'=>'Documento ocupado. Recupere a mesma operação, sem gerar outra chave.'],503)->header('Retry-After','5')->header('Cache-Control','no-store');}} + private function exactQuery(Request $r,array $rules):array{abort_if(array_diff(array_keys($r->query()),array_keys($rules)),422);return Validator::make($r->query(),$rules)->validate();} + public function catalogue(Request $r,MultiLineInvoiceCalculation $mi){return$this->response(function()use($r,$mi){$s=$mi->scope($r->user()->id,'unit.view');$d=$this->exactQuery($r,['kind'=>'required|in:clients,services,drafts,rules,series,temporals,calculations','draft_id'=>'sometimes|integer|min:1','search'=>'sometimes|nullable|string|max:100','page'=>'sometimes|integer|min:1|max:100000','per_page'=>'sometimes|integer|in:25,100']);$kind=$d['kind'];$q=null; + if(in_array($kind,['clients','services','drafts'],true)){$area=$kind==='drafts'?'billing_drafts':$kind;app(ServiceWorkflow::class)->scope($area,$r->user()->id,'view');$q=ModuleRecords::query('a_'.$area)->where('tenant_id',$s['tenant_id'])->where('company_id',$s['company_id'])->where('unit_id',$kind==='drafts'?$s['unit_id']:null);if($kind==='drafts')$q->whereIn('id',DB::table('mi_drafts')->where($s)->select('draft_id'));if(!empty($d['search'])){$v='%'.str_replace(['!','%','_'],['!!','!%','!_'],$d['search']).'%';$q->whereRaw("name ILIKE ? ESCAPE '!'",[$v]);}$columns=['id','name','version'];} + elseif($kind==='rules'){$s=$mi->scope($r->user()->id,'fiscal_rules.view');$q=DB::table('fr_events as f')->where(['f.tenant_id'=>$s['tenant_id'],'f.company_id'=>$s['company_id'],'f.unit_id'=>$s['unit_id'],'f.state'=>'reviewed','f.definition->family'=>'qa','f.definition->source_status'=>'qa_checked'])->whereNotExists(function($n){$n->selectRaw('1')->from('fr_events as n')->whereColumn('n.tenant_id','f.tenant_id')->whereColumn('n.company_id','f.company_id')->whereColumn('n.unit_id','f.unit_id')->whereColumn('n.rule_key','f.rule_key')->whereColumn('n.rule_version','f.rule_version')->whereColumn('n.revision','>','f.revision');});$columns=['f.id','f.rule_key','f.rule_version','f.definition'];} + elseif($kind==='series'||$kind==='temporals'){$mi->scope($r->user()->id,'service_invoices.view');$q=DB::table('si_proposals')->where($s)->where('kind',$kind==='series'?'series':'temporal');if($kind==='series')$q->where('identifier','like','TEST-MULTI-%');else{abort_unless(isset($d['draft_id']),422);app(ServiceWorkflow::class)->show('billing_drafts',(int)$d['draft_id'],$r->user()->id);$q->where('data->draft_id',(int)$d['draft_id'])->where('data->calculation_policy',MultiLineInvoiceCalculation::POLICY);}$columns=['id','identifier','version','kind','actor_id','data','created_at'];} + else{abort_unless(isset($d['draft_id']),422);app(ServiceWorkflow::class)->show('billing_drafts',(int)$d['draft_id'],$r->user()->id);$q=DB::table('mi_calculations')->where($s)->where('draft_id',(int)$d['draft_id']);$columns=['id','actor_id','snapshot','created_at'];} + if(!empty($d['search'])&&in_array($kind,['rules','series','temporals','calculations'],true)){$needle='%'.str_replace(['!','%','_'],['!!','!%','!_'],$d['search']).'%';$column=match($kind){'rules'=>"f.definition->>'label'",'calculations'=>'id::text',default=>'identifier'};$q->whereRaw($column." ILIKE ? ESCAPE '!'",[$needle]);} + $page=$q->orderByDesc($kind==='rules'?'f.id':'id')->paginate((int)($d['per_page']??25),$columns,'page',(int)($d['page']??1));$decisions=in_array($kind,['series','temporals'],true)?DB::table('si_decisions')->whereIn('proposal_id',$page->pluck('id')->all())->get(['proposal_id','actor_id','decision','reason'])->keyBy('proposal_id'):collect();$items=[];foreach($page as$row){$v=(array)$row;if($kind==='rules'){$definition=json_decode($v['definition'],true);unset($v['definition']);$v+=['name'=>$definition['label'],'valid_from'=>$definition['valid_from'],'valid_until'=>$definition['valid_until'],'rate'=>$definition['parameters']['rate']];}elseif(in_array($kind,['series','temporals'],true)){$v['data']=json_decode($v['data'],true);$v['decision']=$decisions->get($v['id']);}elseif($kind==='calculations'){$snapshot=json_decode($v['snapshot'],true,64);unset($v['snapshot']);$v+=['name'=>'Cálculo #'.$v['id'],'result'=>$snapshot['common']['result'],'rule_event_id'=>$snapshot['common']['rule']['id'],'draft_version'=>$snapshot['document']['draft_version'],'preparation_version'=>$snapshot['document']['preparation_version'],'date'=>$snapshot['expected_date']];}$items[]=$v;}$page->setCollection(collect($items));return ['kind'=>$kind,'records'=>$page->toArray()];});} + public function editor(Request $r,int $id,MultiLineInvoiceCalculation $mi){abort_if($r->query()!==[],422);return$this->response(function()use($r,$id,$mi){$s=$mi->scope($r->user()->id);$binding=DB::table('mi_drafts')->where($s)->where('draft_id',$id)->first();abort_unless($binding,403);$w=app(ServiceWorkflow::class);$billing=$w->show('billing_drafts',$id,$r->user()->id);$source=$w->show('service_drafts',(int)$binding->source_id,$r->user()->id);$p=app(\App\Modules\Identity\Services\Permissions::class);$prep=$p->has($r->user()->id,$s['tenant_id'],$s['company_id'],'draft_documents.view',$s['unit_id'])?app(DocumentPreparation::class)->show('billing_drafts',$id,$r->user()->id):null;$parties=[];foreach(['issuer'=>$s['company_id'],'client'=>$billing['client_id']]as$kind=>$target){if($p->has($r->user()->id,$s['tenant_id'],$s['company_id'],$kind==='issuer'?'issuer_documents.view':'client_documents.view'))$parties[$kind]=app(DocumentPreparation::class)->show($kind,(int)$target,$r->user()->id);}$invoice=ModuleRecords::query('si_invoices')->where($s)->where('draft_id',$id)->first(['id','series_code','number']);if($invoice&&$p->has($r->user()->id,$s['tenant_id'],$s['company_id'],'service_invoices.view',$s['unit_id']))$invoice->totals=app(ServiceInvoicing::class)->show((int)$invoice->id,$r->user()->id)['snapshot']['calculation']['common_document_result']??null;return['billing'=>$billing,'source'=>$source,'preparation'=>$prep,'parties'=>$parties,'invoice'=>$invoice,'policy'=>MultiLineInvoiceCalculation::POLICY,'server_date'=>now(ServiceInvoicing::ZONE)->format('Y-m-d')];});} + public function draftPreview(Request $r,MultiLineDrafts $d){abort_if(strlen(json_encode($r->all()))>65536,422);return$this->response(fn()=>$d->preview($r->user()->id,$r->all(),strtolower((string)$r->header('Idempotency-Key'))));} + public function draftConfirm(Request $r,MultiLineDrafts $d){abort_if(array_diff(array_keys($r->all()),['preview']),422);$r->validate(['preview'=>'required|string|max:262144']);return$this->response(fn()=>$d->confirm($r->user()->id,$r->input('preview')));} + public function calculationPreview(Request $r,MultiLineInvoiceCalculation $c){abort_if(strlen(json_encode($r->all()))>16384,422);return$this->response(fn()=>$c->preview($r->user()->id,$r->all(),strtolower((string)$r->header('Idempotency-Key'))));} + public function calculationConfirm(Request $r,MultiLineInvoiceCalculation $c){abort_if(array_diff(array_keys($r->all()),['preview']),422);$r->validate(['preview'=>'required|string|max:262144']);return$this->response(fn()=>$c->confirm($r->user()->id,$r->input('preview')));} + public function receipt(Request $r,string $key,MultiLineInvoiceCalculation $c){abort_if($r->query()!==[],422);return$this->response(fn()=>$c->receipt($r->user()->id,strtolower($key)));} + private function command(Request $r,\Closure $work){return$this->response(fn()=>DB::transaction(function()use($r,$work){DB::statement("SET LOCAL lock_timeout='5s'");app(MultiLineInvoiceCalculation::class)->scope($r->user()->id);return$work();}));} + private function administrative(Request $r,string $domain,string $right,?int $target,\Closure $work){return$this->command($r,function()use($r,$domain,$right,$target,$work){$s=app(MultiLineInvoiceCalculation::class)->scope($r->user()->id);app(\App\Modules\Identity\Services\Permissions::class)->require($r->user()->id,$s['tenant_id'],$s['company_id'],$right);$key=strtolower((string)$r->header('Idempotency-Key'));abort_unless(preg_match('/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/D',$key),422);$input=['domain'=>$domain,'target'=>$target,'data'=>$r->all()];$hash=hash('sha256',\Tempo\Finance\Payables\Payables::canonical($input));DB::table('companies')->where(['tenant_id'=>$s['tenant_id'],'id'=>$s['company_id']])->lockForUpdate()->firstOrFail();$s=app(MultiLineInvoiceCalculation::class)->scope($r->user()->id);app(\App\Modules\Identity\Services\Permissions::class)->require($r->user()->id,$s['tenant_id'],$s['company_id'],$right);$q=$s+['actor_id'=>$r->user()->id,'command_key'=>$key];$old=DB::table('mi_commands')->where($q)->first();if($old){abort_unless(hash_equals($old->request_hash,$hash),409,'Chave usada noutra operação.');return json_decode($old->result,true)+['replayed'=>true];}$out=$work();$out['type']=$domain;DB::table('mi_commands')->insert($q+['request_hash'=>$hash,'input'=>\Tempo\Finance\Payables\Payables::canonical($input),'result'=>\Tempo\Finance\Payables\Payables::canonical($out),'created_at'=>now()->utc()]);return$out+['replayed'=>false];});} + public function temporal(Request $r){abort_if(($r->input('kind')!=='temporal')||$r->input('data.calculation_policy')!==MultiLineInvoiceCalculation::POLICY,422);return$this->administrative($r,'temporal','service_fiscal.propose',null,fn()=>app(ServiceInvoicing::class)->propose($r->user()->id,$r->all()));} + public function temporalReview(Request $r,int $id){return$this->administrative($r,'temporal_review','service_fiscal.approve',$id,function()use($r,$id){$s=app(MultiLineInvoiceCalculation::class)->scope($r->user()->id);abort_unless(DB::table('si_proposals')->where($s)->where('id',$id)->where('data->calculation_policy',MultiLineInvoiceCalculation::POLICY)->exists(),403);return app(ServiceInvoicing::class)->decide($id,$r->user()->id,$r->all());});} + public function readiness(Request $r,int $id){abort_if(array_diff(array_keys($r->all()),['series_id','rule_id']),422);$d=$r->validate(['series_id'=>'required|integer|min:1','rule_id'=>'required|integer|min:1']);return$this->command($r,fn()=>app(ServiceInvoicing::class)->review($id,(int)$d['series_id'],(int)$d['rule_id'],$r->user()->id));} + public function issue(Request $r,int $id){return$this->command($r,function()use($r,$id){$s=app(MultiLineInvoiceCalculation::class)->scope($r->user()->id);abort_unless(DB::table('mi_drafts')->where($s)->where('draft_id',$id)->exists()&&DB::table('si_proposals')->where($s)->where('id',$r->input('rule_id'))->where('data->calculation_policy',MultiLineInvoiceCalculation::POLICY)->exists(),403);return app(ServiceInvoicing::class)->issue($id,$r->user()->id,$r->all(),(string)$r->header('Idempotency-Key'));});} + public function issueReceipt(Request $r,string $key,MultiLineInvoiceCalculation $c){abort_if($r->query()!==[],422);return$this->response(function()use($r,$key,$c){$s=$c->scope($r->user()->id,'service_invoices.view');abort_unless(preg_match('/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/D',$key),422);$row=DB::table('si_commands')->where($s+['actor_id'=>$r->user()->id,'command_key'=>$key])->first();abort_unless($row,404);$result=json_decode($row->result,true);$invoice=app(ServiceInvoicing::class)->show((int)$result['id'],$r->user()->id);abort_unless(($invoice['snapshot']['calculation_policy']??null)===MultiLineInvoiceCalculation::POLICY,404);return ['result'=>$result,'historical_receipt_only'=>true,'operation_executed'=>false];});} +} diff --git a/app/Modules/Services/InvoicePdf.php b/app/Modules/Services/InvoicePdf.php index 9e3e6a1..31fd724 100644 --- a/app/Modules/Services/InvoicePdf.php +++ b/app/Modules/Services/InvoicePdf.php @@ -7,6 +7,7 @@ final class InvoicePdf public const VERSION='sgf-test-invoice-text-v2'; public static function lines(array $s): array { + if(($s['calculation_policy']??null)===MultiLineInvoiceCalculation::POLICY)return MultiLineInvoicePdf::lines($s); $lines=[['title','Factura de serviço - demonstração isolada'],['text',$s['series_code'].' / '.$s['number']],['text','Emissão: '.$s['issued_local'].' ('.$s['timezone'].')'],['section','Emitente']]; foreach(['issuer'=>'Emitente','client'=>'Cliente'] as $key=>$title) { if($key==='client') $lines[]=['section',$title]; diff --git a/app/Modules/Services/MultiLineDrafts.php b/app/Modules/Services/MultiLineDrafts.php new file mode 100644 index 0000000..a76e98c --- /dev/null +++ b/app/Modules/Services/MultiLineDrafts.php @@ -0,0 +1,31 @@ +'present|nullable|integer|min:1','version'=>'required|integer|min:0','source_version'=>'required|integer|min:0','preparation_version'=>'required|integer|min:0','name'=>'required|string|min:3|max:255','client_id'=>'required|integer|min:1','currency'=>'required|in:MZN','price_mode'=>'required|in:undefined,added,included','service_date'=>'present|nullable|date_format:Y-m-d','period_start'=>'present|nullable|date_format:Y-m-d','period_end'=>'present|nullable|date_format:Y-m-d|after_or_equal:period_start','proposed_due_date'=>'present|nullable|date_format:Y-m-d','payment_terms'=>'present|nullable|string|max:1000','lines'=>'required|array|min:1|max:25','lines.*'=>'array:service_id,description,quantity,unit_price,unit','lines.*.service_id'=>'required|integer|min:1','lines.*.description'=>'nullable|string|max:2000','lines.*.quantity'=>'present|nullable|string|max:30','lines.*.unit_price'=>'present|nullable|string|max:30','lines.*.unit'=>'present|nullable|string|max:80','reason'=>'required|string|min:10|max:2000'];abort_if(array_diff(array_keys($v),array_filter(array_keys($rules),fn($k)=>!str_contains($k,'*'))),422);$d=Validator::make($v,$rules)->validate();foreach(['draft_id','version','source_version','preparation_version','client_id']as$k)if($d[$k]!==null)$d[$k]=(int)$d[$k];abort_unless(array_is_list($d['lines']),422);foreach($d['lines']as$i=>$row){$d['lines'][$i]['service_id']=(int)$row['service_id'];foreach(['quantity','unit_price']as$f)$d['lines'][$i][$f]=DraftValues::decimal($row[$f],'lines.'.$i.'.'.$f,$f==='quantity');}foreach([$d['name'],$d['payment_terms'],$d['reason'],...array_column($d['lines'],'description')]as$value)if(is_string($value))abort_if((bool)preg_match('/(?:password|palavra.passe|token|secret|cvv|cvc|pin)\\s*[:=]/iu',$value),422,'Não introduza credenciais ou segredos.');return$d; + } + private function scope(int $actor,array $d):array{ + $s=app(MultiLineInvoiceCalculation::class)->scope($actor,$d['draft_id']===null?'billing_drafts.create':'billing_drafts.edit');$m=DB::table('mi_draft_meta')->where('policy',MultiLineInvoiceCalculation::POLICY)->first();abort_unless($m&&$m->composition===\App\Shared\Installation\CompositionRuntime::physical()['composition']&&hash_equals($m->source_hash,(string)config('multiline_invoices.draft_schema_sha256')),503,'Composição de rascunhos não conferida.');return$s; + } + private function basis(int $actor,array $s,array $d):array{ + $w=app(ServiceWorkflow::class);$w->scope('clients',$actor,'view');$w->scope('services',$actor,'view');$client=\App\Shared\Context\ModuleRecords::query('a_clients')->where('tenant_id',$s['tenant_id'])->where('company_id',$s['company_id'])->where('id',$d['client_id'])->first();abort_unless($client,403);$ids=array_unique(array_column($d['lines'],'service_id'));$services=\App\Shared\Context\ModuleRecords::query('a_services')->where('tenant_id',$s['tenant_id'])->where('company_id',$s['company_id'])->whereIn('id',$ids)->orderBy('id')->get();abort_unless($services->count()===count($ids),403,'Serviço fora do módulo/contexto.');$before=null;$binding=null; + if($d['draft_id']!==null){$binding=DB::table('mi_drafts')->where($s)->where('draft_id',$d['draft_id'])->first();abort_unless($binding,403,'Este editor altera apenas os novos rascunhos multilinha.');$bill=$w->show('billing_drafts',$d['draft_id'],$actor);$source=$w->show('service_drafts',(int)$binding->source_id,$actor);$prepared=app(DocumentPreparation::class)->show('billing_drafts',$d['draft_id'],$actor);abort_unless($bill['version']===$d['version']&&$source['version']===$d['source_version']&&$prepared['version']===$d['preparation_version'],409,'Origem/rascunho/preparação alterados.');abort_if(DB::table('si_invoices')->where('draft_id',$d['draft_id'])->orWhere('source_id',$binding->source_id)->exists(),409,'Documento emitido preservado; não editar a origem por este percurso.');$before=['source'=>$source,'billing'=>$bill,'preparation'=>$prepared];}else abort_unless($d['version']===0&&$d['source_version']===0&&$d['preparation_version']===0,409); + return['client'=>(array)$client,'services'=>$services->map(fn($r)=>(array)$r)->all(),'binding'=>$binding?(array)$binding:null,'before'=>$before]; + } + private function write(int $actor,array $s,array $d,array $b,string $key):array{ + $w=app(ServiceWorkflow::class);$lines=array_map(function($l){unset($l['unit']);return$l;},$d['lines']);$sourceInput=['name'=>$d['name'],'client_id'=>$d['client_id'],'currency'=>$d['currency'],'lines'=>$lines];if($b['binding'])$sourceInput['version']=$d['source_version'];$source=$w->save('service_drafts',$b['binding']['source_id']??null,$actor,$sourceInput,$key.'-source');$billInput=$b['binding']?['version'=>$d['version'],'reload_source'=>true,'name'=>$d['name']]:['source_id'=>$source['id'],'name'=>$d['name']];$bill=$w->save('billing_drafts',$d['draft_id'],$actor,$billInput,$key.'-billing');$currentPrep=app(DocumentPreparation::class)->show('billing_drafts',$bill['id'],$actor);$prepared=app(DocumentPreparation::class)->save('billing_drafts',$bill['id'],$actor,['version'=>$currentPrep['version'],'draft_version'=>$bill['version'],'currency'=>$d['currency'],'service_date'=>$d['service_date'],'period_start'=>$d['period_start'],'period_end'=>$d['period_end'],'proposed_due_date'=>$d['proposed_due_date'],'payment_terms'=>$d['payment_terms'],'price_mode'=>$d['price_mode'],'units'=>array_column($d['lines'],'unit'),'payment_instruction_ids'=>[]]); + if(!$b['binding'])DB::table('mi_drafts')->insert($s+['draft_id'=>$bill['id'],'source_id'=>$source['id'],'actor_id'=>$actor,'created_at'=>now()->utc()]);return ['type'=>'draft','id'=>$bill['id'],'source_id'=>$source['id'],'version'=>$bill['version'],'source_version'=>$source['version'],'preparation_version'=>$prepared['version'],'new_documents'=>0,'new_receivables'=>0,'new_journals'=>0]; + } + private function impact(array $d,array $b):array{$lines=$d['lines'];$map=collect($b['services'])->keyBy('id');foreach($lines as&$l)$l['service_name']=$map[$l['service_id']]['name'];unset($l);return['before'=>$b['before']?['source_version'=>$b['before']['source']['version'],'version'=>$b['before']['billing']['version'],'preparation_version'=>$b['before']['preparation']['version']]:null,'after'=>['client'=>$b['client']['name'],'name'=>$d['name'],'currency'=>$d['currency'],'price_mode'=>$d['price_mode'],'lines'=>$lines,'arithmetic_amount'=>DraftValues::base(['lines'=>$lines]),'period_start'=>$d['period_start'],'period_end'=>$d['period_end']],'new_documents'=>0,'new_receivables'=>0,'new_journals'=>0];} + public function preview(int $actor,array $v,string $key):array{ + $d=$this->input($v);abort_unless(preg_match('/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/D',$key),422);$s=$this->scope($actor,$d);return DB::transaction(function()use($actor,$s,$d,$key){DB::statement("SET LOCAL lock_timeout='5s'");DB::table('companies')->where(['tenant_id'=>$s['tenant_id'],'id'=>$s['company_id']])->lockForUpdate()->firstOrFail();$b=$this->basis($actor,$s,$d);DB::beginTransaction();try{$this->write($actor,$s,$d,$b,$key);}finally{DB::rollBack();}$c=['actor'=>$actor,'scope'=>$s,'generation'=>request()->session()->get('company_context.token'),'input'=>$d,'key'=>$key,'basis'=>$this->hash($b),'observed'=>time()];return ['preview'=>Crypt::encryptString(Payables::canonical($c)),'impact'=>$this->impact($d,$b)];}); + } + public function confirm(int $actor,string $token):array{ + try{$c=json_decode(Crypt::decryptString($token),true,64,JSON_THROW_ON_ERROR);}catch(\Throwable){abort(409,'Prévia inválida.');}$d=$this->input($c['input']??[]);$s=$this->scope($actor,$d);return DB::transaction(function()use($actor,$s,$c,$d){DB::statement("SET LOCAL lock_timeout='5s'");DB::table('companies')->where(['tenant_id'=>$s['tenant_id'],'id'=>$s['company_id']])->lockForUpdate()->firstOrFail();$this->scope($actor,$d);abort_unless($c['actor']===$actor&&$this->hash($c['scope'])===$this->hash($s)&&$c['generation']===request()->session()->get('company_context.token')&&$c['observed']>=time()-600&&$c['observed']<=time(),409);$q=$s+['actor_id'=>$actor,'command_key'=>$c['key']];$request=['domain'=>'draft','input'=>$d];$old=DB::table('mi_commands')->where($q)->first();if($old){abort_unless(hash_equals($old->request_hash,$this->hash($request)),409);return json_decode($old->result,true)+['replayed'=>true];}$b=$this->basis($actor,$s,$d);abort_unless(hash_equals($c['basis'],$this->hash($b)),409,'Origem ou versão alteradas; confira novamente.');$result=$this->write($actor,$s,$d,$b,$c['key']);DB::table('mi_commands')->insert($q+['request_hash'=>$this->hash($request),'input'=>Payables::canonical($request),'result'=>Payables::canonical($result),'created_at'=>now()->utc()]);\App\Modules\Identity\Services\Audit::record('service_invoice.multiline_draft_prepared',$actor,$s['tenant_id'],$s['company_id'],$s['unit_id'],details:['record_id'=>$result['id'],'version'=>$result['version'],'reason'=>$d['reason']]);return$result+['replayed'=>false];}); + } +} diff --git a/app/Modules/Services/MultiLineInvoiceCalculation.php b/app/Modules/Services/MultiLineInvoiceCalculation.php new file mode 100644 index 0000000..78f330c --- /dev/null +++ b/app/Modules/Services/MultiLineInvoiceCalculation.php @@ -0,0 +1,37 @@ +validate();} + public function scope(int $actor,string $right='billing_drafts.view'):array{ + abort_unless(config('multiline_invoices.enabled')===true,404);CompositionRuntime::assert();ServiceInvoicing::guard();$s=$this->context->identifiers()+['unit_id'=>$this->context->unitId()];abort_unless($actor===$this->context->userId()&&$s['unit_id']&&ModuleRecords::active()==='commercial',403);$this->context->selectFor($actor,$s['tenant_id'],$s['company_id'],$s['unit_id']);$this->permissions->require($actor,$s['tenant_id'],$s['company_id'],$right,str_starts_with($right,'service_fiscal.')&&$right!=='service_fiscal.calculate'?null:$s['unit_id']);if(str_starts_with($right,'service_fiscal.'))$this->permissions->require($actor,$s['tenant_id'],$s['company_id'],'service_fiscal.view');$m=DB::table('mi_meta')->where('policy',self::POLICY)->first();abort_unless($m&&$m->composition===CompositionRuntime::physical()['composition']&&hash_equals($m->source_hash,(string)config('multiline_invoices.schema_sha256')),503,'Captura multilinha não conferida.');return$s; + } + private function capture(int $actor,int $draft,int $event,?int $bound=null,?int $preparer=null):array{ + $s=$this->scope($actor);$binding=DB::table('mi_drafts')->where($s)->where('draft_id',$draft)->first();abort_unless($binding,403,'A captura nova exige rascunho criado neste percurso multilinha.');$view=app(DocumentPreview::class)->captureDraftForIssuance($draft,$actor);abort_unless((int)$binding->source_id===$view['source_id'],409);abort_unless(count($view['data']['lines'])>=1&&count($view['data']['lines'])<=25&&$view['data']['currency']==='MZN',422,'Seleccione1–25 linhas em MZN.');abort_unless(!empty($view['prepared']['period_start'])&&!empty($view['prepared']['period_end']),422,'Identifique a cobertura antes do cálculo documental.');$mode=match($view['prepared']['price_mode']??null){'added'=>'net_added','included'=>'gross_included',default=>abort(422,'Modalidade de preço explícita obrigatória.')};$rows=[];foreach($view['data']['lines']as$i=>$line){abort_unless(isset($view['prepared']['units'][$i])&&trim($view['prepared']['units'][$i])!==''&&$line['quantity']!==null&&$line['unit_price']!==null,422,'Quantidade/preço/unidade obrigatórios em cada linha.');$rows[]=['reference'=>'LINE-'.($i+1),'quantity'=>$line['quantity'],'unit_price'=>$line['unit_price']];} + unset($view['consulted_at'],$view['revision'],$view['calculation'],$view['fiscal_history']);$date=now(ServiceInvoicing::ZONE)->format('Y-m-d');$common=app(FiscalRules::class)->invoiceDocument($actor,$s,$event,['currency'=>'MZN','date'=>$date,'price_mode'=>$mode,'lines'=>$rows],$bound);abort_unless($common['result']['state']==='determined_qa',409,implode(' ',$common['result']['pending']));$prepared=DB::table('users')->where('id',$preparer??$actor)->first(['id','name','email']);abort_unless($prepared,403);return ['prepared_identity'=>$this->hash((array)$prepared),'prepared_by'=>$preparer??$actor,'policy'=>self::POLICY,'document'=>$view,'common'=>$common,'expected_date'=>$date,'synthetic_only'=>true]; + } + private function input(array $v):array{$d=$this->exact($v,['draft_id'=>'required|integer|min:1','rule_event_id'=>'required|integer|min:1','draft_version'=>'required|integer|min:1','preparation_version'=>'required|integer|min:1','reason'=>'required|string|min:10|max:2000']);foreach(['draft_id','rule_event_id','draft_version','preparation_version']as$k)$d[$k]=(int)$d[$k];return$d;} + private function lock(array $s):void{DB::statement("SET LOCAL lock_timeout='5s'");DB::table('companies')->where(['tenant_id'=>$s['tenant_id'],'id'=>$s['company_id']])->lockForUpdate()->firstOrFail();} + public function preview(int $actor,array $input,string $key):array{ + $d=$this->input($input);$this->exact(['key'=>$key],['key'=>'required|uuid']);$s=$this->scope($actor,'fiscal_rules.prepare');return DB::transaction(function()use($actor,$s,$d,$key){$this->lock($s);$snapshot=$this->capture($actor,$d['draft_id'],$d['rule_event_id']);$v=$snapshot['document'];abort_unless($v['draft_version']===$d['draft_version']&&$v['preparation_version']===$d['preparation_version'],409,'Versões desactualizadas.');$c=['actor'=>$actor,'scope'=>$s,'generation'=>request()->session()->get('company_context.token'),'input'=>$d,'key'=>strtolower($key),'fingerprint'=>$this->hash($snapshot),'observed'=>time()];return ['preview'=>Crypt::encryptString(Payables::canonical($c)),'impact'=>['result'=>$snapshot['common']['result'],'new_documents'=>0,'new_receivables'=>0,'new_journals'=>0]];}); + } + public function confirm(int $actor,string $preview):array{ + try{$c=json_decode(Crypt::decryptString($preview),true,64,JSON_THROW_ON_ERROR);}catch(\Throwable){abort(409,'Prévia inválida.');}$d=$this->input($c['input']??[]);$s=$this->scope($actor,'fiscal_rules.prepare');return DB::transaction(function()use($actor,$s,$c,$d){$this->lock($s);$this->scope($actor,'fiscal_rules.prepare');abort_unless($c['actor']===$actor&&$this->hash($c['scope'])===$this->hash($s)&&$c['generation']===request()->session()->get('company_context.token')&&$c['observed']>=time()-600&&$c['observed']<=time(),409,'Pessoa/contexto/prévia alterados.');$q=$s+['actor_id'=>$actor,'command_key'=>$c['key']];$old=DB::table('mi_commands')->where($q)->first();if($old){abort_unless(hash_equals($old->request_hash,$this->hash($d)),409);return json_decode($old->result,true)+['replayed'=>true];}$snapshot=$this->capture($actor,$d['draft_id'],$d['rule_event_id']);$fingerprint=$this->hash($snapshot);abort_unless(hash_equals($c['fingerprint'],$fingerprint),409,'Origem/regra/versão/data alteradas. Confira novamente.');$existing=DB::table('mi_calculations')->where($s+['draft_id'=>$d['draft_id'],'fingerprint'=>$fingerprint])->first();$id=$existing?->id??DB::table('mi_calculations')->insertGetId($s+['draft_id'=>$d['draft_id'],'actor_id'=>$actor,'fingerprint'=>$fingerprint,'snapshot'=>Payables::canonical($snapshot),'reason'=>$d['reason'],'created_at'=>now()->utc()]);$result=['id'=>$id,'draft_id'=>$d['draft_id'],'type'=>'calculation','rule_event_id'=>$d['rule_event_id'],'draft_version'=>$d['draft_version'],'preparation_version'=>$d['preparation_version'],'date'=>$snapshot['expected_date'],'result'=>$snapshot['common']['result'],'policy'=>self::POLICY];DB::table('mi_commands')->insert($q+['request_hash'=>$this->hash($d),'input'=>Payables::canonical($d),'result'=>Payables::canonical($result),'created_at'=>now()->utc()]);Audit::record('service_invoice.multiline_calculated',$actor,$s['tenant_id'],$s['company_id'],$s['unit_id'],details:['record_id'=>$id,'reason'=>$d['reason'],'review_hash'=>$fingerprint]);return$result+['replayed'=>false];}); + } + public function current(int $actor,int $id,?int $draft=null):array{ + $s=$this->scope($actor);$r=DB::table('mi_calculations')->where($s)->where('id',$id)->first();abort_unless($r&&($draft===null||(int)$r->draft_id===$draft),403,'Cálculo fora do rascunho/contexto.');$this->permissions->require((int)$r->actor_id,$s['tenant_id'],$s['company_id'],'fiscal_rules.prepare',$s['unit_id']);abort_unless(DB::table('company_memberships')->where(['tenant_id'=>$s['tenant_id'],'company_id'=>$s['company_id'],'user_id'=>$r->actor_id])->exists(),403);$snapshot=json_decode($r->snapshot,true,64,JSON_THROW_ON_ERROR);$now=$this->capture($actor,(int)$r->draft_id,(int)$snapshot['common']['rule']['id'],$id,(int)$r->actor_id);abort_unless(hash_equals($r->fingerprint,$this->hash($now)),409,'Cálculo anterior à origem/regra/data actual.');return ['id'=>$r->id,'fingerprint'=>$r->fingerprint,'snapshot'=>$snapshot]; + } + public static function financialAmounts(array $snapshot):array{ + abort_unless(($snapshot['calculation_policy']??null)===self::POLICY&&($snapshot['renderer']??null)===MultiLineInvoicePdf::VERSION,409,'Tipo de fotografia multilinha incompatível.');$c=$snapshot['calculation'];$r=$c['common_document_result'];$lines=$snapshot['document']['data']['lines'];abort_unless(count($lines)>=1&&count($lines)<=25&&($snapshot['document']['data']['currency']??null)==='MZN',409);$in=[];foreach($lines as$i=>$line)$in[]=['reference'=>'LINE-'.($i+1),'quantity'=>$line['quantity'],'unit_price'=>$line['unit_price']];$mode=match($snapshot['document']['prepared']['price_mode']){'added'=>'net_added','included'=>'gross_included',default=>abort(409)};$expected=FiscalDocumentMath::calculate($in,$mode,$r['rate']);$frozen=$r;unset($frozen['currency'],$frozen['rule']);abort_unless(Payables::canonical($expected)===Payables::canonical($frozen)&&$expected['state']==='determined_qa'&&$c['base_document_proposed']===$r['base_document']&&$c['vat_document_proposed']===$r['tax_document']&&$c['total_document_proposed']===$r['total'],409,'Valores documentais por linha não conciliam com a fotografia.');return['base'=>$r['base_document'],'tax'=>$r['tax_document'],'total'=>$r['total']]; + } + public function receipt(int $actor,string $key):array{$s=$this->scope($actor);$this->exact(['key'=>$key],['key'=>'required|uuid']);$r=DB::table('mi_commands')->where($s+['actor_id'=>$actor,'command_key'=>strtolower($key)])->first();abort_unless($r,404);return ['result'=>json_decode($r->result,true),'historical_receipt_only'=>true,'operation_executed'=>false];} +} diff --git a/app/Modules/Services/MultiLineInvoiceIssuance.php b/app/Modules/Services/MultiLineInvoiceIssuance.php new file mode 100644 index 0000000..ee28a4e --- /dev/null +++ b/app/Modules/Services/MultiLineInvoiceIssuance.php @@ -0,0 +1,27 @@ +'required|in:'.MultiLineInvoiceCalculation::POLICY,'common_calculation_id'=>'required|integer|min:1','common_rule_event_id'=>'required|integer|min:1','draft_id'=>'required|integer|min:1','draft_version'=>'required|integer|min:1','preparation_version'=>'required|integer|min:1','valid_from'=>'required|date_format:Y-m-d','valid_until'=>'nullable|date_format:Y-m-d|after_or_equal:valid_from','period_start'=>'required|date_format:Y-m-d','period_end'=>'required|date_format:Y-m-d|after_or_equal:period_start','issue_window_start'=>'required|date_format:Y-m-d','issue_window_end'=>'required|date_format:Y-m-d|after_or_equal:issue_window_start','event'=>'required|in:on_issue_within_deadline','readiness_basis'=>'required|string|min:10|max:4000','evidence'=>'required|string|min:10|max:4000'])->validate();foreach(['common_calculation_id','common_rule_event_id','draft_id','draft_version','preparation_version']as$k)$d[$k]=(int)$d[$k];$calc=app(MultiLineInvoiceCalculation::class)->current($actor,$d['common_calculation_id'],$d['draft_id']);$snapshot=$calc['snapshot'];$view=$snapshot['document'];abort_unless($d['draft_version']===$view['draft_version']&&$d['preparation_version']===$view['preparation_version']&&$d['period_start']===($view['prepared']['period_start']??null)&&$d['period_end']===($view['prepared']['period_end']??null)&&$d['common_rule_event_id']===$snapshot['common']['rule']['id'],409,'A regra temporal não corresponde à captura revista.');if($previous)abort_unless((int)$previous['draft_id']===$d['draft_id']&&($previous['calculation_policy']??null)===MultiLineInvoiceCalculation::POLICY,409,'A regra temporal não pode trocar de origem/política.');$d['calculation_fingerprint']=$calc['fingerprint'];return$d; + } + private function proposal(int $actor,int $id,array $s,string $date):array{ + abort_unless(app(MultiLineInvoiceCalculation::class)->scope($actor)===$s,403);$r=DB::table('si_proposals')->where($s)->where('id',$id)->first();abort_unless($r,403);$d=json_decode($r->data,true);$approved=DB::table('si_decisions')->where(['proposal_id'=>$r->id,'decision'=>'approved'])->exists()&&$d['valid_from']<=$date&&(!isset($d['valid_until'])||$d['valid_until']===''||$date<=$d['valid_until'])&&!DB::table('si_proposals as n')->where($s)->where(['kind'=>$r->kind,'identifier'=>$r->identifier])->where('version','>',$r->version)->whereRaw("data->>'valid_from'<=?",[$date])->whereExists(fn($q)=>$q->selectRaw('1')->from('si_decisions as d')->whereColumn('d.proposal_id','n.id')->where('d.decision','approved'))->exists();return ['row'=>$r,'data'=>$d,'approved'=>$approved]; + } + public function review(int $draft,int $series,int $rule,int $actor,array $s):array{ + $date=now(ServiceInvoicing::ZONE)->format('Y-m-d');$p=$this->proposal($actor,$series,$s,$date);$t=$this->proposal($actor,$rule,$s,$date);$d=$t['data'];$blocks=[];abort_unless(($d['calculation_policy']??null)===MultiLineInvoiceCalculation::POLICY,422);$calc=app(MultiLineInvoiceCalculation::class)->current($actor,(int)$d['common_calculation_id'],$draft);$snap=$calc['snapshot'];$view=$snap['document'];$common=$snap['common']; + if($p['row']->kind!=='series'||!$p['approved']||($p['data']['state']??null)!=='open'||!str_starts_with($p['data']['code']??'','TEST-MULTI-'))$blocks[]='Série de teste exacta não revista/vigente/aberta.';if($t['row']->kind!=='temporal'||!$t['approved']||(int)$d['draft_id']!==$draft)$blocks[]='Regra temporal exacta não revista/vigente.'; + if(($d['calculation_fingerprint']??null)!==$calc['fingerprint']||$d['draft_version']!==$view['draft_version']||$d['preparation_version']!==$view['preparation_version']||$d['period_start']!==($view['prepared']['period_start']??null)||$d['period_end']!==($view['prepared']['period_end']??null))$blocks[]='Origem/versão/período alterados; exige nova conferência.'; + if($date<$d['issue_window_start']||$date>$d['issue_window_end'])$blocks[]='Data actual fora da janela de emissão fundamentada.'; + foreach(['issuer'=>'Emitente','client'=>'Cliente']as$party=>$label)foreach(['name','nuit','address','locality','country']as$f){$v=$f==='name'?($view[$party]['name']??''):($view[$party]['data'][$f]??'');if(!is_string($v)||trim($v)==='')$blocks[]=$label.': '.$f.' por completar.';} + if(DB::table('si_invoices')->where('draft_id',$draft)->orWhere('source_id',$view['source_id'])->exists())$blocks[]='Já existe documento desta origem.'; + $r=$common['result'];try{$minor=BigDecimal::of($r['total'])->multipliedBy(100)->toInt();if($minor<=0)$blocks[]='Documento financeiro exige total positivo.';if(BigDecimal::of($r['base_document'])->isLessThanOrEqualTo(0)||BigDecimal::of($r['tax_document'])->isLessThanOrEqualTo(0))$blocks[]='O plano contabilístico QA de três linhas exige base e imposto positivos.';}catch(\Brick\Math\Exception\MathException){$blocks[]='Montante fora da representação contabilística inteira.';}$def=$common['rule'];$rate=(string)BigDecimal::of($def['definition']['parameters']['rate'])->multipliedBy(100)->stripTrailingZeros();$result=['policy'=>MultiLineInvoiceCalculation::POLICY,'currency'=>'MZN','state'=>'synthetic_proposed','price_mode'=>$view['prepared']['price_mode'],'base_exact'=>$r['base_exact_fraction'],'base_document_proposed'=>$r['base_document'],'vat_exact'=>$r['tax_exact_fraction'],'vat_document_proposed'=>$r['tax_document'],'discarded_difference'=>$r['tax_difference_fraction'],'total_document_proposed'=>$r['total'],'common_document_result'=>$r,'common_rule'=>$def,'pending'=>[]];$profile=['kind'=>'synthetic_common_rule','rate_percent'=>$rate,'valid_from'=>$def['definition']['valid_from'],'valid_until'=>$def['definition']['valid_until'],'source_sha256'=>$def['definition']['source_sha256']]; + $entry=['id'=>$calc['id'],'snapshot'=>['input'=>['policy'=>MultiLineInvoiceCalculation::POLICY,'profile_identifier'=>$def['rule_key'],'profile_version'=>$def['rule_version'],'profile'=>$profile,'common_capture'=>$snap],'result'=>$result]]; + $payload=['calculation_policy'=>MultiLineInvoiceCalculation::POLICY,'draft_id'=>$draft,'series_id'=>$series,'series_version'=>(int)$p['row']->version,'series_code'=>$p['data']['code'],'series_definition'=>$p['data'],'rule_id'=>$rule,'rule_version'=>(int)$t['row']->version,'rule'=>$d,'expected_date'=>$date,'document'=>$view,'calculation_entry'=>$entry,'profile'=>$profile,'blocks'=>$blocks]; + if(!$blocks){$sample=app(ServiceInvoicing::class)->multiLineSnapshot($payload,0,now()->toIso8601String());if(!InvoicePdf::supports(MultiLineInvoicePdf::lines($sample)))$payload['blocks'][]='Texto não representável no PDF sem perda; confira os dados.';} + return ['ready'=>$payload['blocks']===[],'notice'=>ServiceInvoicing::NOTICE,'payload'=>$payload,'review_hash'=>hash_hmac('sha256',json_encode($payload,JSON_THROW_ON_ERROR),(string)config('app.key'))]; + } +} diff --git a/app/Modules/Services/MultiLineInvoicePdf.php b/app/Modules/Services/MultiLineInvoicePdf.php new file mode 100644 index 0000000..e8b5794 --- /dev/null +++ b/app/Modules/Services/MultiLineInvoicePdf.php @@ -0,0 +1,13 @@ +'Emitente','client'=>'Cliente']as$k=>$label){$party=$s['document'][$k];$rows[]=['section',$label];$rows[]=['text',$party['name']];foreach(['nuit'=>'NUIT','address'=>'Morada','locality'=>'Localidade','country'=>'Pais']as$f=>$n)$rows[]=['text',$n.': '.($party['data'][$f]??'Nao indicado')];} + $rows[]=['section','Linhas do documento'];$rows[]=['text','Modalidade: '.($r['price_mode']==='gross_included'?'preco com imposto incluido':'preco sem imposto, acrescido')]; + foreach($s['document']['data']['lines']as$i=>$line){$v=$r['lines'][$i];$rows[]=['section','Linha '.($i+1).' - '.$line['service_name']];if(!empty($line['description']))$rows[]=['text',$line['description']];$rows[]=['text','Quantidade '.$line['quantity'].' | Unidade '.$p['units'][$i].' | Preco '.$line['unit_price']];$rows[]=['text','Base documental '.$v['base_document'].' | Imposto '.$v['tax_document'].' | Total '.$v['total_document']];$rows[]=['text','Base exacta '.$v['base_exact_fraction'].' | Imposto exacto '.$v['tax_exact_fraction']];$rows[]=['text','Diferenca base '.$v['base_difference_fraction'].' | Diferenca imposto '.$v['tax_difference_fraction']];} + $rows[]=['section','Totais MZN - soma dos valores documentais por linha'];$rows[]=['text','Base '.$r['base_document'].' | Imposto '.$r['tax_document'].' | Total '.$r['total']];$rows[]=['text','Regra QA '.$s['profile_identifier'].' v'.$s['profile_version'].' | Taxa artificial '.$s['rate_percent'].'%'];$rows[]=['text','Politica '.$r['policy']];$rows[]=['text','Periodo '.$p['period_start'].' a '.$p['period_end']];$rows[]=['text','Condicoes: '.($p['payment_terms']??'Nao indicadas')];$rows[]=['text','Vencimento proposto: '.($p['proposed_due_date']??'Nao indicado')];$rows[]=['text','Origem: rascunho #'.$s['draft_id'].' v'.$s['document']['draft_version'].' | Regra temporal #'.$s['rule_id'].' v'.$s['rule_version']];$rows[]=['text','Emissao nao comprova recebimento ou aplicacao contabilistica. Sem comunicacao a AT.'];return$rows; + } +} diff --git a/app/Modules/Services/QualifiedInvoiceSource.php b/app/Modules/Services/QualifiedInvoiceSource.php new file mode 100644 index 0000000..06bb81b --- /dev/null +++ b/app/Modules/Services/QualifiedInvoiceSource.php @@ -0,0 +1,15 @@ +identifiers()+['unit_id'=>$c->unitId()];$actor=$c->userId();if(!$s['unit_id']||!app(Permissions::class)->has($actor,$s['tenant_id'],$s['company_id'],$module==='finance'?'service_receivables.view':'general_ledger.view',$s['unit_id']))return$legacy; + $owned=DB::table('a_billing_drafts');ModuleRecords::constrain($owned,'a_billing_drafts','billing_drafts','commercial');$owned->where($s); + return DB::table('si_invoices')->where($s)->where(function($q)use($legacy,$s,$owned){$q->whereIn('id',$legacy->select('id'))->orWhere(function($q)use($s,$owned){$q->whereRaw("si_invoices.snapshot::jsonb->>'calculation_policy'=?",[MultiLineInvoiceCalculation::POLICY])->whereExists(function($m)use($s,$owned){$m->selectRaw('1')->from('mi_drafts as md')->whereColumn('md.draft_id','si_invoices.draft_id')->whereColumn('md.source_id','si_invoices.source_id')->where(['md.tenant_id'=>$s['tenant_id'],'md.company_id'=>$s['company_id'],'md.unit_id'=>$s['unit_id']])->whereIn('md.draft_id',$owned->select('id'));});});}); + } +} diff --git a/app/Modules/Services/ServiceInvoicing.php b/app/Modules/Services/ServiceInvoicing.php index 0b5be81..bf6b831 100644 --- a/app/Modules/Services/ServiceInvoicing.php +++ b/app/Modules/Services/ServiceInvoicing.php @@ -68,6 +68,9 @@ final class ServiceInvoicing abort_unless($d['code']===$in['identifier'],422,'Identidade da série deve coincidir com o código sintético.'); if($previous) { $old=$this->data($previous); abort_unless($old['code']===$d['code'] && (int)$old['first_number']===(int)$d['first_number'],422,'Não reiniciar ou transferir contador existente.'); } $d['first_number']=(int)$d['first_number']; + } elseif(($in['data']['calculation_policy']??null)===MultiLineInvoiceCalculation::POLICY){ + $d=app(MultiLineInvoiceIssuance::class)->temporal($actor,$in['data'],$s,$previous?$this->data($previous):null); + abort_if(DB::table('si_proposals')->where($s)->where('kind','temporal')->whereRaw("data->>'draft_id' = ?",[(string)$d['draft_id']])->where('identifier','<>',$in['identifier'])->exists(),409,'Use a identidade temporal existente.'); } else { $d=$this->exact($in['data'],$common+['draft_id'=>'required|integer|min:1','draft_version'=>'required|integer|min:1','preparation_version'=>'required|integer|min:1','profile_proposal_id'=>'required|integer|min:1','period_start'=>'required|date_format:Y-m-d','period_end'=>'required|date_format:Y-m-d|after_or_equal:period_start','issue_window_start'=>'required|date_format:Y-m-d','issue_window_end'=>'required|date_format:Y-m-d|after_or_equal:issue_window_start','event'=>'required|in:on_issue_within_deadline','readiness_basis'=>'required|string|max:4000']); $view=$this->preview->captureDraftForIssuance((int)$d['draft_id'],$actor); @@ -90,6 +93,7 @@ final class ServiceInvoicing $d=$this->exact($input,['version'=>'required|integer|min:1','decision'=>'required|in:approved,refused','reason'=>'required|string|max:4000']); $p=$this->row($id,$s); abort_unless((int)$p->version===(int)$d['version'] && (int)$p->actor_id!==$actor,403,'Validador distinto e versão exacta obrigatórios.'); abort_if($this->decision($id),409,'Decisão imutável; correcção exige nova versão.'); + $mi=$this->data($p);if(($mi['calculation_policy']??null)===MultiLineInvoiceCalculation::POLICY){$fingerprint=$mi['calculation_fingerprint'];unset($mi['calculation_fingerprint']);$fresh=app(MultiLineInvoiceIssuance::class)->temporal($actor,$mi,$s);abort_unless(hash_equals($fingerprint,$fresh['calculation_fingerprint']),409,'A captura mudou; confira nova proposta.');} DB::table('si_decisions')->insert(['proposal_id'=>$id,'actor_id'=>$actor,'decision'=>$d['decision'],'reason'=>$d['reason'],'created_at'=>now()]); Audit::record('service_invoice.decided',$actor,$s['tenant_id'],$s['company_id'],$s['unit_id'],details:['record_id'=>$id,'version'=>$p->version,'reason'=>$d['reason']]); return ['id'=>$id,'decision'=>$d['decision']]; @@ -101,7 +105,7 @@ final class ServiceInvoicing if(!ServiceCalculation::isolated()) return ['real_blocked'=>true,'test_enabled'=>false,'notice'=>'Emissão real bloqueada: conformidade ainda não demonstrada.','proposals'=>[],'invoices'=>[]]; TestingDatabaseGuard::assertSafe(); if(!Schema::hasTable('si_invoices')) return ['real_blocked'=>true,'notice'=>'Migração documental ainda não aplicada.','proposals'=>[],'invoices'=>[]]; - return ['real_blocked'=>true,'test_enabled'=>true,'notice'=>self::NOTICE,'proposals'=>DB::table('si_proposals')->where($s)->orderByDesc('id')->get()->map(fn($p)=>(array)$p+['definition'=>$this->data($p),'decision'=>$this->decision($p->id)])->all(),'invoices'=>\App\Shared\Context\ModuleRecords::query('si_invoices')->where($s)->orderByDesc('id')->get(['id','series_code','number','draft_id','issued_at'])->all()]; + return ['real_blocked'=>true,'test_enabled'=>true,'notice'=>self::NOTICE,'proposals'=>DB::table('si_proposals')->where($s)->orderByDesc('id')->get()->map(fn($p)=>(array)$p+['definition'=>$this->data($p),'decision'=>$this->decision($p->id)])->all(),'invoices'=>QualifiedInvoiceSource::query()->where($s)->orderByDesc('id')->get(['id','series_code','number','draft_id','issued_at'])->all()]; } public function review(int $draft,int $series,int $rule,int $actor): array { @@ -115,6 +119,7 @@ final class ServiceInvoicing } private function reviewInside(int $draft,int $series,int $rule,int $actor,array $s): array { + $mi=$this->data($this->row($rule,$s));if(($mi['calculation_policy']??null)===MultiLineInvoiceCalculation::POLICY)return app(MultiLineInvoiceIssuance::class)->review($draft,$series,$rule,$actor,$s); $view=$this->preview->captureDraftForIssuance($draft,$actor); $date=now(self::ZONE)->format('Y-m-d'); $blocks=[]; $p=$this->row($series,$s); $t=$this->row($rule,$s); $sd=$this->data($p); $td=$this->data($t); if($p->kind!=='series' || !$this->approved($p,$s,$date) || ($sd['state'] ?? null)!=='open') $blocks[]='Série sintética sem configuração aprovada/vigente e aberta.'; @@ -147,11 +152,12 @@ final class ServiceInvoicing } private function snapshot(array $p,int $number,string $instant): array { - return ['notice'=>self::NOTICE,'renderer'=>InvoicePdf::VERSION,'draft_id'=>$p['draft_id'],'series_code'=>$p['series_code'],'number'=>$number,'issued_at'=>$instant,'issued_local'=>\Carbon\CarbonImmutable::parse($instant)->timezone(self::ZONE)->format('Y-m-d H:i:s'),'timezone'=>self::ZONE, + return ['notice'=>self::NOTICE,'renderer'=>isset($p['calculation_policy'])?MultiLineInvoicePdf::VERSION:InvoicePdf::VERSION,'draft_id'=>$p['draft_id'],'series_code'=>$p['series_code'],'number'=>$number,'issued_at'=>$instant,'issued_local'=>\Carbon\CarbonImmutable::parse($instant)->timezone(self::ZONE)->format('Y-m-d H:i:s'),'timezone'=>self::ZONE, 'series_id'=>$p['series_id'],'series_version'=>$p['series_version'],'series_definition'=>$p['series_definition'],'rule_id'=>$p['rule_id'],'rule_version'=>$p['rule_version'],'rule'=>$p['rule'],'document'=>$p['document'], 'calculation'=>array_replace($p['calculation_entry']['snapshot']['result'],['temporal_state'=>'issue_simulated','exigibility_date'=>$p['expected_date'],'assessment_period'=>substr($p['expected_date'],0,7),'pending'=>[]]),'prior_calculation_snapshot'=>$p['calculation_entry']['snapshot'],'calculation_id'=>$p['calculation_entry']['id'],'profile_identifier'=>$p['calculation_entry']['snapshot']['input']['profile_identifier'],'profile_version'=>$p['calculation_entry']['snapshot']['input']['profile_version'],'profile'=>$p['profile'],'rate_percent'=>$p['profile']['rate_percent'], 'exigibility_date'=>$p['expected_date'],'assessment_period'=>substr($p['expected_date'],0,7),'simulation_only'=>true]; } + public function multiLineSnapshot(array $payload,int $number,string $instant):array{abort_unless(($payload['calculation_policy']??null)===MultiLineInvoiceCalculation::POLICY,422);return ['calculation_policy'=>MultiLineInvoiceCalculation::POLICY]+$this->snapshot($payload,$number,$instant);} public function issue(int $draft,int $actor,array $input,string $key): array { return $this->command($actor,'service_invoices.issue',function($s)use($draft,$actor,$input,$key) { @@ -170,7 +176,7 @@ final class ServiceInvoicing DB::table('si_counters')->where('id',$counter->id)->update(['last_number'=>$number]); $instant=now()->toIso8601String(); abort_unless(\Carbon\CarbonImmutable::parse($instant)->timezone(self::ZONE)->format('Y-m-d')===$p['expected_date'],409,'Data mudou durante a confirmação; reveja antes de emitir.'); - $snapshot=$this->snapshot($p,$number,$instant); + $snapshot=isset($p['calculation_policy'])?$this->multiLineSnapshot($p,$number,$instant):$this->snapshot($p,$number,$instant); $snapshot['document_lines']=InvoicePdf::lines($snapshot); // Freeze browser representation as well as PDF bytes. $bytes=$this->pdf->render($snapshot); $id=\App\Shared\Context\ModuleRecords::query('si_invoices')->insertGetId($s+['draft_id'=>$draft,'source_id'=>$p['document']['source_id'],'series_id'=>$p['series_id'],'rule_id'=>$p['rule_id'],'series_code'=>$sd['code'],'number'=>$number,'snapshot'=>json_encode($snapshot,JSON_THROW_ON_ERROR),'pdf_base64'=>base64_encode($bytes),'pdf_sha256'=>hash('sha256',$bytes),'actor_id'=>$actor,'issued_at'=>$instant]); @@ -185,13 +191,13 @@ final class ServiceInvoicing public function show(int $id,int $actor): array { self::guard(); $s=$this->scope($actor,'service_invoices.view'); - $r=\App\Shared\Context\ModuleRecords::query('si_invoices')->where($s)->where('id',$id)->first(); abort_unless($r,403,'Documento fora do âmbito.'); + $r=QualifiedInvoiceSource::query()->where($s)->where('id',$id)->first(); abort_unless($r,403,'Documento fora do âmbito.'); $snapshot=json_decode($r->snapshot,true,32,JSON_THROW_ON_ERROR); return ['id'=>$r->id,'snapshot'=>$snapshot,'document_lines'=>$snapshot['document_lines'] ?? InvoicePdf::lines($snapshot),'pdf_sha256'=>$r->pdf_sha256,'corrections'=>DB::table('si_correction_requests')->where('invoice_id',$id)->get(['id','reason','actor_id','created_at'])->all()]; } public function pdf(int $id,int $actor): string { - $this->show($id,$actor); $r=\App\Shared\Context\ModuleRecords::query('si_invoices')->where('id',$id)->first(['pdf_base64','pdf_sha256']); + $this->show($id,$actor); $r=QualifiedInvoiceSource::query()->where('id',$id)->first(['pdf_base64','pdf_sha256']); $bytes=base64_decode($r->pdf_base64,true); abort_unless($bytes!==false && hash_equals($r->pdf_sha256,hash('sha256',$bytes)),409,'Integridade do PDF não confirmada.'); return $bytes; } public function requestCorrection(int $id,int $actor,array $input): array diff --git a/app/Modules/Services/ServiceReceivables.php b/app/Modules/Services/ServiceReceivables.php index 13a8ebc..f456b51 100644 --- a/app/Modules/Services/ServiceReceivables.php +++ b/app/Modules/Services/ServiceReceivables.php @@ -90,7 +90,7 @@ final class ServiceReceivables $right=$kind==='correction'?'correction_request':'prepare'; if(isset($d['amount'])) $d['amount']=self::money($d['amount'],$kind!=='observation'); return $this->command($actor,$right,$key,$d,function($s)use($actor,$d,$kind) { - $inv=\App\Shared\Context\ModuleRecords::query('si_invoices')->where($s)->where('id',$d['invoice_id'])->first(); + $inv=QualifiedInvoiceSource::query()->where($s)->where('id',$d['invoice_id'])->first(); abort_unless($inv,403,'Documento de origem não autorizado.'); $snapshot=json_decode($inv->snapshot,true,32,JSON_THROW_ON_ERROR); abort_unless(($snapshot['simulation_only']??false)===true && ($snapshot['notice']??'')===self::NOTICE && ($snapshot['document']['data']['currency']??'')==='MZN',422,'Origem sintética compatível necessária.'); @@ -252,7 +252,7 @@ final class ServiceReceivables return DB::transaction(function()use($s) { DB::table('companies')->where('id',$s['company_id'])->lockForUpdate()->firstOrFail(); $records=DB::table('sr_records')->where($s)->orderBy('id')->get(); - return ['blocked'=>false,'notice'=>self::NOTICE,'simulation'=>self::SIMULATION,'records'=>$records->map(function($r){$r->data=$this->data($r);return $r;})->all(),'obligations'=>$records->where('kind','obligation')->map(fn($r)=>$this->state($r->id))->values()->all(),'documents'=>\App\Shared\Context\ModuleRecords::query('si_invoices')->where($s)->get(['id','series_code','number'])->all()]; + return ['blocked'=>false,'notice'=>self::NOTICE,'simulation'=>self::SIMULATION,'records'=>$records->map(function($r){$r->data=$this->data($r);return $r;})->all(),'obligations'=>$records->where('kind','obligation')->map(fn($r)=>$this->state($r->id))->values()->all(),'documents'=>QualifiedInvoiceSource::query()->where($s)->get(['id','series_code','number'])->all()]; }); } /** Scoped single-position read reuses the authoritative state calculation without writer locks. */ diff --git a/app/Shared/Context/ModuleNavigation.php b/app/Shared/Context/ModuleNavigation.php index 8f555e6..4c9eafa 100644 --- a/app/Shared/Context/ModuleNavigation.php +++ b/app/Shared/Context/ModuleNavigation.php @@ -13,7 +13,7 @@ final class ModuleNavigation 'purchasing'=>['purchasing_budget.', 'purchasing_requisitions.', 'purchasing_receipts.', 'purchasing_orders.', 'purchasing_sourcing.', 'purchasing_suppliers.', 'purchasing_requests.', 'purchasing_invoices.', 'purchasing_obligations.', 'supplier_payables.'], 'stock'=>['stock.'], 'workshop'=>['workshop.', 'workshop_billing.', 'workshop_corrections.'], 'finance'=>['financial_analysis.','bank_reconciliation.','cash.', 'direct_costs.', 'industrial_costs.', 'industrial_budgets.', 'industrial_budget_comparisons.', 'service_receivables.'], - 'accounting'=>['fiscal_obligations.','fiscal_rules.','fiscal_assessments.','fiscal_forms.','catalogue_accounting.', 'service_fiscal.', 'general_ledger.'], + 'accounting'=>['fixed_assets.','fiscal_obligations.','fiscal_rules.','fiscal_assessments.','fiscal_forms.','catalogue_accounting.', 'service_fiscal.', 'general_ledger.'], ]; public function available(CompanyContext $context): array @@ -53,7 +53,9 @@ final class ModuleNavigation { if(preg_match('~^(stock/catalogue|api/stock-catalogue)(/|$)~',$path))return 'stock'; if(preg_match('~^(finance/cash|api/cash)(/|$)~',$path))return 'finance'; + if(preg_match('~^api/multiline-invoices(/|$)~',$path))return 'commercial'; if(preg_match('~^api/(remunerations|hr-personnel)(/|$)~',$path))return 'human_resources'; + if(preg_match('~^(accounting/assets|api/fixed-assets)(/|$)~',$path))return 'accounting'; if(preg_match('~^api/fiscal-obligations(/|$)~',$path))return 'accounting'; if(preg_match('~^api/finance/payable-positions(/|$)~',$path))return 'finance'; if(preg_match('~^api/finance/payable-due(/|$)~',$path))return 'finance'; diff --git a/app/Shared/Installation/PresentationEntry.php b/app/Shared/Installation/PresentationEntry.php index bef0d83..c4602a6 100644 --- a/app/Shared/Installation/PresentationEntry.php +++ b/app/Shared/Installation/PresentationEntry.php @@ -17,7 +17,7 @@ final class PresentationEntry 'workshop'=>['sgf:workshop_manage','/?start=workshop','Oficinas','Equipamento, cliente, agenda, ordem, autorização e apontamentos verificados'], 'finance'=>['sgf:read_only','/?start=service_receivables','Financeiro','Recebíveis, obrigações, Caixa, conferência de extracto e indicadores'], 'accounting'=>['sgf:read_only','/general-ledger','Contabilidade','Origens, diário integrado, plano, balancete, razão e mapas fiscais internos'], - 'human_resources'=>['hr:hr_read','/?start=remunerations','Recursos Humanos','Cadastro fictício, regras artificiais, processamento interno e revisão distinta; acesso próprio'], + 'human_resources'=>['hr:hr_read','/?start=remunerations','Recursos Humanos','Cadastro, contratos, assiduidade, férias e remunerações internas QA; acesso próprio e revisão distinta'], ]; private function guard():void { diff --git a/config/fixed_assets.php b/config/fixed_assets.php new file mode 100644 index 0000000..1b4b705 --- /dev/null +++ b/config/fixed_assets.php @@ -0,0 +1,2 @@ +false,'schema_sha256'=>null,'native_schema_sha256'=>null,'access_sha256'=>null]; diff --git a/config/multiline_invoices.php b/config/multiline_invoices.php new file mode 100644 index 0000000..09c8d26 --- /dev/null +++ b/config/multiline_invoices.php @@ -0,0 +1,2 @@ +false,'schema_sha256'=>null,'draft_schema_sha256'=>null]; diff --git a/database/sql/fixed_assets.sql b/database/sql/fixed_assets.sql new file mode 100644 index 0000000..7bc8d6e --- /dev/null +++ b/database/sql/fixed_assets.sql @@ -0,0 +1,28 @@ +CREATE TABLE fa_meta(policy text PRIMARY KEY,composition text NOT NULL,source_hash char(64) NOT NULL); +CREATE TABLE fa_versions( + id bigserial PRIMARY KEY,tenant_id bigint NOT NULL,company_id bigint NOT NULL,unit_id bigint NOT NULL, + kind text NOT NULL CHECK(kind IN('asset','plan','charge','disposal')),record_key uuid NOT NULL,version bigint NOT NULL CHECK(version>0),previous_id bigint UNIQUE REFERENCES fa_versions(id), + action text NOT NULL CHECK(action IN('prepare','review')),state text NOT NULL CHECK(state IN('prepared','approved','refused')), + prepared_by bigint NOT NULL REFERENCES users(id),actor_id bigint NOT NULL REFERENCES users(id), + preparer_identity char(64) NOT NULL,data jsonb NOT NULL,source_basis char(64) NOT NULL, + impact jsonb NOT NULL,reason text NOT NULL CHECK(length(reason)>=10),created_at timestamptz NOT NULL, + UNIQUE(tenant_id,company_id,unit_id,record_key,version), + FOREIGN KEY(tenant_id,company_id,unit_id) REFERENCES units(tenant_id,company_id,id), + CHECK(jsonb_typeof(data)='object'),CHECK(jsonb_typeof(impact)='object'), + CHECK((action='prepare' AND state='prepared') OR (action='review' AND state IN('approved','refused'))), + CHECK(action<>'review' OR prepared_by<>actor_id) +); +CREATE TABLE fa_commands( + id bigserial PRIMARY KEY,tenant_id bigint NOT NULL,company_id bigint NOT NULL,unit_id bigint NOT NULL, + actor_id bigint NOT NULL REFERENCES users(id),command_key uuid NOT NULL,request_hash char(64) NOT NULL, + result jsonb NOT NULL,input jsonb NOT NULL,created_at timestamptz NOT NULL, + UNIQUE(tenant_id,company_id,unit_id,actor_id,command_key) +); +CREATE FUNCTION fa_append_only() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN RAISE EXCEPTION 'SGF_FIXED_ASSETS_APPEND_ONLY'; END; $$; +CREATE TRIGGER fa_meta_immutable BEFORE UPDATE OR DELETE ON fa_meta FOR EACH ROW EXECUTE FUNCTION fa_append_only(); +CREATE TRIGGER fa_versions_immutable BEFORE UPDATE OR DELETE ON fa_versions FOR EACH ROW EXECUTE FUNCTION fa_append_only(); +CREATE TRIGGER fa_commands_immutable BEFORE UPDATE OR DELETE ON fa_commands FOR EACH ROW EXECUTE FUNCTION fa_append_only(); + +CREATE INDEX fa_versions_scope_latest ON fa_versions(tenant_id,company_id,unit_id,record_key,version DESC); +CREATE INDEX fa_versions_kind ON fa_versions(tenant_id,company_id,unit_id,kind,id DESC); diff --git a/database/sql/fixed_assets_native.sql b/database/sql/fixed_assets_native.sql new file mode 100644 index 0000000..ed9655a --- /dev/null +++ b/database/sql/fixed_assets_native.sql @@ -0,0 +1,40 @@ +CREATE TABLE gl_asset_books(tenant_id bigint NOT NULL,company_id bigint NOT NULL,unit_id bigint NOT NULL,policy text NOT NULL,host_database text NOT NULL,schema_sha256 char(64) NOT NULL,PRIMARY KEY(tenant_id,company_id,unit_id)); +CREATE FUNCTION gl_asset_guard() RETURNS trigger LANGUAGE plpgsql AS $$ +DECLARE s jsonb;k text;cost bigint;acc bigint;loss bigint;g gl_correction_results%ROWTYPE;o gl_journals%ROWTYPE;expected text[]; +BEGIN + s:=NEW.snapshot->'source';k:=s->>'effect_type';IF k NOT LIKE 'assets.%' THEN RETURN NEW; END IF; + IF NOT EXISTS(SELECT 1 FROM gl_asset_books b WHERE (b.tenant_id,b.company_id,b.unit_id)=(NEW.tenant_id,NEW.company_id,NEW.unit_id) AND b.policy='QA-ASSET-JOURNAL-v1' AND b.host_database=s->>'host_database') OR NEW.snapshot->'rule'->>'policy'<>'QA-ASSET-JOURNAL-v1' OR NEW.snapshot->'rule'->>'effect_type'<>k OR (s->>'tax_minor')::bigint<>0 OR (s->>'amount_minor')::bigint<>NEW.debit_minor THEN RAISE EXCEPTION 'ASSET_SOURCE_OR_RULE' USING ERRCODE='23514';END IF; + expected:=CASE k WHEN 'assets.admitted' THEN ARRAY['QA-ACTIVO-CUSTO','QA-ACTIVO-ORIGEM-QA'] WHEN 'assets.depreciation' THEN ARRAY['QA-ACTIVO-GASTO-DEP','QA-ACTIVO-DEP-ACUM'] WHEN 'assets.depreciation.replacement' THEN ARRAY['QA-ACTIVO-GASTO-DEP','QA-ACTIVO-DEP-ACUM'] WHEN 'assets.depreciation.reversal' THEN ARRAY['QA-ACTIVO-DEP-ACUM','QA-ACTIVO-GASTO-DEP'] ELSE NULL END; + IF k='assets.writeoff' THEN + cost:=(s->>'cost_minor')::bigint;acc:=(s->>'accumulated_minor')::bigint;loss:=(s->>'carrying_minor')::bigint; + IF cost<>NEW.debit_minor OR acc<0 OR loss<0 OR acc+loss<>cost THEN RAISE EXCEPTION 'ASSET_WRITEOFF_VALUES' USING ERRCODE='23514';END IF; + expected:=CASE WHEN acc=0 THEN ARRAY['QA-ACTIVO-GASTO-ABATE','QA-ACTIVO-CUSTO'] WHEN loss=0 THEN ARRAY['QA-ACTIVO-DEP-ACUM','QA-ACTIVO-CUSTO'] ELSE ARRAY['QA-ACTIVO-DEP-ACUM','QA-ACTIVO-GASTO-ABATE','QA-ACTIVO-CUSTO'] END; + END IF; + IF expected IS NULL OR jsonb_array_length(NEW.snapshot->'lines')<>cardinality(expected) OR EXISTS(SELECT 1 FROM jsonb_array_elements(NEW.snapshot->'lines') WITH ORDINALITY x(line,n) WHERE x.line->>'account_code'<>expected[x.n]) THEN RAISE EXCEPTION 'ASSET_ACCOUNT_LINES' USING ERRCODE='23514';END IF; + IF k IN('assets.depreciation.reversal','assets.depreciation.replacement') THEN + SELECT * INTO g FROM gl_correction_results WHERE reverse_journal_id=NEW.id OR replacement_journal_id=NEW.id; + SELECT * INTO o FROM gl_journals WHERE id=g.original_journal_id AND (tenant_id,company_id,unit_id)=(NEW.tenant_id,NEW.company_id,NEW.unit_id); + IF g.id IS NULL OR o.id IS NULL OR o.snapshot->'source'->>'effect_type'<>'assets.depreciation' OR (s->>'source_id')::bigint<>o.id OR s->>'asset_key'<>o.snapshot->'source'->>'asset_key' OR (k='assets.depreciation.reversal' AND NEW.debit_minor<>o.debit_minor) THEN RAISE EXCEPTION 'ASSET_CORRECTION_PAIR_REQUIRED' USING ERRCODE='23514';END IF; + END IF; + RETURN NEW; +END $$; +CREATE CONSTRAINT TRIGGER gl_asset_journal_guard AFTER INSERT ON gl_journals DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION gl_asset_guard(); +CREATE TRIGGER gl_asset_books_immutable BEFORE UPDATE OR DELETE ON gl_asset_books FOR EACH ROW EXECUTE FUNCTION gl_append_only(); +REVOKE ALL ON TABLE gl_asset_books FROM PUBLIC; +REVOKE ALL ON FUNCTION gl_asset_guard() FROM PUBLIC; + +-- ASSET_PAIR_EXTENSION_v1 +CREATE TABLE gl_asset_extension_versions(version int PRIMARY KEY,source_hash char(64) NOT NULL); +CREATE TRIGGER gl_asset_extension_versions_immutable BEFORE UPDATE OR DELETE ON gl_asset_extension_versions FOR EACH ROW EXECUTE FUNCTION gl_append_only(); +REVOKE ALL ON TABLE gl_asset_extension_versions FROM PUBLIC; +CREATE FUNCTION gl_asset_pair_assert(group_id bigint) RETURNS void LANGUAGE plpgsql AS $$ +DECLARE g gl_corrections%ROWTYPE;r gl_correction_results%ROWTYPE;o gl_journals%ROWTYPE;a gl_journals%ROWTYPE;b gl_journals%ROWTYPE; +BEGIN + SELECT * INTO STRICT g FROM gl_corrections WHERE id=group_id; + SELECT * INTO STRICT r FROM gl_correction_results WHERE correction_id=g.id AND (tenant_id,company_id,unit_id)=(g.tenant_id,g.company_id,g.unit_id); + SELECT * INTO STRICT o FROM gl_journals WHERE id=g.original_journal_id AND (tenant_id,company_id,unit_id)=(g.tenant_id,g.company_id,g.unit_id); + SELECT * INTO STRICT a FROM gl_journals WHERE id=r.reverse_journal_id AND proposal_id=g.reverse_proposal_id AND (tenant_id,company_id,unit_id)=(g.tenant_id,g.company_id,g.unit_id); + SELECT * INTO STRICT b FROM gl_journals WHERE id=r.replacement_journal_id AND proposal_id=g.replacement_proposal_id AND (tenant_id,company_id,unit_id)=(g.tenant_id,g.company_id,g.unit_id); + IF r.original_journal_id<>o.id OR r.actor_id<>g.prepared_by OR a.posted_by<>g.prepared_by OR b.posted_by<>g.prepared_by OR o.snapshot->'source'->>'effect_type'<>'assets.depreciation' OR a.snapshot->'source'->>'effect_type'<>'assets.depreciation.reversal' OR b.snapshot->'source'->>'effect_type'<>'assets.depreciation.replacement' OR (a.snapshot->'source'->>'source_id')::bigint<>o.id OR (b.snapshot->'source'->>'source_id')::bigint<>o.id OR a.debit_minor<>o.debit_minor OR a.snapshot->'source'->>'asset_key'<>o.snapshot->'source'->>'asset_key' OR b.snapshot->'source'->>'asset_key'<>o.snapshot->'source'->>'asset_key' OR a.snapshot->'source'->>'replacement_source_revision'<>b.snapshot->'source'->>'replacement_source_revision' THEN RAISE EXCEPTION 'ASSET_CORRECTION_PAIR_REQUIRED' USING ERRCODE='23514';END IF; +END $$; +REVOKE ALL ON FUNCTION gl_asset_pair_assert(bigint) FROM PUBLIC; diff --git a/database/sql/multiline_invoice_drafts.sql b/database/sql/multiline_invoice_drafts.sql new file mode 100644 index 0000000..cc28106 --- /dev/null +++ b/database/sql/multiline_invoice_drafts.sql @@ -0,0 +1,7 @@ +CREATE TABLE mi_draft_meta(policy text PRIMARY KEY,composition text NOT NULL,source_hash char(64) NOT NULL); +CREATE TABLE mi_drafts(id bigserial PRIMARY KEY,tenant_id bigint NOT NULL,company_id bigint NOT NULL,unit_id bigint NOT NULL,draft_id bigint UNIQUE NOT NULL REFERENCES a_billing_drafts(id),source_id bigint UNIQUE NOT NULL REFERENCES a_service_drafts(id),actor_id bigint NOT NULL REFERENCES users(id),created_at timestamptz NOT NULL,FOREIGN KEY(tenant_id,company_id,unit_id) REFERENCES units(tenant_id,company_id,id)); +CREATE TRIGGER mi_draft_meta_immutable BEFORE UPDATE OR DELETE ON mi_draft_meta FOR EACH ROW EXECUTE FUNCTION mi_append_only(); +CREATE TRIGGER mi_drafts_immutable BEFORE UPDATE OR DELETE ON mi_drafts FOR EACH ROW EXECUTE FUNCTION mi_append_only(); +CREATE INDEX mi_drafts_scope ON mi_drafts(tenant_id,company_id,unit_id,id DESC); +REVOKE ALL ON TABLE mi_draft_meta,mi_drafts FROM PUBLIC; +REVOKE ALL ON SEQUENCE mi_drafts_id_seq FROM PUBLIC; diff --git a/database/sql/multiline_invoices.sql b/database/sql/multiline_invoices.sql new file mode 100644 index 0000000..2211e50 --- /dev/null +++ b/database/sql/multiline_invoices.sql @@ -0,0 +1,17 @@ +CREATE TABLE mi_meta(policy text PRIMARY KEY,composition text NOT NULL,source_hash char(64) NOT NULL); +CREATE TABLE mi_calculations( + id bigserial PRIMARY KEY,tenant_id bigint NOT NULL,company_id bigint NOT NULL,unit_id bigint NOT NULL, + draft_id bigint NOT NULL REFERENCES a_billing_drafts(id),actor_id bigint NOT NULL REFERENCES users(id), + fingerprint char(64) NOT NULL,snapshot jsonb NOT NULL,reason text NOT NULL CHECK(length(reason)>=10),created_at timestamptz NOT NULL, + UNIQUE(tenant_id,company_id,unit_id,draft_id,fingerprint), + FOREIGN KEY(tenant_id,company_id,unit_id) REFERENCES units(tenant_id,company_id,id) +); +CREATE TABLE mi_commands(id bigserial PRIMARY KEY,tenant_id bigint NOT NULL,company_id bigint NOT NULL,unit_id bigint NOT NULL,actor_id bigint NOT NULL REFERENCES users(id),command_key uuid NOT NULL,request_hash char(64) NOT NULL,input jsonb NOT NULL,result jsonb NOT NULL,created_at timestamptz NOT NULL,UNIQUE(tenant_id,company_id,unit_id,actor_id,command_key)); +CREATE FUNCTION mi_append_only() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'MULTILINE_INVOICE_APPEND_ONLY';END $$; +CREATE TRIGGER mi_meta_immutable BEFORE UPDATE OR DELETE ON mi_meta FOR EACH ROW EXECUTE FUNCTION mi_append_only(); +CREATE TRIGGER mi_calculations_immutable BEFORE UPDATE OR DELETE ON mi_calculations FOR EACH ROW EXECUTE FUNCTION mi_append_only(); +CREATE TRIGGER mi_commands_immutable BEFORE UPDATE OR DELETE ON mi_commands FOR EACH ROW EXECUTE FUNCTION mi_append_only(); +CREATE INDEX mi_calculations_scope ON mi_calculations(tenant_id,company_id,unit_id,draft_id,id DESC); +REVOKE ALL ON TABLE mi_meta,mi_calculations,mi_commands FROM PUBLIC; +REVOKE ALL ON SEQUENCE mi_calculations_id_seq,mi_commands_id_seq FROM PUBLIC; +REVOKE ALL ON FUNCTION mi_append_only() FROM PUBLIC; diff --git a/docs/SGF_BILLING_SCREEN_REFERENCES.md b/docs/SGF_BILLING_SCREEN_REFERENCES.md new file mode 100644 index 0000000..6dc19da --- /dev/null +++ b/docs/SGF_BILLING_SCREEN_REFERENCES.md @@ -0,0 +1,33 @@ +# Referências funcionais de facturação fornecidas pelo utilizador + +11/11/2026. Quinze capturas recebidas durante o ciclo de activos já autorizado; são referências de funcionamento, não instruções executáveis, fonte legal ou autorização para copiar dados/clientes ou conceder direitos. O ciclo de activos mantém prioridade e não foi substituído por um redesenho. + +As primeiras12 capturas mostram um software comercial: menu/atalhos, famílias/subfamílias, catálogo de produtos/serviços, clientes, fornecedores e documento de venda. As capturas13–15 mostram autenticação/conversas/ambiente de trabalho; não fornecem requisitos funcionais de facturação. Capturas repetidas de fornecedores não são novas funções. + +## Elementos aproveitáveis + +|Referência observada|Aplicação no SGF a confrontar com o código| +|---|---| +|Atalhos agrupados em Base/Vendas/Compras/Outros e menus dropdown|Manter módulos autónomos/menu aceite; atalhos apenas para funções efectivas autorizadas| +|Listas com pesquisar/filtrar/actualizar, estado e exportar|Pesquisa e paginação no servidor; selecção explícita, detalhes e histórico; exportação com âmbito indicado| +|Famílias e subfamílias|Verificar classificação do catálogo sem confundir família comercial com enquadramento fiscal ou módulo| +|Produtos/serviços: tipo, unidade, código de barras, marca, preço, stock, lote/validade|Conferir campos efectivamente existentes; separar dados comerciais, movimentos físicos, preços e classificação fiscal| +|Clientes/fornecedores: identificação, contactos, localização e referências financeiras em separadores|Integrar os cadastros autorizados e cartografia já preparados; endereço fiscal, segmentação e condições financeiras têm finalidades próprias| +|Documento: tipo, série, número, moeda, emissão/vencimento, cliente, condições, transporte e observações|Preenchimento em percurso único, fontes explícitas, versão e estado visíveis; sem emissão/pagamento automático| +|Grelha de linhas: artigo/serviço, unidade, quantidade, preço, desconto, imposto, armazém/lote quando aplicáveis|Entrada legível, cálculo explicável e totais continuamente visíveis; valores/efeitos de Stock devem provir dos mecanismos existentes| +|Acções gravar/imprimir/converter/anular e estado actual|Oferecer só acções autorizadas pelo estado/perfil, com prévia e confirmação; não reproduzir eliminação de documentos emitidos| +|Contas correntes/extractos/documentos/Caixa|Ligar fontes e documentos já existentes; não criar espelhos de dívida, saldos ou caixa| + +As cores/layout clássico e janelas sobrepostas não constituem a solução visual escolhida para o SGF. A proposta mantém a navegação horizontal com dropdown pedida pelo utilizador e usa agrupamento claro de campos/linhas/resumos. Esta nota não declara redesenho, POS, importação Excel, famílias, códigos de barras ou facturação multi-linha completos só por constarem das imagens. + +Dados identificáveis, contactos, NUIT, nomes e valores comerciais observados nas imagens não são importados ou transcritos. Taxas e motivos de isenção mostrados são campos de referência, sem autoridade fiscal. Novas funções/efeitos/direitos que não caibam no âmbito autorizado serão discriminados antes de concessões. Preservar8085, seis registos/cache e trabalho em curso. Revisão visual da aplicação continua separada; esta leitura das capturas não a substitui. + +## Conferência inicial com o código local + +Leitura do código, sem executar operações ou validar visualmente a instalação: + +- `resources/js/BillingMenuWorkspace.jsx` já reúne acessos documentais, filtros e abertura do documento. Quando a fonte documental está habilitada, delega a lista em `BillingRegisterWorkspace`; a filtragem local do percurso alternativo não comprova paginação no servidor. +- `resources/js/DocumentPreparationWorkspace.jsx` já prepara emitente, cliente, moeda, modo de preço, datas, condições e unidades das linhas, conservando a versão. Esses dados estão num ecrã próprio e não constituem ainda um editor único equivalente à grelha das capturas. +- `resources/js/ServiceInvoiceWorkspace.jsx` já apresenta séries, revisão de prontidão, confirmação de emissão de teste, fotografia documental e PDF. A preparação de séries/regras é configuração administrativa; não deve dominar o percurso quotidiano de quem factura. + +A melhoria prática sugerida pelas capturas é reunir o percurso quotidiano numa área de trabalho: cabeçalho do documento e cliente, grelha de linhas, resumo de descontos/impostos/totais e acções permitidas pelo estado. A configuração de séries e regras fica acessível em área própria. Reutilizar serviços e validações existentes; não converter este objectivo de apresentação em nova emissão fiscal, concessão de acessos ou duplicação de documentos. Esta conferência não declara a alteração implementada. diff --git a/docs/SGF_COVERAGE_RECONCILIATION.md b/docs/SGF_COVERAGE_RECONCILIATION.md new file mode 100644 index 0000000..0bae121 --- /dev/null +++ b/docs/SGF_COVERAGE_RECONCILIATION.md @@ -0,0 +1,11 @@ +# Cobertura reconciliada e próxima lacuna funcional + +11/11/2026. Na8085, o painel fiscal distingue agora as capacidades internas de RH já entregues dos motores legais ainda pendentes. IRPS/INSS indicam o processamento QA e respectivas rubricas artificiais como cobertura parcial, mantendo cálculo/enquadramento/modelos legais por concluir. RH autónomo aparece com cadastro/contratos/tempos/férias/conferência. Contabilidade reconhece as demonstrações internas parciais e assinala activos/depreciações operacionais em falta. + +Não foram acrescentados motores fiscais ou taxas, nem activação legal. É reconciliação da informação mostrada com a matriz/código/provas. Só foi actualizado `FiscalCoverage.php`, pela instalação8085 existente, sem compilação de interface, cache clear ou reinício. Leitura fiscal mantém as permissões existentes e não expõe dados de trabalhadores; perfil RH continua sem acesso fiscal por inferência. + +Sete verificações de metadados e oito HTTP,66 conjuntos originais/três bindings intactos, HP16/RM6/GL27. Zero comandos de negócio, novos direitos, perfis, schema ou dados atribuídos. [Provas](proofs/sgf-coverage-reconciliation-20261111.json). Provas funcionais dos módulos reutilizadas sem repetição. Visual, carga e capacidade de implantação permanecem separados. + +A próxima lacuna concreta é [activos, depreciação e Contabilidade integrados QA](SGF_FIXED_ASSETS_SCOPE.md), FIS-SGF-15. Não existe cadastro operacional; o escritor comum tem lista fechada de origens sem activos. Foi preparado um âmbito único: cadastro/custódia/plano/revisão, admissão e quota no mesmo diário, relatório e nova demonstração interna parcial; abate/correcção/recuperação na regressão. A proposta pede dez concessões QA no total, cinco contas artificiais por livro e dois lançamentos novos8085 (27→29;19→24contas), preservando os anteriores. O snapshot antigo fica histórico, sem reescrita. Não foi concedido nem contabilizado nada deste bloco. + +O cálculo puro proposto `AssetDepreciation` passou13 verificações de centavos/residual/ausência de parâmetros. Não é funcionalidade instalada. Referências de cálculo fiscal do acervo foram localizadas, sem taxas legais adoptadas ou nova pesquisa fiscal presumida. A proposta explicita as correspondências das contas e a excepção necessária para estender o mesmo núcleo, sem segundo diário/escritor. diff --git a/docs/SGF_CURRENT_DELIVERY_20261110.md b/docs/SGF_CURRENT_DELIVERY_20261110.md index 8996522..9a85d27 100644 --- a/docs/SGF_CURRENT_DELIVERY_20261110.md +++ b/docs/SGF_CURRENT_DELIVERY_20261110.md @@ -1,10 +1,15 @@ # SGF — estado consolidado da instalação8085 -Estado actual: [matriz v25](integration/sgf-solution-contract.json), [última entrega de RH autónomo](SGF_HUMAN_RESOURCES_DELIVERY.md) e [conferência estrutural sem escrita](proofs/sgf-solution-structure-v25-final-20261111.json). As secções seguintes conservam a evolução histórica; propostas marcadas por autorizar são substituídas pelas respectivas entregas posteriores. +Actualização11/11: [facturação multilinha QA entregue](SGF_MULTI_LINE_INVOICE_DELIVERY.md), mesma8085: dois documentos1160, dois recebíveis total2320, dois lançamentos29→31,24contas sem direitos novos. Resultado interno parcial v6/subtotal2286,21, dez contas por classificar; antigos snapshots preservados. Conferência44factos/0pendentes, período aberto. Matrizv30 mantém produto completo/apresentação aceite pendentes. Os números abaixo são históricos. + +Actualização11/11: **[activos integrados QA entregues](SGF_FIXED_ASSETS_DELIVERY.md)**; matriz v30. Mesma8085,29 lançamentos/24 contas, sem alterar27 lançamentos/19 contas anteriores. Cadastro1200/quota100/líquido1100; nova demonstração parcial e snapshot anterior preservado. A evolução abaixo mantém números históricos. + + +Estado actual: [matriz v30](integration/sgf-solution-contract.json), [activos integrados QA](SGF_FIXED_ASSETS_DELIVERY.md) e [conferência estrutural sem escrita](proofs/sgf-solution-structure-v30-final-20261111.json). As secções seguintes conservam a evolução histórica; propostas marcadas por autorizar são substituídas pelas respectivas entregas posteriores. Actualização11/11/2026. Código funcional, interfaces e ligações na instalação existente, sem dependência do SGC, novas demonstrações, publicação ou checkpoint. Esta actualização não constitui aceitação do utilizador nem validação de implantação. -RH autónomo: [entregue com perfis próprios e percurso interno QA](SGF_HUMAN_RESOURCES_DELIVERY.md). São agora sete módulos na8085. Não integra o menu Financeiro nem conclui todas as funções laborais. +RH autónomo: [entregue com perfis próprios e percurso interno QA](SGF_HUMAN_RESOURCES_DELIVERY.md). São agora sete módulos na8085. Não integra o menu Financeiro. [Contratos, assiduidade e férias internos QA também entregues](SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md); conformidade laboral geral continua pendente. ## Incrementos concluídos nesta continuação @@ -83,4 +88,6 @@ Decisão seguinte preparada, sem concessões novas: `SGF_MULTI_LINE_INCLUDED_SCO A conferência detectou scripts `.mjs` a receber HTML200. [Correcção transversal](SGF_CLIENT_ASSET_DELIVERY.md), apenas8085: dez módulos exactos e dependência comum servidos como JavaScript, MIME/SHA conferidos; desconhecido404.17 fronteiras e13 HTTP. Preserva corpo original do router/outras composições, sem reinício ou cache. As provas antigas de API/DOM não demonstravam essa entrega e não são apresentadas como validação visual. Restantes lacunas/visual/carga/implantação permanecem na matriz v22. -Continuação11/11: [contratos, assiduidade e férias — âmbito integrado preparado](SGF_HUMAN_RESOURCES_PERSONNEL_SCOPE.md). Nove direitos novos,15 concessões por livro existente aos mesmos perfis RH; por autorizar/não aplicados. Apenas cálculo puro de tempos/saldo preparado e22 verificações; funções novas ainda não entregues na8085. +Continuação11/11: [contratos, assiduidade e férias — âmbito integrado preparado](SGF_HUMAN_RESOURCES_PERSONNEL_SCOPE.md). Âmbito posteriormente autorizado e [entregue](SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md), com15 concessões por livro existente aos mesmos perfis RH; indicação de preparação/por autorizar é histórica. + +Próxima decisão: [ciclo completo de activos QA](SGF_FIXED_ASSETS_SCOPE.md), dez concessões aos perfis contabilísticos existentes, cinco contas artificiais e dois lançamentos novos. Por autorizar; nenhuma conta/activo/lançamento foi criado. diff --git a/docs/SGF_FIXED_ASSETS_DELIVERY.md b/docs/SGF_FIXED_ASSETS_DELIVERY.md new file mode 100644 index 0000000..e96fa15 --- /dev/null +++ b/docs/SGF_FIXED_ASSETS_DELIVERY.md @@ -0,0 +1,61 @@ +# Activos integrados no SGF — entrega QA + +11/11/2026. Implementação local na **mesma8085**, sem SGC ou outra demonstração. Autorização: «Autorizar o ciclo integrado de activos QA», conforme [âmbito](SGF_FIXED_ASSETS_SCOPE.md) e [direitos/contas](integration/sgf-fixed-assets-access-proposal.json). Não é aceitação do utilizador, publicação ou validação de implantação. + +## Percurso disponível + +Em Contabilidade → **Activos e depreciações internas QA**, os perfis Contabilidade de preparação, revisão e consulta têm acessos próprios. Cadastro, localização/custódia, plano, quota e abate são versionados. A listagem pesquisa literalmente e pagina25/100 no servidor; referências de activos/planos são pesquisáveis e escolhidas explicitamente. O histórico conserva preparador, revisor, data, fundamento e estado anterior/posterior. + +Uma preparação não activa um plano nem aplica um lançamento. A prévia administrativa é vinculada à pessoa/contexto/versão/dependências e vale600segundos; confirmação revalida tudo e conserva recibo append-only. A revisão exige pessoa distinta e mantém a autoridade do preparador actual. Quotas com custo/residual/vida desconhecidos não são aprovadas como zero. Custódia/localização são administrativas, sem Stock, ligação automática a RH ou novo custo. + +A origem revista entra no **mesmo PayableJournal**: prévia do comando num savepoint revertido, revisão distinta, aplicação explícita e recuperação no recibo original. A versão administrativa invalida conferências pendentes mesmo quando a mudança não altera o valor financeiro; a revisão financeira estável não obriga a estornar uma admissão já aplicada só por mudança de custódia. Não há segundo escritor nem INSERT directo de diários nos adaptadores. + +Os tipos de activos são capturados por LedgerAssets com contas/data/período/valores explícitos. O schema conserva os ramos anteriores e acrescenta guardas tipadas de activos e do par de correcção. Versões de código e definições SQL anteriores estão guardadas em privado. Os três ficheiros de ligação antigos não mudaram. A inicialização HTTP reutiliza ComposedLedger/ReceiptRuntime para conferir a ligação nativa, sem copiar parâmetros de acesso para o produto. + +## Exemplo efectivo8085 + +|Elemento|Estado observado| +|---|---| +|Cadastro|QA-ACTIVO-01, origem QA-ABERTURA-ACTIVO-01, revisto| +|Custódia/localização|QA-RESPONSAVEL-ACTIVO-01 / QA-SALA-A; códigos fictícios| +|Custo/residual/uso|1200MZN /0 explícito /01-10-2026| +|Plano|12meses artificiais; revisto distintamente| +|Admissão|01-10: D QA-ACTIVO-CUSTO / C QA-ACTIVO-ORIGEM-QA,1200| +|Quota Outubro|31-10: D QA-ACTIVO-GASTO-DEP / C QA-ACTIVO-DEP-ACUM,100| +|Saldo|1200 custo /100 acumulado /1100 líquido; activo não abatido| +|Administração|Seis versões e seis recibos: preparar/rever cadastro, plano e quota| +|Diário|Dois novos lançamentos; total29, mantendo os27 anteriores idênticos| +|Plano de contas|Cinco novas contas artificiais; total24, mantendo as19 anteriores| + +Não repetir confirmações para apresentar este exemplo. Progresso e chaves recuperáveis estão em ficheiro privado, fora do repositório. Abate, transferência de custódia, suspensão/retoma e correcção foram ensaiados na regressão; não mudaram o activo8085 nem acrescentaram outros lançamentos aí. + +## Demonstração interna e período + +Quatro comandos adicionais prepararam/reviram a correspondência ampliada e o novo resultado parcial. São24 contas no catálogo/correspondência e **23 contas com movimento** no resultado: a conta de gasto de abate tem zero movimentos e o leitor original não a materializa. Não foi alterado o leitor para fabricar uma linha. O corte novo é01-01–11-11-2026; o antigo relatório tinha o seu próprio corte. + +Valores classificados do corte novo: activos3255MZN, fornecedor100, rendimentos2586,21, gastos2300 e subtotal conhecido286,21. Permanecem dez contas por classificar; a origem artificial de activos não é capital nem dívida presumidos. Capital, resultado completo e balanço legal continuam indeterminados. O snapshot anterior19contas/27lançamentos e o CSV original mantêm os mesmos bytes/SHA e aparecem como históricos. + +A conferência actual da8085 foi apenas lida: **40factos, zero pendentes e débitos iguais a créditos**. Nenhum fecho foi executado aí. Na regressão, o fecho do recorte de activos foi preparado/revisto/aplicado e integralmente revertido: o harness isolou as outras origens financeiras de suporte não contabilizadas. Essa prova não equivale ao fecho integral da instalação ou de uma empresa. Ambos os períodos permanecem abertos. + +## Provas dirigidas + +[Consolidação verificável](proofs/sgf-fixed-assets-20261111.json): **141verificações dirigidas**, mais um cenário de concorrência e **87verificações de preservação**. As13 provas aritméticas anteriores foram reutilizadas, não repetidas nem somadas. Não se repetiram os percursos antigos de Compras/Stock/RH/recebimentos para gerar novas provas. + +- Admissão/quota pelo escritor comum; custo1200, acumulado100, líquido1100. +- Correcção100→120 após plano12→10 revisto: estorno/substituição indivisíveis. A falha injectada após o primeiro lançamento revertia o par; a asserção concluída foi reutilizada de um ensaio cujo passo posterior encontrou outro erro, entretanto corrigido. Não foi contada uma segunda vez. +- Abate original Dacumulado100+Dperda1100/Ccusto1200, numa fixture revertida; também abate após correcção,120+1080/1200. +- Acesso/perfil/módulo/unidade, pessoa/contexto/chave, versões/dependências, custo já lançado, desconhecidos, plano suspenso/retoma, pesquisa literal/páginas25/100, recibos e falha entre versão/recibo. Fixtures de volume e fronteiras revertidas. +- Concorrência: bloqueio na linha host de empresa por FOR UPDATE, espera transactionid observada com pg_blocking_pids;503 após5,24segundos. Após libertação, a segunda decisão recusou409 por versão antiga; replay da primeira200, duas versões/dois recibos, sem duplicação. A regressão conserva o cadastro de suporte revisto, ainda sem admissão, explicitamente pendente na conferência de activos. +- 15handlers de interface com API simulada, build privado e JavaScript servido com MIME/SHA correctos; HTTP8085 real efectuou o percurso uma vez. Nenhum destes ensaios é revisão visual de navegador. + +O runner SSR legado de navegação não terminou e foi interrompido sem resultado. As suas importações/expectativas precedem o dropdown aceite; não foi apresentado como aprovado nem usado para justificar a entrega. Os testes dirigidos do novo menu/handlers e a compilação privada passaram. + +## Preservação e limites + +As linhas originais dos78conjuntos verificados são idênticas. Os appends são apenas os discriminados na autorização/prova: cinco direitos por livro QA existente(dez concessões no total), cinco contas/seis regras tipadas por livro, decisões/diário deste exemplo e quatro comandos da nova demonstração parcial. Sem utilizadores/perfis/PGroles novos ou direitos PUBLIC. Seis registos protegidos, Caixa950, RH, Stock, dados/documentos antigos e três bindings preservados; cache bloqueada não foi limpa. + +Contas e política de depreciação são artificiais: cêntimos/racionais exactos, truncagem intermédia e ajuste final ao residual; sem pro-rata, taxa fiscal legal, dedução IRPC, venda, contraprestação, pagamento, reabertura ou correcção do custo já admitido. Correcção técnica por original é um par único, sem cadeias posteriores presumidas. Não é gestão geral de activos concluída. + +Matrizv29 continua com produto amplo incompleto. Fiscalidade/modelos oficiais, operação geral, revisão visual, carga sustentada, armazenamento e implantação conservam as suas pendências. Sem publicação, SGC, nova demonstração, candidata de Compras, aceitação ou checkpoint. + +Guardas finais: quotas novas exigem sequência/acumulado coerentes; alteração de vida com várias quotas já aplicadas fica fora da correcção individual e é recusada. Abate é recusado com plano/quota pendentes ou correcção de quota ainda não aplicada. Seis provas adicionais em fixtures revertidas, sem novas operações8085. diff --git a/docs/SGF_FIXED_ASSETS_SCOPE.md b/docs/SGF_FIXED_ASSETS_SCOPE.md new file mode 100644 index 0000000..a5102c2 --- /dev/null +++ b/docs/SGF_FIXED_ASSETS_SCOPE.md @@ -0,0 +1,82 @@ +# Activos — cadastro, depreciação e Contabilidade integrados QA + +11/11/2026. **Autorização expressa11/11: Autorizar o ciclo integrado de activos QA. **Entregue no recorte QA**: ver [relatório](SGF_FIXED_ASSETS_DELIVERY.md).** FIS-SGF-15 exige activos/depreciações articulados com diário/relatórios. A conferência do código não encontrou cadastro operacional de activos no SGF. O núcleo comum aceita uma lista fechada de origens, que ainda não inclui activos. A implementação destas origens e novos efeitos exige esta decisão; não se presume da autorização de custos/Stock/RH. + +## Entrega funcional agrupada + +Na mesma8085, em **Contabilidade → Activos e depreciações internas QA**: cadastro individual, descrição/referência, origem artificial explícita, custo/residual, entrada em uso, localização/custódia interna, plano mensal, prévias/decisões distintas, histórico, depreciação individual, ligação ao mesmo diário, razão/balancete, demonstração interna parcial e conferência do período. Transferência de custódia/localização dentro da unidade é administrativa; não gera movimento de Stock ou custo. Nada é atribuído automaticamente a empregados RH ou facturas existentes. + +Activos, planos e estados são versionados e revistos distintamente. Estado desconhecido impede cálculo determinado. Preparação não activa nem contabiliza. Alteração com efeitos já lançados mostra dependências e exige correcção explícita; não reescreve factos ou recalcula passado. Inclui suspensão/retoma de plano, terminação/abate sem contraprestação e correcção técnica de quota por par imutável no período aberto, com os ramos negativos e de recuperação demonstrados na regressão existente. Sem gozo/depreciação por passagem de data ou tarefa agendada automática. + +A admissão e cada quota são origens distintas e exclusivas; cada origem tem um efeito estável e um lançamento, sem segunda execução por repetição ou nova versão. A conferência revalida activo/plano, pessoas, direitos, contexto, datas/versões, conta e período antes de preparar/rever/aplicar. Fecho QA é ensaiado em rollback; a instalação permanece com o período actual aberto, sem reabertura. + +## Caso efectivo e efeitos apresentados + +Criar apenas **QA-ACTIVO-01**, equipamento fictício sem relação com recepções/Stock/custos/documentos anteriores. Origem declarada: abertura **exclusivamente de ensaio**, referência QA-ABERTURA-ACTIVO-01, custo1200MZN, residual explicitamente0, uso desde01/10/2026, vida artificial12meses, quota linear100MZN por mês completo. A localização inicial QA-SALA-A e a custódia QA-RESPONSAVEL-ACTIVO-01 são códigos internos artificiais, não pessoas/contas reais. Revisão distinta do cadastro e plano. + +Período existente confirmado por SELECT: **QA-2026-INTEGRADO**,01/01–31/12/2026, aberto. Dois efeitos novos na8085, exclusivamente após decisões individuais: + +|Origem/data QA|Débito|Crédito|Montante| +|---|---|---|---| +|Admissão01/10/2026|QA-ACTIVO-CUSTO|QA-ACTIVO-ORIGEM-QA|1200MZN| +|Quota de Outubro31/10/2026|QA-ACTIVO-GASTO-DEP|QA-ACTIVO-DEP-ACUM|100MZN| + +Resultado: activo ainda em uso, custo1200/acumulado100/valor líquido1100. Não comprova compra, dinheiro, dívida ou capital. Não capitalizar os custos directos/industriais ou bens de Stock já lançados. Abate/correcção/suspensão/retoma/transferência e recuperação são testados na regressão; não acrescentam lançamentos8085 além destes dois nem mudam o activo de apresentação para abatido. + +Abate QA sem contraprestação na regressão: depois da quota100, D depreciação acumulada100 + D perda no abate1100 / C custo1200, no mesmo escritor e com três linhas equilibradas. Correcção técnica de quota só por estorno/substituição e revisão distinta em período aberto; alterações do custo/plano com quotas aplicadas ficam bloqueadas até tratamento coordenado explícito. Não vender activo, presumir IVA/mais-valia ou gerar recebimento. + +### Política de representação proposta + +Montantes exactos em centavos/racionais. Base depreciável=custo−residual; quota mensal linear exacta=base/meses. Truncar quotas documentais intermédias ao centavo e ajustar somente a última para atingir exactamente o residual, preservando diferenças racionais assinadas. Exemplo100MZN/3meses:33,33/33,33/33,34. Sem pro-rata diário, taxas fiscais, coeficientes/tabelas legais ou dedução IRPC presumidos. Custo/residual/vida em falta são desconhecidos, não zero. Limite de ensaio1–600meses, sem materializar o universo de activos. + +O acervo InvesteMoz contém classificação/cálculo fiscal de depreciações (`irpc-normal-depreciation.ts` e dependências); será inventariado/reutilizado quando a correspondência fiscal for expressamente validada. Este caso é **depreciação contabilística artificial**, sem adoptar taxas do catálogo como política legal. Não refazer pesquisa legal já aceite nem declarar que parâmetros do código são lei vigente. + +## Direitos e contas exactos + +Novos direitos apenas aos perfis Contabilidade QA já existentes: + +|Direito novo|Perfis existentes| +|---|---| +|`fixed_assets.view`|15,16,17| +|`fixed_assets.prepare`|15| +|`fixed_assets.review`|16| + +**Cinco concessões por livro QA existente,10 no total**; tenant1/empresa2/unidade2. Sem novas identidades/perfis/PGroles, poderes para RH/Financeiro ou direitos PUBLIC. Os direitos de preparar/rever/aplicar/corrigir e conferir o período no diário já existem nos perfis15/16/17, como confirmado por SELECT; não conceder de novo. Não inferir esses direitos de consultar um activo. + +Cinco contas novas, exclusivamente artificiais, em cada livro QA existente: + +|Código|Correspondência interna proposta| +|---|---| +|QA-ACTIVO-CUSTO|Activo, débito| +|QA-ACTIVO-DEP-ACUM|Activo, saldo credor deduz o custo| +|QA-ACTIVO-GASTO-DEP|Gasto| +|QA-ACTIVO-ORIGEM-QA|**Por classificar**; não inventar capital nem dívida| +|QA-ACTIVO-GASTO-ABATE|Gasto; sem movimentos no exemplo8085| + +Não adoptar conta preexistente diferente pelo nome nem substituir/alterar as19 contas actuais. A classificação destas cinco é explicitamente parte desta proposta. A extensão da correspondência revista e um novo resultado interno parcial usam os poderes existentes `general_ledger.statements.prepare/review`, sem concessões adicionais. + +## Integração do núcleo e impacto sobre relatórios + +Autorizar extensão **tipada e versionada do mesmo núcleo contabilístico**, incluindo origens de admissão/depreciação/abate/correcção e planos de duas/três linhas. Em concreto, a lista e captura de origens de `packages/general-ledger/src/PayableJournal.php` e os respectivos adaptadores/schema/pins QA terão de reconhecer os novos tipos. Este recorte constitui a excepção específica à preservação estrita do escritor/pacotes fixados **somente para a extensão de activos**. Preservar a semântica, provas e fontes dos tipos já aceites; conservar as versões/pacotes anteriores em privado. Não usar INSERT directo em diários nem criar um segundo escritor, diário ou livro paralelo. + +Novas tabelas administrativas e de proveniência apenas nos dois livros QA existentes. Identidade empresa/unidade e cruzamento host/livro nativo explícitos; recuperação pelo mesmo protocolo quando o resultado da confirmação for incerto. Sem migrações globais, índices em tabelas antigas ou grants PostgreSQL/PUBLIC. + +|Estado|Antes8085|Após o exemplo proposto| +|---|---|---| +|Lançamentos|27|29; os27 originais byte a byte intactos| +|Contas|19|24; as19 originais intactas| +|Activo deste exemplo|Inexistente|1200 custo /100 acumulado /1100 líquido| +|Caixa|950|950, sem pagamento novo| +|Dívidas/recebíveis/Stock/RH|Exemplos aceites|Sem alterações| +|Demonstração antiga|Snapshot19contas/27lançamentos|Artefacto original intacto e explicitamente histórico| +|Nova correspondência/resultado parcial|Ausente|24contas/29lançamentos, cinco correspondências discriminadas, sem capital/resultado completo presumidos| + +Os novos lançamentos alterarão as consultas vivas do balancete/razão e tornarão o snapshot anterior histórico; não se pode afirmar que todos os saldos globais permanecem iguais. A revisão de correspondências e o novo resultado parcial acrescentam **quatro comandos administrativos** (preparar/rever cada um), sem reescrever as quatro versões/snapshot anteriores. As nove contas antigas por classificar permanecem assim; soma-se a nova origem QA por classificar. Cálculos antigos, PDFs/CSV e respectivas provas não são regenerados. Prévia pendente com fonte alterada exige nova conferência. Nenhuma candidata de Compras é activada. + +## Provas e condições de execução + +Após aprovação, implementar todo o percurso/UI/diário/relatórios na mesma8085 e regressão existente. Testar primeiro regressão: isolamento/direitos/pessoas, dados desconhecidos, planos/centavos/residual, vigência, competências repetidas, fonte alterada, períodos fechados, correcção, abate, concorrência com bloqueador identificado e pós-libertação, idempotência e falha/recuperação. Provas afectadas do núcleo e fronteiras antigas por paridade; não repetir suites alheias ou novos efeitos anteriores. Fixtures de volume e ramos negativos só na regressão e revertidos quando possível; identificar os factos de suporte que ficarem. HTTP8085 executa o percurso positivo uma vez com recuperação por recibo. + +Já preparado, sem asset/schema/grants/GL: cálculo puro `AssetDepreciation`,13 verificações, incluindo centavos/residual/desconhecidos. O prototype não é cadastro, depreciação registada, fonte de diário ou funcionalidade disponibilizada. Os três direitos estão ausentes nos dois livros, confirmado por SELECT. Estado anterior à autorização, conservado como contexto. Após autorização, as concessões e o ciclo QA foram aplicados nos dois livros existentes conforme o relatório; não houve activação real. + +Preservar os seis registos protegidos, cache bloqueada,27lançamentos/19contas originais, Caixa950, salários/pessoal/Stock/Compras e demais provas/dados/trabalho alheio. A aprovação autoriza apenas os novos efeitos explicitamente discriminados. Sem operações/contas/políticas reais, dinheiro ou dívida nova, cálculo/dedução fiscal legal, reabertura, migração no destino real, demos/bases/portas, SGC, publicação, aceitação/checkpoint ou capacidade de implantação presumidos. Visual e carga sustentada ficam separados. diff --git a/docs/SGF_GUIAO_APRESENTACAO.md b/docs/SGF_GUIAO_APRESENTACAO.md index 7e6d0d2..7750812 100644 --- a/docs/SGF_GUIAO_APRESENTACAO.md +++ b/docs/SGF_GUIAO_APRESENTACAO.md @@ -1,6 +1,6 @@ # Guião de apresentação local do SGF -Entrada: **http://127.0.0.1:8085/sgf**. A aplicação é o SGF; não precisa do SGC ou de outra demonstração. Os seis módulos estão alinhados horizontalmente: abra o dropdown do módulo e escolha **Consultar** para chegar directamente ao exemplo, ou escolha a função de trabalho indicada. Os nomes QA identificam dados de teste. A lista anterior dos17 perfis continua em «Todos os perfis e módulos autorizados». +Entrada: **http://127.0.0.1:8085/sgf**. A aplicação é o SGF; não precisa do SGC ou de outra demonstração. Os sete módulos, incluindo RH autónomo, estão alinhados horizontalmente: abra o dropdown do módulo e escolha **Consultar** para chegar directamente ao exemplo, ou escolha a função de trabalho indicada. Os nomes QA identificam dados de teste. A lista anterior dos17 perfis continua em «Todos os perfis e módulos autorizados». Dentro da aplicação, cada módulo tem o seu dropdown de funções. Escolher uma função de outro módulo muda explicitamente o contexto; continua a exigir a mesma autoridade e protege formulários não gravados/confirmações pendentes. Início e opções gerais estão separados dos módulos. @@ -71,4 +71,17 @@ Na8085, entrar na função Contabilidade de consulta QA17 e escolher «Demonstra Oficinas → Equipamentos → **Consultar ordens deste equipamento**: escolher um registo existente, consultar identificação/ordens actuais, pesquisar/paginar e abrir a ordem exacta. **Voltar ao catálogo de equipamentos** conserva a pesquisa anterior. Equipamento sem ordens mostra zero; não é necessário criar operações para apresentar. [Detalhes](SGF_WORKSHOP_EQUIPMENT_ORDERS_DELIVERY.md). -**7. Recursos Humanos (autónomo):** na entrada `/sgf`, abra o dropdown Recursos Humanos e escolha RH consulta, preparação ou revisão. Dentro, abra Cadastro e remunerações internas QA: cadastro QA-COLABORADOR-01, regras artificiais3/4/8 e processamento2026-11 revistos, bruto10000/líquido8900/custo10400, mapa conservado e histórico. Consultar não repete seis decisões. Os perfis Financeiros não têm acesso a RH. Ainda não há contratos, assiduidade/férias ou cálculo salarial legal. [Relatório](SGF_HUMAN_RESOURCES_DELIVERY.md). +**7. Recursos Humanos (autónomo):** na entrada `/sgf`, abra o dropdown Recursos Humanos e escolha RH consulta, preparação ou revisão. Dentro, abra Cadastro e remunerações internas QA: cadastro QA-COLABORADOR-01, regras artificiais3/4/8 e processamento2026-11 revistos, bruto10000/líquido8900/custo10400, mapa conservado e histórico. Consultar não repete seis decisões. Os perfis Financeiros não têm acesso a RH. Contratos, assiduidade e férias estão na função própria de RH; o cálculo salarial legal continua pendente. [Relatório](SGF_HUMAN_RESOURCES_DELIVERY.md). + +**RH → Contratos, assiduidade e férias:** escolher QA-COLABORADOR-01 explicitamente, consultar QA-CONTRATO-01, dias09/10Nov, condição fictícia960min, pedido/gozo480 e disponível480. Em Conferência2026-11 mostrar os dois dias conhecidos e28 sem cobertura, o mapa/SHA e os históricos. Não repetir16 decisões para apresentar. O saldo conservado na condição é960; o saldo efectivo actual é480, identificado separadamente. [Detalhes](SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md). + +Numa versão revista de RH, **Descarregar resumo legível desta versão** apresenta campos, tempos e saldos em formato de leitura. O CSV original conservado mantém botão/SHA separados. O resumo indica a fonte e não substitui o original nem representa um documento legal. [Detalhes](SGF_HUMAN_RESOURCES_READABLE_DELIVERY.md). + + +## Activos na mesma8085 + +Escolha Contabilidade — preparação, revisão ou consulta, conforme o trabalho, e abra Contabilidade → **Activos e depreciações internas QA**. Seleccione QA-ACTIVO-01 para consultar cadastro revisto, histórico e diário: custo1200, acumulado100, líquido1100. Em Planos mensais e Quotas por competência, escolha explicitamente a origem existente; não repita as confirmações do exemplo. Os dois lançamentos já estão aplicados. A nova demonstração interna parcial usa24 contas de plano/23 com movimento/29 lançamentos; o relatório anterior19/27 continua histórico e inalterado. Não apresentar taxas fiscais legais ou pagamentos deste activo. + +## Facturação multilinha —11/11 + +Na mesma8085, Comercial → Facturação → **Documento com várias linhas QA**. Abra os rascunhos QA-MULTI-ACRESCIDO-01 e QA-MULTI-INCLUIDO-01 explicitamente para consultar as duas linhas, os totais1000+160=1160 e o PDF conservado TEST-MULTI-2026/1 e/2. Não repetir preparações/revisões/emissões. No Financeiro, as posições filtradas por TEST-MULTI-2026 mostram dois recebíveis1160, total2320, sem recebimentos e com vencimento efectivo desconhecido. Na Contabilidade, as duas origens têm lançamento no mesmo diário, agora31. A demonstração interna parcial v6 conserva resultado conhecido2286,21 e dez contas por classificar; as antigas continuam históricas. [Entrega e limitações](SGF_MULTI_LINE_INVOICE_DELIVERY.md). diff --git a/docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md b/docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md new file mode 100644 index 0000000..232a7ed --- /dev/null +++ b/docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md @@ -0,0 +1,62 @@ +# RH — contratos, assiduidade e férias integrados + +11/11/2026. Entregue na **mesma8085**, entrada `/sgf` → **Recursos Humanos** → **Contratos, assiduidade e férias**. Escolher explicitamente o colaborador revisto; as cinco funções mantêm a pessoa/contexto: contratos, planos/assiduidade, condições de férias, pedidos/gozo e conferência do período. Não depende do SGC ou de outra demonstração. + +## Percurso utilizável + +Contrato interno individual preparado/revisto → planos e declarações de tempos separados → condição de férias explícita preparada/revista → pedido/revisão/reserva → declaração/revisão do gozo → conferência do dia/período e mapa conservado por SHA. Novas decisões exigem prévia, fundamento e revisão por outra pessoa; formulários e confirmação incerta bloqueiam troca de pessoa/função. As fontes são escolhidas por selectores pesquisáveis/paginados, sem primeiro registo automático. Pesquisa, pessoa, mês/estado e páginas25/100 são aplicados no servidor antes de devolver registos. + +Contratos têm referência/pessoa estáveis, início/fim ou modalidade de fim desconhecido/sem termo declarado, bruto mensal, evidência, correcção, terminação/cancelamento e versões. Bloqueia sobreposição efectiva e alterações que excluam datas de dependentes. Fim desconhecido não comprova vigência para operações que a exigem. Não é contrato juridicamente validado ou assinatura electrónica. + +Assiduidade distingue plano, declaração e conferência de férias: conjuntos desconhecidos são nulos, conjuntos explicitamente vazios são diferentes de desconhecido. Intervalos locais até oito por conjunto/dia, pausas separadas, fronteiras UTC verificáveis e fim à meia-noite explícito. Trabalho nocturno reparte-se em registos diários escolhidos, sem linhas automáticas. Horas fora do plano não são horas extra autorizadas; dias sem evidência não são faltas presumidas. + +Férias usam minutos de orçamento QA explícito, sem direito anual/feriados/dias úteis inferidos. Reserva/gozo/cancelamento contribuem uma única vez pelo estado efectivo. Revalida saldo, plano, contrato, intervalos e conflitos dentro da transacção; sem saldo negativo, sobreposição trabalho/férias ou dupla reserva. A ficha distingue **saldo efectivo da consulta** do resultado conservado na versão. Os saldos das condições da página são projectados conjuntamente em SQL, sem somar versões históricas nem o universo em memória. + +Correcções, recusas e cancelamentos mantêm originais. Uma proposta tornada inviável pode ser recusada mediante **nova conferência explícita**: a prévia mostra fonte alterada e saldo actual, e não aplica a proposta. Uma prévia antiga continua inválida se algo mudar antes da confirmação. Isso evita manter uma proposta pendente para sempre apenas porque o saldo se esgotou. + +A conferência por pessoa/período conserva fontes/versões, dias conhecidos, desconhecidos e saldos, com histórico/recibo/exportação autenticada e SHA. É uma cobertura dos dias registados; não declara mês ou calendário legal completos. Cada campo de resultado é explicado em horas/minutos, sem expor instruções de implementação ao utilizador. + +## Exemplo conservado8085 + +Usou exclusivamente **QA-COLABORADOR-01**, já revisto: + +|Origem|Resultado| +|---|---| +|QA-CONTRATO-01|01/11–31/12/2026, bruto mensal interno10000MZN, preparado e revisto| +|09/11|Plano08–12/13–17 e declaração480min revistos; zero minutos por conferir| +|10/11|Plano480min inicialmente com declaração desconhecida; após gozo, declaração explicitamente vazia e férias conferidas| +|Condição fictícia|960min, sem direito legal a férias presumido| +|Pedido/gozo10/11|Reserva480 → gozo480 por decisões distintas; saldo disponível480, reservado0, gozado480| +|Conferência2026-11|Dois dias conferidos;28 outros dias sem cobertura comprovada, sem inferir faltas ou mês completo| + +São seis origens e **16 versões/recibos técnicos**: contrato2, dias6, condição2, pedido/gozo4 e conferência2. O mapa é conservado em CSV/SHA. Não repetir estas decisões para apresentar ou testar. Progresso/chaves/artefactos ficam privados. + +Novas preparações salariais podem escolher explicitamente contrato revisto, capturando versão/vigência/bruto e revalidando-os antes de gravar. Essa ligação foi provada **apenas na regressão**, incluindo alteração posterior do contrato e preservação do mapa antigo. As seis versões/recibos e o CSV salarial8085 continuam byte a byte intactos; o mapa de pessoal identifica que o processamento anterior não tem ligação contratual histórica comprovada. Não se criou outro salário, dívida, pagamento ou lançamento. + +## Perfis, provas e preservação + +Autorização: **Autorizar o bloco e as30 concessões QA apresentadas**. Nove nomes novos (`human_resources.contracts/attendance/leave` × `view/prepare/review`),15 concessões em cada livro QA existente,30 total, só aos perfis RH já existentes: consultas aos três, preparação a `hr_prepare`, revisão a `hr_review`. Sem utilizadores/perfis/roles novos, mudança dos pins/registos iniciais, acesso Financeiro ou direitos PostgreSQL/PUBLIC. + +[Índice de provas com hashes](proofs/sgf-hr-personnel-20261111.json): + +-21 verificações do percurso na regressão, totalmente revertido: contrato/dias/reserva/gozo/cancelamento/conferência/CSV e recusas fundamentais. +-26 fronteiras, revertidas: fonte/versão alterada, saldo desconhecido/insuficiente, conflitos, recuperação atómica após falha entre versão/recibo/auditoria, segunda pessoa/contrato e ligação salarial explícita, terminação compatível, filtros25+2/100 e pesquisa literal.27 linhas adicionais são fixtures SQL de leitura, não contratos reais/workflows. +-Nove de privacidade/isolamento/revogação de acesso, com fixture SQL estrangeira e alterações de direitos revertidas. +-23 do cálculo puro de intervalos/desconhecidos/conflitos/saldo, incluindo ordem de campos JSON semanticamente equivalente. Não somar a passagem inicial22 outra vez. +-19 handlers de interface com API simulada, mais dois testes finais da distinção saldo actual/conservado e da recusa conferida; não são revisão visual. +-Oito da leitura conjunta dos saldos:26 condições SQL artificiais revertidas; quatro projecções SQL para páginas25 e100, incluindo os saldos efectivos da página e desconhecidos. Exclui guardas/host; não é medição de tempo/memória/carga ou prova de implantação. +-Sete da recusa após alteração/esgotamento de saldo, sem reserva aplicada e com repetição única; tudo revertido. +-97 HTTP8085,16 comandos individuais realizados uma única vez;18 leituras finais e três entregas finais de JS/MIME/SHA, sem repetir comandos. Autenticação401/CSRF419, fronteiras entre módulos/perfis, dados efectivos, historial de desconhecido, CSV original/novo e saldos conferidos. +-40 verificações de compatibilidade salarial e16 de interface salarial, reexecutadas porque foram alteradas a opção de ligação contratual e a indicação de funções já entregues. O teste de cancelamento foi limitado ao código do trabalhador de ensaio para não confundir a origem válida de concorrência já existente. Suites alheias não foram repetidas. + +São **233 verificações dirigidas**,56 de compatibilidade afectada, mais a concorrência observada e78 de preservação. Não somar passagens substituídas ou provas anteriores reutilizadas como novas. + +Concorrência só na regressão: duas revisões de pedidos para o mesmo orçamento480min. A primeira manteve `FOR UPDATE` da empresa; `pg_blocking_pids` identificou a segunda à espera de `transactionid`. Segunda confirmação503 após5,09s; depois da libertação409 por fonte desactualizada, nova preparação409 por saldo insuficiente, repetição da primeira200. Só um pedido aprovado, três recibos dos pedidos. A preparação deixou11 versões/recibos administrativos de suporte apenas na regressão; sem salários/efeitos financeiros. Não é carga sustentada. + +Preservação: **62 conjuntos originais idênticos**, incluindo utilizadores, memberships, perfis/pins, seis versões/recibos salariais e restantes fontes; todas as linhas antigas de permissões iguais, só15 grants exactos acrescentados. Três bindings iguais desde a linha de base aceite,27 lançamentos/19 contas/Caixa950 e artefactos salariais preservados. Seis registos/cache não foram alvo de comandos. Tabelas técnicas `hp_meta/hp_versions/hp_commands` apenas nos dois livros QA existentes, sem migrations globais, novos índices em tabelas antigas ou concessões públicas. Ficheiros anteriores/assets conservados em privado. Sem limpeza da cache, reinício, nova demonstração, base ou porta. + +## Limites restantes + +É um percurso administrativo **sintético interno**, não validação da legislação laboral/fiscal. Não implementa assinatura/arquivo binário legal de contrato, relógio de ponto/biometria, gestão geral de turnos nocturnos ou feriados, acréscimo legal anual de férias/transferências de saldo, benefícios ou cálculo salarial legal IRPS/INSS. Intervalos nocturnos são divididos explicitamente por dia; até oito intervalos efectivos por dia e64 intervalos brutos de pedidos exigem consolidação individual quando excedidos. Não se propagam decisões, alterações contratuais ou correcções a dependentes ou salários. + +Visual, carga sustentada, armazenamento e capacidade de implantação continuam pendentes com evidência própria. O novo mapa não declara empresa, mês ou produto completo. Sem SGC, publicação, candidata de Compras, aceitação ou checkpoint presumidos. diff --git a/docs/SGF_HUMAN_RESOURCES_PERSONNEL_SCOPE.md b/docs/SGF_HUMAN_RESOURCES_PERSONNEL_SCOPE.md index 77d0267..dc0f6e7 100644 --- a/docs/SGF_HUMAN_RESOURCES_PERSONNEL_SCOPE.md +++ b/docs/SGF_HUMAN_RESOURCES_PERSONNEL_SCOPE.md @@ -1,6 +1,6 @@ # RH — contratos, assiduidade e férias integrados -11/11/2026. Pedido expresso para concluir estas três áreas no módulo autónomo RH, na mesma8085. **Bloco e30 concessões QA autorizados expressamente11/11: Autorizar o bloco e as30 concessões QA apresentadas. Implementação em curso.** A conferência SELECT-only dos dois livros existentes confirma nove nomes de direito sem nenhuma concessão. +11/11/2026. Pedido expresso para concluir estas três áreas no módulo autónomo RH, na mesma8085. **Bloco e30 concessões QA autorizados expressamente11/11: Autorizar o bloco e as30 concessões QA apresentadas. [Entrega concluída](SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md).** A conferência SELECT-only dos dois livros existentes confirma nove nomes de direito sem nenhuma concessão. ## Entrega única proposta diff --git a/docs/SGF_HUMAN_RESOURCES_READABLE_DELIVERY.md b/docs/SGF_HUMAN_RESOURCES_READABLE_DELIVERY.md new file mode 100644 index 0000000..c475e9d --- /dev/null +++ b/docs/SGF_HUMAN_RESOURCES_READABLE_DELIVERY.md @@ -0,0 +1,21 @@ +# RH — resumo legível das versões revistas + +11/11/2026. Disponível na mesma8085: **Recursos Humanos → Contratos, assiduidade e férias → escolher uma versão revista → Descarregar resumo legível desta versão**. Não requer novos direitos. Continua disponível a descarga separada do CSV original conservado. + +O resumo apresenta campos com nomes legíveis para contrato, assiduidade do dia, condição/pedido de férias e conferência do período. Inclui colaborador fictício, versão, preparação/revisão, data, fundamento, evidência, valores e limites. O relatório do período separa os dois dias conhecidos dos restantes sem cobertura. Tempo desconhecido permanece desconhecido; declaração explicitamente vazia é identificada. Saldos são os da versão revista, não o estado actual. + +É uma **vista derivada**, formato `SGF-HR-READABLE-CSV-v1`, exclusivamente da versão aprovada e imutável; não consulta fontes actuais, não recalcula o resultado nem grava outro documento/decisão. O próprio resumo identifica o SHA do CSV original e o formato de leitura. O SHA do resumo é distinto e calculado sobre os bytes efectivamente enviados. Não apresenta esta derivação como o artefacto original conservado ou como documento legal. + +Descarga pela sessão/contexto existentes, sem tokens no URL. Antes de guardar, o cliente confere contexto ainda actual, tipo CSV, formato, SHA do original escolhido e SHA efectivo da resposta. BOM UTF8, separador ponto-e-vírgula, aspas/quebras escapadas e protecção contra fórmulas em texto livre. Uma resposta atrasada, fonte diferente ou erro de acesso não produz um ficheiro aceite. + +## Correcção da auditoria futura + +As novas decisões de pessoal passam a usar a área correcta (`human_resources.contracts/attendance/leave.prepare/review`), também discriminada nos detalhes permitidos da auditoria comum. A identificação herdada da família de remunerações foi detectada no serviço de pessoal. A correcção aplica-se **apenas a eventos futuros**. Os16 eventos/decisões anteriores da8085 não foram reclassificados ou reescritos: as fontes completas e correctas por tipo/autor/estado continuam em `hp_versions`, como nas provas aceites. O resumo usa essas versões, não tenta deduzir o tipo pela etiqueta histórica da auditoria comum. + +## Provas e preservação + +[Índice de provas](proofs/sgf-hr-readable-20261111.json):17 verificações da leitura/regressão e classificação de um novo evento de contrato, revertido;11 da descarga (integridade, origem, formato,403 e mudança de contexto); três do handler real de interface com fetch/hooks simulados;27 HTTP sobre versões efectivas8085, cinco tipos, históricos desconhecido/vazio, recusas e JS/MIME/SHA. São58 verificações dirigidas. Não são navegador/revisão visual. + +Todos os66 conjuntos originais comparados permanecem idênticos, incluindo16 versões/recibos de pessoal, seis versões/recibos salariais,27 lançamentos, utilizadores, perfis e acessos. Três bindings intactos. Os CSV originais das cinco famílias conservam os seus SHA; o mapa original coincide byte a byte com o ficheiro aceite. Seis registos/cache não foram alvo de comandos. Nenhuma nova decisão de negócio8085, concessão, perfil, schema, base/porta/demo, SGC, limpeza de cache ou reinício. Foram actualizados só seis ficheiros de fonte e os assets compilados, com anteriores conservados em privado. + +As233 provas de pessoal e56 de compatibilidade anteriores foram reutilizadas, sem repetir seus comandos ou suites. A nova leitura não declara carga sustentada, conformidade laboral/fiscal, capacidade de implantação, publicação, candidata de Compras, aceitação ou checkpoint. Visual e restantes lacunas de produto continuam separadas. diff --git a/docs/SGF_MULTI_LINE_INVOICE_DELIVERY.md b/docs/SGF_MULTI_LINE_INVOICE_DELIVERY.md new file mode 100644 index 0000000..3bc4037 --- /dev/null +++ b/docs/SGF_MULTI_LINE_INVOICE_DELIVERY.md @@ -0,0 +1,68 @@ +# Facturação multilinha integrada — entrega QA na8085 + +11/11/2026. Âmbito `SGF-MULTI-LINE-INVOICE-INTEGRATED-QA-v1`, expressamente autorizado. Código, interface e percurso efectivo entregues na instalação existente; sem novas demonstrações ou dependência do SGC. [Prova consolidada](proofs/sgf-multi-line-invoice-20261111.json). Não constitui aceitação, checkpoint, publicação ou conformidade fiscal real. + +## Resultado utilizável + +Em `/sgf`, módulo Comercial → Facturação → **Documento com várias linhas QA**: cabeçalho/cliente e dados documentais,1–25 linhas de serviços, pesquisa paginada, unidade/quantidade/preço e modalidade explícita, totais e diferenças, preparação auditada, cálculo conservado, revisão temporal distinta e emissão de teste/PDF. A configuração das séries permanece separada. O menu horizontal/dropdown aceite é preservado. + +|Exemplo existente|Modalidade|Linhas|Base|Imposto QA|Total|Recebível em aberto| +|---|---|---|---:|---:|---:|---:| +|TEST-MULTI-2026/1|Acrescido|400+600|1000|160|1160|1160MZN| +|TEST-MULTI-2026/2|Incluído|464+696|1000|160|1160|1160MZN| + +Dois recebíveis explicitamente preparados/revistos; nenhum pagamento, recebimento ou afectação. Duas origens explicitamente preparadas/revistas/aplicadas no mesmo diário em três linhas: D QA-CLIENTES1160 / C QA-VENDAS1000 / C QA-IMPOSTO-TESTE160. **29→31 lançamentos;24 contas intactas.** Não há emissão ou contabilização automática ao guardar/calcular. + +Nova preparação/revisão de resultado interno parcial v5/v6, usando a mesma correspondência revista v4:24 contas de plano,23 com movimento,31 lançamentos; activos classificados5575, fornecedor100, rendimentos4586,21, gastos2300, subtotal conhecido2286,21MZN. Dez contas continuam por classificar; capital, resultado completo e balanço completo indeterminados. Os CSV/snapshots19/27 e23/29 mantêm os bytes anteriores. Conferência actual do período somente de leitura:44 factos, zero pendentes, débitos/créditos iguais; período aberto. + +## Distribuição de funções sem novas concessões + +Os22 nomes de direitos verificados não pertencem todos à mesma pessoa. A implementação conserva a distribuição efectiva: + +- Comercial preparação QA8 prepara o rascunho, propõe a regra temporal e emite após revisão; não enumera o catálogo fiscal comum. +- Financeiro preparação QA13, no contexto Comercial explicitamente escolhido, conserva o cálculo com `fiscal_rules.prepare` e consulta a regra com `fiscal_rules.view`. Isto não concede ao Comercial o poder fiscal de13. +- Comercial revisão QA9 decide a versão temporal, com revalidação estritamente vinculada ao cálculo já conservado; não administra regras fiscais. +- Financeiro QA13/14 prepara/revê o recebível no módulo Financeiro. +- Contabilidade QA15/16 prepara/revê/aplica o lançamento no módulo Contabilidade. Consulta conserva os seus direitos existentes. + +Nenhum direito, concessão, utilizador, perfil, conta contabilística ou role PostgreSQL novo. Os rascunhos conservam associação **Comercial**. `QualifiedInvoiceSource` permite a leitura qualificada da dependência tipada pelos módulos financeiros autorizados, sem acrescentar associações modulares. A API Comercial directa continua a exigir troca explícita de módulo; a consulta financeira usa o seu próprio percurso. Não introduzir atalhos que contornem essa guarda. + +## Mecanismos e protecções + +`MultiLineDrafts` compõe atomicamente os escritores originais de prestação, rascunho e preparação. Regista a origem dos novos rascunhos em `mi_drafts`; não adopta ou altera rascunhos antigos. Campos desconhecidos podem permanecer no rascunho; impedem cálculo/emissão em vez de assumir zero. Prévia é revertida; confirmação exige autor/contexto/versões/catálogos actuais e fundamento. Depois de emitida, esta origem não é editável pelo editor novo. + +`MultiLineInvoiceCalculation` usa `FiscalRules::invoiceDocument` e o mesmo `FiscalDocumentMath`: truncatura por linha, base incluída=bruto−imposto documental e conservação das diferenças racionais. A captura vincula origem, preparação, unidades, modalidade, regra/vigência, data e identidade/poderes do preparador. Alterações invalidam conferências pendentes; uma recuperação de comando já concluído devolve o comprovativo histórico, sem revalidar uma emissão nova. + +`MultiLineInvoiceIssuance` adapta apenas o novo tipo de captura ao **ServiceInvoicing original**, seu contador, eventos, PDF guardado e recibos. O ramo de uma linha mantém o seu contrato. `SalesLedgerSource` confere os totais documentais congelados pelo mesmo cálculo e fornece o mesmo escritor de vendas, sem outro diário. Prévia de emissão não atribui número nem cria dívida. Recibos novos são individuais, isolados e append-only. Catálogos novos têm SQL25/100; decisões da página são lidas conjuntamente. Não se declara que a listagem administrativa antiga passou a ser um registo universal paginado. + +Nas operações novas, o bloqueio por empresa serializa confirmações; permissões são novamente conferidas após o bloqueio. O tratamento HTTP traduz espera excedida em503 com indicação para recuperar a mesma chave. A interface conserva pedido/chave quando o resultado é incerto e impede trocar origem/contexto em curso. A recuperação após recarregar a página depende do identificador original/comprovativo; não existe uma caixa de saída persistente nova no navegador. + +## Provas dirigidas e reutilizadas + +**143 verificações dirigidas,122 de preservação, concorrência separada e conferência de período somente de leitura.** Não somar as passagens iniciais substituídas. As suites alheias de Compras/Stock/RH/activos não foram repetidas. + +|Prova final|Verificações|Cobertura| +|---|---:|---| +|Percurso financeiro na regressão|26|Acrescido/incluído,100 incluído86,21+13,79, PDF, recebível, três linhas GL e propriedade Comercial; rollback| +|Controlador|18|Direitos, campos protegidos, SQL25/100, decisões, recibos e unidade estrangeira; rollback| +|Fronteiras|20|Desconhecidos,1/25/26 linhas, identidade/poderes, regra retirada, versões e modalidade; rollback| +|Falha parcial|6|Falha após escritores originais/antes do recibo, rollback integral e recuperação única de rascunho; falha tardia da emissão desfaz contador/factura/PDF/evento/recibo; rollback| +|Interface|21|Handlers com API simulada, escolha explícita, totais, incerteza/mesma operação, revisão distinta/menu; não é navegador| +|HTTP8085|28|Percurso concluído, fotografias/PDF/SHA, permissões e ficheiros efectivamente servidos| +|Resultado interno parcial|12|Duas decisões novas, mesmas correspondências, saldos e exportações históricas| +|Leituras finais HTTP|12|Registo Comercial25/100, posições Financeiras2320, prazo desconhecido e origem/recibos exactos| +|Preservação|122|96 conjuntos originais, appends exactos, três bindings, PDFs anteriores, escritores antigos, contas/diário/período e ficheiros instalados| + +Concorrência só na regressão existente: duas emissões com chaves distintas sobre o mesmo documento. A primeira retém a transacção; `pg_blocking_pids`/`pg_stat_activity` identificam a espera Lock/transactionid no bloqueio da empresa. A segunda recebe503 após **5,09s**. Libertada a primeira, a segunda recusa a conferência desactualizada com409; recuperar a primeira devolve200/replayed. Uma factura, um recibo de emissão e último número1. A fixture de concorrência fica identificada exclusivamente na regressão, sem recebível/GL: não é uma pendência da8085 nem deve ser usada para declarar fecho geral da regressão. + +Os dois percursos8085 somam22 comandos guardados em progresso privado; mais dois de resultado interno. Não os repetir para apresentação. A retomada do ensaio HTTP reutilizou resultados/chaves conservados após corrigir no harness o endereço de comando GL e a consulta de documento no módulo indevido. Não foram criados documentos suplementares. O teste de período inicialmente procurava uma propriedade `balanced` inexistente: a observação válida foi reutilizada e compararam-se `debit_minor`/`credit_minor`, sem repetir a consulta ou fechar o período. + +PDF máximo25linhas: amostra não emitida, cinco páginas renderizadas e inspeccionadas; texto legível, sem corte/colisão observado. O renderizador textual existente pode dividir a descrição de uma linha entre páginas; não é um modelo fiscal oficial nem revisão visual da aplicação. As capturas fornecidas orientaram os campos e percurso, sem importar dados pessoais ou copiar taxas como legislação. + +## Preservação e limites + +Todas as linhas anteriores dos96 conjuntos conferidos permanecem idênticas. Mantêm-se29 lançamentos originais,24 contas, seis versões/recibos de activos, custo1200/acumulado100/líquido1100, Caixa950, RH, Stock, Compras, permissões/utilizadores e três bindings. Novas escritas limitadas aos dois documentos e suas preparações/decisões/recebíveis/GL e resultado parcial autorizados. Os seis registos protegidos não foram escolhidos ou escritos; nenhum comando de limpeza de cache, migração geral, nova demonstração, publicação, SGC ou candidata de Compras foi executado. + +A proposta de vencimento no rascunho não se converte automaticamente em condição do recebível: os dois prazos efectivos permanecem desconhecidos. O livro/mapa IVA anterior conserva quatro versões e não inclui estes documentos. Não presumir que a nova regra artificial16% é taxa legal, que este ciclo valida emissão real ou que foram implementados regimes mistos, isenções, descontos fiscais, artigos/famílias, POS ou Excel. + +A instalação continua QA: revisão visual da aplicação, carga sustentada, armazenamento/recuperação operacional, fiscalidade/modelos legais e capacidade de implantação exigem evidência própria. A [matriz v30](integration/sgf-solution-contract.json) mantém `full_product_complete=false` e `client_presentable=false`. diff --git a/docs/SGF_MULTI_LINE_INVOICE_SCOPE.md b/docs/SGF_MULTI_LINE_INVOICE_SCOPE.md new file mode 100644 index 0000000..7af230c --- /dev/null +++ b/docs/SGF_MULTI_LINE_INVOICE_SCOPE.md @@ -0,0 +1,44 @@ +# Facturação por várias linhas — ciclo integrado QA proposto + +11/11/2026. **Autorizado expressamente11/11: «Autorizar a facturação multilinha QA proposta». Implementado e aplicado na mesma8085; ver [entrega](SGF_MULTI_LINE_INVOICE_DELIVERY.md).** Continuação funcional das referências de facturação fornecidas pelo utilizador, na mesma8085 e regressão existente, sem novas demonstrações ou SGC. + +## Bloqueio concreto e decisão necessária + +O cálculo comum de1–25linhas e de preço com imposto incluído já existe e foi aprovado. Porém, ServiceCalculation::evaluate continua a exigir uma linha/preço acrescido e ServiceInvoicing::reviewInside conserva a mesma restrição. O [âmbito anterior](SGF_MULTI_LINE_INCLUDED_SCOPE.md) diz: «Não emite factura, reconhece dívida, cria pagamento, lançamento ou altera o livro/mapa IVA.» Portanto, não basta retirar uma condição de interface: é preciso autorizar o novo efeito documental/financeiro e a sua ligação ao cálculo revisto. + +Proposta agrupada: terminar o percurso de documento por várias linhas no SGF, desde cabeçalho/cliente/linhas até cálculo, revisão, emissão QA/PDF, recebível e contabilização explícita no diário existente. Não propor uma decisão por campo ou etapa. + +## Funcionalidade e política + +Uma área de trabalho quotidiana reúne cabeçalho, cliente/NUIT reutilizados, moeda/datas/condições, grelha com1–25linhas e resumo de bases/imposto/totais sempre visível. Pesquisa e selecção explícita de serviços existentes; unidade/quantidade/preço por linha; sem importar nomes, contactos ou dados das capturas. Configuração de séries/regras fica em área própria e o menu horizontal/dropdown aceite permanece. Artigos/famílias/POS/importação Excel não são implicitamente implementados por esta grelha. + +Reutilizar o núcleo comum e a política já aprovada: imposto documental truncado por linha; preço incluído decompõe base documental como bruto menos imposto documental; conservar exactos e diferenças racionais e somar montantes documentais das linhas. Sem adoptar taxa como lei, perfil real, regimes mistos por linha, isenções presumidas, descontos fiscais novos ou linhas negativas. Campos não suportados não são mostrados como operações disponíveis. + +A nova captura é tipada/versionada e vinculada ao rascunho/preparação/perfil/regra/data/modalidade/linhas/unidades/contexto/pessoa. Edição ou mudança de origem invalida a conferência. Prévia não numera nem cria dívida; emissão exige revisão/prontidão e confirmação individual, fixa fotografia/PDF/SHA e número uma única vez pelo writer documental existente. Contabilidade mantém preparação/revisão distinta/aplicação próprias, usando o mesmo escritor de vendas em três linhas; não gerar lançamentos silenciosamente ao guardar. + +Os contratos e ramos antigos de uma linha permanecem inalterados. O adaptador novo aplica-se apenas a novas origens deste âmbito QA; nenhum documento/cálculo antigo é recalculado, convertido ou reemitido. + +## Exemplos/efeitos pedidos na8085 + +Reutilizar o cliente fictício autorizado do percurso comercial concluído e os serviços/cadastros QA existentes; escolha por referência exacta, sem usar ou associar os seis registos protegidos. Se for necessário preparar novos rascunhos/prestações, fazê-lo pelos mecanismos actuais, com proveniência Comercial explícita. Preços das linhas são dados de preparação auditados, não alteração automática do catálogo. + +Preparar/rever uma série nova **TEST-MULTI-2026**, pela administração já existente. Os números só são atribuídos nas confirmações efectivas; séries e números anteriores ficam intactos. Dois documentos QA de duas linhas: + +|Modalidade artificial16%|Linhas|Base documental|Imposto|Total| +|---|---|---:|---:|---:| +|Acrescido|400+600|1000|160|1160MZN| +|Incluído|464+696|1000|160|1160MZN| + +A regra QA16% existente é escolhida explicitamente na sua vigência; nenhuma alteração legal de taxa é declarada. O caso100incluído=86,21+13,79, diferenças racionais e limites1/25/26linhas são provas de regressão, sem terceiro documento8085. + +Cada documento tem um recebível1160MZN, total novo2320, sem recebimento ou afectação. Cada origem é conferida/aplicada individualmente no mesmo diário: D QA-CLIENTES1160 / C QA-VENDAS1000 / C QA-IMPOSTO-TESTE160. **Dois lançamentos novos,29→31**, preservando os29 originais. Não criar contas: mantém24. Não cria pagamento, Caixa, Stock, compra, custo ou salário. + +O relatório interno parcial pode receber uma nova preparação/revisão de resultado sobre as mesmas correspondências aprovadas, sem reescrever snapshots anteriores ou presumir contas por classificar como capital. Mantêm-se as quatro versões do livro/mapa IVA; os dois documentos novos não serão apresentados como incluídos nesse mapa antigo nem como apuramento geral. + +## Acessos e execução + +**Sem direitos, concessões, utilizadores ou perfis novos.** Conferência SELECT-only nos dois livros encontrou os22 nomes de direitos necessários já distribuídos pelos perfis QA existentes, sem alterar a distribuição. Comercial conserva cadastros/rascunhos; os responsáveis documentais conservam configuração/revisão/emissão que já possuem; Contabilidade conserva preparar/rever/aplicar. Consulta não passa a permitir emissão. Se uma concessão adicional se revelar indispensável, deve ser apresentada antes de a conceder. + +Primeiro implementar/provar todo o bloco na regressão existente; depois executar apenas os dois percursos positivos uma vez na8085 com chaves/progresso/recibos privados. Reutilizar as provas válidas de cálculos, emissão original, recebíveis, diário e navegação. Acrescentar apenas fronteiras alteradas: equivalência dos totais/PDF/fotografia/fonte GL, dados desconhecidos, modalidade/linhas divergentes, permissões/empresa/unidade/pessoa, versão/data alteradas, concorrência/numeração/idempotência e recuperação após falhas parciais, sem números/documentos/dívida/diários duplicados. + +Preservar os seis registos, cache bloqueada,29 lançamentos/24 contas, activo1200/100/1100, Caixa950, RH, Stock, compras e snapshots/provas aceites. Sem novas demonstrações, SGC, dados/acessos/operações reais, emissão fiscal legal, comunicação/envio à AT/cliente, modelos oficiais, publicação, candidata de Compras, reabertura, checkpoint ou aceitação presumida. Visual, carga sustentada e implantação mantêm pendências separadas. diff --git a/docs/integration/sgf-fixed-assets-access-proposal.json b/docs/integration/sgf-fixed-assets-access-proposal.json new file mode 100644 index 0000000..0bc7d41 --- /dev/null +++ b/docs/integration/sgf-fixed-assets-access-proposal.json @@ -0,0 +1,97 @@ +{ + "version": "SGF-FIXED-ASSETS-INTEGRATED-QA-v1", + "approved": true, + "implemented": true, + "applied": true, + "scope_file": "docs/SGF_FIXED_ASSETS_SCOPE.md", + "module": "accounting", + "books": [ + "existing8085", + "existing_regression" + ], + "scope": { + "tenant_id": 1, + "company_id": 2, + "unit_id": 2 + }, + "new_host_rights": [ + "fixed_assets.view", + "fixed_assets.prepare", + "fixed_assets.review" + ], + "grants_per_book": [ + { + "user_id": 15, + "permission": "fixed_assets.view" + }, + { + "user_id": 16, + "permission": "fixed_assets.view" + }, + { + "user_id": 17, + "permission": "fixed_assets.view" + }, + { + "user_id": 15, + "permission": "fixed_assets.prepare" + }, + { + "user_id": 16, + "permission": "fixed_assets.review" + } + ], + "grant_count_per_book": 5, + "total_grants_both_books": 10, + "new_users": 0, + "new_profiles": 0, + "new_postgres_grants": 0, + "new_public_rights": 0, + "new_demo": false, + "new_accounts_per_book": [ + { + "code": "QA-ACTIVO-CUSTO", + "purpose": "Custo bruto do activo fictício", + "statement_group": "assets" + }, + { + "code": "QA-ACTIVO-DEP-ACUM", + "purpose": "Depreciação acumulada (saldo credor no activo)", + "statement_group": "assets" + }, + { + "code": "QA-ACTIVO-GASTO-DEP", + "purpose": "Gasto de depreciação QA", + "statement_group": "expenses" + }, + { + "code": "QA-ACTIVO-ORIGEM-QA", + "purpose": "Contrapartida artificial de abertura, sem capital ou dívida presumidos", + "statement_group": "unclassified" + }, + { + "code": "QA-ACTIVO-GASTO-ABATE", + "purpose": "Perda interna no abate sem contraprestação", + "statement_group": "expenses" + } + ], + "original_journals_preserved": 27, + "proposed_new_journals_8085": 2, + "journal_count_after_8085": 29, + "original_accounts_preserved": 19, + "account_count_after_8085": 24, + "new_cash_or_debt_effects": false, + "typed_common_writer_extension_required": true, + "new_statement_mapping_and_result_commands_8085": 4, + "evidence": [ + "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/single-integration/20261111-103845-dbdf9215/assets-access-readonly.json", + "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-b4093c6f6707/single-integration/20261111-103847-36def480/assets-access-readonly.json" + ], + "authorization": { + "date": "2026-11-11", + "decision": "Autorizar o ciclo integrado de activos QA" + }, + "delivery": "docs/SGF_FIXED_ASSETS_DELIVERY.md", + "proof": "docs/proofs/sgf-fixed-assets-20261111.json", + "application_limits": "Only both existing QA books; no real accesses or production activation." +} diff --git a/docs/integration/sgf-human-resources-personnel-access-proposal.json b/docs/integration/sgf-human-resources-personnel-access-proposal.json index 81a1cf7..24ff1a1 100644 --- a/docs/integration/sgf-human-resources-personnel-access-proposal.json +++ b/docs/integration/sgf-human-resources-personnel-access-proposal.json @@ -1,8 +1,8 @@ { "version": "SGF-HR-CONTRACTS-ATTENDANCE-LEAVE-QA-v1", "approved": true, - "implemented": false, - "applied": false, + "implemented": true, + "applied": true, "scope_file": "docs/SGF_HUMAN_RESOURCES_PERSONNEL_SCOPE.md", "module": "human_resources", "books": [ @@ -110,5 +110,6 @@ "authorization": { "date": "2026-11-11", "decision": "Autorizar o bloco e as30 concessões QA apresentadas" - } + }, + "delivery": "docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md" } diff --git a/docs/integration/sgf-multi-line-invoice-proposal.json b/docs/integration/sgf-multi-line-invoice-proposal.json new file mode 100644 index 0000000..b06259c --- /dev/null +++ b/docs/integration/sgf-multi-line-invoice-proposal.json @@ -0,0 +1,39 @@ +{ + "version": "SGF-MULTI-LINE-INVOICE-INTEGRATED-QA-v1", + "approved": true, + "implemented": true, + "applied": true, + "scope_file": "docs/SGF_MULTI_LINE_INVOICE_SCOPE.md", + "target": "existing8085", + "regression": "existing_book", + "new_demo": false, + "sgc_required": false, + "new_rights": [], + "new_grants": 0, + "new_users": 0, + "new_accounts": 0, + "existing_permission_names_verified": 22, + "new_documents": 2, + "new_receivables_minor": [ + 116000, + 116000 + ], + "new_journals": 2, + "before_journals": 29, + "after_journals": 31, + "chart_accounts_preserved": 24, + "new_payments": 0, + "cash_preserved_minor": 95000, + "protected_records_preserved": 6, + "cache_cleared": false, + "new_series": "TEST-MULTI-2026", + "policy_reused": "SGF-MULTI-LINE-INCLUDED-QA-v1", + "decision_required": "Autorizar a facturação multilinha QA proposta", + "reason": "Previous calculation scope expressly excludes invoices, debt and journals; current invoicing engine still restricts one line and added price.", + "authorization": { + "date": "2026-11-11", + "decision": "Autorizar a facturação multilinha QA proposta" + }, + "delivery": "docs/SGF_MULTI_LINE_INVOICE_DELIVERY.md", + "proof": "docs/proofs/sgf-multi-line-invoice-20261111.json" +} diff --git a/docs/integration/sgf-solution-contract.json b/docs/integration/sgf-solution-contract.json index 24f9ea6..fe24fb5 100644 --- a/docs/integration/sgf-solution-contract.json +++ b/docs/integration/sgf-solution-contract.json @@ -1,5 +1,5 @@ { - "version": "SGF-SOLUTION-CLOSURE-v25", + "version": "SGF-SOLUTION-CLOSURE-v30", "target": "existing8085", "sgc_required": false, "new_demonstrations": false, @@ -16,7 +16,8 @@ "Recibo de afectação efectiva400, crédito nominal100/saldo500 e factura-recibo1000 atómica", "PDF/SHA, histórico e anulação técnica com reversão explícita e diário único", "Registo unificado autorizado com seis famílias, filtros/SQL25/100 e projecções limitadas à página", - "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Editor unificado1–25 serviços, imposto acrescido/incluído, cálculo comum revisto, PDF e passagem explícita recebível/diário QA" ], "required_host_tables": [ "a_clients", @@ -32,7 +33,12 @@ "sd_versions", "sd_outputs", "sd_commands", - "sd_cancellations" + "sd_cancellations", + "mi_meta", + "mi_calculations", + "mi_commands", + "mi_draft_meta", + "mi_drafts" ], "required_source_files": [ "app/Modules/Services/ServiceWorkflow.php", @@ -44,14 +50,21 @@ "app/Modules/Services/BillingRegister.php", "resources/js/BillingRegisterWorkspace.jsx", "resources/js/WorkspaceHome.jsx", - "resources/js/workspace-overview.js" + "resources/js/workspace-overview.js", + "app/Modules/Services/MultiLineInvoiceCalculation.php", + "app/Modules/Services/MultiLineInvoiceIssuance.php", + "app/Modules/Services/MultiLineDrafts.php", + "app/Modules/Services/QualifiedInvoiceSource.php", + "resources/js/MultiLineInvoiceWorkspace.jsx" ], "proofs": [ "docs/proofs/invoice-test-results.json", "docs/proofs/sgf-six-module-functional-review-20261110.json", "docs/SGF_SERVICE_DOCUMENTS_DELIVERY.md", "docs/SGF_BILLING_REGISTER_DELIVERY.md", - "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md" + "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "docs/SGF_MULTI_LINE_INVOICE_DELIVERY.md", + "docs/proofs/sgf-multi-line-invoice-20261111.json" ], "work_remaining": [ "Revisão visual, aceitação explícita e conformidade real; emissão fiscal real permanece bloqueada", @@ -309,7 +322,8 @@ "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", "Submenus distintos e balancete/razão por datas explícitas, escolha de conta por linha, páginas25/100, detecção de mudança de livro e CSV da página", "Controlo administrativo QA de obrigação/prazo/responsável do mapa interno revisto, preparação13/revisão14, correcção/cancelamento, entrega apenas simulada com SHA verificado, histórico/recibo/SQL25/100/CSV; sem calendário legal ou submissão", - "Correspondências explícitas revistas15/16 e balanço/resultados internos parciais QA:19contas/nove pendentes, resultado congelado19linhas/SHA, história/recibo/CSV contextual, ligação exacta ao razão; capital e resultado completo não presumidos" + "Correspondências explícitas revistas15/16 e balanço/resultados internos parciais QA:19contas/nove pendentes, resultado congelado19linhas/SHA, história/recibo/CSV contextual, ligação exacta ao razão; capital e resultado completo não presumidos", + "Activos QA: cadastro/custódia/plano/quota revistos, diário único1200/100 e líquido1100; correcção/abate/fecho do recorte na regressão" ], "required_host_tables": [ "ca_proposals", @@ -321,7 +335,10 @@ "fr_commands", "fo_meta", "fo_versions", - "fo_commands" + "fo_commands", + "fa_meta", + "fa_versions", + "fa_commands" ], "required_source_files": [ "app/Modules/Accounting/GeneralLedger.php", @@ -357,7 +374,12 @@ "resources/js/statement-download.js", "resources/js/FiscalObligationsWorkspace.jsx", "database/sql/internal_statements.sql", - "config/internal_statements.php" + "config/internal_statements.php", + "app/Modules/Accounting/FixedAssets.php", + "app/Modules/Accounting/FixedAssetLedgerSource.php", + "app/Modules/Accounting/FixedAssetJournalController.php", + "resources/js/FixedAssetsWorkspace.jsx", + "packages/general-ledger/src/LedgerAssets.php" ], "proofs": [ "docs/proofs/purchasing-completion-matrix-final-20261104.json", @@ -369,11 +391,14 @@ "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", "docs/SGF_ACCOUNTING_REPORTS_DELIVERY.md", "docs/SGF_FISCAL_OBLIGATIONS_DELIVERY.md", - "docs/SGF_FINANCIAL_STATEMENTS_DELIVERY.md" + "docs/SGF_FINANCIAL_STATEMENTS_DELIVERY.md", + "docs/SGF_COVERAGE_RECONCILIATION.md", + "docs/proofs/sgf-fixed-assets-20261111.json" ], "work_remaining": [ "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", - "Backlog fiscal/financeiro amplo pendente; tabela de cobertura não implementa motores ou modelos" + "Backlog fiscal/financeiro amplo pendente; tabela de cobertura não implementa motores ou modelos", + "Activos gerais e tratamento fiscal/legal, alienação/contraprestação, correcções de custo já admitido e cadeias adicionais fora do recorte QA" ], "required_native_tables": [ "gl_accounts", @@ -384,7 +409,9 @@ "fs_versions", "fs_results", "fs_result_lines", - "fs_commands" + "fs_commands", + "gl_asset_books", + "gl_asset_extension_versions" ] }, { @@ -396,29 +423,46 @@ "Regras artificiais versionadas com vigência e fonte explícitas", "Processamento exacto por trabalhador/período com escolha de fontes e revisão distinta", "Correcção/cancelamento versionados, prévia, histórico, recibos e recuperação", - "Mapa individual revisto conservado com SHA e exportação autenticada; filtros SQL25/100" + "Mapa individual revisto conservado com SHA e exportação autenticada; filtros SQL25/100", + "Contratos internos QA revistos com correcção/terminação/cancelamento e dependências", + "Plano e assiduidade por dia com intervalos explícitos, dados desconhecidos e revisão distinta", + "Condição de férias QA, reserva/gozo/cancelamento individuais, saldo efectivo sem duplicação", + "Conferência parcial do período por pessoa, fontes/versões e CSV/SHA conservado", + "Ligação contratual explícita opcional em novas preparações salariais, provada na regressão sem alterar salários8085", + "Resumo CSV legível derivado de versão revista, com SHA próprio/original, sem alterar o artefacto conservado" ], "required_host_tables": [ "rm_meta", "rm_versions", "rm_commands", - "hr_qa_profiles" + "hr_qa_profiles", + "hp_meta", + "hp_versions", + "hp_commands" ], "required_source_files": [ "app/Modules/HumanResources/Remunerations.php", "app/Modules/HumanResources/Http/RemunerationsController.php", "app/Shared/Installation/HumanResourcesProfiles.php", "resources/js/RemunerationsWorkspace.jsx", - "resources/js/statement-download.js" + "resources/js/statement-download.js", + "app/Modules/HumanResources/Personnel.php", + "app/Modules/HumanResources/PersonnelTime.php", + "app/Modules/HumanResources/Http/PersonnelController.php", + "resources/js/PersonnelWorkspace.jsx", + "app/Modules/HumanResources/PersonnelReadableCsv.php" ], "proofs": [ "docs/SGF_HUMAN_RESOURCES_DELIVERY.md", - "docs/proofs/sgf-human-resources-20261111.json" + "docs/proofs/sgf-human-resources-20261111.json", + "docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md", + "docs/proofs/sgf-hr-personnel-20261111.json", + "docs/SGF_HUMAN_RESOURCES_READABLE_DELIVERY.md" ], "work_remaining": [ - "Contratos/alterações contratuais, assiduidade, turnos, férias e ausências", - "Regimes, IRPS/INSS/modelos legais e ligação salarial a dívida/pagamentos/Contabilidade", - "Revisão visual, carga sustentada e capacidade de implantação" + "Conformidade e aplicabilidade laboral real: contratos legais, turnos/calendário, férias legais, benefícios e modelos", + "Cálculo legal IRPS/INSS e ligação a dívida/pagamento/Contabilidade sem efeitos implícitos", + "Revisão visual, carga sustentada, armazenamento e capacidade de implantação" ] } ], @@ -458,7 +502,7 @@ ], "acceptance_proof": "docs/proofs/sgf-single-solution-acceptance.json", "scope_policy": "Recorte QA dos seis módulos e requisitos amplos de produto separados explicitamente; nenhum painel de cobertura equivale à implementação dos motores ou modelos pendentes.", - "revision_note": "A prova funcional anterior conserva o contrato v1 histórico; as lacunas agora discriminadas não são aceitação nem capacidade de implantação.", + "revision_note": "11/11: activos QA integrados na8085, origem1200/quota100 no diário único,29 lançamentos/24 contas; demonstração nova parcial23 contas com movimento e snapshot antigo19/27 intacto. Produto amplo não concluído; revisão visual/carga/legal/implantação separadas.", "proposed_scope_extensions": [ { "id": "cash", @@ -529,6 +573,23 @@ "access": "docs/integration/sgf-statements-access-proposal.json", "approval_required_for_new_qa_rights": false, "implemented": true + }, + { + "id": "fixed_assets_qa", + "scope": "docs/SGF_FIXED_ASSETS_SCOPE.md", + "access": "docs/integration/sgf-fixed-assets-access-proposal.json", + "approval_required_for_new_qa_rights": false, + "implemented": true, + "delivery": "docs/SGF_FIXED_ASSETS_DELIVERY.md" + }, + { + "id": "multi_line_invoice_qa", + "scope": "docs/SGF_MULTI_LINE_INVOICE_SCOPE.md", + "decision": "docs/integration/sgf-multi-line-invoice-proposal.json", + "approval_required_for_new_effects": false, + "new_rights": [], + "implemented": true, + "delivery": "docs/SGF_MULTI_LINE_INVOICE_DELIVERY.md" } ], "full_product_complete": false, @@ -545,7 +606,7 @@ "status": "partial", "implemented": false, "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", - "description": "Cadastro/regras/processamento interno artificial QA em RH autónomo entregue; operações laborais gerais, cálculo legal IRPS/INSS/modelos e ligação salarial financeira/contabilística pendentes" + "description": "RH autónomo QA com cadastro/contratos/assiduidade/férias e remunerações internas entregue; regimes laborais gerais, IRPS/INSS/modelos legais e ligação financeira/contabilística salarial pendentes" }, { "id": "irpc", @@ -588,6 +649,17 @@ "implemented": false, "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", "description": "Dois critérios/indicadores QA com fontes conciliadas entregues; rácios gerais, critérios reais e tendências/séries permanecem pendentes" + }, + { + "id": "fixed_assets", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md FIS-SGF-15", + "description": "Ciclo integrado QA entregue na8085: cadastro/plano/quota/diário/relatórios e ramos de correcção/abate/fecho do recorte na regressão. Operação geral/legal de activos e taxas fiscais continuam pendentes.", + "scope": "docs/SGF_FIXED_ASSETS_SCOPE.md", + "access_proposal": "docs/integration/sgf-fixed-assets-access-proposal.json", + "qa_implemented": true, + "delivery": "docs/SGF_FIXED_ASSETS_DELIVERY.md" } ], "client_delivery_sources": [ diff --git a/docs/proofs/sgf-coverage-reconciliation-20261111.json b/docs/proofs/sgf-coverage-reconciliation-20261111.json new file mode 100644 index 0000000..f65dc5a --- /dev/null +++ b/docs/proofs/sgf-coverage-reconciliation-20261111.json @@ -0,0 +1,33 @@ +{ + "passed": true, + "date": "2026-11-11", + "directed_checks": 15, + "identical_original_tables": 66, + "new_grants": 0, + "business_commands": 0, + "cache_cleared": false, + "new_demo": false, + "visual_review": false, + "evidence": [ + { + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-b4093c6f6707/single-integration/20261111-103843-f515a584/coverage-reconciliation.json", + "sha256": "6154e9b5cc8ba35db480fdb330a33c5857d372cb2ed64ea9e5985eaa12a3c028", + "checks": 7 + }, + { + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/coverage-reconciliation-http-20261111.json", + "sha256": "78f94a8cc1f758e3ffe06dc23986ab574c5929ac8bacda3f430cc0d4ec9a90cb", + "checks": 8 + }, + { + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/single-integration/20261111-103940-12c6c0e7/coverage-reconciliation-installation.json", + "sha256": "477e55ca47bf452a26d83aac316c8fcd621e5c5f5eecfb658a7281187dd3c358", + "checks": 0 + }, + { + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/single-integration/20261111-104407-f54005b6/coverage-reconciliation-preservation.json", + "sha256": "decb9c3c3c42f78b6826c547a242b9c5e489db22790d89b192da4e55fafb0846", + "checks": 0 + } + ] +} diff --git a/docs/proofs/sgf-fixed-assets-20261111.json b/docs/proofs/sgf-fixed-assets-20261111.json new file mode 100644 index 0000000..90bcdce --- /dev/null +++ b/docs/proofs/sgf-fixed-assets-20261111.json @@ -0,0 +1,101 @@ +{ + "passed": true, + "date": "2026-11-11", + "installation": "existing8085", + "contract": "SGF-FIXED-ASSETS-INTEGRATED-QA-v1", + "checks_by_group": { + "functional": 17, + "correction": 16, + "boundaries": 23, + "journal_boundaries": 9, + "period_sources": 2, + "writeoff_and_close": 5, + "ui_handlers": 15, + "http8085": 25, + "statements_http": 14, + "source_version": 3, + "final_read_and_coverage": 3, + "sequential_quota_writeoff_guards": 6 + }, + "new_checks_total": 141, + "additional_assertions": { + "atomic_failure_reused_from_completed_assertion_before_later_harness_error": true, + "current_period_facts40_pending0": true, + "current_period_balanced": true + }, + "concurrency": { + "during_lock": 503, + "after_release": 409, + "original_replay": 200, + "seconds_waiting": 5.242017030715942, + "blocker": "host company row FOR UPDATE, transactionid wait", + "approved_versions": 2, + "receipts": 2 + }, + "preservation": { + "checks": 87, + "original_sets": 78, + "original_rows_identical": true, + "bindings_unchanged": 3, + "approved_appends": { + "host.permission_grants": 5, + "native.gl_journals": 2, + "native.audit_logs": 4, + "native.fs_versions": 4, + "native.fs_results": 1, + "native.fs_result_lines": 23, + "native.fs_commands": 4, + "native.gl_accounts": 5, + "native.gl_rules": 6, + "native.gl_proposals": 2, + "native.gl_reviews": 2, + "native.gl_lines": 4, + "native.gl_commands": 6, + "native.gl_events": 6 + } + }, + "new8085": { + "asset_records": 1, + "administrative_versions": 6, + "administrative_receipts": 6, + "grants": 5, + "new_accounts": 5, + "journals": 2, + "old_journals_preserved": 27, + "total_journals": 29, + "chart_accounts": 24, + "statement_accounts_with_movements": 23, + "statement_new_commands": 4, + "cost_minor": 120000, + "accumulated_minor": 10000, + "carrying_minor": 110000 + }, + "arithmetic13_reused_not_rerun": true, + "old_snapshot19_accounts27_journals_preserved": true, + "old_suites_reused": true, + "legacy_ssr_navigation_runner": "Terminated without a result; imports/expectations predate accepted dropdown. Not counted as passed. New asset handler/navigation checks and private build passed.", + "period_test_limits": "Asset-only regression harness closed and rolled back. Application8085 period read40facts0pending only; no closure executed.", + "browser_visual_review": false, + "sustained_load_validated": false, + "deployment_validated": false, + "full_product_complete": false, + "new_demo": false, + "sgc_required": false, + "cache_cleared": false, + "published": false, + "installed_final_source_sha256": { + "app/Modules/Accounting/FixedAssets.php": "390051a6bf8d7fc02cad111b707ea4bceba1c8d39986af146370c0191d6de760", + "app/Modules/Accounting/FixedAssetLedgerSource.php": "3facfc05db0636f01fd84f97f76d19cd2efd1599dfe4ab20a53ca4c8fe077f9c", + "app/Modules/Accounting/FixedAssetJournalController.php": "eba98630909ee5f7b5cd18718dbebf599b408eccdb201c3d171ae80b4dfb1502", + "app/Modules/Accounting/ComposedLedger.php": "25e14af7f62dea2f205f9946135fb016580d1ca2446c9303313d76c2ccd99e04", + "packages/general-ledger/src/LedgerAssets.php": "aa5b0e7766c02587d28d55c8566b6f186ffbca60c180401ba0ead31f9a426cfe", + "packages/general-ledger/src/PayableJournal.php": "2c45e2c5635d4ae60577f6261bf749f9ca91bb09435fcbe373d58a265583c1be", + "packages/general-ledger/src/LedgerCycle.php": "4defe64d7c65165c4f4567628b43b387c82c8219ada26dc3119d5d37635ec63b", + "packages/general-ledger/bootstrap.php": "748fd49400440b5d09996f3bad5613053f7094ae5903aa7f6169e7c945e4d26b", + "app/Shared/Context/ModuleNavigation.php": "36d94a8131290d77fb0ae5e5a308e28cbb4c7644455d654c1b979f2324292e4a", + "app/Modules/Identity/Services/IdentityWorkflow.php": "79a2bd9e91a77b4fe4c36efa863e4ca843ae0464e75dc9f54ba21a59beb7020b", + "routes/web.php": "f45e10e0dd7feef43767956d0b46663f46d3845e48c0f40c3980c9e930dda0f2", + "app/Modules/Services/FiscalCoverage.php": "592d56e2e19a21c9955eddf4bd2c83af610f87536bc0d6ed85b034b797bf2791" + }, + "last_changes_read_only_verified": true +} diff --git a/docs/proofs/sgf-hr-personnel-20261111.json b/docs/proofs/sgf-hr-personnel-20261111.json new file mode 100644 index 0000000..71e9ecc --- /dev/null +++ b/docs/proofs/sgf-hr-personnel-20261111.json @@ -0,0 +1,137 @@ +{ + "passed": true, + "date": "2026-11-11", + "installation": "cc50a276c10c", + "port": 8085, + "module": "human_resources", + "directed_checks": 233, + "affected_compatibility_checks": 56, + "preservation_checks": 78, + "new_grants_both_existing_books": 30, + "new_users": 0, + "individual_commands_8085": 16, + "new_salary_versions_8085": 0, + "new_journals": 0, + "new_payments": 0, + "new_demo": false, + "cache_cleared": false, + "protected_six_targeted": false, + "browser_visual_review": false, + "deployment_validated": false, + "evidence": [ + { + "name": "core", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-b4093c6f6707/single-integration/20261111-085805-d6985581/hp-result.json", + "sha256": "b5f584c55f1cba6b6cf52066a24f76a32c8d257cec0556cb2bf0a3e7eadaa8dc", + "checks": 21 + }, + { + "name": "boundaries", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-b4093c6f6707/single-integration/20261111-085910-105a205c/hp-boundaries.json", + "sha256": "1180d1d3750e388ff5f0a05d1edce3faee2ce967f0820121916b5e52088668ed", + "checks": 26 + }, + { + "name": "privacy", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-b4093c6f6707/single-integration/20261111-090216-f627a6b2/hp-privacy.json", + "sha256": "3039bc3baba6ba1247b2efbc1edf4034f193241246ef856cc3927422c8b72ae9", + "checks": 9 + }, + { + "name": "time", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-b4093c6f6707/single-integration/20261111-090139-572f98b6/hr-personnel-time-core.json", + "sha256": "bbe501658c5c2b9832772c033dc09e74d823450345f8bd833fb45c126ec8d09b", + "checks": 23 + }, + { + "name": "ui", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/hp-ui-20261111.json", + "sha256": "7ed05fd4d86a9d39952cd895e0db0d3eccf1fb3606fbf95d7cb1725b12e2523a", + "checks": 19 + }, + { + "name": "balances", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-b4093c6f6707/single-integration/20261111-090617-cbd50595/hp-current-balances.json", + "sha256": "df7189fdf4cd3bd021c3b25eb20c647163558f84f669efbc868fb38c8a4cabcc", + "checks": 8 + }, + { + "name": "refusal", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-b4093c6f6707/single-integration/20261111-090954-e38c2dc5/hp-refusal.json", + "sha256": "c5f487a72036f61f1c7bdbaeea152ad240705eabfa3349199340bd23de6117e5", + "checks": 7 + }, + { + "name": "http", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/hp-http-20261111.json", + "sha256": "b449e88a1bb980ed5f5747e09e7e7893271c247d7774d5f24aa63951f131aa38", + "checks": 97 + }, + { + "name": "final_http", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/hp-final-http-20261111.json", + "sha256": "00b9d2febacf6bfac8fe2dcc742e5ff7528ccbf268fb6db76e192708201f253f", + "checks": 18 + }, + { + "name": "final_assets", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/hp-final-assets-20261111.json", + "sha256": "ce63881589643172007f473b632071dcbaf576ed337ffee0c8e45830339ca084", + "checks": 3 + }, + { + "name": "final_ui", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/hp-final-ui-20261111.json", + "sha256": "3e6bf571464e9f66f843464381bdcf91a856dc7a13cbe9ff79a2dca19c37c040", + "checks": 2 + }, + { + "name": "compat_salary", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-b4093c6f6707/single-integration/20261111-090053-0013d8b7/remunerations-result.json", + "sha256": "9ac93a64316a4c7d8168e2977ddb2f4364ff9c49f6c80b9b6f070600e6cd2e4f", + "checks": 40 + }, + { + "name": "compat_salary_ui", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/hp-remunerations-ui-20261111.json", + "sha256": "48480a08f3e06d1851897c206b9eb64209c7f826ad63d5c2647b23d178fe7dff", + "checks": 16 + }, + { + "name": "concurrency", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-b4093c6f6707/hp-concurrency-1794380380400/result.json", + "sha256": "98343e82d1b47b10e60602fbb81aab31e4cb54e96eb5018d9dd09802535187b5", + "checks": 0 + }, + { + "name": "preservation", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/single-integration/20261111-091110-94c14a72/hp-preservation.json", + "sha256": "59ac15dfe6ce0f8139d9b90a1eb1cbaa1bc315ac1a2b6a81bee1c7be3f32f78e", + "checks": 78 + }, + { + "name": "installation", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/single-integration/20261111-090956-998c70ef/hp-installation.json", + "sha256": "b1634a18931d7e8455c054d4cae91f6fecc6a38e7c6bdc7eed05ea275deab97a", + "checks": 0 + }, + { + "name": "reg_setup", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-b4093c6f6707/single-integration/20261111-085106-4ec92771/hp-setup.json", + "sha256": "4dc61775465ad1ce91fd5712dd4f9b6a1d6cea94bd1f824027de7c10d5438c3a", + "checks": 0 + }, + { + "name": "8085_setup", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/single-integration/20261111-085107-4f556a85/hp-setup.json", + "sha256": "cb01234621369a924d933e09744e24d475492c46064d3c82f5dfb23c1d28b02b", + "checks": 0 + }, + { + "name": "finalization", + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/single-integration/20261111-091440-125e5a2d/hp-finalization.json", + "sha256": "ec0f3c3c152f5185bd3f6493109ce21fda7d892791d80f405bf3165ec7f5a610", + "checks": 0 + } + ] +} diff --git a/docs/proofs/sgf-hr-readable-20261111.json b/docs/proofs/sgf-hr-readable-20261111.json new file mode 100644 index 0000000..8272085 --- /dev/null +++ b/docs/proofs/sgf-hr-readable-20261111.json @@ -0,0 +1,47 @@ +{ + "passed": true, + "date": "2026-11-11", + "installation": "cc50a276c10c", + "port": 8085, + "directed_checks": 58, + "identical_original_tables": 66, + "business_commands_8085": 0, + "new_grants": 0, + "new_profiles": 0, + "cache_cleared": false, + "protected_six_targeted": false, + "browser_visual_review": false, + "deployment_validated": false, + "evidence": [ + { + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-b4093c6f6707/single-integration/20261111-095512-0ff4f79e/hp-readable-result.json", + "sha256": "d246d5aea13cf9c3fee44e4e9b9198f8ee034967c9f4884983a966a0d86eafd1", + "checks": 17 + }, + { + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/hp-readable-download-20261111.json", + "sha256": "6e5a4963862cda1a906498454eb3ec6f24ead4599a9a822dbea1ff1b53950cac", + "checks": 11 + }, + { + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/hp-readable-ui-20261111.json", + "sha256": "ef91bcdd098eee3dd627cf5cfd73df4c6be24ce870ec38e02394fb6718428ec3", + "checks": 3 + }, + { + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/hp-readable-http-20261111.json", + "sha256": "1613c8edf712e7e04673d424ff6cacde6473e24fbce4e7e75611a429ad9931c6", + "checks": 27 + }, + { + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/single-integration/20261111-095619-e3fff98c/hp-readable-installation.json", + "sha256": "fdc157d3b3a597a9abe7453beb1b742559faae59a2fe8495953b799fc72de3e0", + "checks": 0 + }, + { + "file": "C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-cc50a276c10c/single-integration/20261111-095812-c5cef31b/hp-readable-preservation.json", + "sha256": "decb9c3c3c42f78b6826c547a242b9c5e489db22790d89b192da4e55fafb0846", + "checks": 0 + } + ] +} diff --git a/docs/proofs/sgf-multi-line-invoice-20261111.json b/docs/proofs/sgf-multi-line-invoice-20261111.json new file mode 100644 index 0000000..e14c34b --- /dev/null +++ b/docs/proofs/sgf-multi-line-invoice-20261111.json @@ -0,0 +1,143 @@ +{ + "date": "2026-11-11", + "policy": "SGF-MULTI-LINE-INVOICE-INTEGRATED-QA-v1", + "passed": true, + "installation": "cc50a276c10c", + "port": 8085, + "new_demo": false, + "sgc_dependency": false, + "new_rights": 0, + "new_grants": 0, + "new_users": 0, + "new_accounts": 0, + "functional_checks": 143, + "preservation_checks": 122, + "evidence": { + "financial": { + "passed": true, + "sha256": "b4bcc83818e48dab9330e6aa7df9b3d16748e6bc7463515ab215386c53161745", + "checks": 26 + }, + "controller": { + "passed": true, + "sha256": "9323c91c1435c92adc234bed5efac71244ff4de647f425221b30700964d65235", + "checks": 18 + }, + "boundaries": { + "passed": true, + "sha256": "86ca6422aedb175dc92b4a3101819ebdf71e64cbd19cb9f3b25e7deaa3892bb1", + "checks": 20 + }, + "recovery": { + "passed": true, + "sha256": "b8483f0a923bc2fcd4ed11d4f2002b3894b84e39e9ab5c29f9b9f675bb893bcb", + "checks": 6 + }, + "ui": { + "passed": true, + "sha256": "0b5f0aee35a9cbad21c8d39ec7030a737a26a5f9f1d43852e561c08f70b15375", + "checks": 21 + }, + "http": { + "passed": true, + "sha256": "313f2fc804fdf6b1caf857efed417555b211aab714880fa92d7769994095c77b", + "checks": 28 + }, + "statements": { + "passed": true, + "sha256": "49c5149b634a5ea6c7245c159d8662afdd225fd0e4b356bdaf76f94db2d6a8e7", + "checks": 12 + }, + "final_read": { + "passed": true, + "sha256": "121e57c1a1df92e1851250efe2c10f4d498a0a77a9751f437e4e96a1dd9595c1", + "checks": 12 + }, + "preservation": { + "passed": true, + "sha256": "5f8eebb052df1f4a4539d6704e926a76a0f0d08eae61eae4ebe6fe6186cebc98", + "checks": 122 + }, + "concurrency": { + "passed": true, + "sha256": "837f72bdf8e842e4d054a2261135e2d4b9208bd4387b3a868686965d59932502", + "during_lock": 503, + "seconds_waiting": 5.086719036102295, + "after_release": 409, + "original_replay": 200, + "invoices": 1, + "receipts": 1, + "last_number": 1, + "regression_only": true, + "pending_receivable_and_journal": true + }, + "period": { + "passed": true, + "sha256": "db4802a351a5b8e46ba9e67dfbe5beb25e19f17d22033d045ed6c7404537e128" + } + }, + "actual_effects": { + "series": "TEST-MULTI-2026", + "documents": 2, + "numbers": [ + 1, + 2 + ], + "documentary_base_each": "1000.00", + "documentary_tax_each": "160.00", + "total_each": "1160.00", + "uncollected_receivables": 2, + "uncollected_total": "2320.00", + "new_payments": 0, + "journals_before": 29, + "journals_after": 31, + "chart_accounts": 24, + "current_period_facts": 44, + "current_period_pending": 0, + "current_period_balanced": true, + "period_closed": false, + "cash_unchanged": "950.00", + "new_statement_versions": [ + 5, + 6 + ], + "statement_mapping_version_unchanged": 4, + "statement_known_result_subtotal": "2286.21", + "statement_unclassified_accounts": 10, + "statement_complete_result": null, + "iva_old_versions_unchanged": 4 + }, + "preservation": { + "original_sets": 96, + "original_rows_identical": true, + "bindings_unchanged": 3, + "protected_six_not_written": true, + "cache_cleared": false, + "prior_payroll_stock_asset_documents_unchanged": true + }, + "pdf_review": { + "sample_25_lines_not_issued": true, + "rendered_pages": 5, + "pages_inspected": [ + 1, + 2, + 3, + 4, + 5 + ], + "text_legible_no_clipping_observed": true, + "browser_review": false, + "limitation": "Existing text renderer can split a line section across pages; this is not visual acceptance of the application." + }, + "limits": [ + "QA only; no legal invoice validity or tax-rate assertion.", + "No mixed regimes, exemptions, discounts, stock-product/POS or Excel import implemented by this block.", + "Dates and validity are revalidated; an unissued old-day calculation requires a new conference.", + "Receivable maturity is unknown until a separate explicit agreement; draft proposed date is not propagated.", + "Old IVA snapshot does not include these two documents.", + "UI recovery keeps the operation in memory; reload recovery uses the original private key/receipt and does not automatically reissue.", + "Existing legacy administrative listing is not converted to a bounded universal register.", + "Regression concurrency leaves one identified synthetic invoice without receivable/journal in regression only; it is not an8085 pending fact.", + "No sustained-load/deployment capability or user acceptance declared." + ] +} diff --git a/docs/proofs/sgf-solution-structure-v26-20261111.json b/docs/proofs/sgf-solution-structure-v26-20261111.json new file mode 100644 index 0000000..bcb9c47 --- /dev/null +++ b/docs/proofs/sgf-solution-structure-v26-20261111.json @@ -0,0 +1,1206 @@ +{ + "date": "2026-11-11", + "contract_sha256": "54258cfc021909a91b66522cc1497f2a20588bf44141470810cda401f237a45c", + "installation": "cc50a276c10c", + "port": 8085, + "sgc_dependency": false, + "full_product_complete": false, + "product_backlog": [ + { + "id": "fiscal_books", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Livro/mapa interno QA de uma origem entregue; IVA geral, dedução/regularizações e restantes tratamentos pendentes" + }, + { + "id": "irps_inss", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "RH autónomo QA com cadastro/contratos/assiduidade/férias e remunerações internas entregue; regimes laborais gerais, IRPS/INSS/modelos legais e ligação financeira/contabilística salarial pendentes" + }, + { + "id": "irpc", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "IRPC operacional: ajustamentos, retenções, apuramento" + }, + { + "id": "ispc_ice", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Motores operacionais ISPC e ICE" + }, + { + "id": "official_forms", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Modelos oficiais com correspondências e reconciliação de campos" + }, + { + "id": "fiscal_calendar", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Controlo administrativo de prazo e entrega simulada de uma origem QA entregue; calendário legal, aplicabilidade geral, prazos por fonte/vigência e submissão continuam pendentes" + }, + { + "id": "bank_reconciliation", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Conferência de duas linhas/extracto artificial QA entregue; integração e reconciliação bancária gerais/reais pendentes" + }, + { + "id": "financial_analysis", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Dois critérios/indicadores QA com fontes conciliadas entregues; rácios gerais, critérios reais e tendências/séries permanecem pendentes" + } + ], + "modules": [ + { + "id": "commercial", + "label": "Comercial", + "features": [ + "Clientes e serviços com associação explícita", + "Rascunho, revisão e cálculo", + "Documento emitido de teste e PDF", + "Continuação para recebível sem duplicação", + "Orçamentos Comercial/Oficina, revisão e aceitação documentadas e conversão explícita em rascunho", + "Recibo de afectação efectiva400, crédito nominal100/saldo500 e factura-recibo1000 atómica", + "PDF/SHA, histórico e anulação técnica com reversão explícita e diário único", + "Registo unificado autorizado com seis famílias, filtros/SQL25/100 e projecções limitadas à página", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceWorkflow.php", + "present": true, + "sha256": "6ea92e6216bec9a0062d9f152d1b758a5527360456589a98f525429402cb94da" + }, + { + "file": "app/Modules/Services/ServiceInvoicing.php", + "present": true, + "sha256": "4138afb7e001b546a65552d71223cd642154945b8021e1ff45ef60a34076e3c5" + }, + { + "file": "resources/js/ServicesWorkspace.jsx", + "present": true, + "sha256": "c170b195ccd78aa74f283a91b0b4f4cd93aea4677e3aaefe0b50af46f7e31669" + }, + { + "file": "resources/js/ServiceInvoiceWorkspace.jsx", + "present": true, + "sha256": "8f95d1775b30a6de73d65eed428595d74cc837755d8157bf8db49a1151350277" + }, + { + "file": "app/Modules/Services/ServiceDocuments.php", + "present": true, + "sha256": "816a85be48b05043f41f9c7d5f842a3fa139b21aaedf9c944d811f74be6f6208" + }, + { + "file": "app/Modules/Services/DocumentPeriod.php", + "present": true, + "sha256": "100c6cf038b925126ddc8fb50adde2f6d938b4d68df6d0ab193f16ea28836ae2" + }, + { + "file": "app/Modules/Services/BillingRegister.php", + "present": true, + "sha256": "115e7832b63e5c29be716140629845137d951da7790421832e1ab4058fb468cc" + }, + { + "file": "resources/js/BillingRegisterWorkspace.jsx", + "present": true, + "sha256": "9ab8d036c9e6d20c588427f5a8c44677810e9713722baea908b8f6b76c8b79f9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/invoice-test-results.json", + "present": true, + "sha256": "fd04a58727ffddf1c62ac184d7834b81228dce2abb5b0c1e397505df6a45237c", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_SERVICE_DOCUMENTS_DELIVERY.md", + "present": true, + "sha256": "a13713f05feaa84766dfaba870e72c98d23aa6223389d1627de911b0856cd15d", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BILLING_REGISTER_DELIVERY.md", + "present": true, + "sha256": "fec1b76003187d5f38c8aca8804c26129e312e517dd5b2247f54f0019c2cccf6", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual, aceitação explícita e conformidade real; emissão fiscal real permanece bloqueada", + "Carga sustentada, armazenamento e capacidade de implantação exigem evidências próprias" + ] + }, + { + "id": "purchasing", + "label": "Compras", + "features": [ + "Fornecedores e revisão distinta", + "Requisição até adjudicação e ordem", + "Recepção, documento e stock único", + "Factura conferida e obrigação reconhecida", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Purchasing/ReceiptRuntime.php", + "present": true, + "sha256": "b08af1f8772cbfafa1f85e37c832f2c6916ccabcfbd4a8bb98c5ef7690c3f07f" + }, + { + "file": "app/Modules/Purchasing/RequestController.php", + "present": true, + "sha256": "c5c2b78866be7f0e1c8ba09c1af538cefcfbafbea81197833b50821bba0fcbca" + }, + { + "file": "app/Modules/Purchasing/InvoiceController.php", + "present": true, + "sha256": "85eb46ca7e65c60bed190e592a134eb48db5762c0a90b8cd4c9d6fbd81198ba4" + }, + { + "file": "app/Modules/Purchasing/ObligationController.php", + "present": true, + "sha256": "347e629306d6c1f4873afac547d13fae8ade7b268906d1312a64e7eb6998e701" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-final-20261104-r2.json", + "present": true, + "sha256": "3b63f8653e8dbb089dfdd8f389e271c022a070d5a67a9f9d4585ce84438070cc", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ], + "accepted_component_journey": true + }, + { + "id": "stock", + "label": "Stock", + "features": [ + "Artigos, armazéns e saldos físicos", + "Entradas, saídas e origem autorizada", + "Transferência e conferência de destino", + "Contagens, ajustamentos e devoluções", + "Movimentos e relatórios por período", + "Política QA revista e devolução ao fornecedor por origem exacta; saldo8UN/160MZN no mesmo diário", + "Inactividade de pares com história conhecida, corte UTC explícito, filtros e SQL25/100 sem inferir ausência de actividade", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Cadastro de artigos: preparação/revisão distintas, criação sem saldo, designação revista só dos novos, código/unidade imutáveis, histórico/recibos e catálogo canónico único" + ], + "sources": [ + { + "file": "app/Modules/Inventory/StockReadController.php", + "present": true, + "sha256": "fd02da1a69f76bf1412f86f43095a0a476de49fbb7d2676a51fe3b29ec66e298" + }, + { + "file": "app/Modules/Inventory/CompositionStockRead.php", + "present": true, + "sha256": "81bdb5fb374a60974bbad421c461cd12079951fb426361200692a838e413b49e" + }, + { + "file": "app/Modules/Inventory/StockPeriodReport.php", + "present": true, + "sha256": "d491541c6c60bc0c661dc442b8db8ce2e757f1d63dfc4982a3631f820cfb7a06" + }, + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "app/Modules/Inventory/StockOperationsController.php", + "present": true, + "sha256": "34d6c7be4787f4f18ca2a7aa322f6f33dfb281e22336599234c93c655ab4eebf" + }, + { + "file": "resources/views/stock-operations-workspace.blade.php", + "present": true, + "sha256": "5db67c2d23e5557548c38c82a0d7f6e373651849727b2237dcd73f620309c25a" + }, + { + "file": "public/stock-operations-workspace.mjs", + "present": true, + "sha256": "56734499e8dd89da293c2edadd0d0e10ac6d06b39f0365e5ae29a8d14e9176d8" + }, + { + "file": "app/Modules/Inventory/StockFinalController.php", + "present": true, + "sha256": "9d9a242fd8e143fe076ece0454d0789eabf543e37b332db4806d905fad13cdda" + }, + { + "file": "resources/views/stock-final-workspace.blade.php", + "present": true, + "sha256": "93764471a215735a65e5c07121989b1e2f6499f631a11b9f1177bb0fe8def232" + }, + { + "file": "public/sgf-stock-final.mjs", + "present": true, + "sha256": "77860b080015528fe4dd84b2e71787ee668da9be17653c64a5d8e64539b5e592" + }, + { + "file": "app/Modules/Inventory/StockInactiveReport.php", + "present": true, + "sha256": "7db06225fb7bb40c9a4140383caef6dd96c430e3a3e1be8ef7fed2253f2681e2" + }, + { + "file": "resources/js/StockInactiveReportWorkspace.jsx", + "present": true, + "sha256": "5d4f7222bf325550cebddbd00198515da55f15f52b4c99ecaef19f45820a165e" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Inventory/StockCatalogueController.php", + "present": true, + "sha256": "ab0a6bc52e635d5bcdee9163b68b1ec6fc14b717de7b887fcc832336961fa128" + }, + { + "file": "resources/views/stock-catalogue-workspace.blade.php", + "present": true, + "sha256": "4381f81a9aea04fe851f8c7af17cafb98f6bef7cbf52ac2630b0b9912a3dca57" + }, + { + "file": "public/sgf-stock-catalogue.mjs", + "present": true, + "sha256": "10eedcf1881ad3f1cb71813f0547eae7246795273457cc33b1a2a6761a0a2d6e" + }, + { + "file": "config/stock_catalogue.php", + "present": true, + "sha256": "811f844cecf76bff62da943042a272301cb8a7f7a586c4f2648ef5b8e5e31dae" + }, + { + "file": "database/sql/stock_catalogue.sql", + "present": true, + "sha256": "c9c99cec20e6ca60cf0ee56210eac9a2365a2ef9b482698929a70295d2893809" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/stock-period-report-20261103.json", + "present": true, + "sha256": "0ab25af6ffbff7d8ecb651df833771b082bf92d52f97eca4278fcac3fd5ea53b", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-stock-operations-20261110.json", + "present": true, + "sha256": "1d241fcfb81fcf6c1b374f9fc89c1f2130b17eee1314f06b4b8e4b59c476fc08", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_POLICIES_SUPPLIER_RETURN.md", + "present": true, + "sha256": "b8848090f1e19a6b7c19b684e07c0c95cafcf93bd1551357f6afc314f4413d8b", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_INACTIVITY_DELIVERY.md", + "present": true, + "sha256": "8773d53140b291c01358b8e71c584373cd0e440f67d0107e64ecea13980990d3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_CATALOGUE_DELIVERY.md", + "present": true, + "sha256": "43ce839f882aeeab48b272d5eb1a4e34fcd855052db88643a8ca7b08556631f8", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual dos novos relatórios e aceitação explícita do conjunto; carga sustentada e implantação separadas" + ] + }, + { + "id": "workshop", + "label": "Oficinas", + "features": [ + "Ordem e autorização", + "Apontamentos e revisão distinta", + "Material consumido ligado ao movimento físico", + "Custo directo/industrial e correcção", + "Continuação comercial por documento de origem", + "Agenda sem sobreposição, reagendamento e cancelamento versionados", + "Cadastro independente e reutilização de equipamento por cliente", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Ordens SQL25/100 por cliente/equipamento/responsável/estado, apontamentos actuais e minutos verificados, sem mutex de escrita na leitura", + "Relatório por período de abertura explícito/dia local, SQL25/100 antes de paginar, apontamentos actuais e CSV da página sem fecho histórico presumido", + "Equipamento escolhido → ordens exactas SQL25/100 com filtros, identificação actual, CSV da página e regresso ao catálogo" + ], + "sources": [ + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "resources/js/WorkshopWorkspace.jsx", + "present": true, + "sha256": "a1ceca33c234a759b2abf13b087bba8d18d70187ad9161e59425a5932771f8bb" + }, + { + "file": "resources/js/DirectCostsWorkspace.jsx", + "present": true, + "sha256": "dcaf72fe36d80e16bab8e962f939168650336d1450fc3eb01693acb7ef7e0743" + }, + { + "file": "resources/js/IndustrialCostsWorkspace.jsx", + "present": true, + "sha256": "51a3307fa8b2526efa12d8a16fee613bdfe141d02bd6e5af4535d581ed69c6d0" + }, + { + "file": "resources/js/WorkshopBillingWorkspace.jsx", + "present": true, + "sha256": "601853bd383ef2d1891158e89578bbc7f26ae1c7220bc11ddfadf1868cb63afc" + }, + { + "file": "app/Modules/Workshop/ComposedWorkshopStockBook.php", + "present": true, + "sha256": "01e7392827a6453a357a0b5140905757bbee76f57fae6aa9906490788690e24c" + }, + { + "file": "app/Modules/Workshop/WorkshopBilling.php", + "present": true, + "sha256": "0eac2d91fd9f006aa8c198e3652731142aee478c93de090c408df54cdd9d292d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Workshop/WorkshopOrderRead.php", + "present": true, + "sha256": "f741a5ac6c16376ea26b1bacd7b5e706b14a4548a26f59c4a96ec882fb6a6fa5" + }, + { + "file": "app/Modules/Workshop/Http/WorkshopOrderReadController.php", + "present": true, + "sha256": "f4dd027d01c0730603f3b312c2eb263740f6ebe1c969c762ed99d19520fab584" + }, + { + "file": "resources/js/WorkshopOrderList.jsx", + "present": true, + "sha256": "4cad148295acb7c462425fa300dccc7c203255f5f616163575e205bfc8a80e34" + }, + { + "file": "resources/js/workshop-report-view.js", + "present": true, + "sha256": "28125af02e25b4befb9914c5dee753dae4164100f3c0f94988c332a50021bf9f" + }, + { + "file": "app/Modules/Workshop/WorkshopEquipment.php", + "present": true, + "sha256": "37223b732865f273831d0bf61a464137cc53526a4c55750347450ba20bcbbc37" + }, + { + "file": "resources/js/WorkshopEquipmentWorkspace.jsx", + "present": true, + "sha256": "450af001d385f42cd4d0ae42b34fa6b373cdd6811d94f8f4a1f5de2c591ed843" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/workshop-stock-closure-20261102.json", + "present": true, + "sha256": "261d5502f6905d53ef8046e0e900c9bee159c8c9d7e1bae4fddd8ec698313bf7", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_AGENDA_EQUIPMENT.md", + "present": true, + "sha256": "3a44a1b6ac5148013c815cc6cc8919f08c15ac911f900aac5b4a0af903589481", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_PERIOD_REPORT_DELIVERY.md", + "present": true, + "sha256": "1455e3dc4203c81f49ddaba05a7bf7de10cfd9b3bb4dbd2eb4480aa05b7ccf08", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_EQUIPMENT_ORDERS_DELIVERY.md", + "present": true, + "sha256": "4d4249613a1b3fc339f342c3854eda08bf1ec280c0681eb3b2551469b2da2b36", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ] + }, + { + "id": "finance", + "label": "Financeiro", + "features": [ + "Obrigações a fornecedores", + "Recebíveis de clientes", + "Liquidação/afectação e reversão sintéticas", + "Posição financeira e comprovativos por origem", + "Caixa QA explícita0+1000-50=950, revisão distinta, contagem e fecho sem reabertura", + "Extracto paginado, recuperação de confirmação e classificação/despesa no mesmo diário", + "Extracto artificial QA e correspondências individuais com duas linhas nativas existentes, revisão distinta, conferência final e relatório", + "Critérios e indicadores QA950/100=9,5 vezes e50/1000=5%, revisão distinta, fontes conciliadas/retrato/fracção/histórico/relatório", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Posições actuais de clientes por documento, pesquisa/estado/vencimento, SQL25/100, saldos reconhecidos e CSV da página; referência de vencimento distinta de saldo histórico", + "Posições de fornecedores por factura/fornecedor e estado documental, SQL25/100, saldos do motor original, CSV da página e abertura exacta do documento; vencimento desconhecido sem atraso presumido", + "Condições administrativas QA de vencimento: preparação13/revisão14, evidência/fundamento, prévia/revalidação de origem/saldo/versão/pessoa, recusa/correcção, histórico25/100, recibo e recuperação; lista/CSV separam original e condição revista, referência explícita no detalhe", + "Mapa de antiguidade de fornecedores por referência explícita e condição administrativa revista, faixas0/1–30/31–60/61–90/>90/desconhecido, valores exactos por moeda só da página/CSV, sem alterar saldo ou vencimento original" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceReceivables.php", + "present": true, + "sha256": "79e1669b2423c521c0c56ab6fb07e085daef88eff8a41748c424e4a827f1e453" + }, + { + "file": "app/Modules/Purchasing/SupplierPayables.php", + "present": true, + "sha256": "c17d44391cbd96941aa2dd2eac2cb167508faf6453c6a1e86333846df1ba18b9" + }, + { + "file": "resources/js/ServiceReceivablesWorkspace.jsx", + "present": true, + "sha256": "6e654a03cb47e52dedd52ce6b2abf89a61d22860e4ffae92fd393027a54cc445" + }, + { + "file": "app/Modules/Purchasing/ComposedFinance.php", + "present": true, + "sha256": "ad7e837c7608ab85c59eb169bcdd986b08c5802a0f218926e1abd7568e8a4b16" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "app/Modules/Finance/CashWorkspace.php", + "present": true, + "sha256": "bb65060461cea3a844cf523733644cdd630f35ba02c82aeed961821ec5970800" + }, + { + "file": "app/Modules/Finance/Http/CashController.php", + "present": true, + "sha256": "982c161f3ef7314a9671f848f0da2aefb27841e40003ef47102d0d956c08cd78" + }, + { + "file": "app/Modules/Accounting/CashLedgerSource.php", + "present": true, + "sha256": "3a46162f0a62363dd4015f0047a5bff95e8aebd8dd3007fc705daaf58c10684b" + }, + { + "file": "app/Modules/Finance/BankReconciliation.php", + "present": true, + "sha256": "5421141bcc6e15bda142687661774238c7203c85d0ef30696fd4d56dd5b9e328" + }, + { + "file": "app/Modules/Finance/BankLedgerSource.php", + "present": true, + "sha256": "f33779fa736aea7027d4cf449ba85c5982bfb565c75bbb2a477d7a2666820338" + }, + { + "file": "app/Modules/Finance/Http/BankReconciliationController.php", + "present": true, + "sha256": "896d710db108364263a1404d2aafdc52eff4d3b6efcbe3dbdbcca1978befffe4" + }, + { + "file": "public/sgf-bank.mjs", + "present": true, + "sha256": "959214ad94bb5c29ffdaf1c8fd3e59bf8718837d4c1fe9bea52d3b04c261a80b" + }, + { + "file": "app/Modules/Finance/FinancialIndicators.php", + "present": true, + "sha256": "9785a801c5fb13b34770bdd05c828f5e924058eb872a7b33bc1db139a3024d10" + }, + { + "file": "app/Modules/Finance/FinancialIndicatorSource.php", + "present": true, + "sha256": "72039264cbb25ae2eaa5eaa4af488aabf7061a4a298c09490b8957b4ade55731" + }, + { + "file": "app/Modules/Finance/Http/FinancialIndicatorsController.php", + "present": true, + "sha256": "5245064e40b884e432afa2fd26d45fddccebf6963ef3366f585b93ee78f12cca" + }, + { + "file": "public/sgf-indicators.mjs", + "present": true, + "sha256": "62442edc14da534e0166cc65074389e74cc67246cd5922994628644fd900cbb9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Services/ReceivablePositionRead.php", + "present": true, + "sha256": "6aaf0211c4427643c84152edc6a7937dbf48387e2844d3bbe31328d1f6789f2d" + }, + { + "file": "app/Modules/Services/Http/ReceivablePositionReadController.php", + "present": true, + "sha256": "9e8be7c2cd328aebb31cbe0be4eeaaa6abc02d5bff0895aeb90c24ab8d134541" + }, + { + "file": "resources/js/ReceivablePositionsWorkspace.jsx", + "present": true, + "sha256": "5bd8ee952aeee87d733ab434cfe50b675d84c0341ade4f354c93e4acfdbe57e9" + }, + { + "file": "resources/js/receivable-position-view.js", + "present": true, + "sha256": "08f8cb6d17b79b89ee8cebe4723458c1a4979d8fb824afa0b585ae51076e6f21" + }, + { + "file": "app/Modules/Purchasing/PayablePositionReadController.php", + "present": true, + "sha256": "1d147f14826793f97605ea442c6a93612a55caf265311a86824611650dfc0005" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "resources/js/PayablePositionsWorkspace.jsx", + "present": true, + "sha256": "e52fa90276e6251b38bb48f83a11ec2b11ac819ffa3c2239da9712e50ce66343" + }, + { + "file": "resources/js/payable-position-view.js", + "present": true, + "sha256": "ff4b32f404cd575ddc134cf373ae6ded5d0abae8641b14b591c0f5a856437938" + }, + { + "file": "app/Modules/Purchasing/PayableDueController.php", + "present": true, + "sha256": "39e4c95b910d3b1c4aa227c7c2aa0ee24399921b9578748b95ff5aa519f34db7" + }, + { + "file": "resources/js/PayableDueWorkspace.jsx", + "present": true, + "sha256": "c7127bbb2edc6c5acce57a55a6c3c5b94969170ccf1adb7236fee80d45f555ae" + }, + { + "file": "database/sql/payable_due.sql", + "present": true, + "sha256": "faefce9f2c11da613293e23bbc2f630bdae345d872cca7f3c2923c77fea0f75e" + }, + { + "file": "config/payable_due.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Purchasing/PayableAge.php", + "present": true, + "sha256": "19f4a3994b679b74275e72e906251c8d32ee9950ebedfae07bd4002abe740a9e" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/supplier-settlements.json", + "present": true, + "sha256": "b1bce9218f99ea2d8c780022ad385e169c7acc5778ac6950236eef0630f77ad0", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_CASH_DELIVERY.md", + "present": true, + "sha256": "bfb40ff8849eb4d9fcb641aa9b8d07bbaab89326a0f89b8f113cf0bf62653093", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BANK_RECONCILIATION_DELIVERY.md", + "present": true, + "sha256": "acf53f7dfd2ec0f501cef8b1623e090c1e1b8a07a8591f4f042fb8a0b7b57273", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_INDICATORS_DELIVERY.md", + "present": true, + "sha256": "9902e37504dd9f5e1d3c73cd78a283e29be00ffb33ce13ca8f76577d1163cc65", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_RECEIVABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "7a829a6dcae28fa42e996874b7b65ed1de325dbab5bae12a28731ecfc2e849d7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "2c0a25051540527e87ca9abfa43db5efb288e0b40e2315e06f6084693ce9c8c3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_DUE_DELIVERY.md", + "present": true, + "sha256": "614ca3b1c432760e1911112951f138cf72d5d8370173336472529e9549e965b8", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_AGING_DELIVERY.md", + "present": true, + "sha256": "a0af96108702bbb687a655de744e78e01a3e7c98365244410eb291fcb7a5fafa", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Conferência de extracto QA entregue; reconciliação bancária geral/real permanece fora do recorte" + ] + }, + { + "id": "accounting", + "label": "Contabilidade", + "features": [ + "Plano e classificação", + "Diário com preparação/revisão distintas", + "Razão e balancete", + "Estorno/correcção e conferência de período", + "Fecho sintético com rastreabilidade de origens", + "Livro de saídas e mapa interno IVA QA da origem FR seleccionada, revisão distinta, histórico e CSV congelado", + "Catálogo de fontes/regras versionadas QA, revisão distinta, vigência/conflitos/retirada e cálculo explicável com história preservada", + "Cálculo QA por1–25linhas sem/com imposto incluído, representação por linha aprovada, exactos/diferenças e mapa interno recuperável", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Submenus distintos e balancete/razão por datas explícitas, escolha de conta por linha, páginas25/100, detecção de mudança de livro e CSV da página", + "Controlo administrativo QA de obrigação/prazo/responsável do mapa interno revisto, preparação13/revisão14, correcção/cancelamento, entrega apenas simulada com SHA verificado, histórico/recibo/SQL25/100/CSV; sem calendário legal ou submissão", + "Correspondências explícitas revistas15/16 e balanço/resultados internos parciais QA:19contas/nove pendentes, resultado congelado19linhas/SHA, história/recibo/CSV contextual, ligação exacta ao razão; capital e resultado completo não presumidos" + ], + "sources": [ + { + "file": "app/Modules/Accounting/GeneralLedger.php", + "present": true, + "sha256": "a87d2ea373bc61bb139aa29f2a354b9813bbb2d6229a389f8d37fd8e9890edec" + }, + { + "file": "app/Modules/Accounting/GeneralLedgerController.php", + "present": true, + "sha256": "afd2f4ae55e694f0aff18233a6f5cf52444e185aaf5a159865f74f0243783f2a" + }, + { + "file": "packages/general-ledger/src/LedgerSchema.php", + "present": true, + "sha256": "d9c203286b3a04121c265dffe412d2e86726ec4bb9222a2ed654d0296d86b5d8" + }, + { + "file": "app/Modules/Accounting/ComposedLedger.php", + "present": true, + "sha256": "2e4dfa133831c78514e1010d373db905b892c315c455ba53b997edd514cac035" + }, + { + "file": "app/Modules/Accounting/ComposedLedgerController.php", + "present": true, + "sha256": "b96749981b0d4daa7861eab62c5de60c96c5ffa9c72183f88bb89e2d4f7bdf2b" + }, + { + "file": "packages/general-ledger/src/StockTransitEvidence.php", + "present": true, + "sha256": "eb8eee3bb293e77689eb6470ac8aa12049b2ff8cfb8694d3e15eb17e097d2da6" + }, + { + "file": "app/Modules/Services/FiscalCoverage.php", + "present": true, + "sha256": "55f8b947bd63cffdc63f268a88ac441fda62e144d1a2013eaa954d57970fa716" + }, + { + "file": "app/Modules/Services/FiscalAssessment.php", + "present": true, + "sha256": "c2dbe192e6bba75f6fc4057bbb51c85bcf024a674cfeb7950ba239e700213128" + }, + { + "file": "app/Modules/Services/Http/FiscalAssessmentController.php", + "present": true, + "sha256": "425278110af20393e71de446f9b0ce41408352216249e5055de4165b075631e4" + }, + { + "file": "public/sgf-iva.mjs", + "present": true, + "sha256": "1d8c2a07f606799257c090ab2ddb52c309553a15009d6fb457dbbf4770c51ee6" + }, + { + "file": "app/Modules/Services/FiscalRules.php", + "present": true, + "sha256": "520edda0a013d41a80e3e3b56a1b39597a4f1b6407d78118f8855277ffeb3548" + }, + { + "file": "app/Modules/Services/FiscalRuleExpression.php", + "present": true, + "sha256": "108d8401c9c2bc85b3e5f663322c3d36c267afd8529b370e7e16c1d565a3daef" + }, + { + "file": "app/Modules/Services/Http/FiscalRulesController.php", + "present": true, + "sha256": "cc7e05e18c4bbc98a70a19eabdd653bbb44a717214f8d8ee19cd2ffeb1c032c9" + }, + { + "file": "public/sgf-fiscal-rules.mjs", + "present": true, + "sha256": "5c865e018c5b7d3b3f1232b80e81da379c729ea816a9e08a73225e76a678502c" + }, + { + "file": "app/Modules/Services/FiscalDocumentMath.php", + "present": true, + "sha256": "6e67b7b1363d24b38ca0dd40c4349dba82a4b746965f0af8aa7d65bb4f8f731d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "resources/js/ComposedAccountingWorkspace.jsx", + "present": true, + "sha256": "9288df122be42e436bb4aeffc3e84cc1687ba82ab4cab07754d27d507d0012ac" + }, + { + "file": "resources/js/LedgerReportsWorkspace.jsx", + "present": true, + "sha256": "312157b80599e0f4407520ec968460af9c31913f79365ef6efa9a243c98ba419" + }, + { + "file": "resources/js/ledger-report-view.js", + "present": true, + "sha256": "54a1eb09ab4b451b4aa34d8b0c1ebc3d8cea91da4f242ea25a0c4b3bb1db79b4" + }, + { + "file": "app/Modules/Services/FiscalObligations.php", + "present": true, + "sha256": "bcf24717cf5828fed440b8e86f29a5ed4d325d392000240b224d155954170d1d" + }, + { + "file": "app/Modules/Services/Http/FiscalObligationsController.php", + "present": true, + "sha256": "95b03b505e588333206766b862d0f0cff006b1af1a3dc0466370ac6a7518e097" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "resources/js/fiscal-obligations-view.js", + "present": true, + "sha256": "d3c1aafddac987de0a8e432746533da8832c140474f9d9d49055020f7c9e67dd" + }, + { + "file": "database/sql/fiscal_obligations.sql", + "present": true, + "sha256": "c68988342ca1ed5817485355844c2f502f5c2ca12954461c1555d5c5f2abf7fe" + }, + { + "file": "config/fiscal_obligations.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Accounting/InternalStatementsController.php", + "present": true, + "sha256": "b05990c7fdb195957808b63a77599eaaabed5c5eb435dd8e7dd13d662c22fd8d" + }, + { + "file": "app/Modules/Accounting/InternalStatementTotals.php", + "present": true, + "sha256": "f13f6f356100e49786869196c54343fd629f6a1d38fa81665eb768dae4731601" + }, + { + "file": "resources/js/InternalStatementsWorkspace.jsx", + "present": true, + "sha256": "2fadac08cd6162dd06d056bfc4fb00479e9a5d930fdde10c1a4b57507982e76e" + }, + { + "file": "resources/js/internal-statements-view.js", + "present": true, + "sha256": "f1f7b44f2dc1679e537d77522e0fc5ee84da23068ef3157677bc20a8409d3464" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "adb8a84a7de7678f67761cd64c4752adb3c6c802ff15754752d2417c5c35b646" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "database/sql/internal_statements.sql", + "present": true, + "sha256": "2beccb0bb7d91f48e5a5e386c1e010fb7d95cbfa90aa35a68e28dc63ae6a6f6b" + }, + { + "file": "config/internal_statements.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-matrix-final-20261104.json", + "present": true, + "sha256": "94cb37698f294c38df9b94a5535ba4b9ac63907f94dab1e7f951a3bb27623083", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_COVERAGE_DELIVERY.md", + "present": true, + "sha256": "4951a2514253c71710fd18ddbffcec1c4dca597355b1cf998b1c7f1b38078506", + "not_reexecuted": true + }, + { + "file": "docs/SGF_IVA_ASSESSMENT_DELIVERY.md", + "present": true, + "sha256": "b0a3ae64699352242108d782a229f0250344f238fad02e14e6fa4dbb520e997f", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_RULES_COMMON_DELIVERY.md", + "present": true, + "sha256": "10f8801d59dbdeb3f075e5bf2650e4538097a92151db516e3c0357ac011c8a5a", + "not_reexecuted": true + }, + { + "file": "docs/SGF_MULTI_LINE_INCLUDED_DELIVERY.md", + "present": true, + "sha256": "7b1d6bbdea15845cf7f4fe104057a4053be1c0f0edb2186da26d11dded45b690", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_ACCOUNTING_REPORTS_DELIVERY.md", + "present": true, + "sha256": "f910d6d11fd7f4e18a675fea9f07a50137bbed72d4718bb7ec6e022bdd1959e5", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_OBLIGATIONS_DELIVERY.md", + "present": true, + "sha256": "028e3172128435924dc9594b68b25560cb0b30495f27c522864a94a1f8cc9504", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_STATEMENTS_DELIVERY.md", + "present": true, + "sha256": "53a07e2c0517de5b6881584c598b2f1d1d5665422ac2f8f5932626822daf3359", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Backlog fiscal/financeiro amplo pendente; tabela de cobertura não implementa motores ou modelos" + ] + }, + { + "id": "human_resources", + "label": "Recursos Humanos", + "features": [ + "Módulo autónomo com perfis QA próprios separados do Financeiro", + "Cadastro pseudónimo individual preparado e revisto", + "Regras artificiais versionadas com vigência e fonte explícitas", + "Processamento exacto por trabalhador/período com escolha de fontes e revisão distinta", + "Correcção/cancelamento versionados, prévia, histórico, recibos e recuperação", + "Mapa individual revisto conservado com SHA e exportação autenticada; filtros SQL25/100", + "Contratos internos QA revistos com correcção/terminação/cancelamento e dependências", + "Plano e assiduidade por dia com intervalos explícitos, dados desconhecidos e revisão distinta", + "Condição de férias QA, reserva/gozo/cancelamento individuais, saldo efectivo sem duplicação", + "Conferência parcial do período por pessoa, fontes/versões e CSV/SHA conservado", + "Ligação contratual explícita opcional em novas preparações salariais, provada na regressão sem alterar salários8085" + ], + "sources": [ + { + "file": "app/Modules/HumanResources/Remunerations.php", + "present": true, + "sha256": "950333ced35d6949487bdf78ef109fad608338525acfe60b05b9a320fd3aac01" + }, + { + "file": "app/Modules/HumanResources/Http/RemunerationsController.php", + "present": true, + "sha256": "3725145a623791a6c2825d8aaa4f122ba7b80908c08603211d6d9e74df76e801" + }, + { + "file": "app/Shared/Installation/HumanResourcesProfiles.php", + "present": true, + "sha256": "f908b8cf9f7121bb9b43733f507771a60aa34eb553df8342c250e5b39940ae93" + }, + { + "file": "resources/js/RemunerationsWorkspace.jsx", + "present": true, + "sha256": "edb225504bff359d5c53c040421e322bb4cd887ac946b3316852ecd1ed25b76e" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "adb8a84a7de7678f67761cd64c4752adb3c6c802ff15754752d2417c5c35b646" + }, + { + "file": "app/Modules/HumanResources/Personnel.php", + "present": true, + "sha256": "c0bf3d63c5d5ec5b7556af6abe36b94eef35f2e73235103e2aff67e08015b538" + }, + { + "file": "app/Modules/HumanResources/PersonnelTime.php", + "present": true, + "sha256": "e0fc1bdecbbd24c1ac21a426d8547fc049c00fd18ae6faa7ded64128252bbfd6" + }, + { + "file": "app/Modules/HumanResources/Http/PersonnelController.php", + "present": true, + "sha256": "642ea349b2ae84b3ad3e40a47e6c2644df1bf6a3d9c44a862e7c92d299f339a0" + }, + { + "file": "resources/js/PersonnelWorkspace.jsx", + "present": true, + "sha256": "1752295b0c3de5250387226e2200e0ca1204cbd7b97388ab72a2b07a74b526fe" + } + ], + "existing_evidence": [ + { + "file": "docs/SGF_HUMAN_RESOURCES_DELIVERY.md", + "present": true, + "sha256": "1494d29938565bb6f5d552385d7b32dd0a8f22856755bf638d81cf9a41a65f60", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-human-resources-20261111.json", + "present": true, + "sha256": "95d0c3fcd407cfbb03226288b94570478b906fdf3cb50c26a9960956f536cf29", + "not_reexecuted": true + }, + { + "file": "docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md", + "present": true, + "sha256": "045d111cd08ad58c22a802d6d728c7f300d91b4078b65c3ac61683791747a02e", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-hr-personnel-20261111.json", + "present": true, + "sha256": "76f1d92bf596293703b877284b5dc283f78f61c83e9437f958af948efcfdb066", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conformidade e aplicabilidade laboral real: contratos legais, turnos/calendário, férias legais, benefícios e modelos", + "Cálculo legal IRPS/INSS e ligação a dívida/pagamento/Contabilidade sem efeitos implícitos", + "Revisão visual, carga sustentada, armazenamento e capacidade de implantação" + ] + } + ], + "release_gates": [ + { + "id": "single_installation", + "criterion": "Os seis módulos operam no mesmo endereço e empresa/unidade, sem mudar de demonstração", + "passed": false + }, + { + "id": "functional_modules", + "criterion": "Cada funcionalidade obrigatória tem ecrã/comando/consulta executável; menus e fontes isoladas não bastam", + "passed": false + }, + { + "id": "cross_module_flows", + "criterion": "Os documentos atravessam as fronteiras mantendo origem, versão, autoridade e efeito único", + "passed": false + }, + { + "id": "access_isolation", + "criterion": "Perfis explícitos e recusas de empresa/unidade/módulo/permissão verificadas", + "passed": false + }, + { + "id": "presentation", + "criterion": "Percurso de apresentação coerente, dados fictícios identificados, linguagem e navegação revistas", + "passed": false + }, + { + "id": "operations", + "criterion": "Arranque e recuperação da instalação entregues e ensaiados, sem depender do SGC", + "passed": false + }, + { + "id": "commercial_honesty", + "criterion": "Documentos de teste não são emissão fiscal; capacidade de implantação exige prova própria", + "passed": false + }, + { + "id": "preservation", + "criterion": "Seis registos, cache, dados aprovados e trabalho alheio preservados", + "passed": false + } + ], + "client_presentable": false, + "limits": [ + "Host-table footprint is a minimum projection, not a full migration/dependency validation.", + "Native purchasing/stock tables live in their existing separate native book; this scan does not call that missing.", + "Source/evidence presence does not mean a feature is integrated, visually accepted or deployment-ready." + ], + "database_writes": 0, + "application_bootstraps": 0, + "cache_cleared": false, + "new_demo": false, + "new_rights": false +} diff --git a/docs/proofs/sgf-solution-structure-v26-final-20261111.json b/docs/proofs/sgf-solution-structure-v26-final-20261111.json new file mode 100644 index 0000000..2f9cdd7 --- /dev/null +++ b/docs/proofs/sgf-solution-structure-v26-final-20261111.json @@ -0,0 +1,1206 @@ +{ + "date": "2026-11-11", + "contract_sha256": "54258cfc021909a91b66522cc1497f2a20588bf44141470810cda401f237a45c", + "installation": "cc50a276c10c", + "port": 8085, + "sgc_dependency": false, + "full_product_complete": false, + "product_backlog": [ + { + "id": "fiscal_books", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Livro/mapa interno QA de uma origem entregue; IVA geral, dedução/regularizações e restantes tratamentos pendentes" + }, + { + "id": "irps_inss", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "RH autónomo QA com cadastro/contratos/assiduidade/férias e remunerações internas entregue; regimes laborais gerais, IRPS/INSS/modelos legais e ligação financeira/contabilística salarial pendentes" + }, + { + "id": "irpc", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "IRPC operacional: ajustamentos, retenções, apuramento" + }, + { + "id": "ispc_ice", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Motores operacionais ISPC e ICE" + }, + { + "id": "official_forms", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Modelos oficiais com correspondências e reconciliação de campos" + }, + { + "id": "fiscal_calendar", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Controlo administrativo de prazo e entrega simulada de uma origem QA entregue; calendário legal, aplicabilidade geral, prazos por fonte/vigência e submissão continuam pendentes" + }, + { + "id": "bank_reconciliation", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Conferência de duas linhas/extracto artificial QA entregue; integração e reconciliação bancária gerais/reais pendentes" + }, + { + "id": "financial_analysis", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Dois critérios/indicadores QA com fontes conciliadas entregues; rácios gerais, critérios reais e tendências/séries permanecem pendentes" + } + ], + "modules": [ + { + "id": "commercial", + "label": "Comercial", + "features": [ + "Clientes e serviços com associação explícita", + "Rascunho, revisão e cálculo", + "Documento emitido de teste e PDF", + "Continuação para recebível sem duplicação", + "Orçamentos Comercial/Oficina, revisão e aceitação documentadas e conversão explícita em rascunho", + "Recibo de afectação efectiva400, crédito nominal100/saldo500 e factura-recibo1000 atómica", + "PDF/SHA, histórico e anulação técnica com reversão explícita e diário único", + "Registo unificado autorizado com seis famílias, filtros/SQL25/100 e projecções limitadas à página", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceWorkflow.php", + "present": true, + "sha256": "6ea92e6216bec9a0062d9f152d1b758a5527360456589a98f525429402cb94da" + }, + { + "file": "app/Modules/Services/ServiceInvoicing.php", + "present": true, + "sha256": "4138afb7e001b546a65552d71223cd642154945b8021e1ff45ef60a34076e3c5" + }, + { + "file": "resources/js/ServicesWorkspace.jsx", + "present": true, + "sha256": "c170b195ccd78aa74f283a91b0b4f4cd93aea4677e3aaefe0b50af46f7e31669" + }, + { + "file": "resources/js/ServiceInvoiceWorkspace.jsx", + "present": true, + "sha256": "8f95d1775b30a6de73d65eed428595d74cc837755d8157bf8db49a1151350277" + }, + { + "file": "app/Modules/Services/ServiceDocuments.php", + "present": true, + "sha256": "816a85be48b05043f41f9c7d5f842a3fa139b21aaedf9c944d811f74be6f6208" + }, + { + "file": "app/Modules/Services/DocumentPeriod.php", + "present": true, + "sha256": "100c6cf038b925126ddc8fb50adde2f6d938b4d68df6d0ab193f16ea28836ae2" + }, + { + "file": "app/Modules/Services/BillingRegister.php", + "present": true, + "sha256": "115e7832b63e5c29be716140629845137d951da7790421832e1ab4058fb468cc" + }, + { + "file": "resources/js/BillingRegisterWorkspace.jsx", + "present": true, + "sha256": "9ab8d036c9e6d20c588427f5a8c44677810e9713722baea908b8f6b76c8b79f9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/invoice-test-results.json", + "present": true, + "sha256": "fd04a58727ffddf1c62ac184d7834b81228dce2abb5b0c1e397505df6a45237c", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_SERVICE_DOCUMENTS_DELIVERY.md", + "present": true, + "sha256": "a13713f05feaa84766dfaba870e72c98d23aa6223389d1627de911b0856cd15d", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BILLING_REGISTER_DELIVERY.md", + "present": true, + "sha256": "fec1b76003187d5f38c8aca8804c26129e312e517dd5b2247f54f0019c2cccf6", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual, aceitação explícita e conformidade real; emissão fiscal real permanece bloqueada", + "Carga sustentada, armazenamento e capacidade de implantação exigem evidências próprias" + ] + }, + { + "id": "purchasing", + "label": "Compras", + "features": [ + "Fornecedores e revisão distinta", + "Requisição até adjudicação e ordem", + "Recepção, documento e stock único", + "Factura conferida e obrigação reconhecida", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Purchasing/ReceiptRuntime.php", + "present": true, + "sha256": "b08af1f8772cbfafa1f85e37c832f2c6916ccabcfbd4a8bb98c5ef7690c3f07f" + }, + { + "file": "app/Modules/Purchasing/RequestController.php", + "present": true, + "sha256": "c5c2b78866be7f0e1c8ba09c1af538cefcfbafbea81197833b50821bba0fcbca" + }, + { + "file": "app/Modules/Purchasing/InvoiceController.php", + "present": true, + "sha256": "85eb46ca7e65c60bed190e592a134eb48db5762c0a90b8cd4c9d6fbd81198ba4" + }, + { + "file": "app/Modules/Purchasing/ObligationController.php", + "present": true, + "sha256": "347e629306d6c1f4873afac547d13fae8ade7b268906d1312a64e7eb6998e701" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-final-20261104-r2.json", + "present": true, + "sha256": "3b63f8653e8dbb089dfdd8f389e271c022a070d5a67a9f9d4585ce84438070cc", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ], + "accepted_component_journey": true + }, + { + "id": "stock", + "label": "Stock", + "features": [ + "Artigos, armazéns e saldos físicos", + "Entradas, saídas e origem autorizada", + "Transferência e conferência de destino", + "Contagens, ajustamentos e devoluções", + "Movimentos e relatórios por período", + "Política QA revista e devolução ao fornecedor por origem exacta; saldo8UN/160MZN no mesmo diário", + "Inactividade de pares com história conhecida, corte UTC explícito, filtros e SQL25/100 sem inferir ausência de actividade", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Cadastro de artigos: preparação/revisão distintas, criação sem saldo, designação revista só dos novos, código/unidade imutáveis, histórico/recibos e catálogo canónico único" + ], + "sources": [ + { + "file": "app/Modules/Inventory/StockReadController.php", + "present": true, + "sha256": "fd02da1a69f76bf1412f86f43095a0a476de49fbb7d2676a51fe3b29ec66e298" + }, + { + "file": "app/Modules/Inventory/CompositionStockRead.php", + "present": true, + "sha256": "81bdb5fb374a60974bbad421c461cd12079951fb426361200692a838e413b49e" + }, + { + "file": "app/Modules/Inventory/StockPeriodReport.php", + "present": true, + "sha256": "d491541c6c60bc0c661dc442b8db8ce2e757f1d63dfc4982a3631f820cfb7a06" + }, + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "app/Modules/Inventory/StockOperationsController.php", + "present": true, + "sha256": "34d6c7be4787f4f18ca2a7aa322f6f33dfb281e22336599234c93c655ab4eebf" + }, + { + "file": "resources/views/stock-operations-workspace.blade.php", + "present": true, + "sha256": "5db67c2d23e5557548c38c82a0d7f6e373651849727b2237dcd73f620309c25a" + }, + { + "file": "public/stock-operations-workspace.mjs", + "present": true, + "sha256": "56734499e8dd89da293c2edadd0d0e10ac6d06b39f0365e5ae29a8d14e9176d8" + }, + { + "file": "app/Modules/Inventory/StockFinalController.php", + "present": true, + "sha256": "9d9a242fd8e143fe076ece0454d0789eabf543e37b332db4806d905fad13cdda" + }, + { + "file": "resources/views/stock-final-workspace.blade.php", + "present": true, + "sha256": "93764471a215735a65e5c07121989b1e2f6499f631a11b9f1177bb0fe8def232" + }, + { + "file": "public/sgf-stock-final.mjs", + "present": true, + "sha256": "77860b080015528fe4dd84b2e71787ee668da9be17653c64a5d8e64539b5e592" + }, + { + "file": "app/Modules/Inventory/StockInactiveReport.php", + "present": true, + "sha256": "7db06225fb7bb40c9a4140383caef6dd96c430e3a3e1be8ef7fed2253f2681e2" + }, + { + "file": "resources/js/StockInactiveReportWorkspace.jsx", + "present": true, + "sha256": "5d4f7222bf325550cebddbd00198515da55f15f52b4c99ecaef19f45820a165e" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Inventory/StockCatalogueController.php", + "present": true, + "sha256": "ab0a6bc52e635d5bcdee9163b68b1ec6fc14b717de7b887fcc832336961fa128" + }, + { + "file": "resources/views/stock-catalogue-workspace.blade.php", + "present": true, + "sha256": "4381f81a9aea04fe851f8c7af17cafb98f6bef7cbf52ac2630b0b9912a3dca57" + }, + { + "file": "public/sgf-stock-catalogue.mjs", + "present": true, + "sha256": "10eedcf1881ad3f1cb71813f0547eae7246795273457cc33b1a2a6761a0a2d6e" + }, + { + "file": "config/stock_catalogue.php", + "present": true, + "sha256": "811f844cecf76bff62da943042a272301cb8a7f7a586c4f2648ef5b8e5e31dae" + }, + { + "file": "database/sql/stock_catalogue.sql", + "present": true, + "sha256": "c9c99cec20e6ca60cf0ee56210eac9a2365a2ef9b482698929a70295d2893809" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/stock-period-report-20261103.json", + "present": true, + "sha256": "0ab25af6ffbff7d8ecb651df833771b082bf92d52f97eca4278fcac3fd5ea53b", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-stock-operations-20261110.json", + "present": true, + "sha256": "1d241fcfb81fcf6c1b374f9fc89c1f2130b17eee1314f06b4b8e4b59c476fc08", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_POLICIES_SUPPLIER_RETURN.md", + "present": true, + "sha256": "b8848090f1e19a6b7c19b684e07c0c95cafcf93bd1551357f6afc314f4413d8b", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_INACTIVITY_DELIVERY.md", + "present": true, + "sha256": "8773d53140b291c01358b8e71c584373cd0e440f67d0107e64ecea13980990d3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_CATALOGUE_DELIVERY.md", + "present": true, + "sha256": "43ce839f882aeeab48b272d5eb1a4e34fcd855052db88643a8ca7b08556631f8", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual dos novos relatórios e aceitação explícita do conjunto; carga sustentada e implantação separadas" + ] + }, + { + "id": "workshop", + "label": "Oficinas", + "features": [ + "Ordem e autorização", + "Apontamentos e revisão distinta", + "Material consumido ligado ao movimento físico", + "Custo directo/industrial e correcção", + "Continuação comercial por documento de origem", + "Agenda sem sobreposição, reagendamento e cancelamento versionados", + "Cadastro independente e reutilização de equipamento por cliente", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Ordens SQL25/100 por cliente/equipamento/responsável/estado, apontamentos actuais e minutos verificados, sem mutex de escrita na leitura", + "Relatório por período de abertura explícito/dia local, SQL25/100 antes de paginar, apontamentos actuais e CSV da página sem fecho histórico presumido", + "Equipamento escolhido → ordens exactas SQL25/100 com filtros, identificação actual, CSV da página e regresso ao catálogo" + ], + "sources": [ + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "resources/js/WorkshopWorkspace.jsx", + "present": true, + "sha256": "a1ceca33c234a759b2abf13b087bba8d18d70187ad9161e59425a5932771f8bb" + }, + { + "file": "resources/js/DirectCostsWorkspace.jsx", + "present": true, + "sha256": "dcaf72fe36d80e16bab8e962f939168650336d1450fc3eb01693acb7ef7e0743" + }, + { + "file": "resources/js/IndustrialCostsWorkspace.jsx", + "present": true, + "sha256": "51a3307fa8b2526efa12d8a16fee613bdfe141d02bd6e5af4535d581ed69c6d0" + }, + { + "file": "resources/js/WorkshopBillingWorkspace.jsx", + "present": true, + "sha256": "601853bd383ef2d1891158e89578bbc7f26ae1c7220bc11ddfadf1868cb63afc" + }, + { + "file": "app/Modules/Workshop/ComposedWorkshopStockBook.php", + "present": true, + "sha256": "01e7392827a6453a357a0b5140905757bbee76f57fae6aa9906490788690e24c" + }, + { + "file": "app/Modules/Workshop/WorkshopBilling.php", + "present": true, + "sha256": "0eac2d91fd9f006aa8c198e3652731142aee478c93de090c408df54cdd9d292d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Workshop/WorkshopOrderRead.php", + "present": true, + "sha256": "f741a5ac6c16376ea26b1bacd7b5e706b14a4548a26f59c4a96ec882fb6a6fa5" + }, + { + "file": "app/Modules/Workshop/Http/WorkshopOrderReadController.php", + "present": true, + "sha256": "f4dd027d01c0730603f3b312c2eb263740f6ebe1c969c762ed99d19520fab584" + }, + { + "file": "resources/js/WorkshopOrderList.jsx", + "present": true, + "sha256": "4cad148295acb7c462425fa300dccc7c203255f5f616163575e205bfc8a80e34" + }, + { + "file": "resources/js/workshop-report-view.js", + "present": true, + "sha256": "28125af02e25b4befb9914c5dee753dae4164100f3c0f94988c332a50021bf9f" + }, + { + "file": "app/Modules/Workshop/WorkshopEquipment.php", + "present": true, + "sha256": "37223b732865f273831d0bf61a464137cc53526a4c55750347450ba20bcbbc37" + }, + { + "file": "resources/js/WorkshopEquipmentWorkspace.jsx", + "present": true, + "sha256": "450af001d385f42cd4d0ae42b34fa6b373cdd6811d94f8f4a1f5de2c591ed843" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/workshop-stock-closure-20261102.json", + "present": true, + "sha256": "261d5502f6905d53ef8046e0e900c9bee159c8c9d7e1bae4fddd8ec698313bf7", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_AGENDA_EQUIPMENT.md", + "present": true, + "sha256": "3a44a1b6ac5148013c815cc6cc8919f08c15ac911f900aac5b4a0af903589481", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_PERIOD_REPORT_DELIVERY.md", + "present": true, + "sha256": "1455e3dc4203c81f49ddaba05a7bf7de10cfd9b3bb4dbd2eb4480aa05b7ccf08", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_EQUIPMENT_ORDERS_DELIVERY.md", + "present": true, + "sha256": "4d4249613a1b3fc339f342c3854eda08bf1ec280c0681eb3b2551469b2da2b36", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ] + }, + { + "id": "finance", + "label": "Financeiro", + "features": [ + "Obrigações a fornecedores", + "Recebíveis de clientes", + "Liquidação/afectação e reversão sintéticas", + "Posição financeira e comprovativos por origem", + "Caixa QA explícita0+1000-50=950, revisão distinta, contagem e fecho sem reabertura", + "Extracto paginado, recuperação de confirmação e classificação/despesa no mesmo diário", + "Extracto artificial QA e correspondências individuais com duas linhas nativas existentes, revisão distinta, conferência final e relatório", + "Critérios e indicadores QA950/100=9,5 vezes e50/1000=5%, revisão distinta, fontes conciliadas/retrato/fracção/histórico/relatório", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Posições actuais de clientes por documento, pesquisa/estado/vencimento, SQL25/100, saldos reconhecidos e CSV da página; referência de vencimento distinta de saldo histórico", + "Posições de fornecedores por factura/fornecedor e estado documental, SQL25/100, saldos do motor original, CSV da página e abertura exacta do documento; vencimento desconhecido sem atraso presumido", + "Condições administrativas QA de vencimento: preparação13/revisão14, evidência/fundamento, prévia/revalidação de origem/saldo/versão/pessoa, recusa/correcção, histórico25/100, recibo e recuperação; lista/CSV separam original e condição revista, referência explícita no detalhe", + "Mapa de antiguidade de fornecedores por referência explícita e condição administrativa revista, faixas0/1–30/31–60/61–90/>90/desconhecido, valores exactos por moeda só da página/CSV, sem alterar saldo ou vencimento original" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceReceivables.php", + "present": true, + "sha256": "79e1669b2423c521c0c56ab6fb07e085daef88eff8a41748c424e4a827f1e453" + }, + { + "file": "app/Modules/Purchasing/SupplierPayables.php", + "present": true, + "sha256": "c17d44391cbd96941aa2dd2eac2cb167508faf6453c6a1e86333846df1ba18b9" + }, + { + "file": "resources/js/ServiceReceivablesWorkspace.jsx", + "present": true, + "sha256": "6e654a03cb47e52dedd52ce6b2abf89a61d22860e4ffae92fd393027a54cc445" + }, + { + "file": "app/Modules/Purchasing/ComposedFinance.php", + "present": true, + "sha256": "ad7e837c7608ab85c59eb169bcdd986b08c5802a0f218926e1abd7568e8a4b16" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "app/Modules/Finance/CashWorkspace.php", + "present": true, + "sha256": "bb65060461cea3a844cf523733644cdd630f35ba02c82aeed961821ec5970800" + }, + { + "file": "app/Modules/Finance/Http/CashController.php", + "present": true, + "sha256": "982c161f3ef7314a9671f848f0da2aefb27841e40003ef47102d0d956c08cd78" + }, + { + "file": "app/Modules/Accounting/CashLedgerSource.php", + "present": true, + "sha256": "3a46162f0a62363dd4015f0047a5bff95e8aebd8dd3007fc705daaf58c10684b" + }, + { + "file": "app/Modules/Finance/BankReconciliation.php", + "present": true, + "sha256": "5421141bcc6e15bda142687661774238c7203c85d0ef30696fd4d56dd5b9e328" + }, + { + "file": "app/Modules/Finance/BankLedgerSource.php", + "present": true, + "sha256": "f33779fa736aea7027d4cf449ba85c5982bfb565c75bbb2a477d7a2666820338" + }, + { + "file": "app/Modules/Finance/Http/BankReconciliationController.php", + "present": true, + "sha256": "896d710db108364263a1404d2aafdc52eff4d3b6efcbe3dbdbcca1978befffe4" + }, + { + "file": "public/sgf-bank.mjs", + "present": true, + "sha256": "959214ad94bb5c29ffdaf1c8fd3e59bf8718837d4c1fe9bea52d3b04c261a80b" + }, + { + "file": "app/Modules/Finance/FinancialIndicators.php", + "present": true, + "sha256": "9785a801c5fb13b34770bdd05c828f5e924058eb872a7b33bc1db139a3024d10" + }, + { + "file": "app/Modules/Finance/FinancialIndicatorSource.php", + "present": true, + "sha256": "72039264cbb25ae2eaa5eaa4af488aabf7061a4a298c09490b8957b4ade55731" + }, + { + "file": "app/Modules/Finance/Http/FinancialIndicatorsController.php", + "present": true, + "sha256": "5245064e40b884e432afa2fd26d45fddccebf6963ef3366f585b93ee78f12cca" + }, + { + "file": "public/sgf-indicators.mjs", + "present": true, + "sha256": "62442edc14da534e0166cc65074389e74cc67246cd5922994628644fd900cbb9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Services/ReceivablePositionRead.php", + "present": true, + "sha256": "6aaf0211c4427643c84152edc6a7937dbf48387e2844d3bbe31328d1f6789f2d" + }, + { + "file": "app/Modules/Services/Http/ReceivablePositionReadController.php", + "present": true, + "sha256": "9e8be7c2cd328aebb31cbe0be4eeaaa6abc02d5bff0895aeb90c24ab8d134541" + }, + { + "file": "resources/js/ReceivablePositionsWorkspace.jsx", + "present": true, + "sha256": "5bd8ee952aeee87d733ab434cfe50b675d84c0341ade4f354c93e4acfdbe57e9" + }, + { + "file": "resources/js/receivable-position-view.js", + "present": true, + "sha256": "08f8cb6d17b79b89ee8cebe4723458c1a4979d8fb824afa0b585ae51076e6f21" + }, + { + "file": "app/Modules/Purchasing/PayablePositionReadController.php", + "present": true, + "sha256": "1d147f14826793f97605ea442c6a93612a55caf265311a86824611650dfc0005" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "resources/js/PayablePositionsWorkspace.jsx", + "present": true, + "sha256": "e52fa90276e6251b38bb48f83a11ec2b11ac819ffa3c2239da9712e50ce66343" + }, + { + "file": "resources/js/payable-position-view.js", + "present": true, + "sha256": "ff4b32f404cd575ddc134cf373ae6ded5d0abae8641b14b591c0f5a856437938" + }, + { + "file": "app/Modules/Purchasing/PayableDueController.php", + "present": true, + "sha256": "39e4c95b910d3b1c4aa227c7c2aa0ee24399921b9578748b95ff5aa519f34db7" + }, + { + "file": "resources/js/PayableDueWorkspace.jsx", + "present": true, + "sha256": "c7127bbb2edc6c5acce57a55a6c3c5b94969170ccf1adb7236fee80d45f555ae" + }, + { + "file": "database/sql/payable_due.sql", + "present": true, + "sha256": "faefce9f2c11da613293e23bbc2f630bdae345d872cca7f3c2923c77fea0f75e" + }, + { + "file": "config/payable_due.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Purchasing/PayableAge.php", + "present": true, + "sha256": "19f4a3994b679b74275e72e906251c8d32ee9950ebedfae07bd4002abe740a9e" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/supplier-settlements.json", + "present": true, + "sha256": "b1bce9218f99ea2d8c780022ad385e169c7acc5778ac6950236eef0630f77ad0", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_CASH_DELIVERY.md", + "present": true, + "sha256": "bfb40ff8849eb4d9fcb641aa9b8d07bbaab89326a0f89b8f113cf0bf62653093", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BANK_RECONCILIATION_DELIVERY.md", + "present": true, + "sha256": "acf53f7dfd2ec0f501cef8b1623e090c1e1b8a07a8591f4f042fb8a0b7b57273", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_INDICATORS_DELIVERY.md", + "present": true, + "sha256": "9902e37504dd9f5e1d3c73cd78a283e29be00ffb33ce13ca8f76577d1163cc65", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_RECEIVABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "7a829a6dcae28fa42e996874b7b65ed1de325dbab5bae12a28731ecfc2e849d7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "2c0a25051540527e87ca9abfa43db5efb288e0b40e2315e06f6084693ce9c8c3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_DUE_DELIVERY.md", + "present": true, + "sha256": "614ca3b1c432760e1911112951f138cf72d5d8370173336472529e9549e965b8", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_AGING_DELIVERY.md", + "present": true, + "sha256": "a0af96108702bbb687a655de744e78e01a3e7c98365244410eb291fcb7a5fafa", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Conferência de extracto QA entregue; reconciliação bancária geral/real permanece fora do recorte" + ] + }, + { + "id": "accounting", + "label": "Contabilidade", + "features": [ + "Plano e classificação", + "Diário com preparação/revisão distintas", + "Razão e balancete", + "Estorno/correcção e conferência de período", + "Fecho sintético com rastreabilidade de origens", + "Livro de saídas e mapa interno IVA QA da origem FR seleccionada, revisão distinta, histórico e CSV congelado", + "Catálogo de fontes/regras versionadas QA, revisão distinta, vigência/conflitos/retirada e cálculo explicável com história preservada", + "Cálculo QA por1–25linhas sem/com imposto incluído, representação por linha aprovada, exactos/diferenças e mapa interno recuperável", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Submenus distintos e balancete/razão por datas explícitas, escolha de conta por linha, páginas25/100, detecção de mudança de livro e CSV da página", + "Controlo administrativo QA de obrigação/prazo/responsável do mapa interno revisto, preparação13/revisão14, correcção/cancelamento, entrega apenas simulada com SHA verificado, histórico/recibo/SQL25/100/CSV; sem calendário legal ou submissão", + "Correspondências explícitas revistas15/16 e balanço/resultados internos parciais QA:19contas/nove pendentes, resultado congelado19linhas/SHA, história/recibo/CSV contextual, ligação exacta ao razão; capital e resultado completo não presumidos" + ], + "sources": [ + { + "file": "app/Modules/Accounting/GeneralLedger.php", + "present": true, + "sha256": "a87d2ea373bc61bb139aa29f2a354b9813bbb2d6229a389f8d37fd8e9890edec" + }, + { + "file": "app/Modules/Accounting/GeneralLedgerController.php", + "present": true, + "sha256": "afd2f4ae55e694f0aff18233a6f5cf52444e185aaf5a159865f74f0243783f2a" + }, + { + "file": "packages/general-ledger/src/LedgerSchema.php", + "present": true, + "sha256": "d9c203286b3a04121c265dffe412d2e86726ec4bb9222a2ed654d0296d86b5d8" + }, + { + "file": "app/Modules/Accounting/ComposedLedger.php", + "present": true, + "sha256": "2e4dfa133831c78514e1010d373db905b892c315c455ba53b997edd514cac035" + }, + { + "file": "app/Modules/Accounting/ComposedLedgerController.php", + "present": true, + "sha256": "b96749981b0d4daa7861eab62c5de60c96c5ffa9c72183f88bb89e2d4f7bdf2b" + }, + { + "file": "packages/general-ledger/src/StockTransitEvidence.php", + "present": true, + "sha256": "eb8eee3bb293e77689eb6470ac8aa12049b2ff8cfb8694d3e15eb17e097d2da6" + }, + { + "file": "app/Modules/Services/FiscalCoverage.php", + "present": true, + "sha256": "55f8b947bd63cffdc63f268a88ac441fda62e144d1a2013eaa954d57970fa716" + }, + { + "file": "app/Modules/Services/FiscalAssessment.php", + "present": true, + "sha256": "c2dbe192e6bba75f6fc4057bbb51c85bcf024a674cfeb7950ba239e700213128" + }, + { + "file": "app/Modules/Services/Http/FiscalAssessmentController.php", + "present": true, + "sha256": "425278110af20393e71de446f9b0ce41408352216249e5055de4165b075631e4" + }, + { + "file": "public/sgf-iva.mjs", + "present": true, + "sha256": "1d8c2a07f606799257c090ab2ddb52c309553a15009d6fb457dbbf4770c51ee6" + }, + { + "file": "app/Modules/Services/FiscalRules.php", + "present": true, + "sha256": "520edda0a013d41a80e3e3b56a1b39597a4f1b6407d78118f8855277ffeb3548" + }, + { + "file": "app/Modules/Services/FiscalRuleExpression.php", + "present": true, + "sha256": "108d8401c9c2bc85b3e5f663322c3d36c267afd8529b370e7e16c1d565a3daef" + }, + { + "file": "app/Modules/Services/Http/FiscalRulesController.php", + "present": true, + "sha256": "cc7e05e18c4bbc98a70a19eabdd653bbb44a717214f8d8ee19cd2ffeb1c032c9" + }, + { + "file": "public/sgf-fiscal-rules.mjs", + "present": true, + "sha256": "5c865e018c5b7d3b3f1232b80e81da379c729ea816a9e08a73225e76a678502c" + }, + { + "file": "app/Modules/Services/FiscalDocumentMath.php", + "present": true, + "sha256": "6e67b7b1363d24b38ca0dd40c4349dba82a4b746965f0af8aa7d65bb4f8f731d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "resources/js/ComposedAccountingWorkspace.jsx", + "present": true, + "sha256": "9288df122be42e436bb4aeffc3e84cc1687ba82ab4cab07754d27d507d0012ac" + }, + { + "file": "resources/js/LedgerReportsWorkspace.jsx", + "present": true, + "sha256": "312157b80599e0f4407520ec968460af9c31913f79365ef6efa9a243c98ba419" + }, + { + "file": "resources/js/ledger-report-view.js", + "present": true, + "sha256": "54a1eb09ab4b451b4aa34d8b0c1ebc3d8cea91da4f242ea25a0c4b3bb1db79b4" + }, + { + "file": "app/Modules/Services/FiscalObligations.php", + "present": true, + "sha256": "bcf24717cf5828fed440b8e86f29a5ed4d325d392000240b224d155954170d1d" + }, + { + "file": "app/Modules/Services/Http/FiscalObligationsController.php", + "present": true, + "sha256": "95b03b505e588333206766b862d0f0cff006b1af1a3dc0466370ac6a7518e097" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "resources/js/fiscal-obligations-view.js", + "present": true, + "sha256": "d3c1aafddac987de0a8e432746533da8832c140474f9d9d49055020f7c9e67dd" + }, + { + "file": "database/sql/fiscal_obligations.sql", + "present": true, + "sha256": "c68988342ca1ed5817485355844c2f502f5c2ca12954461c1555d5c5f2abf7fe" + }, + { + "file": "config/fiscal_obligations.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Accounting/InternalStatementsController.php", + "present": true, + "sha256": "b05990c7fdb195957808b63a77599eaaabed5c5eb435dd8e7dd13d662c22fd8d" + }, + { + "file": "app/Modules/Accounting/InternalStatementTotals.php", + "present": true, + "sha256": "f13f6f356100e49786869196c54343fd629f6a1d38fa81665eb768dae4731601" + }, + { + "file": "resources/js/InternalStatementsWorkspace.jsx", + "present": true, + "sha256": "2fadac08cd6162dd06d056bfc4fb00479e9a5d930fdde10c1a4b57507982e76e" + }, + { + "file": "resources/js/internal-statements-view.js", + "present": true, + "sha256": "f1f7b44f2dc1679e537d77522e0fc5ee84da23068ef3157677bc20a8409d3464" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "adb8a84a7de7678f67761cd64c4752adb3c6c802ff15754752d2417c5c35b646" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "database/sql/internal_statements.sql", + "present": true, + "sha256": "2beccb0bb7d91f48e5a5e386c1e010fb7d95cbfa90aa35a68e28dc63ae6a6f6b" + }, + { + "file": "config/internal_statements.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-matrix-final-20261104.json", + "present": true, + "sha256": "94cb37698f294c38df9b94a5535ba4b9ac63907f94dab1e7f951a3bb27623083", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_COVERAGE_DELIVERY.md", + "present": true, + "sha256": "4951a2514253c71710fd18ddbffcec1c4dca597355b1cf998b1c7f1b38078506", + "not_reexecuted": true + }, + { + "file": "docs/SGF_IVA_ASSESSMENT_DELIVERY.md", + "present": true, + "sha256": "b0a3ae64699352242108d782a229f0250344f238fad02e14e6fa4dbb520e997f", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_RULES_COMMON_DELIVERY.md", + "present": true, + "sha256": "10f8801d59dbdeb3f075e5bf2650e4538097a92151db516e3c0357ac011c8a5a", + "not_reexecuted": true + }, + { + "file": "docs/SGF_MULTI_LINE_INCLUDED_DELIVERY.md", + "present": true, + "sha256": "7b1d6bbdea15845cf7f4fe104057a4053be1c0f0edb2186da26d11dded45b690", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_ACCOUNTING_REPORTS_DELIVERY.md", + "present": true, + "sha256": "f910d6d11fd7f4e18a675fea9f07a50137bbed72d4718bb7ec6e022bdd1959e5", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_OBLIGATIONS_DELIVERY.md", + "present": true, + "sha256": "028e3172128435924dc9594b68b25560cb0b30495f27c522864a94a1f8cc9504", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_STATEMENTS_DELIVERY.md", + "present": true, + "sha256": "53a07e2c0517de5b6881584c598b2f1d1d5665422ac2f8f5932626822daf3359", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Backlog fiscal/financeiro amplo pendente; tabela de cobertura não implementa motores ou modelos" + ] + }, + { + "id": "human_resources", + "label": "Recursos Humanos", + "features": [ + "Módulo autónomo com perfis QA próprios separados do Financeiro", + "Cadastro pseudónimo individual preparado e revisto", + "Regras artificiais versionadas com vigência e fonte explícitas", + "Processamento exacto por trabalhador/período com escolha de fontes e revisão distinta", + "Correcção/cancelamento versionados, prévia, histórico, recibos e recuperação", + "Mapa individual revisto conservado com SHA e exportação autenticada; filtros SQL25/100", + "Contratos internos QA revistos com correcção/terminação/cancelamento e dependências", + "Plano e assiduidade por dia com intervalos explícitos, dados desconhecidos e revisão distinta", + "Condição de férias QA, reserva/gozo/cancelamento individuais, saldo efectivo sem duplicação", + "Conferência parcial do período por pessoa, fontes/versões e CSV/SHA conservado", + "Ligação contratual explícita opcional em novas preparações salariais, provada na regressão sem alterar salários8085" + ], + "sources": [ + { + "file": "app/Modules/HumanResources/Remunerations.php", + "present": true, + "sha256": "950333ced35d6949487bdf78ef109fad608338525acfe60b05b9a320fd3aac01" + }, + { + "file": "app/Modules/HumanResources/Http/RemunerationsController.php", + "present": true, + "sha256": "3725145a623791a6c2825d8aaa4f122ba7b80908c08603211d6d9e74df76e801" + }, + { + "file": "app/Shared/Installation/HumanResourcesProfiles.php", + "present": true, + "sha256": "f908b8cf9f7121bb9b43733f507771a60aa34eb553df8342c250e5b39940ae93" + }, + { + "file": "resources/js/RemunerationsWorkspace.jsx", + "present": true, + "sha256": "edb225504bff359d5c53c040421e322bb4cd887ac946b3316852ecd1ed25b76e" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "adb8a84a7de7678f67761cd64c4752adb3c6c802ff15754752d2417c5c35b646" + }, + { + "file": "app/Modules/HumanResources/Personnel.php", + "present": true, + "sha256": "c0bf3d63c5d5ec5b7556af6abe36b94eef35f2e73235103e2aff67e08015b538" + }, + { + "file": "app/Modules/HumanResources/PersonnelTime.php", + "present": true, + "sha256": "e0fc1bdecbbd24c1ac21a426d8547fc049c00fd18ae6faa7ded64128252bbfd6" + }, + { + "file": "app/Modules/HumanResources/Http/PersonnelController.php", + "present": true, + "sha256": "642ea349b2ae84b3ad3e40a47e6c2644df1bf6a3d9c44a862e7c92d299f339a0" + }, + { + "file": "resources/js/PersonnelWorkspace.jsx", + "present": true, + "sha256": "1752295b0c3de5250387226e2200e0ca1204cbd7b97388ab72a2b07a74b526fe" + } + ], + "existing_evidence": [ + { + "file": "docs/SGF_HUMAN_RESOURCES_DELIVERY.md", + "present": true, + "sha256": "1494d29938565bb6f5d552385d7b32dd0a8f22856755bf638d81cf9a41a65f60", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-human-resources-20261111.json", + "present": true, + "sha256": "95d0c3fcd407cfbb03226288b94570478b906fdf3cb50c26a9960956f536cf29", + "not_reexecuted": true + }, + { + "file": "docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md", + "present": true, + "sha256": "045d111cd08ad58c22a802d6d728c7f300d91b4078b65c3ac61683791747a02e", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-hr-personnel-20261111.json", + "present": true, + "sha256": "b4ce53d6099a13acbe6bd2d7431f9109b8a318000f9d69813353349cd823cdc7", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conformidade e aplicabilidade laboral real: contratos legais, turnos/calendário, férias legais, benefícios e modelos", + "Cálculo legal IRPS/INSS e ligação a dívida/pagamento/Contabilidade sem efeitos implícitos", + "Revisão visual, carga sustentada, armazenamento e capacidade de implantação" + ] + } + ], + "release_gates": [ + { + "id": "single_installation", + "criterion": "Os seis módulos operam no mesmo endereço e empresa/unidade, sem mudar de demonstração", + "passed": false + }, + { + "id": "functional_modules", + "criterion": "Cada funcionalidade obrigatória tem ecrã/comando/consulta executável; menus e fontes isoladas não bastam", + "passed": false + }, + { + "id": "cross_module_flows", + "criterion": "Os documentos atravessam as fronteiras mantendo origem, versão, autoridade e efeito único", + "passed": false + }, + { + "id": "access_isolation", + "criterion": "Perfis explícitos e recusas de empresa/unidade/módulo/permissão verificadas", + "passed": false + }, + { + "id": "presentation", + "criterion": "Percurso de apresentação coerente, dados fictícios identificados, linguagem e navegação revistas", + "passed": false + }, + { + "id": "operations", + "criterion": "Arranque e recuperação da instalação entregues e ensaiados, sem depender do SGC", + "passed": false + }, + { + "id": "commercial_honesty", + "criterion": "Documentos de teste não são emissão fiscal; capacidade de implantação exige prova própria", + "passed": false + }, + { + "id": "preservation", + "criterion": "Seis registos, cache, dados aprovados e trabalho alheio preservados", + "passed": false + } + ], + "client_presentable": false, + "limits": [ + "Host-table footprint is a minimum projection, not a full migration/dependency validation.", + "Native purchasing/stock tables live in their existing separate native book; this scan does not call that missing.", + "Source/evidence presence does not mean a feature is integrated, visually accepted or deployment-ready." + ], + "database_writes": 0, + "application_bootstraps": 0, + "cache_cleared": false, + "new_demo": false, + "new_rights": false +} diff --git a/docs/proofs/sgf-solution-structure-v27-20261111.json b/docs/proofs/sgf-solution-structure-v27-20261111.json new file mode 100644 index 0000000..0ebcbb3 --- /dev/null +++ b/docs/proofs/sgf-solution-structure-v27-20261111.json @@ -0,0 +1,1218 @@ +{ + "date": "2026-11-11", + "contract_sha256": "e64aa24bcaf3fe8f14e9d9d5461772da741e048bad56181615ba53e0ff1cc70d", + "installation": "cc50a276c10c", + "port": 8085, + "sgc_dependency": false, + "full_product_complete": false, + "product_backlog": [ + { + "id": "fiscal_books", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Livro/mapa interno QA de uma origem entregue; IVA geral, dedução/regularizações e restantes tratamentos pendentes" + }, + { + "id": "irps_inss", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "RH autónomo QA com cadastro/contratos/assiduidade/férias e remunerações internas entregue; regimes laborais gerais, IRPS/INSS/modelos legais e ligação financeira/contabilística salarial pendentes" + }, + { + "id": "irpc", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "IRPC operacional: ajustamentos, retenções, apuramento" + }, + { + "id": "ispc_ice", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Motores operacionais ISPC e ICE" + }, + { + "id": "official_forms", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Modelos oficiais com correspondências e reconciliação de campos" + }, + { + "id": "fiscal_calendar", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Controlo administrativo de prazo e entrega simulada de uma origem QA entregue; calendário legal, aplicabilidade geral, prazos por fonte/vigência e submissão continuam pendentes" + }, + { + "id": "bank_reconciliation", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Conferência de duas linhas/extracto artificial QA entregue; integração e reconciliação bancária gerais/reais pendentes" + }, + { + "id": "financial_analysis", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Dois critérios/indicadores QA com fontes conciliadas entregues; rácios gerais, critérios reais e tendências/séries permanecem pendentes" + } + ], + "modules": [ + { + "id": "commercial", + "label": "Comercial", + "features": [ + "Clientes e serviços com associação explícita", + "Rascunho, revisão e cálculo", + "Documento emitido de teste e PDF", + "Continuação para recebível sem duplicação", + "Orçamentos Comercial/Oficina, revisão e aceitação documentadas e conversão explícita em rascunho", + "Recibo de afectação efectiva400, crédito nominal100/saldo500 e factura-recibo1000 atómica", + "PDF/SHA, histórico e anulação técnica com reversão explícita e diário único", + "Registo unificado autorizado com seis famílias, filtros/SQL25/100 e projecções limitadas à página", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceWorkflow.php", + "present": true, + "sha256": "6ea92e6216bec9a0062d9f152d1b758a5527360456589a98f525429402cb94da" + }, + { + "file": "app/Modules/Services/ServiceInvoicing.php", + "present": true, + "sha256": "4138afb7e001b546a65552d71223cd642154945b8021e1ff45ef60a34076e3c5" + }, + { + "file": "resources/js/ServicesWorkspace.jsx", + "present": true, + "sha256": "c170b195ccd78aa74f283a91b0b4f4cd93aea4677e3aaefe0b50af46f7e31669" + }, + { + "file": "resources/js/ServiceInvoiceWorkspace.jsx", + "present": true, + "sha256": "8f95d1775b30a6de73d65eed428595d74cc837755d8157bf8db49a1151350277" + }, + { + "file": "app/Modules/Services/ServiceDocuments.php", + "present": true, + "sha256": "816a85be48b05043f41f9c7d5f842a3fa139b21aaedf9c944d811f74be6f6208" + }, + { + "file": "app/Modules/Services/DocumentPeriod.php", + "present": true, + "sha256": "100c6cf038b925126ddc8fb50adde2f6d938b4d68df6d0ab193f16ea28836ae2" + }, + { + "file": "app/Modules/Services/BillingRegister.php", + "present": true, + "sha256": "115e7832b63e5c29be716140629845137d951da7790421832e1ab4058fb468cc" + }, + { + "file": "resources/js/BillingRegisterWorkspace.jsx", + "present": true, + "sha256": "9ab8d036c9e6d20c588427f5a8c44677810e9713722baea908b8f6b76c8b79f9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/invoice-test-results.json", + "present": true, + "sha256": "fd04a58727ffddf1c62ac184d7834b81228dce2abb5b0c1e397505df6a45237c", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_SERVICE_DOCUMENTS_DELIVERY.md", + "present": true, + "sha256": "a13713f05feaa84766dfaba870e72c98d23aa6223389d1627de911b0856cd15d", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BILLING_REGISTER_DELIVERY.md", + "present": true, + "sha256": "fec1b76003187d5f38c8aca8804c26129e312e517dd5b2247f54f0019c2cccf6", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual, aceitação explícita e conformidade real; emissão fiscal real permanece bloqueada", + "Carga sustentada, armazenamento e capacidade de implantação exigem evidências próprias" + ] + }, + { + "id": "purchasing", + "label": "Compras", + "features": [ + "Fornecedores e revisão distinta", + "Requisição até adjudicação e ordem", + "Recepção, documento e stock único", + "Factura conferida e obrigação reconhecida", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Purchasing/ReceiptRuntime.php", + "present": true, + "sha256": "b08af1f8772cbfafa1f85e37c832f2c6916ccabcfbd4a8bb98c5ef7690c3f07f" + }, + { + "file": "app/Modules/Purchasing/RequestController.php", + "present": true, + "sha256": "c5c2b78866be7f0e1c8ba09c1af538cefcfbafbea81197833b50821bba0fcbca" + }, + { + "file": "app/Modules/Purchasing/InvoiceController.php", + "present": true, + "sha256": "85eb46ca7e65c60bed190e592a134eb48db5762c0a90b8cd4c9d6fbd81198ba4" + }, + { + "file": "app/Modules/Purchasing/ObligationController.php", + "present": true, + "sha256": "347e629306d6c1f4873afac547d13fae8ade7b268906d1312a64e7eb6998e701" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-final-20261104-r2.json", + "present": true, + "sha256": "3b63f8653e8dbb089dfdd8f389e271c022a070d5a67a9f9d4585ce84438070cc", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ], + "accepted_component_journey": true + }, + { + "id": "stock", + "label": "Stock", + "features": [ + "Artigos, armazéns e saldos físicos", + "Entradas, saídas e origem autorizada", + "Transferência e conferência de destino", + "Contagens, ajustamentos e devoluções", + "Movimentos e relatórios por período", + "Política QA revista e devolução ao fornecedor por origem exacta; saldo8UN/160MZN no mesmo diário", + "Inactividade de pares com história conhecida, corte UTC explícito, filtros e SQL25/100 sem inferir ausência de actividade", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Cadastro de artigos: preparação/revisão distintas, criação sem saldo, designação revista só dos novos, código/unidade imutáveis, histórico/recibos e catálogo canónico único" + ], + "sources": [ + { + "file": "app/Modules/Inventory/StockReadController.php", + "present": true, + "sha256": "fd02da1a69f76bf1412f86f43095a0a476de49fbb7d2676a51fe3b29ec66e298" + }, + { + "file": "app/Modules/Inventory/CompositionStockRead.php", + "present": true, + "sha256": "81bdb5fb374a60974bbad421c461cd12079951fb426361200692a838e413b49e" + }, + { + "file": "app/Modules/Inventory/StockPeriodReport.php", + "present": true, + "sha256": "d491541c6c60bc0c661dc442b8db8ce2e757f1d63dfc4982a3631f820cfb7a06" + }, + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "app/Modules/Inventory/StockOperationsController.php", + "present": true, + "sha256": "34d6c7be4787f4f18ca2a7aa322f6f33dfb281e22336599234c93c655ab4eebf" + }, + { + "file": "resources/views/stock-operations-workspace.blade.php", + "present": true, + "sha256": "5db67c2d23e5557548c38c82a0d7f6e373651849727b2237dcd73f620309c25a" + }, + { + "file": "public/stock-operations-workspace.mjs", + "present": true, + "sha256": "56734499e8dd89da293c2edadd0d0e10ac6d06b39f0365e5ae29a8d14e9176d8" + }, + { + "file": "app/Modules/Inventory/StockFinalController.php", + "present": true, + "sha256": "9d9a242fd8e143fe076ece0454d0789eabf543e37b332db4806d905fad13cdda" + }, + { + "file": "resources/views/stock-final-workspace.blade.php", + "present": true, + "sha256": "93764471a215735a65e5c07121989b1e2f6499f631a11b9f1177bb0fe8def232" + }, + { + "file": "public/sgf-stock-final.mjs", + "present": true, + "sha256": "77860b080015528fe4dd84b2e71787ee668da9be17653c64a5d8e64539b5e592" + }, + { + "file": "app/Modules/Inventory/StockInactiveReport.php", + "present": true, + "sha256": "7db06225fb7bb40c9a4140383caef6dd96c430e3a3e1be8ef7fed2253f2681e2" + }, + { + "file": "resources/js/StockInactiveReportWorkspace.jsx", + "present": true, + "sha256": "5d4f7222bf325550cebddbd00198515da55f15f52b4c99ecaef19f45820a165e" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Inventory/StockCatalogueController.php", + "present": true, + "sha256": "ab0a6bc52e635d5bcdee9163b68b1ec6fc14b717de7b887fcc832336961fa128" + }, + { + "file": "resources/views/stock-catalogue-workspace.blade.php", + "present": true, + "sha256": "4381f81a9aea04fe851f8c7af17cafb98f6bef7cbf52ac2630b0b9912a3dca57" + }, + { + "file": "public/sgf-stock-catalogue.mjs", + "present": true, + "sha256": "10eedcf1881ad3f1cb71813f0547eae7246795273457cc33b1a2a6761a0a2d6e" + }, + { + "file": "config/stock_catalogue.php", + "present": true, + "sha256": "811f844cecf76bff62da943042a272301cb8a7f7a586c4f2648ef5b8e5e31dae" + }, + { + "file": "database/sql/stock_catalogue.sql", + "present": true, + "sha256": "c9c99cec20e6ca60cf0ee56210eac9a2365a2ef9b482698929a70295d2893809" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/stock-period-report-20261103.json", + "present": true, + "sha256": "0ab25af6ffbff7d8ecb651df833771b082bf92d52f97eca4278fcac3fd5ea53b", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-stock-operations-20261110.json", + "present": true, + "sha256": "1d241fcfb81fcf6c1b374f9fc89c1f2130b17eee1314f06b4b8e4b59c476fc08", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_POLICIES_SUPPLIER_RETURN.md", + "present": true, + "sha256": "b8848090f1e19a6b7c19b684e07c0c95cafcf93bd1551357f6afc314f4413d8b", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_INACTIVITY_DELIVERY.md", + "present": true, + "sha256": "8773d53140b291c01358b8e71c584373cd0e440f67d0107e64ecea13980990d3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_CATALOGUE_DELIVERY.md", + "present": true, + "sha256": "43ce839f882aeeab48b272d5eb1a4e34fcd855052db88643a8ca7b08556631f8", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual dos novos relatórios e aceitação explícita do conjunto; carga sustentada e implantação separadas" + ] + }, + { + "id": "workshop", + "label": "Oficinas", + "features": [ + "Ordem e autorização", + "Apontamentos e revisão distinta", + "Material consumido ligado ao movimento físico", + "Custo directo/industrial e correcção", + "Continuação comercial por documento de origem", + "Agenda sem sobreposição, reagendamento e cancelamento versionados", + "Cadastro independente e reutilização de equipamento por cliente", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Ordens SQL25/100 por cliente/equipamento/responsável/estado, apontamentos actuais e minutos verificados, sem mutex de escrita na leitura", + "Relatório por período de abertura explícito/dia local, SQL25/100 antes de paginar, apontamentos actuais e CSV da página sem fecho histórico presumido", + "Equipamento escolhido → ordens exactas SQL25/100 com filtros, identificação actual, CSV da página e regresso ao catálogo" + ], + "sources": [ + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "resources/js/WorkshopWorkspace.jsx", + "present": true, + "sha256": "a1ceca33c234a759b2abf13b087bba8d18d70187ad9161e59425a5932771f8bb" + }, + { + "file": "resources/js/DirectCostsWorkspace.jsx", + "present": true, + "sha256": "dcaf72fe36d80e16bab8e962f939168650336d1450fc3eb01693acb7ef7e0743" + }, + { + "file": "resources/js/IndustrialCostsWorkspace.jsx", + "present": true, + "sha256": "51a3307fa8b2526efa12d8a16fee613bdfe141d02bd6e5af4535d581ed69c6d0" + }, + { + "file": "resources/js/WorkshopBillingWorkspace.jsx", + "present": true, + "sha256": "601853bd383ef2d1891158e89578bbc7f26ae1c7220bc11ddfadf1868cb63afc" + }, + { + "file": "app/Modules/Workshop/ComposedWorkshopStockBook.php", + "present": true, + "sha256": "01e7392827a6453a357a0b5140905757bbee76f57fae6aa9906490788690e24c" + }, + { + "file": "app/Modules/Workshop/WorkshopBilling.php", + "present": true, + "sha256": "0eac2d91fd9f006aa8c198e3652731142aee478c93de090c408df54cdd9d292d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Workshop/WorkshopOrderRead.php", + "present": true, + "sha256": "f741a5ac6c16376ea26b1bacd7b5e706b14a4548a26f59c4a96ec882fb6a6fa5" + }, + { + "file": "app/Modules/Workshop/Http/WorkshopOrderReadController.php", + "present": true, + "sha256": "f4dd027d01c0730603f3b312c2eb263740f6ebe1c969c762ed99d19520fab584" + }, + { + "file": "resources/js/WorkshopOrderList.jsx", + "present": true, + "sha256": "4cad148295acb7c462425fa300dccc7c203255f5f616163575e205bfc8a80e34" + }, + { + "file": "resources/js/workshop-report-view.js", + "present": true, + "sha256": "28125af02e25b4befb9914c5dee753dae4164100f3c0f94988c332a50021bf9f" + }, + { + "file": "app/Modules/Workshop/WorkshopEquipment.php", + "present": true, + "sha256": "37223b732865f273831d0bf61a464137cc53526a4c55750347450ba20bcbbc37" + }, + { + "file": "resources/js/WorkshopEquipmentWorkspace.jsx", + "present": true, + "sha256": "450af001d385f42cd4d0ae42b34fa6b373cdd6811d94f8f4a1f5de2c591ed843" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/workshop-stock-closure-20261102.json", + "present": true, + "sha256": "261d5502f6905d53ef8046e0e900c9bee159c8c9d7e1bae4fddd8ec698313bf7", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_AGENDA_EQUIPMENT.md", + "present": true, + "sha256": "3a44a1b6ac5148013c815cc6cc8919f08c15ac911f900aac5b4a0af903589481", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_PERIOD_REPORT_DELIVERY.md", + "present": true, + "sha256": "1455e3dc4203c81f49ddaba05a7bf7de10cfd9b3bb4dbd2eb4480aa05b7ccf08", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_EQUIPMENT_ORDERS_DELIVERY.md", + "present": true, + "sha256": "4d4249613a1b3fc339f342c3854eda08bf1ec280c0681eb3b2551469b2da2b36", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ] + }, + { + "id": "finance", + "label": "Financeiro", + "features": [ + "Obrigações a fornecedores", + "Recebíveis de clientes", + "Liquidação/afectação e reversão sintéticas", + "Posição financeira e comprovativos por origem", + "Caixa QA explícita0+1000-50=950, revisão distinta, contagem e fecho sem reabertura", + "Extracto paginado, recuperação de confirmação e classificação/despesa no mesmo diário", + "Extracto artificial QA e correspondências individuais com duas linhas nativas existentes, revisão distinta, conferência final e relatório", + "Critérios e indicadores QA950/100=9,5 vezes e50/1000=5%, revisão distinta, fontes conciliadas/retrato/fracção/histórico/relatório", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Posições actuais de clientes por documento, pesquisa/estado/vencimento, SQL25/100, saldos reconhecidos e CSV da página; referência de vencimento distinta de saldo histórico", + "Posições de fornecedores por factura/fornecedor e estado documental, SQL25/100, saldos do motor original, CSV da página e abertura exacta do documento; vencimento desconhecido sem atraso presumido", + "Condições administrativas QA de vencimento: preparação13/revisão14, evidência/fundamento, prévia/revalidação de origem/saldo/versão/pessoa, recusa/correcção, histórico25/100, recibo e recuperação; lista/CSV separam original e condição revista, referência explícita no detalhe", + "Mapa de antiguidade de fornecedores por referência explícita e condição administrativa revista, faixas0/1–30/31–60/61–90/>90/desconhecido, valores exactos por moeda só da página/CSV, sem alterar saldo ou vencimento original" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceReceivables.php", + "present": true, + "sha256": "79e1669b2423c521c0c56ab6fb07e085daef88eff8a41748c424e4a827f1e453" + }, + { + "file": "app/Modules/Purchasing/SupplierPayables.php", + "present": true, + "sha256": "c17d44391cbd96941aa2dd2eac2cb167508faf6453c6a1e86333846df1ba18b9" + }, + { + "file": "resources/js/ServiceReceivablesWorkspace.jsx", + "present": true, + "sha256": "6e654a03cb47e52dedd52ce6b2abf89a61d22860e4ffae92fd393027a54cc445" + }, + { + "file": "app/Modules/Purchasing/ComposedFinance.php", + "present": true, + "sha256": "ad7e837c7608ab85c59eb169bcdd986b08c5802a0f218926e1abd7568e8a4b16" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "app/Modules/Finance/CashWorkspace.php", + "present": true, + "sha256": "bb65060461cea3a844cf523733644cdd630f35ba02c82aeed961821ec5970800" + }, + { + "file": "app/Modules/Finance/Http/CashController.php", + "present": true, + "sha256": "982c161f3ef7314a9671f848f0da2aefb27841e40003ef47102d0d956c08cd78" + }, + { + "file": "app/Modules/Accounting/CashLedgerSource.php", + "present": true, + "sha256": "3a46162f0a62363dd4015f0047a5bff95e8aebd8dd3007fc705daaf58c10684b" + }, + { + "file": "app/Modules/Finance/BankReconciliation.php", + "present": true, + "sha256": "5421141bcc6e15bda142687661774238c7203c85d0ef30696fd4d56dd5b9e328" + }, + { + "file": "app/Modules/Finance/BankLedgerSource.php", + "present": true, + "sha256": "f33779fa736aea7027d4cf449ba85c5982bfb565c75bbb2a477d7a2666820338" + }, + { + "file": "app/Modules/Finance/Http/BankReconciliationController.php", + "present": true, + "sha256": "896d710db108364263a1404d2aafdc52eff4d3b6efcbe3dbdbcca1978befffe4" + }, + { + "file": "public/sgf-bank.mjs", + "present": true, + "sha256": "959214ad94bb5c29ffdaf1c8fd3e59bf8718837d4c1fe9bea52d3b04c261a80b" + }, + { + "file": "app/Modules/Finance/FinancialIndicators.php", + "present": true, + "sha256": "9785a801c5fb13b34770bdd05c828f5e924058eb872a7b33bc1db139a3024d10" + }, + { + "file": "app/Modules/Finance/FinancialIndicatorSource.php", + "present": true, + "sha256": "72039264cbb25ae2eaa5eaa4af488aabf7061a4a298c09490b8957b4ade55731" + }, + { + "file": "app/Modules/Finance/Http/FinancialIndicatorsController.php", + "present": true, + "sha256": "5245064e40b884e432afa2fd26d45fddccebf6963ef3366f585b93ee78f12cca" + }, + { + "file": "public/sgf-indicators.mjs", + "present": true, + "sha256": "62442edc14da534e0166cc65074389e74cc67246cd5922994628644fd900cbb9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Services/ReceivablePositionRead.php", + "present": true, + "sha256": "6aaf0211c4427643c84152edc6a7937dbf48387e2844d3bbe31328d1f6789f2d" + }, + { + "file": "app/Modules/Services/Http/ReceivablePositionReadController.php", + "present": true, + "sha256": "9e8be7c2cd328aebb31cbe0be4eeaaa6abc02d5bff0895aeb90c24ab8d134541" + }, + { + "file": "resources/js/ReceivablePositionsWorkspace.jsx", + "present": true, + "sha256": "5bd8ee952aeee87d733ab434cfe50b675d84c0341ade4f354c93e4acfdbe57e9" + }, + { + "file": "resources/js/receivable-position-view.js", + "present": true, + "sha256": "08f8cb6d17b79b89ee8cebe4723458c1a4979d8fb824afa0b585ae51076e6f21" + }, + { + "file": "app/Modules/Purchasing/PayablePositionReadController.php", + "present": true, + "sha256": "1d147f14826793f97605ea442c6a93612a55caf265311a86824611650dfc0005" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "resources/js/PayablePositionsWorkspace.jsx", + "present": true, + "sha256": "e52fa90276e6251b38bb48f83a11ec2b11ac819ffa3c2239da9712e50ce66343" + }, + { + "file": "resources/js/payable-position-view.js", + "present": true, + "sha256": "ff4b32f404cd575ddc134cf373ae6ded5d0abae8641b14b591c0f5a856437938" + }, + { + "file": "app/Modules/Purchasing/PayableDueController.php", + "present": true, + "sha256": "39e4c95b910d3b1c4aa227c7c2aa0ee24399921b9578748b95ff5aa519f34db7" + }, + { + "file": "resources/js/PayableDueWorkspace.jsx", + "present": true, + "sha256": "c7127bbb2edc6c5acce57a55a6c3c5b94969170ccf1adb7236fee80d45f555ae" + }, + { + "file": "database/sql/payable_due.sql", + "present": true, + "sha256": "faefce9f2c11da613293e23bbc2f630bdae345d872cca7f3c2923c77fea0f75e" + }, + { + "file": "config/payable_due.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Purchasing/PayableAge.php", + "present": true, + "sha256": "19f4a3994b679b74275e72e906251c8d32ee9950ebedfae07bd4002abe740a9e" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/supplier-settlements.json", + "present": true, + "sha256": "b1bce9218f99ea2d8c780022ad385e169c7acc5778ac6950236eef0630f77ad0", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_CASH_DELIVERY.md", + "present": true, + "sha256": "bfb40ff8849eb4d9fcb641aa9b8d07bbaab89326a0f89b8f113cf0bf62653093", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BANK_RECONCILIATION_DELIVERY.md", + "present": true, + "sha256": "acf53f7dfd2ec0f501cef8b1623e090c1e1b8a07a8591f4f042fb8a0b7b57273", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_INDICATORS_DELIVERY.md", + "present": true, + "sha256": "9902e37504dd9f5e1d3c73cd78a283e29be00ffb33ce13ca8f76577d1163cc65", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_RECEIVABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "7a829a6dcae28fa42e996874b7b65ed1de325dbab5bae12a28731ecfc2e849d7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "2c0a25051540527e87ca9abfa43db5efb288e0b40e2315e06f6084693ce9c8c3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_DUE_DELIVERY.md", + "present": true, + "sha256": "614ca3b1c432760e1911112951f138cf72d5d8370173336472529e9549e965b8", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_AGING_DELIVERY.md", + "present": true, + "sha256": "a0af96108702bbb687a655de744e78e01a3e7c98365244410eb291fcb7a5fafa", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Conferência de extracto QA entregue; reconciliação bancária geral/real permanece fora do recorte" + ] + }, + { + "id": "accounting", + "label": "Contabilidade", + "features": [ + "Plano e classificação", + "Diário com preparação/revisão distintas", + "Razão e balancete", + "Estorno/correcção e conferência de período", + "Fecho sintético com rastreabilidade de origens", + "Livro de saídas e mapa interno IVA QA da origem FR seleccionada, revisão distinta, histórico e CSV congelado", + "Catálogo de fontes/regras versionadas QA, revisão distinta, vigência/conflitos/retirada e cálculo explicável com história preservada", + "Cálculo QA por1–25linhas sem/com imposto incluído, representação por linha aprovada, exactos/diferenças e mapa interno recuperável", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Submenus distintos e balancete/razão por datas explícitas, escolha de conta por linha, páginas25/100, detecção de mudança de livro e CSV da página", + "Controlo administrativo QA de obrigação/prazo/responsável do mapa interno revisto, preparação13/revisão14, correcção/cancelamento, entrega apenas simulada com SHA verificado, histórico/recibo/SQL25/100/CSV; sem calendário legal ou submissão", + "Correspondências explícitas revistas15/16 e balanço/resultados internos parciais QA:19contas/nove pendentes, resultado congelado19linhas/SHA, história/recibo/CSV contextual, ligação exacta ao razão; capital e resultado completo não presumidos" + ], + "sources": [ + { + "file": "app/Modules/Accounting/GeneralLedger.php", + "present": true, + "sha256": "a87d2ea373bc61bb139aa29f2a354b9813bbb2d6229a389f8d37fd8e9890edec" + }, + { + "file": "app/Modules/Accounting/GeneralLedgerController.php", + "present": true, + "sha256": "afd2f4ae55e694f0aff18233a6f5cf52444e185aaf5a159865f74f0243783f2a" + }, + { + "file": "packages/general-ledger/src/LedgerSchema.php", + "present": true, + "sha256": "d9c203286b3a04121c265dffe412d2e86726ec4bb9222a2ed654d0296d86b5d8" + }, + { + "file": "app/Modules/Accounting/ComposedLedger.php", + "present": true, + "sha256": "2e4dfa133831c78514e1010d373db905b892c315c455ba53b997edd514cac035" + }, + { + "file": "app/Modules/Accounting/ComposedLedgerController.php", + "present": true, + "sha256": "b96749981b0d4daa7861eab62c5de60c96c5ffa9c72183f88bb89e2d4f7bdf2b" + }, + { + "file": "packages/general-ledger/src/StockTransitEvidence.php", + "present": true, + "sha256": "eb8eee3bb293e77689eb6470ac8aa12049b2ff8cfb8694d3e15eb17e097d2da6" + }, + { + "file": "app/Modules/Services/FiscalCoverage.php", + "present": true, + "sha256": "55f8b947bd63cffdc63f268a88ac441fda62e144d1a2013eaa954d57970fa716" + }, + { + "file": "app/Modules/Services/FiscalAssessment.php", + "present": true, + "sha256": "c2dbe192e6bba75f6fc4057bbb51c85bcf024a674cfeb7950ba239e700213128" + }, + { + "file": "app/Modules/Services/Http/FiscalAssessmentController.php", + "present": true, + "sha256": "425278110af20393e71de446f9b0ce41408352216249e5055de4165b075631e4" + }, + { + "file": "public/sgf-iva.mjs", + "present": true, + "sha256": "1d8c2a07f606799257c090ab2ddb52c309553a15009d6fb457dbbf4770c51ee6" + }, + { + "file": "app/Modules/Services/FiscalRules.php", + "present": true, + "sha256": "520edda0a013d41a80e3e3b56a1b39597a4f1b6407d78118f8855277ffeb3548" + }, + { + "file": "app/Modules/Services/FiscalRuleExpression.php", + "present": true, + "sha256": "108d8401c9c2bc85b3e5f663322c3d36c267afd8529b370e7e16c1d565a3daef" + }, + { + "file": "app/Modules/Services/Http/FiscalRulesController.php", + "present": true, + "sha256": "cc7e05e18c4bbc98a70a19eabdd653bbb44a717214f8d8ee19cd2ffeb1c032c9" + }, + { + "file": "public/sgf-fiscal-rules.mjs", + "present": true, + "sha256": "5c865e018c5b7d3b3f1232b80e81da379c729ea816a9e08a73225e76a678502c" + }, + { + "file": "app/Modules/Services/FiscalDocumentMath.php", + "present": true, + "sha256": "6e67b7b1363d24b38ca0dd40c4349dba82a4b746965f0af8aa7d65bb4f8f731d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "resources/js/ComposedAccountingWorkspace.jsx", + "present": true, + "sha256": "9288df122be42e436bb4aeffc3e84cc1687ba82ab4cab07754d27d507d0012ac" + }, + { + "file": "resources/js/LedgerReportsWorkspace.jsx", + "present": true, + "sha256": "312157b80599e0f4407520ec968460af9c31913f79365ef6efa9a243c98ba419" + }, + { + "file": "resources/js/ledger-report-view.js", + "present": true, + "sha256": "54a1eb09ab4b451b4aa34d8b0c1ebc3d8cea91da4f242ea25a0c4b3bb1db79b4" + }, + { + "file": "app/Modules/Services/FiscalObligations.php", + "present": true, + "sha256": "bcf24717cf5828fed440b8e86f29a5ed4d325d392000240b224d155954170d1d" + }, + { + "file": "app/Modules/Services/Http/FiscalObligationsController.php", + "present": true, + "sha256": "95b03b505e588333206766b862d0f0cff006b1af1a3dc0466370ac6a7518e097" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "resources/js/fiscal-obligations-view.js", + "present": true, + "sha256": "d3c1aafddac987de0a8e432746533da8832c140474f9d9d49055020f7c9e67dd" + }, + { + "file": "database/sql/fiscal_obligations.sql", + "present": true, + "sha256": "c68988342ca1ed5817485355844c2f502f5c2ca12954461c1555d5c5f2abf7fe" + }, + { + "file": "config/fiscal_obligations.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Accounting/InternalStatementsController.php", + "present": true, + "sha256": "b05990c7fdb195957808b63a77599eaaabed5c5eb435dd8e7dd13d662c22fd8d" + }, + { + "file": "app/Modules/Accounting/InternalStatementTotals.php", + "present": true, + "sha256": "f13f6f356100e49786869196c54343fd629f6a1d38fa81665eb768dae4731601" + }, + { + "file": "resources/js/InternalStatementsWorkspace.jsx", + "present": true, + "sha256": "2fadac08cd6162dd06d056bfc4fb00479e9a5d930fdde10c1a4b57507982e76e" + }, + { + "file": "resources/js/internal-statements-view.js", + "present": true, + "sha256": "f1f7b44f2dc1679e537d77522e0fc5ee84da23068ef3157677bc20a8409d3464" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "2dcbea8a6523970211024dbac28743ce4a128b98808fd6f0d39b73cbf48457ff" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "database/sql/internal_statements.sql", + "present": true, + "sha256": "2beccb0bb7d91f48e5a5e386c1e010fb7d95cbfa90aa35a68e28dc63ae6a6f6b" + }, + { + "file": "config/internal_statements.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-matrix-final-20261104.json", + "present": true, + "sha256": "94cb37698f294c38df9b94a5535ba4b9ac63907f94dab1e7f951a3bb27623083", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_COVERAGE_DELIVERY.md", + "present": true, + "sha256": "4951a2514253c71710fd18ddbffcec1c4dca597355b1cf998b1c7f1b38078506", + "not_reexecuted": true + }, + { + "file": "docs/SGF_IVA_ASSESSMENT_DELIVERY.md", + "present": true, + "sha256": "b0a3ae64699352242108d782a229f0250344f238fad02e14e6fa4dbb520e997f", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_RULES_COMMON_DELIVERY.md", + "present": true, + "sha256": "10f8801d59dbdeb3f075e5bf2650e4538097a92151db516e3c0357ac011c8a5a", + "not_reexecuted": true + }, + { + "file": "docs/SGF_MULTI_LINE_INCLUDED_DELIVERY.md", + "present": true, + "sha256": "7b1d6bbdea15845cf7f4fe104057a4053be1c0f0edb2186da26d11dded45b690", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_ACCOUNTING_REPORTS_DELIVERY.md", + "present": true, + "sha256": "f910d6d11fd7f4e18a675fea9f07a50137bbed72d4718bb7ec6e022bdd1959e5", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_OBLIGATIONS_DELIVERY.md", + "present": true, + "sha256": "028e3172128435924dc9594b68b25560cb0b30495f27c522864a94a1f8cc9504", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_STATEMENTS_DELIVERY.md", + "present": true, + "sha256": "53a07e2c0517de5b6881584c598b2f1d1d5665422ac2f8f5932626822daf3359", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Backlog fiscal/financeiro amplo pendente; tabela de cobertura não implementa motores ou modelos" + ] + }, + { + "id": "human_resources", + "label": "Recursos Humanos", + "features": [ + "Módulo autónomo com perfis QA próprios separados do Financeiro", + "Cadastro pseudónimo individual preparado e revisto", + "Regras artificiais versionadas com vigência e fonte explícitas", + "Processamento exacto por trabalhador/período com escolha de fontes e revisão distinta", + "Correcção/cancelamento versionados, prévia, histórico, recibos e recuperação", + "Mapa individual revisto conservado com SHA e exportação autenticada; filtros SQL25/100", + "Contratos internos QA revistos com correcção/terminação/cancelamento e dependências", + "Plano e assiduidade por dia com intervalos explícitos, dados desconhecidos e revisão distinta", + "Condição de férias QA, reserva/gozo/cancelamento individuais, saldo efectivo sem duplicação", + "Conferência parcial do período por pessoa, fontes/versões e CSV/SHA conservado", + "Ligação contratual explícita opcional em novas preparações salariais, provada na regressão sem alterar salários8085", + "Resumo CSV legível derivado de versão revista, com SHA próprio/original, sem alterar o artefacto conservado" + ], + "sources": [ + { + "file": "app/Modules/HumanResources/Remunerations.php", + "present": true, + "sha256": "950333ced35d6949487bdf78ef109fad608338525acfe60b05b9a320fd3aac01" + }, + { + "file": "app/Modules/HumanResources/Http/RemunerationsController.php", + "present": true, + "sha256": "3725145a623791a6c2825d8aaa4f122ba7b80908c08603211d6d9e74df76e801" + }, + { + "file": "app/Shared/Installation/HumanResourcesProfiles.php", + "present": true, + "sha256": "f908b8cf9f7121bb9b43733f507771a60aa34eb553df8342c250e5b39940ae93" + }, + { + "file": "resources/js/RemunerationsWorkspace.jsx", + "present": true, + "sha256": "edb225504bff359d5c53c040421e322bb4cd887ac946b3316852ecd1ed25b76e" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "2dcbea8a6523970211024dbac28743ce4a128b98808fd6f0d39b73cbf48457ff" + }, + { + "file": "app/Modules/HumanResources/Personnel.php", + "present": true, + "sha256": "da109cf709a6306f7c7a8a3f111514cb1e5c4d1dc0554e15e36022bf1fbb829b" + }, + { + "file": "app/Modules/HumanResources/PersonnelTime.php", + "present": true, + "sha256": "e0fc1bdecbbd24c1ac21a426d8547fc049c00fd18ae6faa7ded64128252bbfd6" + }, + { + "file": "app/Modules/HumanResources/Http/PersonnelController.php", + "present": true, + "sha256": "539fb7ce74dd3b87263d1610fee7ddac2028c4536bc8d549be6d0617c3f947a2" + }, + { + "file": "resources/js/PersonnelWorkspace.jsx", + "present": true, + "sha256": "3bff1b58a1f08c8065abaa68fd7547156f3537f573e0a8d4b922cd3c2cf7ae06" + }, + { + "file": "app/Modules/HumanResources/PersonnelReadableCsv.php", + "present": true, + "sha256": "5e31d7de8aa243b9f9578d859e05ab9071c06dc39bb8616f35eed085856c493c" + } + ], + "existing_evidence": [ + { + "file": "docs/SGF_HUMAN_RESOURCES_DELIVERY.md", + "present": true, + "sha256": "1494d29938565bb6f5d552385d7b32dd0a8f22856755bf638d81cf9a41a65f60", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-human-resources-20261111.json", + "present": true, + "sha256": "95d0c3fcd407cfbb03226288b94570478b906fdf3cb50c26a9960956f536cf29", + "not_reexecuted": true + }, + { + "file": "docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md", + "present": true, + "sha256": "045d111cd08ad58c22a802d6d728c7f300d91b4078b65c3ac61683791747a02e", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-hr-personnel-20261111.json", + "present": true, + "sha256": "b4ce53d6099a13acbe6bd2d7431f9109b8a318000f9d69813353349cd823cdc7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_HUMAN_RESOURCES_READABLE_DELIVERY.md", + "present": true, + "sha256": "18275d4fda727e0082379bcd17804397a13dc3c5f5a88575c48c4e6c8abf95b2", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conformidade e aplicabilidade laboral real: contratos legais, turnos/calendário, férias legais, benefícios e modelos", + "Cálculo legal IRPS/INSS e ligação a dívida/pagamento/Contabilidade sem efeitos implícitos", + "Revisão visual, carga sustentada, armazenamento e capacidade de implantação" + ] + } + ], + "release_gates": [ + { + "id": "single_installation", + "criterion": "Os seis módulos operam no mesmo endereço e empresa/unidade, sem mudar de demonstração", + "passed": false + }, + { + "id": "functional_modules", + "criterion": "Cada funcionalidade obrigatória tem ecrã/comando/consulta executável; menus e fontes isoladas não bastam", + "passed": false + }, + { + "id": "cross_module_flows", + "criterion": "Os documentos atravessam as fronteiras mantendo origem, versão, autoridade e efeito único", + "passed": false + }, + { + "id": "access_isolation", + "criterion": "Perfis explícitos e recusas de empresa/unidade/módulo/permissão verificadas", + "passed": false + }, + { + "id": "presentation", + "criterion": "Percurso de apresentação coerente, dados fictícios identificados, linguagem e navegação revistas", + "passed": false + }, + { + "id": "operations", + "criterion": "Arranque e recuperação da instalação entregues e ensaiados, sem depender do SGC", + "passed": false + }, + { + "id": "commercial_honesty", + "criterion": "Documentos de teste não são emissão fiscal; capacidade de implantação exige prova própria", + "passed": false + }, + { + "id": "preservation", + "criterion": "Seis registos, cache, dados aprovados e trabalho alheio preservados", + "passed": false + } + ], + "client_presentable": false, + "limits": [ + "Host-table footprint is a minimum projection, not a full migration/dependency validation.", + "Native purchasing/stock tables live in their existing separate native book; this scan does not call that missing.", + "Source/evidence presence does not mean a feature is integrated, visually accepted or deployment-ready." + ], + "database_writes": 0, + "application_bootstraps": 0, + "cache_cleared": false, + "new_demo": false, + "new_rights": false +} diff --git a/docs/proofs/sgf-solution-structure-v28-20261111.json b/docs/proofs/sgf-solution-structure-v28-20261111.json new file mode 100644 index 0000000..1a9cc43 --- /dev/null +++ b/docs/proofs/sgf-solution-structure-v28-20261111.json @@ -0,0 +1,1234 @@ +{ + "date": "2026-11-11", + "contract_sha256": "dab7d150c07695f2f845f13d0bb4e4a9c5a9dcae44b913a7f8a3446dddd168bc", + "installation": "cc50a276c10c", + "port": 8085, + "sgc_dependency": false, + "full_product_complete": false, + "product_backlog": [ + { + "id": "fiscal_books", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Livro/mapa interno QA de uma origem entregue; IVA geral, dedução/regularizações e restantes tratamentos pendentes" + }, + { + "id": "irps_inss", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "RH autónomo QA com cadastro/contratos/assiduidade/férias e remunerações internas entregue; regimes laborais gerais, IRPS/INSS/modelos legais e ligação financeira/contabilística salarial pendentes" + }, + { + "id": "irpc", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "IRPC operacional: ajustamentos, retenções, apuramento" + }, + { + "id": "ispc_ice", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Motores operacionais ISPC e ICE" + }, + { + "id": "official_forms", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Modelos oficiais com correspondências e reconciliação de campos" + }, + { + "id": "fiscal_calendar", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Controlo administrativo de prazo e entrega simulada de uma origem QA entregue; calendário legal, aplicabilidade geral, prazos por fonte/vigência e submissão continuam pendentes" + }, + { + "id": "bank_reconciliation", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Conferência de duas linhas/extracto artificial QA entregue; integração e reconciliação bancária gerais/reais pendentes" + }, + { + "id": "financial_analysis", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Dois critérios/indicadores QA com fontes conciliadas entregues; rácios gerais, critérios reais e tendências/séries permanecem pendentes" + }, + { + "id": "fixed_assets", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md FIS-SGF-15", + "description": "Cadastro operacional de activos/depreciações e diário ainda ausentes; ciclo integrado QA proposto com novos direitos/contas/efeitos por autorizar", + "scope": "docs/SGF_FIXED_ASSETS_SCOPE.md", + "access_proposal": "docs/integration/sgf-fixed-assets-access-proposal.json" + } + ], + "modules": [ + { + "id": "commercial", + "label": "Comercial", + "features": [ + "Clientes e serviços com associação explícita", + "Rascunho, revisão e cálculo", + "Documento emitido de teste e PDF", + "Continuação para recebível sem duplicação", + "Orçamentos Comercial/Oficina, revisão e aceitação documentadas e conversão explícita em rascunho", + "Recibo de afectação efectiva400, crédito nominal100/saldo500 e factura-recibo1000 atómica", + "PDF/SHA, histórico e anulação técnica com reversão explícita e diário único", + "Registo unificado autorizado com seis famílias, filtros/SQL25/100 e projecções limitadas à página", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceWorkflow.php", + "present": true, + "sha256": "6ea92e6216bec9a0062d9f152d1b758a5527360456589a98f525429402cb94da" + }, + { + "file": "app/Modules/Services/ServiceInvoicing.php", + "present": true, + "sha256": "4138afb7e001b546a65552d71223cd642154945b8021e1ff45ef60a34076e3c5" + }, + { + "file": "resources/js/ServicesWorkspace.jsx", + "present": true, + "sha256": "c170b195ccd78aa74f283a91b0b4f4cd93aea4677e3aaefe0b50af46f7e31669" + }, + { + "file": "resources/js/ServiceInvoiceWorkspace.jsx", + "present": true, + "sha256": "8f95d1775b30a6de73d65eed428595d74cc837755d8157bf8db49a1151350277" + }, + { + "file": "app/Modules/Services/ServiceDocuments.php", + "present": true, + "sha256": "816a85be48b05043f41f9c7d5f842a3fa139b21aaedf9c944d811f74be6f6208" + }, + { + "file": "app/Modules/Services/DocumentPeriod.php", + "present": true, + "sha256": "100c6cf038b925126ddc8fb50adde2f6d938b4d68df6d0ab193f16ea28836ae2" + }, + { + "file": "app/Modules/Services/BillingRegister.php", + "present": true, + "sha256": "115e7832b63e5c29be716140629845137d951da7790421832e1ab4058fb468cc" + }, + { + "file": "resources/js/BillingRegisterWorkspace.jsx", + "present": true, + "sha256": "9ab8d036c9e6d20c588427f5a8c44677810e9713722baea908b8f6b76c8b79f9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/invoice-test-results.json", + "present": true, + "sha256": "fd04a58727ffddf1c62ac184d7834b81228dce2abb5b0c1e397505df6a45237c", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_SERVICE_DOCUMENTS_DELIVERY.md", + "present": true, + "sha256": "a13713f05feaa84766dfaba870e72c98d23aa6223389d1627de911b0856cd15d", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BILLING_REGISTER_DELIVERY.md", + "present": true, + "sha256": "fec1b76003187d5f38c8aca8804c26129e312e517dd5b2247f54f0019c2cccf6", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual, aceitação explícita e conformidade real; emissão fiscal real permanece bloqueada", + "Carga sustentada, armazenamento e capacidade de implantação exigem evidências próprias" + ] + }, + { + "id": "purchasing", + "label": "Compras", + "features": [ + "Fornecedores e revisão distinta", + "Requisição até adjudicação e ordem", + "Recepção, documento e stock único", + "Factura conferida e obrigação reconhecida", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Purchasing/ReceiptRuntime.php", + "present": true, + "sha256": "b08af1f8772cbfafa1f85e37c832f2c6916ccabcfbd4a8bb98c5ef7690c3f07f" + }, + { + "file": "app/Modules/Purchasing/RequestController.php", + "present": true, + "sha256": "c5c2b78866be7f0e1c8ba09c1af538cefcfbafbea81197833b50821bba0fcbca" + }, + { + "file": "app/Modules/Purchasing/InvoiceController.php", + "present": true, + "sha256": "85eb46ca7e65c60bed190e592a134eb48db5762c0a90b8cd4c9d6fbd81198ba4" + }, + { + "file": "app/Modules/Purchasing/ObligationController.php", + "present": true, + "sha256": "347e629306d6c1f4873afac547d13fae8ade7b268906d1312a64e7eb6998e701" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-final-20261104-r2.json", + "present": true, + "sha256": "3b63f8653e8dbb089dfdd8f389e271c022a070d5a67a9f9d4585ce84438070cc", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ], + "accepted_component_journey": true + }, + { + "id": "stock", + "label": "Stock", + "features": [ + "Artigos, armazéns e saldos físicos", + "Entradas, saídas e origem autorizada", + "Transferência e conferência de destino", + "Contagens, ajustamentos e devoluções", + "Movimentos e relatórios por período", + "Política QA revista e devolução ao fornecedor por origem exacta; saldo8UN/160MZN no mesmo diário", + "Inactividade de pares com história conhecida, corte UTC explícito, filtros e SQL25/100 sem inferir ausência de actividade", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Cadastro de artigos: preparação/revisão distintas, criação sem saldo, designação revista só dos novos, código/unidade imutáveis, histórico/recibos e catálogo canónico único" + ], + "sources": [ + { + "file": "app/Modules/Inventory/StockReadController.php", + "present": true, + "sha256": "fd02da1a69f76bf1412f86f43095a0a476de49fbb7d2676a51fe3b29ec66e298" + }, + { + "file": "app/Modules/Inventory/CompositionStockRead.php", + "present": true, + "sha256": "81bdb5fb374a60974bbad421c461cd12079951fb426361200692a838e413b49e" + }, + { + "file": "app/Modules/Inventory/StockPeriodReport.php", + "present": true, + "sha256": "d491541c6c60bc0c661dc442b8db8ce2e757f1d63dfc4982a3631f820cfb7a06" + }, + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "app/Modules/Inventory/StockOperationsController.php", + "present": true, + "sha256": "34d6c7be4787f4f18ca2a7aa322f6f33dfb281e22336599234c93c655ab4eebf" + }, + { + "file": "resources/views/stock-operations-workspace.blade.php", + "present": true, + "sha256": "5db67c2d23e5557548c38c82a0d7f6e373651849727b2237dcd73f620309c25a" + }, + { + "file": "public/stock-operations-workspace.mjs", + "present": true, + "sha256": "56734499e8dd89da293c2edadd0d0e10ac6d06b39f0365e5ae29a8d14e9176d8" + }, + { + "file": "app/Modules/Inventory/StockFinalController.php", + "present": true, + "sha256": "9d9a242fd8e143fe076ece0454d0789eabf543e37b332db4806d905fad13cdda" + }, + { + "file": "resources/views/stock-final-workspace.blade.php", + "present": true, + "sha256": "93764471a215735a65e5c07121989b1e2f6499f631a11b9f1177bb0fe8def232" + }, + { + "file": "public/sgf-stock-final.mjs", + "present": true, + "sha256": "77860b080015528fe4dd84b2e71787ee668da9be17653c64a5d8e64539b5e592" + }, + { + "file": "app/Modules/Inventory/StockInactiveReport.php", + "present": true, + "sha256": "7db06225fb7bb40c9a4140383caef6dd96c430e3a3e1be8ef7fed2253f2681e2" + }, + { + "file": "resources/js/StockInactiveReportWorkspace.jsx", + "present": true, + "sha256": "5d4f7222bf325550cebddbd00198515da55f15f52b4c99ecaef19f45820a165e" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Inventory/StockCatalogueController.php", + "present": true, + "sha256": "ab0a6bc52e635d5bcdee9163b68b1ec6fc14b717de7b887fcc832336961fa128" + }, + { + "file": "resources/views/stock-catalogue-workspace.blade.php", + "present": true, + "sha256": "4381f81a9aea04fe851f8c7af17cafb98f6bef7cbf52ac2630b0b9912a3dca57" + }, + { + "file": "public/sgf-stock-catalogue.mjs", + "present": true, + "sha256": "10eedcf1881ad3f1cb71813f0547eae7246795273457cc33b1a2a6761a0a2d6e" + }, + { + "file": "config/stock_catalogue.php", + "present": true, + "sha256": "811f844cecf76bff62da943042a272301cb8a7f7a586c4f2648ef5b8e5e31dae" + }, + { + "file": "database/sql/stock_catalogue.sql", + "present": true, + "sha256": "c9c99cec20e6ca60cf0ee56210eac9a2365a2ef9b482698929a70295d2893809" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/stock-period-report-20261103.json", + "present": true, + "sha256": "0ab25af6ffbff7d8ecb651df833771b082bf92d52f97eca4278fcac3fd5ea53b", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-stock-operations-20261110.json", + "present": true, + "sha256": "1d241fcfb81fcf6c1b374f9fc89c1f2130b17eee1314f06b4b8e4b59c476fc08", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_POLICIES_SUPPLIER_RETURN.md", + "present": true, + "sha256": "b8848090f1e19a6b7c19b684e07c0c95cafcf93bd1551357f6afc314f4413d8b", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_INACTIVITY_DELIVERY.md", + "present": true, + "sha256": "8773d53140b291c01358b8e71c584373cd0e440f67d0107e64ecea13980990d3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_CATALOGUE_DELIVERY.md", + "present": true, + "sha256": "43ce839f882aeeab48b272d5eb1a4e34fcd855052db88643a8ca7b08556631f8", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual dos novos relatórios e aceitação explícita do conjunto; carga sustentada e implantação separadas" + ] + }, + { + "id": "workshop", + "label": "Oficinas", + "features": [ + "Ordem e autorização", + "Apontamentos e revisão distinta", + "Material consumido ligado ao movimento físico", + "Custo directo/industrial e correcção", + "Continuação comercial por documento de origem", + "Agenda sem sobreposição, reagendamento e cancelamento versionados", + "Cadastro independente e reutilização de equipamento por cliente", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Ordens SQL25/100 por cliente/equipamento/responsável/estado, apontamentos actuais e minutos verificados, sem mutex de escrita na leitura", + "Relatório por período de abertura explícito/dia local, SQL25/100 antes de paginar, apontamentos actuais e CSV da página sem fecho histórico presumido", + "Equipamento escolhido → ordens exactas SQL25/100 com filtros, identificação actual, CSV da página e regresso ao catálogo" + ], + "sources": [ + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "resources/js/WorkshopWorkspace.jsx", + "present": true, + "sha256": "a1ceca33c234a759b2abf13b087bba8d18d70187ad9161e59425a5932771f8bb" + }, + { + "file": "resources/js/DirectCostsWorkspace.jsx", + "present": true, + "sha256": "dcaf72fe36d80e16bab8e962f939168650336d1450fc3eb01693acb7ef7e0743" + }, + { + "file": "resources/js/IndustrialCostsWorkspace.jsx", + "present": true, + "sha256": "51a3307fa8b2526efa12d8a16fee613bdfe141d02bd6e5af4535d581ed69c6d0" + }, + { + "file": "resources/js/WorkshopBillingWorkspace.jsx", + "present": true, + "sha256": "601853bd383ef2d1891158e89578bbc7f26ae1c7220bc11ddfadf1868cb63afc" + }, + { + "file": "app/Modules/Workshop/ComposedWorkshopStockBook.php", + "present": true, + "sha256": "01e7392827a6453a357a0b5140905757bbee76f57fae6aa9906490788690e24c" + }, + { + "file": "app/Modules/Workshop/WorkshopBilling.php", + "present": true, + "sha256": "0eac2d91fd9f006aa8c198e3652731142aee478c93de090c408df54cdd9d292d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Workshop/WorkshopOrderRead.php", + "present": true, + "sha256": "f741a5ac6c16376ea26b1bacd7b5e706b14a4548a26f59c4a96ec882fb6a6fa5" + }, + { + "file": "app/Modules/Workshop/Http/WorkshopOrderReadController.php", + "present": true, + "sha256": "f4dd027d01c0730603f3b312c2eb263740f6ebe1c969c762ed99d19520fab584" + }, + { + "file": "resources/js/WorkshopOrderList.jsx", + "present": true, + "sha256": "4cad148295acb7c462425fa300dccc7c203255f5f616163575e205bfc8a80e34" + }, + { + "file": "resources/js/workshop-report-view.js", + "present": true, + "sha256": "28125af02e25b4befb9914c5dee753dae4164100f3c0f94988c332a50021bf9f" + }, + { + "file": "app/Modules/Workshop/WorkshopEquipment.php", + "present": true, + "sha256": "37223b732865f273831d0bf61a464137cc53526a4c55750347450ba20bcbbc37" + }, + { + "file": "resources/js/WorkshopEquipmentWorkspace.jsx", + "present": true, + "sha256": "450af001d385f42cd4d0ae42b34fa6b373cdd6811d94f8f4a1f5de2c591ed843" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/workshop-stock-closure-20261102.json", + "present": true, + "sha256": "261d5502f6905d53ef8046e0e900c9bee159c8c9d7e1bae4fddd8ec698313bf7", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_AGENDA_EQUIPMENT.md", + "present": true, + "sha256": "3a44a1b6ac5148013c815cc6cc8919f08c15ac911f900aac5b4a0af903589481", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_PERIOD_REPORT_DELIVERY.md", + "present": true, + "sha256": "1455e3dc4203c81f49ddaba05a7bf7de10cfd9b3bb4dbd2eb4480aa05b7ccf08", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_EQUIPMENT_ORDERS_DELIVERY.md", + "present": true, + "sha256": "4d4249613a1b3fc339f342c3854eda08bf1ec280c0681eb3b2551469b2da2b36", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ] + }, + { + "id": "finance", + "label": "Financeiro", + "features": [ + "Obrigações a fornecedores", + "Recebíveis de clientes", + "Liquidação/afectação e reversão sintéticas", + "Posição financeira e comprovativos por origem", + "Caixa QA explícita0+1000-50=950, revisão distinta, contagem e fecho sem reabertura", + "Extracto paginado, recuperação de confirmação e classificação/despesa no mesmo diário", + "Extracto artificial QA e correspondências individuais com duas linhas nativas existentes, revisão distinta, conferência final e relatório", + "Critérios e indicadores QA950/100=9,5 vezes e50/1000=5%, revisão distinta, fontes conciliadas/retrato/fracção/histórico/relatório", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Posições actuais de clientes por documento, pesquisa/estado/vencimento, SQL25/100, saldos reconhecidos e CSV da página; referência de vencimento distinta de saldo histórico", + "Posições de fornecedores por factura/fornecedor e estado documental, SQL25/100, saldos do motor original, CSV da página e abertura exacta do documento; vencimento desconhecido sem atraso presumido", + "Condições administrativas QA de vencimento: preparação13/revisão14, evidência/fundamento, prévia/revalidação de origem/saldo/versão/pessoa, recusa/correcção, histórico25/100, recibo e recuperação; lista/CSV separam original e condição revista, referência explícita no detalhe", + "Mapa de antiguidade de fornecedores por referência explícita e condição administrativa revista, faixas0/1–30/31–60/61–90/>90/desconhecido, valores exactos por moeda só da página/CSV, sem alterar saldo ou vencimento original" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceReceivables.php", + "present": true, + "sha256": "79e1669b2423c521c0c56ab6fb07e085daef88eff8a41748c424e4a827f1e453" + }, + { + "file": "app/Modules/Purchasing/SupplierPayables.php", + "present": true, + "sha256": "c17d44391cbd96941aa2dd2eac2cb167508faf6453c6a1e86333846df1ba18b9" + }, + { + "file": "resources/js/ServiceReceivablesWorkspace.jsx", + "present": true, + "sha256": "6e654a03cb47e52dedd52ce6b2abf89a61d22860e4ffae92fd393027a54cc445" + }, + { + "file": "app/Modules/Purchasing/ComposedFinance.php", + "present": true, + "sha256": "ad7e837c7608ab85c59eb169bcdd986b08c5802a0f218926e1abd7568e8a4b16" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "app/Modules/Finance/CashWorkspace.php", + "present": true, + "sha256": "bb65060461cea3a844cf523733644cdd630f35ba02c82aeed961821ec5970800" + }, + { + "file": "app/Modules/Finance/Http/CashController.php", + "present": true, + "sha256": "982c161f3ef7314a9671f848f0da2aefb27841e40003ef47102d0d956c08cd78" + }, + { + "file": "app/Modules/Accounting/CashLedgerSource.php", + "present": true, + "sha256": "3a46162f0a62363dd4015f0047a5bff95e8aebd8dd3007fc705daaf58c10684b" + }, + { + "file": "app/Modules/Finance/BankReconciliation.php", + "present": true, + "sha256": "5421141bcc6e15bda142687661774238c7203c85d0ef30696fd4d56dd5b9e328" + }, + { + "file": "app/Modules/Finance/BankLedgerSource.php", + "present": true, + "sha256": "f33779fa736aea7027d4cf449ba85c5982bfb565c75bbb2a477d7a2666820338" + }, + { + "file": "app/Modules/Finance/Http/BankReconciliationController.php", + "present": true, + "sha256": "896d710db108364263a1404d2aafdc52eff4d3b6efcbe3dbdbcca1978befffe4" + }, + { + "file": "public/sgf-bank.mjs", + "present": true, + "sha256": "959214ad94bb5c29ffdaf1c8fd3e59bf8718837d4c1fe9bea52d3b04c261a80b" + }, + { + "file": "app/Modules/Finance/FinancialIndicators.php", + "present": true, + "sha256": "9785a801c5fb13b34770bdd05c828f5e924058eb872a7b33bc1db139a3024d10" + }, + { + "file": "app/Modules/Finance/FinancialIndicatorSource.php", + "present": true, + "sha256": "72039264cbb25ae2eaa5eaa4af488aabf7061a4a298c09490b8957b4ade55731" + }, + { + "file": "app/Modules/Finance/Http/FinancialIndicatorsController.php", + "present": true, + "sha256": "5245064e40b884e432afa2fd26d45fddccebf6963ef3366f585b93ee78f12cca" + }, + { + "file": "public/sgf-indicators.mjs", + "present": true, + "sha256": "62442edc14da534e0166cc65074389e74cc67246cd5922994628644fd900cbb9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Services/ReceivablePositionRead.php", + "present": true, + "sha256": "6aaf0211c4427643c84152edc6a7937dbf48387e2844d3bbe31328d1f6789f2d" + }, + { + "file": "app/Modules/Services/Http/ReceivablePositionReadController.php", + "present": true, + "sha256": "9e8be7c2cd328aebb31cbe0be4eeaaa6abc02d5bff0895aeb90c24ab8d134541" + }, + { + "file": "resources/js/ReceivablePositionsWorkspace.jsx", + "present": true, + "sha256": "5bd8ee952aeee87d733ab434cfe50b675d84c0341ade4f354c93e4acfdbe57e9" + }, + { + "file": "resources/js/receivable-position-view.js", + "present": true, + "sha256": "08f8cb6d17b79b89ee8cebe4723458c1a4979d8fb824afa0b585ae51076e6f21" + }, + { + "file": "app/Modules/Purchasing/PayablePositionReadController.php", + "present": true, + "sha256": "1d147f14826793f97605ea442c6a93612a55caf265311a86824611650dfc0005" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "resources/js/PayablePositionsWorkspace.jsx", + "present": true, + "sha256": "e52fa90276e6251b38bb48f83a11ec2b11ac819ffa3c2239da9712e50ce66343" + }, + { + "file": "resources/js/payable-position-view.js", + "present": true, + "sha256": "ff4b32f404cd575ddc134cf373ae6ded5d0abae8641b14b591c0f5a856437938" + }, + { + "file": "app/Modules/Purchasing/PayableDueController.php", + "present": true, + "sha256": "39e4c95b910d3b1c4aa227c7c2aa0ee24399921b9578748b95ff5aa519f34db7" + }, + { + "file": "resources/js/PayableDueWorkspace.jsx", + "present": true, + "sha256": "c7127bbb2edc6c5acce57a55a6c3c5b94969170ccf1adb7236fee80d45f555ae" + }, + { + "file": "database/sql/payable_due.sql", + "present": true, + "sha256": "faefce9f2c11da613293e23bbc2f630bdae345d872cca7f3c2923c77fea0f75e" + }, + { + "file": "config/payable_due.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Purchasing/PayableAge.php", + "present": true, + "sha256": "19f4a3994b679b74275e72e906251c8d32ee9950ebedfae07bd4002abe740a9e" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/supplier-settlements.json", + "present": true, + "sha256": "b1bce9218f99ea2d8c780022ad385e169c7acc5778ac6950236eef0630f77ad0", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_CASH_DELIVERY.md", + "present": true, + "sha256": "bfb40ff8849eb4d9fcb641aa9b8d07bbaab89326a0f89b8f113cf0bf62653093", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BANK_RECONCILIATION_DELIVERY.md", + "present": true, + "sha256": "acf53f7dfd2ec0f501cef8b1623e090c1e1b8a07a8591f4f042fb8a0b7b57273", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_INDICATORS_DELIVERY.md", + "present": true, + "sha256": "9902e37504dd9f5e1d3c73cd78a283e29be00ffb33ce13ca8f76577d1163cc65", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_RECEIVABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "7a829a6dcae28fa42e996874b7b65ed1de325dbab5bae12a28731ecfc2e849d7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "2c0a25051540527e87ca9abfa43db5efb288e0b40e2315e06f6084693ce9c8c3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_DUE_DELIVERY.md", + "present": true, + "sha256": "614ca3b1c432760e1911112951f138cf72d5d8370173336472529e9549e965b8", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_AGING_DELIVERY.md", + "present": true, + "sha256": "a0af96108702bbb687a655de744e78e01a3e7c98365244410eb291fcb7a5fafa", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Conferência de extracto QA entregue; reconciliação bancária geral/real permanece fora do recorte" + ] + }, + { + "id": "accounting", + "label": "Contabilidade", + "features": [ + "Plano e classificação", + "Diário com preparação/revisão distintas", + "Razão e balancete", + "Estorno/correcção e conferência de período", + "Fecho sintético com rastreabilidade de origens", + "Livro de saídas e mapa interno IVA QA da origem FR seleccionada, revisão distinta, histórico e CSV congelado", + "Catálogo de fontes/regras versionadas QA, revisão distinta, vigência/conflitos/retirada e cálculo explicável com história preservada", + "Cálculo QA por1–25linhas sem/com imposto incluído, representação por linha aprovada, exactos/diferenças e mapa interno recuperável", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Submenus distintos e balancete/razão por datas explícitas, escolha de conta por linha, páginas25/100, detecção de mudança de livro e CSV da página", + "Controlo administrativo QA de obrigação/prazo/responsável do mapa interno revisto, preparação13/revisão14, correcção/cancelamento, entrega apenas simulada com SHA verificado, histórico/recibo/SQL25/100/CSV; sem calendário legal ou submissão", + "Correspondências explícitas revistas15/16 e balanço/resultados internos parciais QA:19contas/nove pendentes, resultado congelado19linhas/SHA, história/recibo/CSV contextual, ligação exacta ao razão; capital e resultado completo não presumidos" + ], + "sources": [ + { + "file": "app/Modules/Accounting/GeneralLedger.php", + "present": true, + "sha256": "a87d2ea373bc61bb139aa29f2a354b9813bbb2d6229a389f8d37fd8e9890edec" + }, + { + "file": "app/Modules/Accounting/GeneralLedgerController.php", + "present": true, + "sha256": "afd2f4ae55e694f0aff18233a6f5cf52444e185aaf5a159865f74f0243783f2a" + }, + { + "file": "packages/general-ledger/src/LedgerSchema.php", + "present": true, + "sha256": "d9c203286b3a04121c265dffe412d2e86726ec4bb9222a2ed654d0296d86b5d8" + }, + { + "file": "app/Modules/Accounting/ComposedLedger.php", + "present": true, + "sha256": "2e4dfa133831c78514e1010d373db905b892c315c455ba53b997edd514cac035" + }, + { + "file": "app/Modules/Accounting/ComposedLedgerController.php", + "present": true, + "sha256": "b96749981b0d4daa7861eab62c5de60c96c5ffa9c72183f88bb89e2d4f7bdf2b" + }, + { + "file": "packages/general-ledger/src/StockTransitEvidence.php", + "present": true, + "sha256": "eb8eee3bb293e77689eb6470ac8aa12049b2ff8cfb8694d3e15eb17e097d2da6" + }, + { + "file": "app/Modules/Services/FiscalCoverage.php", + "present": true, + "sha256": "10a6eb21d40134a36e818c17d12966c57fca60b415ef98bb49e3345eea2dca56" + }, + { + "file": "app/Modules/Services/FiscalAssessment.php", + "present": true, + "sha256": "c2dbe192e6bba75f6fc4057bbb51c85bcf024a674cfeb7950ba239e700213128" + }, + { + "file": "app/Modules/Services/Http/FiscalAssessmentController.php", + "present": true, + "sha256": "425278110af20393e71de446f9b0ce41408352216249e5055de4165b075631e4" + }, + { + "file": "public/sgf-iva.mjs", + "present": true, + "sha256": "1d8c2a07f606799257c090ab2ddb52c309553a15009d6fb457dbbf4770c51ee6" + }, + { + "file": "app/Modules/Services/FiscalRules.php", + "present": true, + "sha256": "520edda0a013d41a80e3e3b56a1b39597a4f1b6407d78118f8855277ffeb3548" + }, + { + "file": "app/Modules/Services/FiscalRuleExpression.php", + "present": true, + "sha256": "108d8401c9c2bc85b3e5f663322c3d36c267afd8529b370e7e16c1d565a3daef" + }, + { + "file": "app/Modules/Services/Http/FiscalRulesController.php", + "present": true, + "sha256": "cc7e05e18c4bbc98a70a19eabdd653bbb44a717214f8d8ee19cd2ffeb1c032c9" + }, + { + "file": "public/sgf-fiscal-rules.mjs", + "present": true, + "sha256": "5c865e018c5b7d3b3f1232b80e81da379c729ea816a9e08a73225e76a678502c" + }, + { + "file": "app/Modules/Services/FiscalDocumentMath.php", + "present": true, + "sha256": "6e67b7b1363d24b38ca0dd40c4349dba82a4b746965f0af8aa7d65bb4f8f731d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "resources/js/ComposedAccountingWorkspace.jsx", + "present": true, + "sha256": "9288df122be42e436bb4aeffc3e84cc1687ba82ab4cab07754d27d507d0012ac" + }, + { + "file": "resources/js/LedgerReportsWorkspace.jsx", + "present": true, + "sha256": "312157b80599e0f4407520ec968460af9c31913f79365ef6efa9a243c98ba419" + }, + { + "file": "resources/js/ledger-report-view.js", + "present": true, + "sha256": "54a1eb09ab4b451b4aa34d8b0c1ebc3d8cea91da4f242ea25a0c4b3bb1db79b4" + }, + { + "file": "app/Modules/Services/FiscalObligations.php", + "present": true, + "sha256": "bcf24717cf5828fed440b8e86f29a5ed4d325d392000240b224d155954170d1d" + }, + { + "file": "app/Modules/Services/Http/FiscalObligationsController.php", + "present": true, + "sha256": "95b03b505e588333206766b862d0f0cff006b1af1a3dc0466370ac6a7518e097" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "resources/js/fiscal-obligations-view.js", + "present": true, + "sha256": "d3c1aafddac987de0a8e432746533da8832c140474f9d9d49055020f7c9e67dd" + }, + { + "file": "database/sql/fiscal_obligations.sql", + "present": true, + "sha256": "c68988342ca1ed5817485355844c2f502f5c2ca12954461c1555d5c5f2abf7fe" + }, + { + "file": "config/fiscal_obligations.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Accounting/InternalStatementsController.php", + "present": true, + "sha256": "b05990c7fdb195957808b63a77599eaaabed5c5eb435dd8e7dd13d662c22fd8d" + }, + { + "file": "app/Modules/Accounting/InternalStatementTotals.php", + "present": true, + "sha256": "f13f6f356100e49786869196c54343fd629f6a1d38fa81665eb768dae4731601" + }, + { + "file": "resources/js/InternalStatementsWorkspace.jsx", + "present": true, + "sha256": "2fadac08cd6162dd06d056bfc4fb00479e9a5d930fdde10c1a4b57507982e76e" + }, + { + "file": "resources/js/internal-statements-view.js", + "present": true, + "sha256": "f1f7b44f2dc1679e537d77522e0fc5ee84da23068ef3157677bc20a8409d3464" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "2dcbea8a6523970211024dbac28743ce4a128b98808fd6f0d39b73cbf48457ff" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "database/sql/internal_statements.sql", + "present": true, + "sha256": "2beccb0bb7d91f48e5a5e386c1e010fb7d95cbfa90aa35a68e28dc63ae6a6f6b" + }, + { + "file": "config/internal_statements.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-matrix-final-20261104.json", + "present": true, + "sha256": "94cb37698f294c38df9b94a5535ba4b9ac63907f94dab1e7f951a3bb27623083", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_COVERAGE_DELIVERY.md", + "present": true, + "sha256": "4951a2514253c71710fd18ddbffcec1c4dca597355b1cf998b1c7f1b38078506", + "not_reexecuted": true + }, + { + "file": "docs/SGF_IVA_ASSESSMENT_DELIVERY.md", + "present": true, + "sha256": "b0a3ae64699352242108d782a229f0250344f238fad02e14e6fa4dbb520e997f", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_RULES_COMMON_DELIVERY.md", + "present": true, + "sha256": "10f8801d59dbdeb3f075e5bf2650e4538097a92151db516e3c0357ac011c8a5a", + "not_reexecuted": true + }, + { + "file": "docs/SGF_MULTI_LINE_INCLUDED_DELIVERY.md", + "present": true, + "sha256": "7b1d6bbdea15845cf7f4fe104057a4053be1c0f0edb2186da26d11dded45b690", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_ACCOUNTING_REPORTS_DELIVERY.md", + "present": true, + "sha256": "f910d6d11fd7f4e18a675fea9f07a50137bbed72d4718bb7ec6e022bdd1959e5", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_OBLIGATIONS_DELIVERY.md", + "present": true, + "sha256": "028e3172128435924dc9594b68b25560cb0b30495f27c522864a94a1f8cc9504", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_STATEMENTS_DELIVERY.md", + "present": true, + "sha256": "53a07e2c0517de5b6881584c598b2f1d1d5665422ac2f8f5932626822daf3359", + "not_reexecuted": true + }, + { + "file": "docs/SGF_COVERAGE_RECONCILIATION.md", + "present": true, + "sha256": "b1fb3722b5a9444699f713f7c8a6535eceb05977dacb194ce7dace0e1a4afa14", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Backlog fiscal/financeiro amplo pendente; tabela de cobertura não implementa motores ou modelos", + "Activos/depreciações: âmbito integrado QA e extensão do núcleo preparados, por autorizar; não implementados" + ] + }, + { + "id": "human_resources", + "label": "Recursos Humanos", + "features": [ + "Módulo autónomo com perfis QA próprios separados do Financeiro", + "Cadastro pseudónimo individual preparado e revisto", + "Regras artificiais versionadas com vigência e fonte explícitas", + "Processamento exacto por trabalhador/período com escolha de fontes e revisão distinta", + "Correcção/cancelamento versionados, prévia, histórico, recibos e recuperação", + "Mapa individual revisto conservado com SHA e exportação autenticada; filtros SQL25/100", + "Contratos internos QA revistos com correcção/terminação/cancelamento e dependências", + "Plano e assiduidade por dia com intervalos explícitos, dados desconhecidos e revisão distinta", + "Condição de férias QA, reserva/gozo/cancelamento individuais, saldo efectivo sem duplicação", + "Conferência parcial do período por pessoa, fontes/versões e CSV/SHA conservado", + "Ligação contratual explícita opcional em novas preparações salariais, provada na regressão sem alterar salários8085", + "Resumo CSV legível derivado de versão revista, com SHA próprio/original, sem alterar o artefacto conservado" + ], + "sources": [ + { + "file": "app/Modules/HumanResources/Remunerations.php", + "present": true, + "sha256": "950333ced35d6949487bdf78ef109fad608338525acfe60b05b9a320fd3aac01" + }, + { + "file": "app/Modules/HumanResources/Http/RemunerationsController.php", + "present": true, + "sha256": "3725145a623791a6c2825d8aaa4f122ba7b80908c08603211d6d9e74df76e801" + }, + { + "file": "app/Shared/Installation/HumanResourcesProfiles.php", + "present": true, + "sha256": "f908b8cf9f7121bb9b43733f507771a60aa34eb553df8342c250e5b39940ae93" + }, + { + "file": "resources/js/RemunerationsWorkspace.jsx", + "present": true, + "sha256": "edb225504bff359d5c53c040421e322bb4cd887ac946b3316852ecd1ed25b76e" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "2dcbea8a6523970211024dbac28743ce4a128b98808fd6f0d39b73cbf48457ff" + }, + { + "file": "app/Modules/HumanResources/Personnel.php", + "present": true, + "sha256": "da109cf709a6306f7c7a8a3f111514cb1e5c4d1dc0554e15e36022bf1fbb829b" + }, + { + "file": "app/Modules/HumanResources/PersonnelTime.php", + "present": true, + "sha256": "e0fc1bdecbbd24c1ac21a426d8547fc049c00fd18ae6faa7ded64128252bbfd6" + }, + { + "file": "app/Modules/HumanResources/Http/PersonnelController.php", + "present": true, + "sha256": "539fb7ce74dd3b87263d1610fee7ddac2028c4536bc8d549be6d0617c3f947a2" + }, + { + "file": "resources/js/PersonnelWorkspace.jsx", + "present": true, + "sha256": "3bff1b58a1f08c8065abaa68fd7547156f3537f573e0a8d4b922cd3c2cf7ae06" + }, + { + "file": "app/Modules/HumanResources/PersonnelReadableCsv.php", + "present": true, + "sha256": "5e31d7de8aa243b9f9578d859e05ab9071c06dc39bb8616f35eed085856c493c" + } + ], + "existing_evidence": [ + { + "file": "docs/SGF_HUMAN_RESOURCES_DELIVERY.md", + "present": true, + "sha256": "1494d29938565bb6f5d552385d7b32dd0a8f22856755bf638d81cf9a41a65f60", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-human-resources-20261111.json", + "present": true, + "sha256": "95d0c3fcd407cfbb03226288b94570478b906fdf3cb50c26a9960956f536cf29", + "not_reexecuted": true + }, + { + "file": "docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md", + "present": true, + "sha256": "045d111cd08ad58c22a802d6d728c7f300d91b4078b65c3ac61683791747a02e", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-hr-personnel-20261111.json", + "present": true, + "sha256": "b4ce53d6099a13acbe6bd2d7431f9109b8a318000f9d69813353349cd823cdc7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_HUMAN_RESOURCES_READABLE_DELIVERY.md", + "present": true, + "sha256": "18275d4fda727e0082379bcd17804397a13dc3c5f5a88575c48c4e6c8abf95b2", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conformidade e aplicabilidade laboral real: contratos legais, turnos/calendário, férias legais, benefícios e modelos", + "Cálculo legal IRPS/INSS e ligação a dívida/pagamento/Contabilidade sem efeitos implícitos", + "Revisão visual, carga sustentada, armazenamento e capacidade de implantação" + ] + } + ], + "release_gates": [ + { + "id": "single_installation", + "criterion": "Os seis módulos operam no mesmo endereço e empresa/unidade, sem mudar de demonstração", + "passed": false + }, + { + "id": "functional_modules", + "criterion": "Cada funcionalidade obrigatória tem ecrã/comando/consulta executável; menus e fontes isoladas não bastam", + "passed": false + }, + { + "id": "cross_module_flows", + "criterion": "Os documentos atravessam as fronteiras mantendo origem, versão, autoridade e efeito único", + "passed": false + }, + { + "id": "access_isolation", + "criterion": "Perfis explícitos e recusas de empresa/unidade/módulo/permissão verificadas", + "passed": false + }, + { + "id": "presentation", + "criterion": "Percurso de apresentação coerente, dados fictícios identificados, linguagem e navegação revistas", + "passed": false + }, + { + "id": "operations", + "criterion": "Arranque e recuperação da instalação entregues e ensaiados, sem depender do SGC", + "passed": false + }, + { + "id": "commercial_honesty", + "criterion": "Documentos de teste não são emissão fiscal; capacidade de implantação exige prova própria", + "passed": false + }, + { + "id": "preservation", + "criterion": "Seis registos, cache, dados aprovados e trabalho alheio preservados", + "passed": false + } + ], + "client_presentable": false, + "limits": [ + "Host-table footprint is a minimum projection, not a full migration/dependency validation.", + "Native purchasing/stock tables live in their existing separate native book; this scan does not call that missing.", + "Source/evidence presence does not mean a feature is integrated, visually accepted or deployment-ready." + ], + "database_writes": 0, + "application_bootstraps": 0, + "cache_cleared": false, + "new_demo": false, + "new_rights": false +} diff --git a/docs/proofs/sgf-solution-structure-v29-20261111.json b/docs/proofs/sgf-solution-structure-v29-20261111.json new file mode 100644 index 0000000..16afe96 --- /dev/null +++ b/docs/proofs/sgf-solution-structure-v29-20261111.json @@ -0,0 +1,1268 @@ +{ + "date": "2026-11-11", + "contract_sha256": "33cc97d5ba93e4e50446a728aca1cc68f6902dbfc46cf4b6fe82610eec978ced", + "installation": "cc50a276c10c", + "port": 8085, + "sgc_dependency": false, + "full_product_complete": false, + "product_backlog": [ + { + "id": "fiscal_books", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Livro/mapa interno QA de uma origem entregue; IVA geral, dedução/regularizações e restantes tratamentos pendentes" + }, + { + "id": "irps_inss", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "RH autónomo QA com cadastro/contratos/assiduidade/férias e remunerações internas entregue; regimes laborais gerais, IRPS/INSS/modelos legais e ligação financeira/contabilística salarial pendentes" + }, + { + "id": "irpc", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "IRPC operacional: ajustamentos, retenções, apuramento" + }, + { + "id": "ispc_ice", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Motores operacionais ISPC e ICE" + }, + { + "id": "official_forms", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Modelos oficiais com correspondências e reconciliação de campos" + }, + { + "id": "fiscal_calendar", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Controlo administrativo de prazo e entrega simulada de uma origem QA entregue; calendário legal, aplicabilidade geral, prazos por fonte/vigência e submissão continuam pendentes" + }, + { + "id": "bank_reconciliation", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Conferência de duas linhas/extracto artificial QA entregue; integração e reconciliação bancária gerais/reais pendentes" + }, + { + "id": "financial_analysis", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Dois critérios/indicadores QA com fontes conciliadas entregues; rácios gerais, critérios reais e tendências/séries permanecem pendentes" + }, + { + "id": "fixed_assets", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md FIS-SGF-15", + "description": "Ciclo integrado QA entregue na8085: cadastro/plano/quota/diário/relatórios e ramos de correcção/abate/fecho do recorte na regressão. Operação geral/legal de activos e taxas fiscais continuam pendentes.", + "scope": "docs/SGF_FIXED_ASSETS_SCOPE.md", + "access_proposal": "docs/integration/sgf-fixed-assets-access-proposal.json", + "qa_implemented": true, + "delivery": "docs/SGF_FIXED_ASSETS_DELIVERY.md" + } + ], + "modules": [ + { + "id": "commercial", + "label": "Comercial", + "features": [ + "Clientes e serviços com associação explícita", + "Rascunho, revisão e cálculo", + "Documento emitido de teste e PDF", + "Continuação para recebível sem duplicação", + "Orçamentos Comercial/Oficina, revisão e aceitação documentadas e conversão explícita em rascunho", + "Recibo de afectação efectiva400, crédito nominal100/saldo500 e factura-recibo1000 atómica", + "PDF/SHA, histórico e anulação técnica com reversão explícita e diário único", + "Registo unificado autorizado com seis famílias, filtros/SQL25/100 e projecções limitadas à página", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceWorkflow.php", + "present": true, + "sha256": "6ea92e6216bec9a0062d9f152d1b758a5527360456589a98f525429402cb94da" + }, + { + "file": "app/Modules/Services/ServiceInvoicing.php", + "present": true, + "sha256": "4138afb7e001b546a65552d71223cd642154945b8021e1ff45ef60a34076e3c5" + }, + { + "file": "resources/js/ServicesWorkspace.jsx", + "present": true, + "sha256": "c170b195ccd78aa74f283a91b0b4f4cd93aea4677e3aaefe0b50af46f7e31669" + }, + { + "file": "resources/js/ServiceInvoiceWorkspace.jsx", + "present": true, + "sha256": "8f95d1775b30a6de73d65eed428595d74cc837755d8157bf8db49a1151350277" + }, + { + "file": "app/Modules/Services/ServiceDocuments.php", + "present": true, + "sha256": "816a85be48b05043f41f9c7d5f842a3fa139b21aaedf9c944d811f74be6f6208" + }, + { + "file": "app/Modules/Services/DocumentPeriod.php", + "present": true, + "sha256": "100c6cf038b925126ddc8fb50adde2f6d938b4d68df6d0ab193f16ea28836ae2" + }, + { + "file": "app/Modules/Services/BillingRegister.php", + "present": true, + "sha256": "115e7832b63e5c29be716140629845137d951da7790421832e1ab4058fb468cc" + }, + { + "file": "resources/js/BillingRegisterWorkspace.jsx", + "present": true, + "sha256": "9ab8d036c9e6d20c588427f5a8c44677810e9713722baea908b8f6b76c8b79f9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/invoice-test-results.json", + "present": true, + "sha256": "fd04a58727ffddf1c62ac184d7834b81228dce2abb5b0c1e397505df6a45237c", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_SERVICE_DOCUMENTS_DELIVERY.md", + "present": true, + "sha256": "a13713f05feaa84766dfaba870e72c98d23aa6223389d1627de911b0856cd15d", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BILLING_REGISTER_DELIVERY.md", + "present": true, + "sha256": "fec1b76003187d5f38c8aca8804c26129e312e517dd5b2247f54f0019c2cccf6", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual, aceitação explícita e conformidade real; emissão fiscal real permanece bloqueada", + "Carga sustentada, armazenamento e capacidade de implantação exigem evidências próprias" + ] + }, + { + "id": "purchasing", + "label": "Compras", + "features": [ + "Fornecedores e revisão distinta", + "Requisição até adjudicação e ordem", + "Recepção, documento e stock único", + "Factura conferida e obrigação reconhecida", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Purchasing/ReceiptRuntime.php", + "present": true, + "sha256": "b08af1f8772cbfafa1f85e37c832f2c6916ccabcfbd4a8bb98c5ef7690c3f07f" + }, + { + "file": "app/Modules/Purchasing/RequestController.php", + "present": true, + "sha256": "c5c2b78866be7f0e1c8ba09c1af538cefcfbafbea81197833b50821bba0fcbca" + }, + { + "file": "app/Modules/Purchasing/InvoiceController.php", + "present": true, + "sha256": "85eb46ca7e65c60bed190e592a134eb48db5762c0a90b8cd4c9d6fbd81198ba4" + }, + { + "file": "app/Modules/Purchasing/ObligationController.php", + "present": true, + "sha256": "347e629306d6c1f4873afac547d13fae8ade7b268906d1312a64e7eb6998e701" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-final-20261104-r2.json", + "present": true, + "sha256": "3b63f8653e8dbb089dfdd8f389e271c022a070d5a67a9f9d4585ce84438070cc", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ], + "accepted_component_journey": true + }, + { + "id": "stock", + "label": "Stock", + "features": [ + "Artigos, armazéns e saldos físicos", + "Entradas, saídas e origem autorizada", + "Transferência e conferência de destino", + "Contagens, ajustamentos e devoluções", + "Movimentos e relatórios por período", + "Política QA revista e devolução ao fornecedor por origem exacta; saldo8UN/160MZN no mesmo diário", + "Inactividade de pares com história conhecida, corte UTC explícito, filtros e SQL25/100 sem inferir ausência de actividade", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Cadastro de artigos: preparação/revisão distintas, criação sem saldo, designação revista só dos novos, código/unidade imutáveis, histórico/recibos e catálogo canónico único" + ], + "sources": [ + { + "file": "app/Modules/Inventory/StockReadController.php", + "present": true, + "sha256": "fd02da1a69f76bf1412f86f43095a0a476de49fbb7d2676a51fe3b29ec66e298" + }, + { + "file": "app/Modules/Inventory/CompositionStockRead.php", + "present": true, + "sha256": "81bdb5fb374a60974bbad421c461cd12079951fb426361200692a838e413b49e" + }, + { + "file": "app/Modules/Inventory/StockPeriodReport.php", + "present": true, + "sha256": "d491541c6c60bc0c661dc442b8db8ce2e757f1d63dfc4982a3631f820cfb7a06" + }, + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "app/Modules/Inventory/StockOperationsController.php", + "present": true, + "sha256": "34d6c7be4787f4f18ca2a7aa322f6f33dfb281e22336599234c93c655ab4eebf" + }, + { + "file": "resources/views/stock-operations-workspace.blade.php", + "present": true, + "sha256": "5db67c2d23e5557548c38c82a0d7f6e373651849727b2237dcd73f620309c25a" + }, + { + "file": "public/stock-operations-workspace.mjs", + "present": true, + "sha256": "56734499e8dd89da293c2edadd0d0e10ac6d06b39f0365e5ae29a8d14e9176d8" + }, + { + "file": "app/Modules/Inventory/StockFinalController.php", + "present": true, + "sha256": "9d9a242fd8e143fe076ece0454d0789eabf543e37b332db4806d905fad13cdda" + }, + { + "file": "resources/views/stock-final-workspace.blade.php", + "present": true, + "sha256": "93764471a215735a65e5c07121989b1e2f6499f631a11b9f1177bb0fe8def232" + }, + { + "file": "public/sgf-stock-final.mjs", + "present": true, + "sha256": "77860b080015528fe4dd84b2e71787ee668da9be17653c64a5d8e64539b5e592" + }, + { + "file": "app/Modules/Inventory/StockInactiveReport.php", + "present": true, + "sha256": "7db06225fb7bb40c9a4140383caef6dd96c430e3a3e1be8ef7fed2253f2681e2" + }, + { + "file": "resources/js/StockInactiveReportWorkspace.jsx", + "present": true, + "sha256": "5d4f7222bf325550cebddbd00198515da55f15f52b4c99ecaef19f45820a165e" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Inventory/StockCatalogueController.php", + "present": true, + "sha256": "ab0a6bc52e635d5bcdee9163b68b1ec6fc14b717de7b887fcc832336961fa128" + }, + { + "file": "resources/views/stock-catalogue-workspace.blade.php", + "present": true, + "sha256": "4381f81a9aea04fe851f8c7af17cafb98f6bef7cbf52ac2630b0b9912a3dca57" + }, + { + "file": "public/sgf-stock-catalogue.mjs", + "present": true, + "sha256": "10eedcf1881ad3f1cb71813f0547eae7246795273457cc33b1a2a6761a0a2d6e" + }, + { + "file": "config/stock_catalogue.php", + "present": true, + "sha256": "811f844cecf76bff62da943042a272301cb8a7f7a586c4f2648ef5b8e5e31dae" + }, + { + "file": "database/sql/stock_catalogue.sql", + "present": true, + "sha256": "c9c99cec20e6ca60cf0ee56210eac9a2365a2ef9b482698929a70295d2893809" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/stock-period-report-20261103.json", + "present": true, + "sha256": "0ab25af6ffbff7d8ecb651df833771b082bf92d52f97eca4278fcac3fd5ea53b", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-stock-operations-20261110.json", + "present": true, + "sha256": "1d241fcfb81fcf6c1b374f9fc89c1f2130b17eee1314f06b4b8e4b59c476fc08", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_POLICIES_SUPPLIER_RETURN.md", + "present": true, + "sha256": "b8848090f1e19a6b7c19b684e07c0c95cafcf93bd1551357f6afc314f4413d8b", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_INACTIVITY_DELIVERY.md", + "present": true, + "sha256": "8773d53140b291c01358b8e71c584373cd0e440f67d0107e64ecea13980990d3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_CATALOGUE_DELIVERY.md", + "present": true, + "sha256": "43ce839f882aeeab48b272d5eb1a4e34fcd855052db88643a8ca7b08556631f8", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual dos novos relatórios e aceitação explícita do conjunto; carga sustentada e implantação separadas" + ] + }, + { + "id": "workshop", + "label": "Oficinas", + "features": [ + "Ordem e autorização", + "Apontamentos e revisão distinta", + "Material consumido ligado ao movimento físico", + "Custo directo/industrial e correcção", + "Continuação comercial por documento de origem", + "Agenda sem sobreposição, reagendamento e cancelamento versionados", + "Cadastro independente e reutilização de equipamento por cliente", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Ordens SQL25/100 por cliente/equipamento/responsável/estado, apontamentos actuais e minutos verificados, sem mutex de escrita na leitura", + "Relatório por período de abertura explícito/dia local, SQL25/100 antes de paginar, apontamentos actuais e CSV da página sem fecho histórico presumido", + "Equipamento escolhido → ordens exactas SQL25/100 com filtros, identificação actual, CSV da página e regresso ao catálogo" + ], + "sources": [ + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "resources/js/WorkshopWorkspace.jsx", + "present": true, + "sha256": "a1ceca33c234a759b2abf13b087bba8d18d70187ad9161e59425a5932771f8bb" + }, + { + "file": "resources/js/DirectCostsWorkspace.jsx", + "present": true, + "sha256": "dcaf72fe36d80e16bab8e962f939168650336d1450fc3eb01693acb7ef7e0743" + }, + { + "file": "resources/js/IndustrialCostsWorkspace.jsx", + "present": true, + "sha256": "51a3307fa8b2526efa12d8a16fee613bdfe141d02bd6e5af4535d581ed69c6d0" + }, + { + "file": "resources/js/WorkshopBillingWorkspace.jsx", + "present": true, + "sha256": "601853bd383ef2d1891158e89578bbc7f26ae1c7220bc11ddfadf1868cb63afc" + }, + { + "file": "app/Modules/Workshop/ComposedWorkshopStockBook.php", + "present": true, + "sha256": "01e7392827a6453a357a0b5140905757bbee76f57fae6aa9906490788690e24c" + }, + { + "file": "app/Modules/Workshop/WorkshopBilling.php", + "present": true, + "sha256": "0eac2d91fd9f006aa8c198e3652731142aee478c93de090c408df54cdd9d292d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Workshop/WorkshopOrderRead.php", + "present": true, + "sha256": "f741a5ac6c16376ea26b1bacd7b5e706b14a4548a26f59c4a96ec882fb6a6fa5" + }, + { + "file": "app/Modules/Workshop/Http/WorkshopOrderReadController.php", + "present": true, + "sha256": "f4dd027d01c0730603f3b312c2eb263740f6ebe1c969c762ed99d19520fab584" + }, + { + "file": "resources/js/WorkshopOrderList.jsx", + "present": true, + "sha256": "4cad148295acb7c462425fa300dccc7c203255f5f616163575e205bfc8a80e34" + }, + { + "file": "resources/js/workshop-report-view.js", + "present": true, + "sha256": "28125af02e25b4befb9914c5dee753dae4164100f3c0f94988c332a50021bf9f" + }, + { + "file": "app/Modules/Workshop/WorkshopEquipment.php", + "present": true, + "sha256": "37223b732865f273831d0bf61a464137cc53526a4c55750347450ba20bcbbc37" + }, + { + "file": "resources/js/WorkshopEquipmentWorkspace.jsx", + "present": true, + "sha256": "450af001d385f42cd4d0ae42b34fa6b373cdd6811d94f8f4a1f5de2c591ed843" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/workshop-stock-closure-20261102.json", + "present": true, + "sha256": "261d5502f6905d53ef8046e0e900c9bee159c8c9d7e1bae4fddd8ec698313bf7", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_AGENDA_EQUIPMENT.md", + "present": true, + "sha256": "3a44a1b6ac5148013c815cc6cc8919f08c15ac911f900aac5b4a0af903589481", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_PERIOD_REPORT_DELIVERY.md", + "present": true, + "sha256": "1455e3dc4203c81f49ddaba05a7bf7de10cfd9b3bb4dbd2eb4480aa05b7ccf08", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_EQUIPMENT_ORDERS_DELIVERY.md", + "present": true, + "sha256": "4d4249613a1b3fc339f342c3854eda08bf1ec280c0681eb3b2551469b2da2b36", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ] + }, + { + "id": "finance", + "label": "Financeiro", + "features": [ + "Obrigações a fornecedores", + "Recebíveis de clientes", + "Liquidação/afectação e reversão sintéticas", + "Posição financeira e comprovativos por origem", + "Caixa QA explícita0+1000-50=950, revisão distinta, contagem e fecho sem reabertura", + "Extracto paginado, recuperação de confirmação e classificação/despesa no mesmo diário", + "Extracto artificial QA e correspondências individuais com duas linhas nativas existentes, revisão distinta, conferência final e relatório", + "Critérios e indicadores QA950/100=9,5 vezes e50/1000=5%, revisão distinta, fontes conciliadas/retrato/fracção/histórico/relatório", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Posições actuais de clientes por documento, pesquisa/estado/vencimento, SQL25/100, saldos reconhecidos e CSV da página; referência de vencimento distinta de saldo histórico", + "Posições de fornecedores por factura/fornecedor e estado documental, SQL25/100, saldos do motor original, CSV da página e abertura exacta do documento; vencimento desconhecido sem atraso presumido", + "Condições administrativas QA de vencimento: preparação13/revisão14, evidência/fundamento, prévia/revalidação de origem/saldo/versão/pessoa, recusa/correcção, histórico25/100, recibo e recuperação; lista/CSV separam original e condição revista, referência explícita no detalhe", + "Mapa de antiguidade de fornecedores por referência explícita e condição administrativa revista, faixas0/1–30/31–60/61–90/>90/desconhecido, valores exactos por moeda só da página/CSV, sem alterar saldo ou vencimento original" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceReceivables.php", + "present": true, + "sha256": "79e1669b2423c521c0c56ab6fb07e085daef88eff8a41748c424e4a827f1e453" + }, + { + "file": "app/Modules/Purchasing/SupplierPayables.php", + "present": true, + "sha256": "c17d44391cbd96941aa2dd2eac2cb167508faf6453c6a1e86333846df1ba18b9" + }, + { + "file": "resources/js/ServiceReceivablesWorkspace.jsx", + "present": true, + "sha256": "6e654a03cb47e52dedd52ce6b2abf89a61d22860e4ffae92fd393027a54cc445" + }, + { + "file": "app/Modules/Purchasing/ComposedFinance.php", + "present": true, + "sha256": "ad7e837c7608ab85c59eb169bcdd986b08c5802a0f218926e1abd7568e8a4b16" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "app/Modules/Finance/CashWorkspace.php", + "present": true, + "sha256": "bb65060461cea3a844cf523733644cdd630f35ba02c82aeed961821ec5970800" + }, + { + "file": "app/Modules/Finance/Http/CashController.php", + "present": true, + "sha256": "982c161f3ef7314a9671f848f0da2aefb27841e40003ef47102d0d956c08cd78" + }, + { + "file": "app/Modules/Accounting/CashLedgerSource.php", + "present": true, + "sha256": "3a46162f0a62363dd4015f0047a5bff95e8aebd8dd3007fc705daaf58c10684b" + }, + { + "file": "app/Modules/Finance/BankReconciliation.php", + "present": true, + "sha256": "5421141bcc6e15bda142687661774238c7203c85d0ef30696fd4d56dd5b9e328" + }, + { + "file": "app/Modules/Finance/BankLedgerSource.php", + "present": true, + "sha256": "f33779fa736aea7027d4cf449ba85c5982bfb565c75bbb2a477d7a2666820338" + }, + { + "file": "app/Modules/Finance/Http/BankReconciliationController.php", + "present": true, + "sha256": "896d710db108364263a1404d2aafdc52eff4d3b6efcbe3dbdbcca1978befffe4" + }, + { + "file": "public/sgf-bank.mjs", + "present": true, + "sha256": "959214ad94bb5c29ffdaf1c8fd3e59bf8718837d4c1fe9bea52d3b04c261a80b" + }, + { + "file": "app/Modules/Finance/FinancialIndicators.php", + "present": true, + "sha256": "9785a801c5fb13b34770bdd05c828f5e924058eb872a7b33bc1db139a3024d10" + }, + { + "file": "app/Modules/Finance/FinancialIndicatorSource.php", + "present": true, + "sha256": "72039264cbb25ae2eaa5eaa4af488aabf7061a4a298c09490b8957b4ade55731" + }, + { + "file": "app/Modules/Finance/Http/FinancialIndicatorsController.php", + "present": true, + "sha256": "5245064e40b884e432afa2fd26d45fddccebf6963ef3366f585b93ee78f12cca" + }, + { + "file": "public/sgf-indicators.mjs", + "present": true, + "sha256": "62442edc14da534e0166cc65074389e74cc67246cd5922994628644fd900cbb9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Services/ReceivablePositionRead.php", + "present": true, + "sha256": "6aaf0211c4427643c84152edc6a7937dbf48387e2844d3bbe31328d1f6789f2d" + }, + { + "file": "app/Modules/Services/Http/ReceivablePositionReadController.php", + "present": true, + "sha256": "9e8be7c2cd328aebb31cbe0be4eeaaa6abc02d5bff0895aeb90c24ab8d134541" + }, + { + "file": "resources/js/ReceivablePositionsWorkspace.jsx", + "present": true, + "sha256": "5bd8ee952aeee87d733ab434cfe50b675d84c0341ade4f354c93e4acfdbe57e9" + }, + { + "file": "resources/js/receivable-position-view.js", + "present": true, + "sha256": "08f8cb6d17b79b89ee8cebe4723458c1a4979d8fb824afa0b585ae51076e6f21" + }, + { + "file": "app/Modules/Purchasing/PayablePositionReadController.php", + "present": true, + "sha256": "1d147f14826793f97605ea442c6a93612a55caf265311a86824611650dfc0005" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "resources/js/PayablePositionsWorkspace.jsx", + "present": true, + "sha256": "e52fa90276e6251b38bb48f83a11ec2b11ac819ffa3c2239da9712e50ce66343" + }, + { + "file": "resources/js/payable-position-view.js", + "present": true, + "sha256": "ff4b32f404cd575ddc134cf373ae6ded5d0abae8641b14b591c0f5a856437938" + }, + { + "file": "app/Modules/Purchasing/PayableDueController.php", + "present": true, + "sha256": "39e4c95b910d3b1c4aa227c7c2aa0ee24399921b9578748b95ff5aa519f34db7" + }, + { + "file": "resources/js/PayableDueWorkspace.jsx", + "present": true, + "sha256": "c7127bbb2edc6c5acce57a55a6c3c5b94969170ccf1adb7236fee80d45f555ae" + }, + { + "file": "database/sql/payable_due.sql", + "present": true, + "sha256": "faefce9f2c11da613293e23bbc2f630bdae345d872cca7f3c2923c77fea0f75e" + }, + { + "file": "config/payable_due.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Purchasing/PayableAge.php", + "present": true, + "sha256": "19f4a3994b679b74275e72e906251c8d32ee9950ebedfae07bd4002abe740a9e" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/supplier-settlements.json", + "present": true, + "sha256": "b1bce9218f99ea2d8c780022ad385e169c7acc5778ac6950236eef0630f77ad0", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_CASH_DELIVERY.md", + "present": true, + "sha256": "bfb40ff8849eb4d9fcb641aa9b8d07bbaab89326a0f89b8f113cf0bf62653093", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BANK_RECONCILIATION_DELIVERY.md", + "present": true, + "sha256": "acf53f7dfd2ec0f501cef8b1623e090c1e1b8a07a8591f4f042fb8a0b7b57273", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_INDICATORS_DELIVERY.md", + "present": true, + "sha256": "9902e37504dd9f5e1d3c73cd78a283e29be00ffb33ce13ca8f76577d1163cc65", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_RECEIVABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "7a829a6dcae28fa42e996874b7b65ed1de325dbab5bae12a28731ecfc2e849d7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "2c0a25051540527e87ca9abfa43db5efb288e0b40e2315e06f6084693ce9c8c3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_DUE_DELIVERY.md", + "present": true, + "sha256": "614ca3b1c432760e1911112951f138cf72d5d8370173336472529e9549e965b8", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_AGING_DELIVERY.md", + "present": true, + "sha256": "a0af96108702bbb687a655de744e78e01a3e7c98365244410eb291fcb7a5fafa", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Conferência de extracto QA entregue; reconciliação bancária geral/real permanece fora do recorte" + ] + }, + { + "id": "accounting", + "label": "Contabilidade", + "features": [ + "Plano e classificação", + "Diário com preparação/revisão distintas", + "Razão e balancete", + "Estorno/correcção e conferência de período", + "Fecho sintético com rastreabilidade de origens", + "Livro de saídas e mapa interno IVA QA da origem FR seleccionada, revisão distinta, histórico e CSV congelado", + "Catálogo de fontes/regras versionadas QA, revisão distinta, vigência/conflitos/retirada e cálculo explicável com história preservada", + "Cálculo QA por1–25linhas sem/com imposto incluído, representação por linha aprovada, exactos/diferenças e mapa interno recuperável", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Submenus distintos e balancete/razão por datas explícitas, escolha de conta por linha, páginas25/100, detecção de mudança de livro e CSV da página", + "Controlo administrativo QA de obrigação/prazo/responsável do mapa interno revisto, preparação13/revisão14, correcção/cancelamento, entrega apenas simulada com SHA verificado, histórico/recibo/SQL25/100/CSV; sem calendário legal ou submissão", + "Correspondências explícitas revistas15/16 e balanço/resultados internos parciais QA:19contas/nove pendentes, resultado congelado19linhas/SHA, história/recibo/CSV contextual, ligação exacta ao razão; capital e resultado completo não presumidos", + "Activos QA: cadastro/custódia/plano/quota revistos, diário único1200/100 e líquido1100; correcção/abate/fecho do recorte na regressão" + ], + "sources": [ + { + "file": "app/Modules/Accounting/GeneralLedger.php", + "present": true, + "sha256": "a87d2ea373bc61bb139aa29f2a354b9813bbb2d6229a389f8d37fd8e9890edec" + }, + { + "file": "app/Modules/Accounting/GeneralLedgerController.php", + "present": true, + "sha256": "afd2f4ae55e694f0aff18233a6f5cf52444e185aaf5a159865f74f0243783f2a" + }, + { + "file": "packages/general-ledger/src/LedgerSchema.php", + "present": true, + "sha256": "d9c203286b3a04121c265dffe412d2e86726ec4bb9222a2ed654d0296d86b5d8" + }, + { + "file": "app/Modules/Accounting/ComposedLedger.php", + "present": true, + "sha256": "25e14af7f62dea2f205f9946135fb016580d1ca2446c9303313d76c2ccd99e04" + }, + { + "file": "app/Modules/Accounting/ComposedLedgerController.php", + "present": true, + "sha256": "b96749981b0d4daa7861eab62c5de60c96c5ffa9c72183f88bb89e2d4f7bdf2b" + }, + { + "file": "packages/general-ledger/src/StockTransitEvidence.php", + "present": true, + "sha256": "eb8eee3bb293e77689eb6470ac8aa12049b2ff8cfb8694d3e15eb17e097d2da6" + }, + { + "file": "app/Modules/Services/FiscalCoverage.php", + "present": true, + "sha256": "592d56e2e19a21c9955eddf4bd2c83af610f87536bc0d6ed85b034b797bf2791" + }, + { + "file": "app/Modules/Services/FiscalAssessment.php", + "present": true, + "sha256": "c2dbe192e6bba75f6fc4057bbb51c85bcf024a674cfeb7950ba239e700213128" + }, + { + "file": "app/Modules/Services/Http/FiscalAssessmentController.php", + "present": true, + "sha256": "425278110af20393e71de446f9b0ce41408352216249e5055de4165b075631e4" + }, + { + "file": "public/sgf-iva.mjs", + "present": true, + "sha256": "1d8c2a07f606799257c090ab2ddb52c309553a15009d6fb457dbbf4770c51ee6" + }, + { + "file": "app/Modules/Services/FiscalRules.php", + "present": true, + "sha256": "520edda0a013d41a80e3e3b56a1b39597a4f1b6407d78118f8855277ffeb3548" + }, + { + "file": "app/Modules/Services/FiscalRuleExpression.php", + "present": true, + "sha256": "108d8401c9c2bc85b3e5f663322c3d36c267afd8529b370e7e16c1d565a3daef" + }, + { + "file": "app/Modules/Services/Http/FiscalRulesController.php", + "present": true, + "sha256": "cc7e05e18c4bbc98a70a19eabdd653bbb44a717214f8d8ee19cd2ffeb1c032c9" + }, + { + "file": "public/sgf-fiscal-rules.mjs", + "present": true, + "sha256": "5c865e018c5b7d3b3f1232b80e81da379c729ea816a9e08a73225e76a678502c" + }, + { + "file": "app/Modules/Services/FiscalDocumentMath.php", + "present": true, + "sha256": "6e67b7b1363d24b38ca0dd40c4349dba82a4b746965f0af8aa7d65bb4f8f731d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "resources/js/ComposedAccountingWorkspace.jsx", + "present": true, + "sha256": "9288df122be42e436bb4aeffc3e84cc1687ba82ab4cab07754d27d507d0012ac" + }, + { + "file": "resources/js/LedgerReportsWorkspace.jsx", + "present": true, + "sha256": "312157b80599e0f4407520ec968460af9c31913f79365ef6efa9a243c98ba419" + }, + { + "file": "resources/js/ledger-report-view.js", + "present": true, + "sha256": "54a1eb09ab4b451b4aa34d8b0c1ebc3d8cea91da4f242ea25a0c4b3bb1db79b4" + }, + { + "file": "app/Modules/Services/FiscalObligations.php", + "present": true, + "sha256": "bcf24717cf5828fed440b8e86f29a5ed4d325d392000240b224d155954170d1d" + }, + { + "file": "app/Modules/Services/Http/FiscalObligationsController.php", + "present": true, + "sha256": "95b03b505e588333206766b862d0f0cff006b1af1a3dc0466370ac6a7518e097" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "resources/js/fiscal-obligations-view.js", + "present": true, + "sha256": "d3c1aafddac987de0a8e432746533da8832c140474f9d9d49055020f7c9e67dd" + }, + { + "file": "database/sql/fiscal_obligations.sql", + "present": true, + "sha256": "c68988342ca1ed5817485355844c2f502f5c2ca12954461c1555d5c5f2abf7fe" + }, + { + "file": "config/fiscal_obligations.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Accounting/InternalStatementsController.php", + "present": true, + "sha256": "b05990c7fdb195957808b63a77599eaaabed5c5eb435dd8e7dd13d662c22fd8d" + }, + { + "file": "app/Modules/Accounting/InternalStatementTotals.php", + "present": true, + "sha256": "f13f6f356100e49786869196c54343fd629f6a1d38fa81665eb768dae4731601" + }, + { + "file": "resources/js/InternalStatementsWorkspace.jsx", + "present": true, + "sha256": "2fadac08cd6162dd06d056bfc4fb00479e9a5d930fdde10c1a4b57507982e76e" + }, + { + "file": "resources/js/internal-statements-view.js", + "present": true, + "sha256": "f1f7b44f2dc1679e537d77522e0fc5ee84da23068ef3157677bc20a8409d3464" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "2dcbea8a6523970211024dbac28743ce4a128b98808fd6f0d39b73cbf48457ff" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "database/sql/internal_statements.sql", + "present": true, + "sha256": "2beccb0bb7d91f48e5a5e386c1e010fb7d95cbfa90aa35a68e28dc63ae6a6f6b" + }, + { + "file": "config/internal_statements.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Accounting/FixedAssets.php", + "present": true, + "sha256": "6568f5e22e586f3dd0c6aa9680cb5fb27848418b2dcb2612bb58f47c586c763e" + }, + { + "file": "app/Modules/Accounting/FixedAssetLedgerSource.php", + "present": true, + "sha256": "4259a63c04fc6543b7430389a4ac134ea81fb07f801061aea41e593ac8216112" + }, + { + "file": "app/Modules/Accounting/FixedAssetJournalController.php", + "present": true, + "sha256": "eba98630909ee5f7b5cd18718dbebf599b408eccdb201c3d171ae80b4dfb1502" + }, + { + "file": "resources/js/FixedAssetsWorkspace.jsx", + "present": true, + "sha256": "f6654d710a8762d02c7bac563f1278c62ed113b00908a219bd9a54078fafa11e" + }, + { + "file": "packages/general-ledger/src/LedgerAssets.php", + "present": true, + "sha256": "aa5b0e7766c02587d28d55c8566b6f186ffbca60c180401ba0ead31f9a426cfe" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-matrix-final-20261104.json", + "present": true, + "sha256": "94cb37698f294c38df9b94a5535ba4b9ac63907f94dab1e7f951a3bb27623083", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_COVERAGE_DELIVERY.md", + "present": true, + "sha256": "4951a2514253c71710fd18ddbffcec1c4dca597355b1cf998b1c7f1b38078506", + "not_reexecuted": true + }, + { + "file": "docs/SGF_IVA_ASSESSMENT_DELIVERY.md", + "present": true, + "sha256": "b0a3ae64699352242108d782a229f0250344f238fad02e14e6fa4dbb520e997f", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_RULES_COMMON_DELIVERY.md", + "present": true, + "sha256": "10f8801d59dbdeb3f075e5bf2650e4538097a92151db516e3c0357ac011c8a5a", + "not_reexecuted": true + }, + { + "file": "docs/SGF_MULTI_LINE_INCLUDED_DELIVERY.md", + "present": true, + "sha256": "7b1d6bbdea15845cf7f4fe104057a4053be1c0f0edb2186da26d11dded45b690", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_ACCOUNTING_REPORTS_DELIVERY.md", + "present": true, + "sha256": "f910d6d11fd7f4e18a675fea9f07a50137bbed72d4718bb7ec6e022bdd1959e5", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_OBLIGATIONS_DELIVERY.md", + "present": true, + "sha256": "028e3172128435924dc9594b68b25560cb0b30495f27c522864a94a1f8cc9504", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_STATEMENTS_DELIVERY.md", + "present": true, + "sha256": "53a07e2c0517de5b6881584c598b2f1d1d5665422ac2f8f5932626822daf3359", + "not_reexecuted": true + }, + { + "file": "docs/SGF_COVERAGE_RECONCILIATION.md", + "present": true, + "sha256": "b1fb3722b5a9444699f713f7c8a6535eceb05977dacb194ce7dace0e1a4afa14", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-fixed-assets-20261111.json", + "present": true, + "sha256": "2950d8e3decf13b1c30f0c8a6c7ab88e72a1dd120995136d3d04914d0eee0043", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Backlog fiscal/financeiro amplo pendente; tabela de cobertura não implementa motores ou modelos", + "Activos gerais e tratamento fiscal/legal, alienação/contraprestação, correcções de custo já admitido e cadeias adicionais fora do recorte QA" + ] + }, + { + "id": "human_resources", + "label": "Recursos Humanos", + "features": [ + "Módulo autónomo com perfis QA próprios separados do Financeiro", + "Cadastro pseudónimo individual preparado e revisto", + "Regras artificiais versionadas com vigência e fonte explícitas", + "Processamento exacto por trabalhador/período com escolha de fontes e revisão distinta", + "Correcção/cancelamento versionados, prévia, histórico, recibos e recuperação", + "Mapa individual revisto conservado com SHA e exportação autenticada; filtros SQL25/100", + "Contratos internos QA revistos com correcção/terminação/cancelamento e dependências", + "Plano e assiduidade por dia com intervalos explícitos, dados desconhecidos e revisão distinta", + "Condição de férias QA, reserva/gozo/cancelamento individuais, saldo efectivo sem duplicação", + "Conferência parcial do período por pessoa, fontes/versões e CSV/SHA conservado", + "Ligação contratual explícita opcional em novas preparações salariais, provada na regressão sem alterar salários8085", + "Resumo CSV legível derivado de versão revista, com SHA próprio/original, sem alterar o artefacto conservado" + ], + "sources": [ + { + "file": "app/Modules/HumanResources/Remunerations.php", + "present": true, + "sha256": "950333ced35d6949487bdf78ef109fad608338525acfe60b05b9a320fd3aac01" + }, + { + "file": "app/Modules/HumanResources/Http/RemunerationsController.php", + "present": true, + "sha256": "3725145a623791a6c2825d8aaa4f122ba7b80908c08603211d6d9e74df76e801" + }, + { + "file": "app/Shared/Installation/HumanResourcesProfiles.php", + "present": true, + "sha256": "f908b8cf9f7121bb9b43733f507771a60aa34eb553df8342c250e5b39940ae93" + }, + { + "file": "resources/js/RemunerationsWorkspace.jsx", + "present": true, + "sha256": "edb225504bff359d5c53c040421e322bb4cd887ac946b3316852ecd1ed25b76e" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "2dcbea8a6523970211024dbac28743ce4a128b98808fd6f0d39b73cbf48457ff" + }, + { + "file": "app/Modules/HumanResources/Personnel.php", + "present": true, + "sha256": "da109cf709a6306f7c7a8a3f111514cb1e5c4d1dc0554e15e36022bf1fbb829b" + }, + { + "file": "app/Modules/HumanResources/PersonnelTime.php", + "present": true, + "sha256": "e0fc1bdecbbd24c1ac21a426d8547fc049c00fd18ae6faa7ded64128252bbfd6" + }, + { + "file": "app/Modules/HumanResources/Http/PersonnelController.php", + "present": true, + "sha256": "539fb7ce74dd3b87263d1610fee7ddac2028c4536bc8d549be6d0617c3f947a2" + }, + { + "file": "resources/js/PersonnelWorkspace.jsx", + "present": true, + "sha256": "3bff1b58a1f08c8065abaa68fd7547156f3537f573e0a8d4b922cd3c2cf7ae06" + }, + { + "file": "app/Modules/HumanResources/PersonnelReadableCsv.php", + "present": true, + "sha256": "5e31d7de8aa243b9f9578d859e05ab9071c06dc39bb8616f35eed085856c493c" + } + ], + "existing_evidence": [ + { + "file": "docs/SGF_HUMAN_RESOURCES_DELIVERY.md", + "present": true, + "sha256": "1494d29938565bb6f5d552385d7b32dd0a8f22856755bf638d81cf9a41a65f60", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-human-resources-20261111.json", + "present": true, + "sha256": "95d0c3fcd407cfbb03226288b94570478b906fdf3cb50c26a9960956f536cf29", + "not_reexecuted": true + }, + { + "file": "docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md", + "present": true, + "sha256": "045d111cd08ad58c22a802d6d728c7f300d91b4078b65c3ac61683791747a02e", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-hr-personnel-20261111.json", + "present": true, + "sha256": "b4ce53d6099a13acbe6bd2d7431f9109b8a318000f9d69813353349cd823cdc7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_HUMAN_RESOURCES_READABLE_DELIVERY.md", + "present": true, + "sha256": "18275d4fda727e0082379bcd17804397a13dc3c5f5a88575c48c4e6c8abf95b2", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conformidade e aplicabilidade laboral real: contratos legais, turnos/calendário, férias legais, benefícios e modelos", + "Cálculo legal IRPS/INSS e ligação a dívida/pagamento/Contabilidade sem efeitos implícitos", + "Revisão visual, carga sustentada, armazenamento e capacidade de implantação" + ] + } + ], + "release_gates": [ + { + "id": "single_installation", + "criterion": "Os seis módulos operam no mesmo endereço e empresa/unidade, sem mudar de demonstração", + "passed": false + }, + { + "id": "functional_modules", + "criterion": "Cada funcionalidade obrigatória tem ecrã/comando/consulta executável; menus e fontes isoladas não bastam", + "passed": false + }, + { + "id": "cross_module_flows", + "criterion": "Os documentos atravessam as fronteiras mantendo origem, versão, autoridade e efeito único", + "passed": false + }, + { + "id": "access_isolation", + "criterion": "Perfis explícitos e recusas de empresa/unidade/módulo/permissão verificadas", + "passed": false + }, + { + "id": "presentation", + "criterion": "Percurso de apresentação coerente, dados fictícios identificados, linguagem e navegação revistas", + "passed": false + }, + { + "id": "operations", + "criterion": "Arranque e recuperação da instalação entregues e ensaiados, sem depender do SGC", + "passed": false + }, + { + "id": "commercial_honesty", + "criterion": "Documentos de teste não são emissão fiscal; capacidade de implantação exige prova própria", + "passed": false + }, + { + "id": "preservation", + "criterion": "Seis registos, cache, dados aprovados e trabalho alheio preservados", + "passed": false + } + ], + "client_presentable": false, + "limits": [ + "Host-table footprint is a minimum projection, not a full migration/dependency validation.", + "Native purchasing/stock tables live in their existing separate native book; this scan does not call that missing.", + "Source/evidence presence does not mean a feature is integrated, visually accepted or deployment-ready." + ], + "database_writes": 0, + "application_bootstraps": 0, + "cache_cleared": false, + "new_demo": false, + "new_rights": false +} diff --git a/docs/proofs/sgf-solution-structure-v29-final-20261111.json b/docs/proofs/sgf-solution-structure-v29-final-20261111.json new file mode 100644 index 0000000..53c3d93 --- /dev/null +++ b/docs/proofs/sgf-solution-structure-v29-final-20261111.json @@ -0,0 +1,1268 @@ +{ + "date": "2026-11-11", + "contract_sha256": "0a078d66a2135276fd64b9c42e674d6727b38ad77e8946661026f42515ebeaa4", + "installation": "cc50a276c10c", + "port": 8085, + "sgc_dependency": false, + "full_product_complete": false, + "product_backlog": [ + { + "id": "fiscal_books", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Livro/mapa interno QA de uma origem entregue; IVA geral, dedução/regularizações e restantes tratamentos pendentes" + }, + { + "id": "irps_inss", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "RH autónomo QA com cadastro/contratos/assiduidade/férias e remunerações internas entregue; regimes laborais gerais, IRPS/INSS/modelos legais e ligação financeira/contabilística salarial pendentes" + }, + { + "id": "irpc", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "IRPC operacional: ajustamentos, retenções, apuramento" + }, + { + "id": "ispc_ice", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Motores operacionais ISPC e ICE" + }, + { + "id": "official_forms", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Modelos oficiais com correspondências e reconciliação de campos" + }, + { + "id": "fiscal_calendar", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Controlo administrativo de prazo e entrega simulada de uma origem QA entregue; calendário legal, aplicabilidade geral, prazos por fonte/vigência e submissão continuam pendentes" + }, + { + "id": "bank_reconciliation", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Conferência de duas linhas/extracto artificial QA entregue; integração e reconciliação bancária gerais/reais pendentes" + }, + { + "id": "financial_analysis", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Dois critérios/indicadores QA com fontes conciliadas entregues; rácios gerais, critérios reais e tendências/séries permanecem pendentes" + }, + { + "id": "fixed_assets", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md FIS-SGF-15", + "description": "Ciclo integrado QA entregue na8085: cadastro/plano/quota/diário/relatórios e ramos de correcção/abate/fecho do recorte na regressão. Operação geral/legal de activos e taxas fiscais continuam pendentes.", + "scope": "docs/SGF_FIXED_ASSETS_SCOPE.md", + "access_proposal": "docs/integration/sgf-fixed-assets-access-proposal.json", + "qa_implemented": true, + "delivery": "docs/SGF_FIXED_ASSETS_DELIVERY.md" + } + ], + "modules": [ + { + "id": "commercial", + "label": "Comercial", + "features": [ + "Clientes e serviços com associação explícita", + "Rascunho, revisão e cálculo", + "Documento emitido de teste e PDF", + "Continuação para recebível sem duplicação", + "Orçamentos Comercial/Oficina, revisão e aceitação documentadas e conversão explícita em rascunho", + "Recibo de afectação efectiva400, crédito nominal100/saldo500 e factura-recibo1000 atómica", + "PDF/SHA, histórico e anulação técnica com reversão explícita e diário único", + "Registo unificado autorizado com seis famílias, filtros/SQL25/100 e projecções limitadas à página", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceWorkflow.php", + "present": true, + "sha256": "6ea92e6216bec9a0062d9f152d1b758a5527360456589a98f525429402cb94da" + }, + { + "file": "app/Modules/Services/ServiceInvoicing.php", + "present": true, + "sha256": "4138afb7e001b546a65552d71223cd642154945b8021e1ff45ef60a34076e3c5" + }, + { + "file": "resources/js/ServicesWorkspace.jsx", + "present": true, + "sha256": "c170b195ccd78aa74f283a91b0b4f4cd93aea4677e3aaefe0b50af46f7e31669" + }, + { + "file": "resources/js/ServiceInvoiceWorkspace.jsx", + "present": true, + "sha256": "8f95d1775b30a6de73d65eed428595d74cc837755d8157bf8db49a1151350277" + }, + { + "file": "app/Modules/Services/ServiceDocuments.php", + "present": true, + "sha256": "816a85be48b05043f41f9c7d5f842a3fa139b21aaedf9c944d811f74be6f6208" + }, + { + "file": "app/Modules/Services/DocumentPeriod.php", + "present": true, + "sha256": "100c6cf038b925126ddc8fb50adde2f6d938b4d68df6d0ab193f16ea28836ae2" + }, + { + "file": "app/Modules/Services/BillingRegister.php", + "present": true, + "sha256": "115e7832b63e5c29be716140629845137d951da7790421832e1ab4058fb468cc" + }, + { + "file": "resources/js/BillingRegisterWorkspace.jsx", + "present": true, + "sha256": "9ab8d036c9e6d20c588427f5a8c44677810e9713722baea908b8f6b76c8b79f9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/invoice-test-results.json", + "present": true, + "sha256": "fd04a58727ffddf1c62ac184d7834b81228dce2abb5b0c1e397505df6a45237c", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_SERVICE_DOCUMENTS_DELIVERY.md", + "present": true, + "sha256": "a13713f05feaa84766dfaba870e72c98d23aa6223389d1627de911b0856cd15d", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BILLING_REGISTER_DELIVERY.md", + "present": true, + "sha256": "fec1b76003187d5f38c8aca8804c26129e312e517dd5b2247f54f0019c2cccf6", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual, aceitação explícita e conformidade real; emissão fiscal real permanece bloqueada", + "Carga sustentada, armazenamento e capacidade de implantação exigem evidências próprias" + ] + }, + { + "id": "purchasing", + "label": "Compras", + "features": [ + "Fornecedores e revisão distinta", + "Requisição até adjudicação e ordem", + "Recepção, documento e stock único", + "Factura conferida e obrigação reconhecida", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Purchasing/ReceiptRuntime.php", + "present": true, + "sha256": "b08af1f8772cbfafa1f85e37c832f2c6916ccabcfbd4a8bb98c5ef7690c3f07f" + }, + { + "file": "app/Modules/Purchasing/RequestController.php", + "present": true, + "sha256": "c5c2b78866be7f0e1c8ba09c1af538cefcfbafbea81197833b50821bba0fcbca" + }, + { + "file": "app/Modules/Purchasing/InvoiceController.php", + "present": true, + "sha256": "85eb46ca7e65c60bed190e592a134eb48db5762c0a90b8cd4c9d6fbd81198ba4" + }, + { + "file": "app/Modules/Purchasing/ObligationController.php", + "present": true, + "sha256": "347e629306d6c1f4873afac547d13fae8ade7b268906d1312a64e7eb6998e701" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-final-20261104-r2.json", + "present": true, + "sha256": "3b63f8653e8dbb089dfdd8f389e271c022a070d5a67a9f9d4585ce84438070cc", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ], + "accepted_component_journey": true + }, + { + "id": "stock", + "label": "Stock", + "features": [ + "Artigos, armazéns e saldos físicos", + "Entradas, saídas e origem autorizada", + "Transferência e conferência de destino", + "Contagens, ajustamentos e devoluções", + "Movimentos e relatórios por período", + "Política QA revista e devolução ao fornecedor por origem exacta; saldo8UN/160MZN no mesmo diário", + "Inactividade de pares com história conhecida, corte UTC explícito, filtros e SQL25/100 sem inferir ausência de actividade", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Cadastro de artigos: preparação/revisão distintas, criação sem saldo, designação revista só dos novos, código/unidade imutáveis, histórico/recibos e catálogo canónico único" + ], + "sources": [ + { + "file": "app/Modules/Inventory/StockReadController.php", + "present": true, + "sha256": "fd02da1a69f76bf1412f86f43095a0a476de49fbb7d2676a51fe3b29ec66e298" + }, + { + "file": "app/Modules/Inventory/CompositionStockRead.php", + "present": true, + "sha256": "81bdb5fb374a60974bbad421c461cd12079951fb426361200692a838e413b49e" + }, + { + "file": "app/Modules/Inventory/StockPeriodReport.php", + "present": true, + "sha256": "d491541c6c60bc0c661dc442b8db8ce2e757f1d63dfc4982a3631f820cfb7a06" + }, + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "app/Modules/Inventory/StockOperationsController.php", + "present": true, + "sha256": "34d6c7be4787f4f18ca2a7aa322f6f33dfb281e22336599234c93c655ab4eebf" + }, + { + "file": "resources/views/stock-operations-workspace.blade.php", + "present": true, + "sha256": "5db67c2d23e5557548c38c82a0d7f6e373651849727b2237dcd73f620309c25a" + }, + { + "file": "public/stock-operations-workspace.mjs", + "present": true, + "sha256": "56734499e8dd89da293c2edadd0d0e10ac6d06b39f0365e5ae29a8d14e9176d8" + }, + { + "file": "app/Modules/Inventory/StockFinalController.php", + "present": true, + "sha256": "9d9a242fd8e143fe076ece0454d0789eabf543e37b332db4806d905fad13cdda" + }, + { + "file": "resources/views/stock-final-workspace.blade.php", + "present": true, + "sha256": "93764471a215735a65e5c07121989b1e2f6499f631a11b9f1177bb0fe8def232" + }, + { + "file": "public/sgf-stock-final.mjs", + "present": true, + "sha256": "77860b080015528fe4dd84b2e71787ee668da9be17653c64a5d8e64539b5e592" + }, + { + "file": "app/Modules/Inventory/StockInactiveReport.php", + "present": true, + "sha256": "7db06225fb7bb40c9a4140383caef6dd96c430e3a3e1be8ef7fed2253f2681e2" + }, + { + "file": "resources/js/StockInactiveReportWorkspace.jsx", + "present": true, + "sha256": "5d4f7222bf325550cebddbd00198515da55f15f52b4c99ecaef19f45820a165e" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Inventory/StockCatalogueController.php", + "present": true, + "sha256": "ab0a6bc52e635d5bcdee9163b68b1ec6fc14b717de7b887fcc832336961fa128" + }, + { + "file": "resources/views/stock-catalogue-workspace.blade.php", + "present": true, + "sha256": "4381f81a9aea04fe851f8c7af17cafb98f6bef7cbf52ac2630b0b9912a3dca57" + }, + { + "file": "public/sgf-stock-catalogue.mjs", + "present": true, + "sha256": "10eedcf1881ad3f1cb71813f0547eae7246795273457cc33b1a2a6761a0a2d6e" + }, + { + "file": "config/stock_catalogue.php", + "present": true, + "sha256": "811f844cecf76bff62da943042a272301cb8a7f7a586c4f2648ef5b8e5e31dae" + }, + { + "file": "database/sql/stock_catalogue.sql", + "present": true, + "sha256": "c9c99cec20e6ca60cf0ee56210eac9a2365a2ef9b482698929a70295d2893809" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/stock-period-report-20261103.json", + "present": true, + "sha256": "0ab25af6ffbff7d8ecb651df833771b082bf92d52f97eca4278fcac3fd5ea53b", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-stock-operations-20261110.json", + "present": true, + "sha256": "1d241fcfb81fcf6c1b374f9fc89c1f2130b17eee1314f06b4b8e4b59c476fc08", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_POLICIES_SUPPLIER_RETURN.md", + "present": true, + "sha256": "b8848090f1e19a6b7c19b684e07c0c95cafcf93bd1551357f6afc314f4413d8b", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_INACTIVITY_DELIVERY.md", + "present": true, + "sha256": "8773d53140b291c01358b8e71c584373cd0e440f67d0107e64ecea13980990d3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_CATALOGUE_DELIVERY.md", + "present": true, + "sha256": "43ce839f882aeeab48b272d5eb1a4e34fcd855052db88643a8ca7b08556631f8", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual dos novos relatórios e aceitação explícita do conjunto; carga sustentada e implantação separadas" + ] + }, + { + "id": "workshop", + "label": "Oficinas", + "features": [ + "Ordem e autorização", + "Apontamentos e revisão distinta", + "Material consumido ligado ao movimento físico", + "Custo directo/industrial e correcção", + "Continuação comercial por documento de origem", + "Agenda sem sobreposição, reagendamento e cancelamento versionados", + "Cadastro independente e reutilização de equipamento por cliente", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Ordens SQL25/100 por cliente/equipamento/responsável/estado, apontamentos actuais e minutos verificados, sem mutex de escrita na leitura", + "Relatório por período de abertura explícito/dia local, SQL25/100 antes de paginar, apontamentos actuais e CSV da página sem fecho histórico presumido", + "Equipamento escolhido → ordens exactas SQL25/100 com filtros, identificação actual, CSV da página e regresso ao catálogo" + ], + "sources": [ + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "resources/js/WorkshopWorkspace.jsx", + "present": true, + "sha256": "a1ceca33c234a759b2abf13b087bba8d18d70187ad9161e59425a5932771f8bb" + }, + { + "file": "resources/js/DirectCostsWorkspace.jsx", + "present": true, + "sha256": "dcaf72fe36d80e16bab8e962f939168650336d1450fc3eb01693acb7ef7e0743" + }, + { + "file": "resources/js/IndustrialCostsWorkspace.jsx", + "present": true, + "sha256": "51a3307fa8b2526efa12d8a16fee613bdfe141d02bd6e5af4535d581ed69c6d0" + }, + { + "file": "resources/js/WorkshopBillingWorkspace.jsx", + "present": true, + "sha256": "601853bd383ef2d1891158e89578bbc7f26ae1c7220bc11ddfadf1868cb63afc" + }, + { + "file": "app/Modules/Workshop/ComposedWorkshopStockBook.php", + "present": true, + "sha256": "01e7392827a6453a357a0b5140905757bbee76f57fae6aa9906490788690e24c" + }, + { + "file": "app/Modules/Workshop/WorkshopBilling.php", + "present": true, + "sha256": "0eac2d91fd9f006aa8c198e3652731142aee478c93de090c408df54cdd9d292d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Workshop/WorkshopOrderRead.php", + "present": true, + "sha256": "f741a5ac6c16376ea26b1bacd7b5e706b14a4548a26f59c4a96ec882fb6a6fa5" + }, + { + "file": "app/Modules/Workshop/Http/WorkshopOrderReadController.php", + "present": true, + "sha256": "f4dd027d01c0730603f3b312c2eb263740f6ebe1c969c762ed99d19520fab584" + }, + { + "file": "resources/js/WorkshopOrderList.jsx", + "present": true, + "sha256": "4cad148295acb7c462425fa300dccc7c203255f5f616163575e205bfc8a80e34" + }, + { + "file": "resources/js/workshop-report-view.js", + "present": true, + "sha256": "28125af02e25b4befb9914c5dee753dae4164100f3c0f94988c332a50021bf9f" + }, + { + "file": "app/Modules/Workshop/WorkshopEquipment.php", + "present": true, + "sha256": "37223b732865f273831d0bf61a464137cc53526a4c55750347450ba20bcbbc37" + }, + { + "file": "resources/js/WorkshopEquipmentWorkspace.jsx", + "present": true, + "sha256": "450af001d385f42cd4d0ae42b34fa6b373cdd6811d94f8f4a1f5de2c591ed843" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/workshop-stock-closure-20261102.json", + "present": true, + "sha256": "261d5502f6905d53ef8046e0e900c9bee159c8c9d7e1bae4fddd8ec698313bf7", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_AGENDA_EQUIPMENT.md", + "present": true, + "sha256": "3a44a1b6ac5148013c815cc6cc8919f08c15ac911f900aac5b4a0af903589481", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_PERIOD_REPORT_DELIVERY.md", + "present": true, + "sha256": "1455e3dc4203c81f49ddaba05a7bf7de10cfd9b3bb4dbd2eb4480aa05b7ccf08", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_EQUIPMENT_ORDERS_DELIVERY.md", + "present": true, + "sha256": "4d4249613a1b3fc339f342c3854eda08bf1ec280c0681eb3b2551469b2da2b36", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ] + }, + { + "id": "finance", + "label": "Financeiro", + "features": [ + "Obrigações a fornecedores", + "Recebíveis de clientes", + "Liquidação/afectação e reversão sintéticas", + "Posição financeira e comprovativos por origem", + "Caixa QA explícita0+1000-50=950, revisão distinta, contagem e fecho sem reabertura", + "Extracto paginado, recuperação de confirmação e classificação/despesa no mesmo diário", + "Extracto artificial QA e correspondências individuais com duas linhas nativas existentes, revisão distinta, conferência final e relatório", + "Critérios e indicadores QA950/100=9,5 vezes e50/1000=5%, revisão distinta, fontes conciliadas/retrato/fracção/histórico/relatório", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Posições actuais de clientes por documento, pesquisa/estado/vencimento, SQL25/100, saldos reconhecidos e CSV da página; referência de vencimento distinta de saldo histórico", + "Posições de fornecedores por factura/fornecedor e estado documental, SQL25/100, saldos do motor original, CSV da página e abertura exacta do documento; vencimento desconhecido sem atraso presumido", + "Condições administrativas QA de vencimento: preparação13/revisão14, evidência/fundamento, prévia/revalidação de origem/saldo/versão/pessoa, recusa/correcção, histórico25/100, recibo e recuperação; lista/CSV separam original e condição revista, referência explícita no detalhe", + "Mapa de antiguidade de fornecedores por referência explícita e condição administrativa revista, faixas0/1–30/31–60/61–90/>90/desconhecido, valores exactos por moeda só da página/CSV, sem alterar saldo ou vencimento original" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceReceivables.php", + "present": true, + "sha256": "79e1669b2423c521c0c56ab6fb07e085daef88eff8a41748c424e4a827f1e453" + }, + { + "file": "app/Modules/Purchasing/SupplierPayables.php", + "present": true, + "sha256": "c17d44391cbd96941aa2dd2eac2cb167508faf6453c6a1e86333846df1ba18b9" + }, + { + "file": "resources/js/ServiceReceivablesWorkspace.jsx", + "present": true, + "sha256": "6e654a03cb47e52dedd52ce6b2abf89a61d22860e4ffae92fd393027a54cc445" + }, + { + "file": "app/Modules/Purchasing/ComposedFinance.php", + "present": true, + "sha256": "ad7e837c7608ab85c59eb169bcdd986b08c5802a0f218926e1abd7568e8a4b16" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "app/Modules/Finance/CashWorkspace.php", + "present": true, + "sha256": "bb65060461cea3a844cf523733644cdd630f35ba02c82aeed961821ec5970800" + }, + { + "file": "app/Modules/Finance/Http/CashController.php", + "present": true, + "sha256": "982c161f3ef7314a9671f848f0da2aefb27841e40003ef47102d0d956c08cd78" + }, + { + "file": "app/Modules/Accounting/CashLedgerSource.php", + "present": true, + "sha256": "3a46162f0a62363dd4015f0047a5bff95e8aebd8dd3007fc705daaf58c10684b" + }, + { + "file": "app/Modules/Finance/BankReconciliation.php", + "present": true, + "sha256": "5421141bcc6e15bda142687661774238c7203c85d0ef30696fd4d56dd5b9e328" + }, + { + "file": "app/Modules/Finance/BankLedgerSource.php", + "present": true, + "sha256": "f33779fa736aea7027d4cf449ba85c5982bfb565c75bbb2a477d7a2666820338" + }, + { + "file": "app/Modules/Finance/Http/BankReconciliationController.php", + "present": true, + "sha256": "896d710db108364263a1404d2aafdc52eff4d3b6efcbe3dbdbcca1978befffe4" + }, + { + "file": "public/sgf-bank.mjs", + "present": true, + "sha256": "959214ad94bb5c29ffdaf1c8fd3e59bf8718837d4c1fe9bea52d3b04c261a80b" + }, + { + "file": "app/Modules/Finance/FinancialIndicators.php", + "present": true, + "sha256": "9785a801c5fb13b34770bdd05c828f5e924058eb872a7b33bc1db139a3024d10" + }, + { + "file": "app/Modules/Finance/FinancialIndicatorSource.php", + "present": true, + "sha256": "72039264cbb25ae2eaa5eaa4af488aabf7061a4a298c09490b8957b4ade55731" + }, + { + "file": "app/Modules/Finance/Http/FinancialIndicatorsController.php", + "present": true, + "sha256": "5245064e40b884e432afa2fd26d45fddccebf6963ef3366f585b93ee78f12cca" + }, + { + "file": "public/sgf-indicators.mjs", + "present": true, + "sha256": "62442edc14da534e0166cc65074389e74cc67246cd5922994628644fd900cbb9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Services/ReceivablePositionRead.php", + "present": true, + "sha256": "6aaf0211c4427643c84152edc6a7937dbf48387e2844d3bbe31328d1f6789f2d" + }, + { + "file": "app/Modules/Services/Http/ReceivablePositionReadController.php", + "present": true, + "sha256": "9e8be7c2cd328aebb31cbe0be4eeaaa6abc02d5bff0895aeb90c24ab8d134541" + }, + { + "file": "resources/js/ReceivablePositionsWorkspace.jsx", + "present": true, + "sha256": "5bd8ee952aeee87d733ab434cfe50b675d84c0341ade4f354c93e4acfdbe57e9" + }, + { + "file": "resources/js/receivable-position-view.js", + "present": true, + "sha256": "08f8cb6d17b79b89ee8cebe4723458c1a4979d8fb824afa0b585ae51076e6f21" + }, + { + "file": "app/Modules/Purchasing/PayablePositionReadController.php", + "present": true, + "sha256": "1d147f14826793f97605ea442c6a93612a55caf265311a86824611650dfc0005" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "resources/js/PayablePositionsWorkspace.jsx", + "present": true, + "sha256": "e52fa90276e6251b38bb48f83a11ec2b11ac819ffa3c2239da9712e50ce66343" + }, + { + "file": "resources/js/payable-position-view.js", + "present": true, + "sha256": "ff4b32f404cd575ddc134cf373ae6ded5d0abae8641b14b591c0f5a856437938" + }, + { + "file": "app/Modules/Purchasing/PayableDueController.php", + "present": true, + "sha256": "39e4c95b910d3b1c4aa227c7c2aa0ee24399921b9578748b95ff5aa519f34db7" + }, + { + "file": "resources/js/PayableDueWorkspace.jsx", + "present": true, + "sha256": "c7127bbb2edc6c5acce57a55a6c3c5b94969170ccf1adb7236fee80d45f555ae" + }, + { + "file": "database/sql/payable_due.sql", + "present": true, + "sha256": "faefce9f2c11da613293e23bbc2f630bdae345d872cca7f3c2923c77fea0f75e" + }, + { + "file": "config/payable_due.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Purchasing/PayableAge.php", + "present": true, + "sha256": "19f4a3994b679b74275e72e906251c8d32ee9950ebedfae07bd4002abe740a9e" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/supplier-settlements.json", + "present": true, + "sha256": "b1bce9218f99ea2d8c780022ad385e169c7acc5778ac6950236eef0630f77ad0", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_CASH_DELIVERY.md", + "present": true, + "sha256": "bfb40ff8849eb4d9fcb641aa9b8d07bbaab89326a0f89b8f113cf0bf62653093", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BANK_RECONCILIATION_DELIVERY.md", + "present": true, + "sha256": "acf53f7dfd2ec0f501cef8b1623e090c1e1b8a07a8591f4f042fb8a0b7b57273", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_INDICATORS_DELIVERY.md", + "present": true, + "sha256": "9902e37504dd9f5e1d3c73cd78a283e29be00ffb33ce13ca8f76577d1163cc65", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_RECEIVABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "7a829a6dcae28fa42e996874b7b65ed1de325dbab5bae12a28731ecfc2e849d7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "2c0a25051540527e87ca9abfa43db5efb288e0b40e2315e06f6084693ce9c8c3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_DUE_DELIVERY.md", + "present": true, + "sha256": "614ca3b1c432760e1911112951f138cf72d5d8370173336472529e9549e965b8", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_AGING_DELIVERY.md", + "present": true, + "sha256": "a0af96108702bbb687a655de744e78e01a3e7c98365244410eb291fcb7a5fafa", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Conferência de extracto QA entregue; reconciliação bancária geral/real permanece fora do recorte" + ] + }, + { + "id": "accounting", + "label": "Contabilidade", + "features": [ + "Plano e classificação", + "Diário com preparação/revisão distintas", + "Razão e balancete", + "Estorno/correcção e conferência de período", + "Fecho sintético com rastreabilidade de origens", + "Livro de saídas e mapa interno IVA QA da origem FR seleccionada, revisão distinta, histórico e CSV congelado", + "Catálogo de fontes/regras versionadas QA, revisão distinta, vigência/conflitos/retirada e cálculo explicável com história preservada", + "Cálculo QA por1–25linhas sem/com imposto incluído, representação por linha aprovada, exactos/diferenças e mapa interno recuperável", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Submenus distintos e balancete/razão por datas explícitas, escolha de conta por linha, páginas25/100, detecção de mudança de livro e CSV da página", + "Controlo administrativo QA de obrigação/prazo/responsável do mapa interno revisto, preparação13/revisão14, correcção/cancelamento, entrega apenas simulada com SHA verificado, histórico/recibo/SQL25/100/CSV; sem calendário legal ou submissão", + "Correspondências explícitas revistas15/16 e balanço/resultados internos parciais QA:19contas/nove pendentes, resultado congelado19linhas/SHA, história/recibo/CSV contextual, ligação exacta ao razão; capital e resultado completo não presumidos", + "Activos QA: cadastro/custódia/plano/quota revistos, diário único1200/100 e líquido1100; correcção/abate/fecho do recorte na regressão" + ], + "sources": [ + { + "file": "app/Modules/Accounting/GeneralLedger.php", + "present": true, + "sha256": "a87d2ea373bc61bb139aa29f2a354b9813bbb2d6229a389f8d37fd8e9890edec" + }, + { + "file": "app/Modules/Accounting/GeneralLedgerController.php", + "present": true, + "sha256": "afd2f4ae55e694f0aff18233a6f5cf52444e185aaf5a159865f74f0243783f2a" + }, + { + "file": "packages/general-ledger/src/LedgerSchema.php", + "present": true, + "sha256": "d9c203286b3a04121c265dffe412d2e86726ec4bb9222a2ed654d0296d86b5d8" + }, + { + "file": "app/Modules/Accounting/ComposedLedger.php", + "present": true, + "sha256": "25e14af7f62dea2f205f9946135fb016580d1ca2446c9303313d76c2ccd99e04" + }, + { + "file": "app/Modules/Accounting/ComposedLedgerController.php", + "present": true, + "sha256": "b96749981b0d4daa7861eab62c5de60c96c5ffa9c72183f88bb89e2d4f7bdf2b" + }, + { + "file": "packages/general-ledger/src/StockTransitEvidence.php", + "present": true, + "sha256": "eb8eee3bb293e77689eb6470ac8aa12049b2ff8cfb8694d3e15eb17e097d2da6" + }, + { + "file": "app/Modules/Services/FiscalCoverage.php", + "present": true, + "sha256": "592d56e2e19a21c9955eddf4bd2c83af610f87536bc0d6ed85b034b797bf2791" + }, + { + "file": "app/Modules/Services/FiscalAssessment.php", + "present": true, + "sha256": "c2dbe192e6bba75f6fc4057bbb51c85bcf024a674cfeb7950ba239e700213128" + }, + { + "file": "app/Modules/Services/Http/FiscalAssessmentController.php", + "present": true, + "sha256": "425278110af20393e71de446f9b0ce41408352216249e5055de4165b075631e4" + }, + { + "file": "public/sgf-iva.mjs", + "present": true, + "sha256": "1d8c2a07f606799257c090ab2ddb52c309553a15009d6fb457dbbf4770c51ee6" + }, + { + "file": "app/Modules/Services/FiscalRules.php", + "present": true, + "sha256": "520edda0a013d41a80e3e3b56a1b39597a4f1b6407d78118f8855277ffeb3548" + }, + { + "file": "app/Modules/Services/FiscalRuleExpression.php", + "present": true, + "sha256": "108d8401c9c2bc85b3e5f663322c3d36c267afd8529b370e7e16c1d565a3daef" + }, + { + "file": "app/Modules/Services/Http/FiscalRulesController.php", + "present": true, + "sha256": "cc7e05e18c4bbc98a70a19eabdd653bbb44a717214f8d8ee19cd2ffeb1c032c9" + }, + { + "file": "public/sgf-fiscal-rules.mjs", + "present": true, + "sha256": "5c865e018c5b7d3b3f1232b80e81da379c729ea816a9e08a73225e76a678502c" + }, + { + "file": "app/Modules/Services/FiscalDocumentMath.php", + "present": true, + "sha256": "6e67b7b1363d24b38ca0dd40c4349dba82a4b746965f0af8aa7d65bb4f8f731d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "resources/js/ComposedAccountingWorkspace.jsx", + "present": true, + "sha256": "9288df122be42e436bb4aeffc3e84cc1687ba82ab4cab07754d27d507d0012ac" + }, + { + "file": "resources/js/LedgerReportsWorkspace.jsx", + "present": true, + "sha256": "312157b80599e0f4407520ec968460af9c31913f79365ef6efa9a243c98ba419" + }, + { + "file": "resources/js/ledger-report-view.js", + "present": true, + "sha256": "54a1eb09ab4b451b4aa34d8b0c1ebc3d8cea91da4f242ea25a0c4b3bb1db79b4" + }, + { + "file": "app/Modules/Services/FiscalObligations.php", + "present": true, + "sha256": "bcf24717cf5828fed440b8e86f29a5ed4d325d392000240b224d155954170d1d" + }, + { + "file": "app/Modules/Services/Http/FiscalObligationsController.php", + "present": true, + "sha256": "95b03b505e588333206766b862d0f0cff006b1af1a3dc0466370ac6a7518e097" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "resources/js/fiscal-obligations-view.js", + "present": true, + "sha256": "d3c1aafddac987de0a8e432746533da8832c140474f9d9d49055020f7c9e67dd" + }, + { + "file": "database/sql/fiscal_obligations.sql", + "present": true, + "sha256": "c68988342ca1ed5817485355844c2f502f5c2ca12954461c1555d5c5f2abf7fe" + }, + { + "file": "config/fiscal_obligations.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Accounting/InternalStatementsController.php", + "present": true, + "sha256": "b05990c7fdb195957808b63a77599eaaabed5c5eb435dd8e7dd13d662c22fd8d" + }, + { + "file": "app/Modules/Accounting/InternalStatementTotals.php", + "present": true, + "sha256": "f13f6f356100e49786869196c54343fd629f6a1d38fa81665eb768dae4731601" + }, + { + "file": "resources/js/InternalStatementsWorkspace.jsx", + "present": true, + "sha256": "2fadac08cd6162dd06d056bfc4fb00479e9a5d930fdde10c1a4b57507982e76e" + }, + { + "file": "resources/js/internal-statements-view.js", + "present": true, + "sha256": "f1f7b44f2dc1679e537d77522e0fc5ee84da23068ef3157677bc20a8409d3464" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "2dcbea8a6523970211024dbac28743ce4a128b98808fd6f0d39b73cbf48457ff" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "database/sql/internal_statements.sql", + "present": true, + "sha256": "2beccb0bb7d91f48e5a5e386c1e010fb7d95cbfa90aa35a68e28dc63ae6a6f6b" + }, + { + "file": "config/internal_statements.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Accounting/FixedAssets.php", + "present": true, + "sha256": "6568f5e22e586f3dd0c6aa9680cb5fb27848418b2dcb2612bb58f47c586c763e" + }, + { + "file": "app/Modules/Accounting/FixedAssetLedgerSource.php", + "present": true, + "sha256": "4259a63c04fc6543b7430389a4ac134ea81fb07f801061aea41e593ac8216112" + }, + { + "file": "app/Modules/Accounting/FixedAssetJournalController.php", + "present": true, + "sha256": "eba98630909ee5f7b5cd18718dbebf599b408eccdb201c3d171ae80b4dfb1502" + }, + { + "file": "resources/js/FixedAssetsWorkspace.jsx", + "present": true, + "sha256": "f6654d710a8762d02c7bac563f1278c62ed113b00908a219bd9a54078fafa11e" + }, + { + "file": "packages/general-ledger/src/LedgerAssets.php", + "present": true, + "sha256": "aa5b0e7766c02587d28d55c8566b6f186ffbca60c180401ba0ead31f9a426cfe" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-matrix-final-20261104.json", + "present": true, + "sha256": "94cb37698f294c38df9b94a5535ba4b9ac63907f94dab1e7f951a3bb27623083", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_COVERAGE_DELIVERY.md", + "present": true, + "sha256": "4951a2514253c71710fd18ddbffcec1c4dca597355b1cf998b1c7f1b38078506", + "not_reexecuted": true + }, + { + "file": "docs/SGF_IVA_ASSESSMENT_DELIVERY.md", + "present": true, + "sha256": "b0a3ae64699352242108d782a229f0250344f238fad02e14e6fa4dbb520e997f", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_RULES_COMMON_DELIVERY.md", + "present": true, + "sha256": "10f8801d59dbdeb3f075e5bf2650e4538097a92151db516e3c0357ac011c8a5a", + "not_reexecuted": true + }, + { + "file": "docs/SGF_MULTI_LINE_INCLUDED_DELIVERY.md", + "present": true, + "sha256": "7b1d6bbdea15845cf7f4fe104057a4053be1c0f0edb2186da26d11dded45b690", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_ACCOUNTING_REPORTS_DELIVERY.md", + "present": true, + "sha256": "f910d6d11fd7f4e18a675fea9f07a50137bbed72d4718bb7ec6e022bdd1959e5", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_OBLIGATIONS_DELIVERY.md", + "present": true, + "sha256": "028e3172128435924dc9594b68b25560cb0b30495f27c522864a94a1f8cc9504", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_STATEMENTS_DELIVERY.md", + "present": true, + "sha256": "53a07e2c0517de5b6881584c598b2f1d1d5665422ac2f8f5932626822daf3359", + "not_reexecuted": true + }, + { + "file": "docs/SGF_COVERAGE_RECONCILIATION.md", + "present": true, + "sha256": "b1fb3722b5a9444699f713f7c8a6535eceb05977dacb194ce7dace0e1a4afa14", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-fixed-assets-20261111.json", + "present": true, + "sha256": "43aa2c20248a6e67506d47fdcbcf774bafd8cab5fae72f38726bcdad2cfb4319", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Backlog fiscal/financeiro amplo pendente; tabela de cobertura não implementa motores ou modelos", + "Activos gerais e tratamento fiscal/legal, alienação/contraprestação, correcções de custo já admitido e cadeias adicionais fora do recorte QA" + ] + }, + { + "id": "human_resources", + "label": "Recursos Humanos", + "features": [ + "Módulo autónomo com perfis QA próprios separados do Financeiro", + "Cadastro pseudónimo individual preparado e revisto", + "Regras artificiais versionadas com vigência e fonte explícitas", + "Processamento exacto por trabalhador/período com escolha de fontes e revisão distinta", + "Correcção/cancelamento versionados, prévia, histórico, recibos e recuperação", + "Mapa individual revisto conservado com SHA e exportação autenticada; filtros SQL25/100", + "Contratos internos QA revistos com correcção/terminação/cancelamento e dependências", + "Plano e assiduidade por dia com intervalos explícitos, dados desconhecidos e revisão distinta", + "Condição de férias QA, reserva/gozo/cancelamento individuais, saldo efectivo sem duplicação", + "Conferência parcial do período por pessoa, fontes/versões e CSV/SHA conservado", + "Ligação contratual explícita opcional em novas preparações salariais, provada na regressão sem alterar salários8085", + "Resumo CSV legível derivado de versão revista, com SHA próprio/original, sem alterar o artefacto conservado" + ], + "sources": [ + { + "file": "app/Modules/HumanResources/Remunerations.php", + "present": true, + "sha256": "950333ced35d6949487bdf78ef109fad608338525acfe60b05b9a320fd3aac01" + }, + { + "file": "app/Modules/HumanResources/Http/RemunerationsController.php", + "present": true, + "sha256": "3725145a623791a6c2825d8aaa4f122ba7b80908c08603211d6d9e74df76e801" + }, + { + "file": "app/Shared/Installation/HumanResourcesProfiles.php", + "present": true, + "sha256": "f908b8cf9f7121bb9b43733f507771a60aa34eb553df8342c250e5b39940ae93" + }, + { + "file": "resources/js/RemunerationsWorkspace.jsx", + "present": true, + "sha256": "edb225504bff359d5c53c040421e322bb4cd887ac946b3316852ecd1ed25b76e" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "2dcbea8a6523970211024dbac28743ce4a128b98808fd6f0d39b73cbf48457ff" + }, + { + "file": "app/Modules/HumanResources/Personnel.php", + "present": true, + "sha256": "da109cf709a6306f7c7a8a3f111514cb1e5c4d1dc0554e15e36022bf1fbb829b" + }, + { + "file": "app/Modules/HumanResources/PersonnelTime.php", + "present": true, + "sha256": "e0fc1bdecbbd24c1ac21a426d8547fc049c00fd18ae6faa7ded64128252bbfd6" + }, + { + "file": "app/Modules/HumanResources/Http/PersonnelController.php", + "present": true, + "sha256": "539fb7ce74dd3b87263d1610fee7ddac2028c4536bc8d549be6d0617c3f947a2" + }, + { + "file": "resources/js/PersonnelWorkspace.jsx", + "present": true, + "sha256": "3bff1b58a1f08c8065abaa68fd7547156f3537f573e0a8d4b922cd3c2cf7ae06" + }, + { + "file": "app/Modules/HumanResources/PersonnelReadableCsv.php", + "present": true, + "sha256": "5e31d7de8aa243b9f9578d859e05ab9071c06dc39bb8616f35eed085856c493c" + } + ], + "existing_evidence": [ + { + "file": "docs/SGF_HUMAN_RESOURCES_DELIVERY.md", + "present": true, + "sha256": "1494d29938565bb6f5d552385d7b32dd0a8f22856755bf638d81cf9a41a65f60", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-human-resources-20261111.json", + "present": true, + "sha256": "95d0c3fcd407cfbb03226288b94570478b906fdf3cb50c26a9960956f536cf29", + "not_reexecuted": true + }, + { + "file": "docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md", + "present": true, + "sha256": "045d111cd08ad58c22a802d6d728c7f300d91b4078b65c3ac61683791747a02e", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-hr-personnel-20261111.json", + "present": true, + "sha256": "b4ce53d6099a13acbe6bd2d7431f9109b8a318000f9d69813353349cd823cdc7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_HUMAN_RESOURCES_READABLE_DELIVERY.md", + "present": true, + "sha256": "18275d4fda727e0082379bcd17804397a13dc3c5f5a88575c48c4e6c8abf95b2", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conformidade e aplicabilidade laboral real: contratos legais, turnos/calendário, férias legais, benefícios e modelos", + "Cálculo legal IRPS/INSS e ligação a dívida/pagamento/Contabilidade sem efeitos implícitos", + "Revisão visual, carga sustentada, armazenamento e capacidade de implantação" + ] + } + ], + "release_gates": [ + { + "id": "single_installation", + "criterion": "Os seis módulos operam no mesmo endereço e empresa/unidade, sem mudar de demonstração", + "passed": false + }, + { + "id": "functional_modules", + "criterion": "Cada funcionalidade obrigatória tem ecrã/comando/consulta executável; menus e fontes isoladas não bastam", + "passed": false + }, + { + "id": "cross_module_flows", + "criterion": "Os documentos atravessam as fronteiras mantendo origem, versão, autoridade e efeito único", + "passed": false + }, + { + "id": "access_isolation", + "criterion": "Perfis explícitos e recusas de empresa/unidade/módulo/permissão verificadas", + "passed": false + }, + { + "id": "presentation", + "criterion": "Percurso de apresentação coerente, dados fictícios identificados, linguagem e navegação revistas", + "passed": false + }, + { + "id": "operations", + "criterion": "Arranque e recuperação da instalação entregues e ensaiados, sem depender do SGC", + "passed": false + }, + { + "id": "commercial_honesty", + "criterion": "Documentos de teste não são emissão fiscal; capacidade de implantação exige prova própria", + "passed": false + }, + { + "id": "preservation", + "criterion": "Seis registos, cache, dados aprovados e trabalho alheio preservados", + "passed": false + } + ], + "client_presentable": false, + "limits": [ + "Host-table footprint is a minimum projection, not a full migration/dependency validation.", + "Native purchasing/stock tables live in their existing separate native book; this scan does not call that missing.", + "Source/evidence presence does not mean a feature is integrated, visually accepted or deployment-ready." + ], + "database_writes": 0, + "application_bootstraps": 0, + "cache_cleared": false, + "new_demo": false, + "new_rights": false +} diff --git a/docs/proofs/sgf-solution-structure-v30-final-20261111.json b/docs/proofs/sgf-solution-structure-v30-final-20261111.json new file mode 100644 index 0000000..1c3a8ad --- /dev/null +++ b/docs/proofs/sgf-solution-structure-v30-final-20261111.json @@ -0,0 +1,1306 @@ +{ + "date": "2026-11-11", + "contract_sha256": "b16f26c6cab64f256a8e2a9be8778103f16d684d98127ebe34de221b60343ba3", + "installation": "cc50a276c10c", + "port": 8085, + "sgc_dependency": false, + "full_product_complete": false, + "product_backlog": [ + { + "id": "fiscal_books", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Livro/mapa interno QA de uma origem entregue; IVA geral, dedução/regularizações e restantes tratamentos pendentes" + }, + { + "id": "irps_inss", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "RH autónomo QA com cadastro/contratos/assiduidade/férias e remunerações internas entregue; regimes laborais gerais, IRPS/INSS/modelos legais e ligação financeira/contabilística salarial pendentes" + }, + { + "id": "irpc", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "IRPC operacional: ajustamentos, retenções, apuramento" + }, + { + "id": "ispc_ice", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Motores operacionais ISPC e ICE" + }, + { + "id": "official_forms", + "status": "pending", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Modelos oficiais com correspondências e reconciliação de campos" + }, + { + "id": "fiscal_calendar", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Controlo administrativo de prazo e entrega simulada de uma origem QA entregue; calendário legal, aplicabilidade geral, prazos por fonte/vigência e submissão continuam pendentes" + }, + { + "id": "bank_reconciliation", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Conferência de duas linhas/extracto artificial QA entregue; integração e reconciliação bancária gerais/reais pendentes" + }, + { + "id": "financial_analysis", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md sections10/11", + "description": "Dois critérios/indicadores QA com fontes conciliadas entregues; rácios gerais, critérios reais e tendências/séries permanecem pendentes" + }, + { + "id": "fixed_assets", + "status": "partial", + "implemented": false, + "basis": "docs/SERVICE_INVOICE_CALCULATION_SPEC.md FIS-SGF-15", + "description": "Ciclo integrado QA entregue na8085: cadastro/plano/quota/diário/relatórios e ramos de correcção/abate/fecho do recorte na regressão. Operação geral/legal de activos e taxas fiscais continuam pendentes.", + "scope": "docs/SGF_FIXED_ASSETS_SCOPE.md", + "access_proposal": "docs/integration/sgf-fixed-assets-access-proposal.json", + "qa_implemented": true, + "delivery": "docs/SGF_FIXED_ASSETS_DELIVERY.md" + } + ], + "modules": [ + { + "id": "commercial", + "label": "Comercial", + "features": [ + "Clientes e serviços com associação explícita", + "Rascunho, revisão e cálculo", + "Documento emitido de teste e PDF", + "Continuação para recebível sem duplicação", + "Orçamentos Comercial/Oficina, revisão e aceitação documentadas e conversão explícita em rascunho", + "Recibo de afectação efectiva400, crédito nominal100/saldo500 e factura-recibo1000 atómica", + "PDF/SHA, histórico e anulação técnica com reversão explícita e diário único", + "Registo unificado autorizado com seis famílias, filtros/SQL25/100 e projecções limitadas à página", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Editor unificado1–25 serviços, imposto acrescido/incluído, cálculo comum revisto, PDF e passagem explícita recebível/diário QA" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceWorkflow.php", + "present": true, + "sha256": "6ea92e6216bec9a0062d9f152d1b758a5527360456589a98f525429402cb94da" + }, + { + "file": "app/Modules/Services/ServiceInvoicing.php", + "present": true, + "sha256": "e3dc7aa479a91bbbab94e1257fd51da649bdfd79223f6c6f1c9885f9314bac9b" + }, + { + "file": "resources/js/ServicesWorkspace.jsx", + "present": true, + "sha256": "c170b195ccd78aa74f283a91b0b4f4cd93aea4677e3aaefe0b50af46f7e31669" + }, + { + "file": "resources/js/ServiceInvoiceWorkspace.jsx", + "present": true, + "sha256": "8f95d1775b30a6de73d65eed428595d74cc837755d8157bf8db49a1151350277" + }, + { + "file": "app/Modules/Services/ServiceDocuments.php", + "present": true, + "sha256": "816a85be48b05043f41f9c7d5f842a3fa139b21aaedf9c944d811f74be6f6208" + }, + { + "file": "app/Modules/Services/DocumentPeriod.php", + "present": true, + "sha256": "100c6cf038b925126ddc8fb50adde2f6d938b4d68df6d0ab193f16ea28836ae2" + }, + { + "file": "app/Modules/Services/BillingRegister.php", + "present": true, + "sha256": "115e7832b63e5c29be716140629845137d951da7790421832e1ab4058fb468cc" + }, + { + "file": "resources/js/BillingRegisterWorkspace.jsx", + "present": true, + "sha256": "9ab8d036c9e6d20c588427f5a8c44677810e9713722baea908b8f6b76c8b79f9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Services/MultiLineInvoiceCalculation.php", + "present": true, + "sha256": "4ee0fb5ad5a05b5e6ba2c792b6940dc5d9ff69fcbfa13ea81b391372b067beda" + }, + { + "file": "app/Modules/Services/MultiLineInvoiceIssuance.php", + "present": true, + "sha256": "2dc530ebb27f28e5fb7cc3f6347616a9c0a0a248581f29b905602ea443671f92" + }, + { + "file": "app/Modules/Services/MultiLineDrafts.php", + "present": true, + "sha256": "7dd57dcb2016b3e4d5e09cadefde791a83060c4713c76854c2f02151bbe8969a" + }, + { + "file": "app/Modules/Services/QualifiedInvoiceSource.php", + "present": true, + "sha256": "7e8348d279943b16c3e51d2edd761d825c703d68cf2a4265a65684dbe2edae8c" + }, + { + "file": "resources/js/MultiLineInvoiceWorkspace.jsx", + "present": true, + "sha256": "4ea1b6359aa1e42c6d243b45120864ce537a4a54d57d2ec1f7fdf99713fd54d3" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/invoice-test-results.json", + "present": true, + "sha256": "fd04a58727ffddf1c62ac184d7834b81228dce2abb5b0c1e397505df6a45237c", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_SERVICE_DOCUMENTS_DELIVERY.md", + "present": true, + "sha256": "a13713f05feaa84766dfaba870e72c98d23aa6223389d1627de911b0856cd15d", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BILLING_REGISTER_DELIVERY.md", + "present": true, + "sha256": "fec1b76003187d5f38c8aca8804c26129e312e517dd5b2247f54f0019c2cccf6", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_MULTI_LINE_INVOICE_DELIVERY.md", + "present": true, + "sha256": "d794217e8aede14e91f37cfad04f2cf8f1e61f1e4fdc4fa63886e814d9a62c6c", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-multi-line-invoice-20261111.json", + "present": true, + "sha256": "b7190498348c2a5a5a5ced30e5e48a4750f56872ad31a3ebc97e647f6f7654f1", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual, aceitação explícita e conformidade real; emissão fiscal real permanece bloqueada", + "Carga sustentada, armazenamento e capacidade de implantação exigem evidências próprias" + ] + }, + { + "id": "purchasing", + "label": "Compras", + "features": [ + "Fornecedores e revisão distinta", + "Requisição até adjudicação e ordem", + "Recepção, documento e stock único", + "Factura conferida e obrigação reconhecida", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes" + ], + "sources": [ + { + "file": "app/Modules/Purchasing/ReceiptRuntime.php", + "present": true, + "sha256": "b08af1f8772cbfafa1f85e37c832f2c6916ccabcfbd4a8bb98c5ef7690c3f07f" + }, + { + "file": "app/Modules/Purchasing/RequestController.php", + "present": true, + "sha256": "c5c2b78866be7f0e1c8ba09c1af538cefcfbafbea81197833b50821bba0fcbca" + }, + { + "file": "app/Modules/Purchasing/InvoiceController.php", + "present": true, + "sha256": "85eb46ca7e65c60bed190e592a134eb48db5762c0a90b8cd4c9d6fbd81198ba4" + }, + { + "file": "app/Modules/Purchasing/ObligationController.php", + "present": true, + "sha256": "347e629306d6c1f4873afac547d13fae8ade7b268906d1312a64e7eb6998e701" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-final-20261104-r2.json", + "present": true, + "sha256": "3b63f8653e8dbb089dfdd8f389e271c022a070d5a67a9f9d4585ce84438070cc", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ], + "accepted_component_journey": true + }, + { + "id": "stock", + "label": "Stock", + "features": [ + "Artigos, armazéns e saldos físicos", + "Entradas, saídas e origem autorizada", + "Transferência e conferência de destino", + "Contagens, ajustamentos e devoluções", + "Movimentos e relatórios por período", + "Política QA revista e devolução ao fornecedor por origem exacta; saldo8UN/160MZN no mesmo diário", + "Inactividade de pares com história conhecida, corte UTC explícito, filtros e SQL25/100 sem inferir ausência de actividade", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Cadastro de artigos: preparação/revisão distintas, criação sem saldo, designação revista só dos novos, código/unidade imutáveis, histórico/recibos e catálogo canónico único" + ], + "sources": [ + { + "file": "app/Modules/Inventory/StockReadController.php", + "present": true, + "sha256": "fd02da1a69f76bf1412f86f43095a0a476de49fbb7d2676a51fe3b29ec66e298" + }, + { + "file": "app/Modules/Inventory/CompositionStockRead.php", + "present": true, + "sha256": "81bdb5fb374a60974bbad421c461cd12079951fb426361200692a838e413b49e" + }, + { + "file": "app/Modules/Inventory/StockPeriodReport.php", + "present": true, + "sha256": "d491541c6c60bc0c661dc442b8db8ce2e757f1d63dfc4982a3631f820cfb7a06" + }, + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "app/Modules/Inventory/StockOperationsController.php", + "present": true, + "sha256": "34d6c7be4787f4f18ca2a7aa322f6f33dfb281e22336599234c93c655ab4eebf" + }, + { + "file": "resources/views/stock-operations-workspace.blade.php", + "present": true, + "sha256": "5db67c2d23e5557548c38c82a0d7f6e373651849727b2237dcd73f620309c25a" + }, + { + "file": "public/stock-operations-workspace.mjs", + "present": true, + "sha256": "56734499e8dd89da293c2edadd0d0e10ac6d06b39f0365e5ae29a8d14e9176d8" + }, + { + "file": "app/Modules/Inventory/StockFinalController.php", + "present": true, + "sha256": "9d9a242fd8e143fe076ece0454d0789eabf543e37b332db4806d905fad13cdda" + }, + { + "file": "resources/views/stock-final-workspace.blade.php", + "present": true, + "sha256": "93764471a215735a65e5c07121989b1e2f6499f631a11b9f1177bb0fe8def232" + }, + { + "file": "public/sgf-stock-final.mjs", + "present": true, + "sha256": "77860b080015528fe4dd84b2e71787ee668da9be17653c64a5d8e64539b5e592" + }, + { + "file": "app/Modules/Inventory/StockInactiveReport.php", + "present": true, + "sha256": "7db06225fb7bb40c9a4140383caef6dd96c430e3a3e1be8ef7fed2253f2681e2" + }, + { + "file": "resources/js/StockInactiveReportWorkspace.jsx", + "present": true, + "sha256": "5d4f7222bf325550cebddbd00198515da55f15f52b4c99ecaef19f45820a165e" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Inventory/StockCatalogueController.php", + "present": true, + "sha256": "ab0a6bc52e635d5bcdee9163b68b1ec6fc14b717de7b887fcc832336961fa128" + }, + { + "file": "resources/views/stock-catalogue-workspace.blade.php", + "present": true, + "sha256": "4381f81a9aea04fe851f8c7af17cafb98f6bef7cbf52ac2630b0b9912a3dca57" + }, + { + "file": "public/sgf-stock-catalogue.mjs", + "present": true, + "sha256": "10eedcf1881ad3f1cb71813f0547eae7246795273457cc33b1a2a6761a0a2d6e" + }, + { + "file": "config/stock_catalogue.php", + "present": true, + "sha256": "811f844cecf76bff62da943042a272301cb8a7f7a586c4f2648ef5b8e5e31dae" + }, + { + "file": "database/sql/stock_catalogue.sql", + "present": true, + "sha256": "c9c99cec20e6ca60cf0ee56210eac9a2365a2ef9b482698929a70295d2893809" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/stock-period-report-20261103.json", + "present": true, + "sha256": "0ab25af6ffbff7d8ecb651df833771b082bf92d52f97eca4278fcac3fd5ea53b", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-stock-operations-20261110.json", + "present": true, + "sha256": "1d241fcfb81fcf6c1b374f9fc89c1f2130b17eee1314f06b4b8e4b59c476fc08", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_POLICIES_SUPPLIER_RETURN.md", + "present": true, + "sha256": "b8848090f1e19a6b7c19b684e07c0c95cafcf93bd1551357f6afc314f4413d8b", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_INACTIVITY_DELIVERY.md", + "present": true, + "sha256": "8773d53140b291c01358b8e71c584373cd0e440f67d0107e64ecea13980990d3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_STOCK_CATALOGUE_DELIVERY.md", + "present": true, + "sha256": "43ce839f882aeeab48b272d5eb1a4e34fcd855052db88643a8ca7b08556631f8", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Revisão visual dos novos relatórios e aceitação explícita do conjunto; carga sustentada e implantação separadas" + ] + }, + { + "id": "workshop", + "label": "Oficinas", + "features": [ + "Ordem e autorização", + "Apontamentos e revisão distinta", + "Material consumido ligado ao movimento físico", + "Custo directo/industrial e correcção", + "Continuação comercial por documento de origem", + "Agenda sem sobreposição, reagendamento e cancelamento versionados", + "Cadastro independente e reutilização de equipamento por cliente", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Ordens SQL25/100 por cliente/equipamento/responsável/estado, apontamentos actuais e minutos verificados, sem mutex de escrita na leitura", + "Relatório por período de abertura explícito/dia local, SQL25/100 antes de paginar, apontamentos actuais e CSV da página sem fecho histórico presumido", + "Equipamento escolhido → ordens exactas SQL25/100 com filtros, identificação actual, CSV da página e regresso ao catálogo" + ], + "sources": [ + { + "file": "app/Modules/Workshop/WorkshopStock.php", + "present": true, + "sha256": "c32024ab178a3db29881902199df4e313be34dd770fc14d55a537656bf6f8fa6" + }, + { + "file": "resources/js/WorkshopWorkspace.jsx", + "present": true, + "sha256": "a1ceca33c234a759b2abf13b087bba8d18d70187ad9161e59425a5932771f8bb" + }, + { + "file": "resources/js/DirectCostsWorkspace.jsx", + "present": true, + "sha256": "dcaf72fe36d80e16bab8e962f939168650336d1450fc3eb01693acb7ef7e0743" + }, + { + "file": "resources/js/IndustrialCostsWorkspace.jsx", + "present": true, + "sha256": "51a3307fa8b2526efa12d8a16fee613bdfe141d02bd6e5af4535d581ed69c6d0" + }, + { + "file": "resources/js/WorkshopBillingWorkspace.jsx", + "present": true, + "sha256": "601853bd383ef2d1891158e89578bbc7f26ae1c7220bc11ddfadf1868cb63afc" + }, + { + "file": "app/Modules/Workshop/ComposedWorkshopStockBook.php", + "present": true, + "sha256": "01e7392827a6453a357a0b5140905757bbee76f57fae6aa9906490788690e24c" + }, + { + "file": "app/Modules/Workshop/WorkshopBilling.php", + "present": true, + "sha256": "0eac2d91fd9f006aa8c198e3652731142aee478c93de090c408df54cdd9d292d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Workshop/WorkshopOrderRead.php", + "present": true, + "sha256": "f741a5ac6c16376ea26b1bacd7b5e706b14a4548a26f59c4a96ec882fb6a6fa5" + }, + { + "file": "app/Modules/Workshop/Http/WorkshopOrderReadController.php", + "present": true, + "sha256": "f4dd027d01c0730603f3b312c2eb263740f6ebe1c969c762ed99d19520fab584" + }, + { + "file": "resources/js/WorkshopOrderList.jsx", + "present": true, + "sha256": "4cad148295acb7c462425fa300dccc7c203255f5f616163575e205bfc8a80e34" + }, + { + "file": "resources/js/workshop-report-view.js", + "present": true, + "sha256": "28125af02e25b4befb9914c5dee753dae4164100f3c0f94988c332a50021bf9f" + }, + { + "file": "app/Modules/Workshop/WorkshopEquipment.php", + "present": true, + "sha256": "37223b732865f273831d0bf61a464137cc53526a4c55750347450ba20bcbbc37" + }, + { + "file": "resources/js/WorkshopEquipmentWorkspace.jsx", + "present": true, + "sha256": "450af001d385f42cd4d0ae42b34fa6b373cdd6811d94f8f4a1f5de2c591ed843" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/workshop-stock-closure-20261102.json", + "present": true, + "sha256": "261d5502f6905d53ef8046e0e900c9bee159c8c9d7e1bae4fddd8ec698313bf7", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_AGENDA_EQUIPMENT.md", + "present": true, + "sha256": "3a44a1b6ac5148013c815cc6cc8919f08c15ac911f900aac5b4a0af903589481", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_PERIOD_REPORT_DELIVERY.md", + "present": true, + "sha256": "1455e3dc4203c81f49ddaba05a7bf7de10cfd9b3bb4dbd2eb4480aa05b7ccf08", + "not_reexecuted": true + }, + { + "file": "docs/SGF_WORKSHOP_EQUIPMENT_ORDERS_DELIVERY.md", + "present": true, + "sha256": "4d4249613a1b3fc339f342c3854eda08bf1ec280c0681eb3b2551469b2da2b36", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md" + ] + }, + { + "id": "finance", + "label": "Financeiro", + "features": [ + "Obrigações a fornecedores", + "Recebíveis de clientes", + "Liquidação/afectação e reversão sintéticas", + "Posição financeira e comprovativos por origem", + "Caixa QA explícita0+1000-50=950, revisão distinta, contagem e fecho sem reabertura", + "Extracto paginado, recuperação de confirmação e classificação/despesa no mesmo diário", + "Extracto artificial QA e correspondências individuais com duas linhas nativas existentes, revisão distinta, conferência final e relatório", + "Critérios e indicadores QA950/100=9,5 vezes e50/1000=5%, revisão distinta, fontes conciliadas/retrato/fracção/histórico/relatório", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Posições actuais de clientes por documento, pesquisa/estado/vencimento, SQL25/100, saldos reconhecidos e CSV da página; referência de vencimento distinta de saldo histórico", + "Posições de fornecedores por factura/fornecedor e estado documental, SQL25/100, saldos do motor original, CSV da página e abertura exacta do documento; vencimento desconhecido sem atraso presumido", + "Condições administrativas QA de vencimento: preparação13/revisão14, evidência/fundamento, prévia/revalidação de origem/saldo/versão/pessoa, recusa/correcção, histórico25/100, recibo e recuperação; lista/CSV separam original e condição revista, referência explícita no detalhe", + "Mapa de antiguidade de fornecedores por referência explícita e condição administrativa revista, faixas0/1–30/31–60/61–90/>90/desconhecido, valores exactos por moeda só da página/CSV, sem alterar saldo ou vencimento original" + ], + "sources": [ + { + "file": "app/Modules/Services/ServiceReceivables.php", + "present": true, + "sha256": "d8c23404ee7bd9ad4e008583e74709de6e504d013c9b03a95b56ffbed59194af" + }, + { + "file": "app/Modules/Purchasing/SupplierPayables.php", + "present": true, + "sha256": "c17d44391cbd96941aa2dd2eac2cb167508faf6453c6a1e86333846df1ba18b9" + }, + { + "file": "resources/js/ServiceReceivablesWorkspace.jsx", + "present": true, + "sha256": "6e654a03cb47e52dedd52ce6b2abf89a61d22860e4ffae92fd393027a54cc445" + }, + { + "file": "app/Modules/Purchasing/ComposedFinance.php", + "present": true, + "sha256": "ad7e837c7608ab85c59eb169bcdd986b08c5802a0f218926e1abd7568e8a4b16" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "app/Modules/Finance/CashWorkspace.php", + "present": true, + "sha256": "bb65060461cea3a844cf523733644cdd630f35ba02c82aeed961821ec5970800" + }, + { + "file": "app/Modules/Finance/Http/CashController.php", + "present": true, + "sha256": "982c161f3ef7314a9671f848f0da2aefb27841e40003ef47102d0d956c08cd78" + }, + { + "file": "app/Modules/Accounting/CashLedgerSource.php", + "present": true, + "sha256": "3a46162f0a62363dd4015f0047a5bff95e8aebd8dd3007fc705daaf58c10684b" + }, + { + "file": "app/Modules/Finance/BankReconciliation.php", + "present": true, + "sha256": "5421141bcc6e15bda142687661774238c7203c85d0ef30696fd4d56dd5b9e328" + }, + { + "file": "app/Modules/Finance/BankLedgerSource.php", + "present": true, + "sha256": "f33779fa736aea7027d4cf449ba85c5982bfb565c75bbb2a477d7a2666820338" + }, + { + "file": "app/Modules/Finance/Http/BankReconciliationController.php", + "present": true, + "sha256": "896d710db108364263a1404d2aafdc52eff4d3b6efcbe3dbdbcca1978befffe4" + }, + { + "file": "public/sgf-bank.mjs", + "present": true, + "sha256": "959214ad94bb5c29ffdaf1c8fd3e59bf8718837d4c1fe9bea52d3b04c261a80b" + }, + { + "file": "app/Modules/Finance/FinancialIndicators.php", + "present": true, + "sha256": "9785a801c5fb13b34770bdd05c828f5e924058eb872a7b33bc1db139a3024d10" + }, + { + "file": "app/Modules/Finance/FinancialIndicatorSource.php", + "present": true, + "sha256": "72039264cbb25ae2eaa5eaa4af488aabf7061a4a298c09490b8957b4ade55731" + }, + { + "file": "app/Modules/Finance/Http/FinancialIndicatorsController.php", + "present": true, + "sha256": "5245064e40b884e432afa2fd26d45fddccebf6963ef3366f585b93ee78f12cca" + }, + { + "file": "public/sgf-indicators.mjs", + "present": true, + "sha256": "62442edc14da534e0166cc65074389e74cc67246cd5922994628644fd900cbb9" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "app/Modules/Services/ReceivablePositionRead.php", + "present": true, + "sha256": "6aaf0211c4427643c84152edc6a7937dbf48387e2844d3bbe31328d1f6789f2d" + }, + { + "file": "app/Modules/Services/Http/ReceivablePositionReadController.php", + "present": true, + "sha256": "9e8be7c2cd328aebb31cbe0be4eeaaa6abc02d5bff0895aeb90c24ab8d134541" + }, + { + "file": "resources/js/ReceivablePositionsWorkspace.jsx", + "present": true, + "sha256": "5bd8ee952aeee87d733ab434cfe50b675d84c0341ade4f354c93e4acfdbe57e9" + }, + { + "file": "resources/js/receivable-position-view.js", + "present": true, + "sha256": "08f8cb6d17b79b89ee8cebe4723458c1a4979d8fb824afa0b585ae51076e6f21" + }, + { + "file": "app/Modules/Purchasing/PayablePositionReadController.php", + "present": true, + "sha256": "1d147f14826793f97605ea442c6a93612a55caf265311a86824611650dfc0005" + }, + { + "file": "app/Modules/Purchasing/ComposedFinanceController.php", + "present": true, + "sha256": "14f8e440d8bd383913129586338521313952f2f14136a34871253dec0fa6b4b4" + }, + { + "file": "resources/js/PayablePositionsWorkspace.jsx", + "present": true, + "sha256": "e52fa90276e6251b38bb48f83a11ec2b11ac819ffa3c2239da9712e50ce66343" + }, + { + "file": "resources/js/payable-position-view.js", + "present": true, + "sha256": "ff4b32f404cd575ddc134cf373ae6ded5d0abae8641b14b591c0f5a856437938" + }, + { + "file": "app/Modules/Purchasing/PayableDueController.php", + "present": true, + "sha256": "39e4c95b910d3b1c4aa227c7c2aa0ee24399921b9578748b95ff5aa519f34db7" + }, + { + "file": "resources/js/PayableDueWorkspace.jsx", + "present": true, + "sha256": "c7127bbb2edc6c5acce57a55a6c3c5b94969170ccf1adb7236fee80d45f555ae" + }, + { + "file": "database/sql/payable_due.sql", + "present": true, + "sha256": "faefce9f2c11da613293e23bbc2f630bdae345d872cca7f3c2923c77fea0f75e" + }, + { + "file": "config/payable_due.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Purchasing/PayableAge.php", + "present": true, + "sha256": "19f4a3994b679b74275e72e906251c8d32ee9950ebedfae07bd4002abe740a9e" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/supplier-settlements.json", + "present": true, + "sha256": "b1bce9218f99ea2d8c780022ad385e169c7acc5778ac6950236eef0630f77ad0", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_CASH_DELIVERY.md", + "present": true, + "sha256": "bfb40ff8849eb4d9fcb641aa9b8d07bbaab89326a0f89b8f113cf0bf62653093", + "not_reexecuted": true + }, + { + "file": "docs/SGF_BANK_RECONCILIATION_DELIVERY.md", + "present": true, + "sha256": "acf53f7dfd2ec0f501cef8b1623e090c1e1b8a07a8591f4f042fb8a0b7b57273", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_INDICATORS_DELIVERY.md", + "present": true, + "sha256": "9902e37504dd9f5e1d3c73cd78a283e29be00ffb33ce13ca8f76577d1163cc65", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_RECEIVABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "7a829a6dcae28fa42e996874b7b65ed1de325dbab5bae12a28731ecfc2e849d7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_POSITIONS_DELIVERY.md", + "present": true, + "sha256": "2c0a25051540527e87ca9abfa43db5efb288e0b40e2315e06f6084693ce9c8c3", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_DUE_DELIVERY.md", + "present": true, + "sha256": "614ca3b1c432760e1911112951f138cf72d5d8370173336472529e9549e965b8", + "not_reexecuted": true + }, + { + "file": "docs/SGF_PAYABLE_AGING_DELIVERY.md", + "present": true, + "sha256": "a0af96108702bbb687a655de744e78e01a3e7c98365244410eb291fcb7a5fafa", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Conferência de extracto QA entregue; reconciliação bancária geral/real permanece fora do recorte" + ] + }, + { + "id": "accounting", + "label": "Contabilidade", + "features": [ + "Plano e classificação", + "Diário com preparação/revisão distintas", + "Razão e balancete", + "Estorno/correcção e conferência de período", + "Fecho sintético com rastreabilidade de origens", + "Livro de saídas e mapa interno IVA QA da origem FR seleccionada, revisão distinta, histórico e CSV congelado", + "Catálogo de fontes/regras versionadas QA, revisão distinta, vigência/conflitos/retirada e cálculo explicável com história preservada", + "Cálculo QA por1–25linhas sem/com imposto incluído, representação por linha aprovada, exactos/diferenças e mapa interno recuperável", + "Início operacional com percurso, evidências, funções autorizadas e consulta explícita das fontes existentes", + "Submenus distintos e balancete/razão por datas explícitas, escolha de conta por linha, páginas25/100, detecção de mudança de livro e CSV da página", + "Controlo administrativo QA de obrigação/prazo/responsável do mapa interno revisto, preparação13/revisão14, correcção/cancelamento, entrega apenas simulada com SHA verificado, histórico/recibo/SQL25/100/CSV; sem calendário legal ou submissão", + "Correspondências explícitas revistas15/16 e balanço/resultados internos parciais QA:19contas/nove pendentes, resultado congelado19linhas/SHA, história/recibo/CSV contextual, ligação exacta ao razão; capital e resultado completo não presumidos", + "Activos QA: cadastro/custódia/plano/quota revistos, diário único1200/100 e líquido1100; correcção/abate/fecho do recorte na regressão" + ], + "sources": [ + { + "file": "app/Modules/Accounting/GeneralLedger.php", + "present": true, + "sha256": "a87d2ea373bc61bb139aa29f2a354b9813bbb2d6229a389f8d37fd8e9890edec" + }, + { + "file": "app/Modules/Accounting/GeneralLedgerController.php", + "present": true, + "sha256": "afd2f4ae55e694f0aff18233a6f5cf52444e185aaf5a159865f74f0243783f2a" + }, + { + "file": "packages/general-ledger/src/LedgerSchema.php", + "present": true, + "sha256": "d9c203286b3a04121c265dffe412d2e86726ec4bb9222a2ed654d0296d86b5d8" + }, + { + "file": "app/Modules/Accounting/ComposedLedger.php", + "present": true, + "sha256": "25e14af7f62dea2f205f9946135fb016580d1ca2446c9303313d76c2ccd99e04" + }, + { + "file": "app/Modules/Accounting/ComposedLedgerController.php", + "present": true, + "sha256": "f3ff3ad5a3d9e501473915792708d3dbc96b36d3252d609274029e901cbb42e4" + }, + { + "file": "packages/general-ledger/src/StockTransitEvidence.php", + "present": true, + "sha256": "eb8eee3bb293e77689eb6470ac8aa12049b2ff8cfb8694d3e15eb17e097d2da6" + }, + { + "file": "app/Modules/Services/FiscalCoverage.php", + "present": true, + "sha256": "592d56e2e19a21c9955eddf4bd2c83af610f87536bc0d6ed85b034b797bf2791" + }, + { + "file": "app/Modules/Services/FiscalAssessment.php", + "present": true, + "sha256": "c2dbe192e6bba75f6fc4057bbb51c85bcf024a674cfeb7950ba239e700213128" + }, + { + "file": "app/Modules/Services/Http/FiscalAssessmentController.php", + "present": true, + "sha256": "425278110af20393e71de446f9b0ce41408352216249e5055de4165b075631e4" + }, + { + "file": "public/sgf-iva.mjs", + "present": true, + "sha256": "1d8c2a07f606799257c090ab2ddb52c309553a15009d6fb457dbbf4770c51ee6" + }, + { + "file": "app/Modules/Services/FiscalRules.php", + "present": true, + "sha256": "9b4a5e787a9ca2e06fa5e33d1ab4aacbda0eb257e4852c5a27f3da7e77c89b53" + }, + { + "file": "app/Modules/Services/FiscalRuleExpression.php", + "present": true, + "sha256": "108d8401c9c2bc85b3e5f663322c3d36c267afd8529b370e7e16c1d565a3daef" + }, + { + "file": "app/Modules/Services/Http/FiscalRulesController.php", + "present": true, + "sha256": "cc7e05e18c4bbc98a70a19eabdd653bbb44a717214f8d8ee19cd2ffeb1c032c9" + }, + { + "file": "public/sgf-fiscal-rules.mjs", + "present": true, + "sha256": "5c865e018c5b7d3b3f1232b80e81da379c729ea816a9e08a73225e76a678502c" + }, + { + "file": "app/Modules/Services/FiscalDocumentMath.php", + "present": true, + "sha256": "6e67b7b1363d24b38ca0dd40c4349dba82a4b746965f0af8aa7d65bb4f8f731d" + }, + { + "file": "resources/js/WorkspaceHome.jsx", + "present": true, + "sha256": "32bf2871a52ad5a5632aec4c2eb74207f49a8765d351ebfa5c14b475d54ebd4f" + }, + { + "file": "resources/js/workspace-overview.js", + "present": true, + "sha256": "3778aeab3b5d98124d9741d91207a4c253de7a3a1396bc6b7f1f4886bc42a7ad" + }, + { + "file": "resources/js/ComposedAccountingWorkspace.jsx", + "present": true, + "sha256": "9288df122be42e436bb4aeffc3e84cc1687ba82ab4cab07754d27d507d0012ac" + }, + { + "file": "resources/js/LedgerReportsWorkspace.jsx", + "present": true, + "sha256": "312157b80599e0f4407520ec968460af9c31913f79365ef6efa9a243c98ba419" + }, + { + "file": "resources/js/ledger-report-view.js", + "present": true, + "sha256": "54a1eb09ab4b451b4aa34d8b0c1ebc3d8cea91da4f242ea25a0c4b3bb1db79b4" + }, + { + "file": "app/Modules/Services/FiscalObligations.php", + "present": true, + "sha256": "bcf24717cf5828fed440b8e86f29a5ed4d325d392000240b224d155954170d1d" + }, + { + "file": "app/Modules/Services/Http/FiscalObligationsController.php", + "present": true, + "sha256": "95b03b505e588333206766b862d0f0cff006b1af1a3dc0466370ac6a7518e097" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "resources/js/fiscal-obligations-view.js", + "present": true, + "sha256": "d3c1aafddac987de0a8e432746533da8832c140474f9d9d49055020f7c9e67dd" + }, + { + "file": "database/sql/fiscal_obligations.sql", + "present": true, + "sha256": "c68988342ca1ed5817485355844c2f502f5c2ca12954461c1555d5c5f2abf7fe" + }, + { + "file": "config/fiscal_obligations.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Accounting/InternalStatementsController.php", + "present": true, + "sha256": "b05990c7fdb195957808b63a77599eaaabed5c5eb435dd8e7dd13d662c22fd8d" + }, + { + "file": "app/Modules/Accounting/InternalStatementTotals.php", + "present": true, + "sha256": "f13f6f356100e49786869196c54343fd629f6a1d38fa81665eb768dae4731601" + }, + { + "file": "resources/js/InternalStatementsWorkspace.jsx", + "present": true, + "sha256": "2fadac08cd6162dd06d056bfc4fb00479e9a5d930fdde10c1a4b57507982e76e" + }, + { + "file": "resources/js/internal-statements-view.js", + "present": true, + "sha256": "f1f7b44f2dc1679e537d77522e0fc5ee84da23068ef3157677bc20a8409d3464" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "2dcbea8a6523970211024dbac28743ce4a128b98808fd6f0d39b73cbf48457ff" + }, + { + "file": "resources/js/FiscalObligationsWorkspace.jsx", + "present": true, + "sha256": "77e6741581305739000e6ac4923f350464338d93346a860bf840ff08d7740b90" + }, + { + "file": "database/sql/internal_statements.sql", + "present": true, + "sha256": "2beccb0bb7d91f48e5a5e386c1e010fb7d95cbfa90aa35a68e28dc63ae6a6f6b" + }, + { + "file": "config/internal_statements.php", + "present": true, + "sha256": "39fc036c111c443ee2bc8fac28d94dde055083b2abfb955d5aa9299b90b26cdb" + }, + { + "file": "app/Modules/Accounting/FixedAssets.php", + "present": true, + "sha256": "390051a6bf8d7fc02cad111b707ea4bceba1c8d39986af146370c0191d6de760" + }, + { + "file": "app/Modules/Accounting/FixedAssetLedgerSource.php", + "present": true, + "sha256": "3facfc05db0636f01fd84f97f76d19cd2efd1599dfe4ab20a53ca4c8fe077f9c" + }, + { + "file": "app/Modules/Accounting/FixedAssetJournalController.php", + "present": true, + "sha256": "eba98630909ee5f7b5cd18718dbebf599b408eccdb201c3d171ae80b4dfb1502" + }, + { + "file": "resources/js/FixedAssetsWorkspace.jsx", + "present": true, + "sha256": "f6654d710a8762d02c7bac563f1278c62ed113b00908a219bd9a54078fafa11e" + }, + { + "file": "packages/general-ledger/src/LedgerAssets.php", + "present": true, + "sha256": "aa5b0e7766c02587d28d55c8566b6f186ffbca60c180401ba0ead31f9a426cfe" + } + ], + "existing_evidence": [ + { + "file": "docs/proofs/purchasing-completion-matrix-final-20261104.json", + "present": true, + "sha256": "94cb37698f294c38df9b94a5535ba4b9ac63907f94dab1e7f951a3bb27623083", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-six-module-functional-review-20261110.json", + "present": true, + "sha256": "64e49bf8681f7555bbff1ccfcb12ad8f42056833c964f3a3a1354ba8160abed7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_COVERAGE_DELIVERY.md", + "present": true, + "sha256": "4951a2514253c71710fd18ddbffcec1c4dca597355b1cf998b1c7f1b38078506", + "not_reexecuted": true + }, + { + "file": "docs/SGF_IVA_ASSESSMENT_DELIVERY.md", + "present": true, + "sha256": "b0a3ae64699352242108d782a229f0250344f238fad02e14e6fa4dbb520e997f", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_RULES_COMMON_DELIVERY.md", + "present": true, + "sha256": "10f8801d59dbdeb3f075e5bf2650e4538097a92151db516e3c0357ac011c8a5a", + "not_reexecuted": true + }, + { + "file": "docs/SGF_MULTI_LINE_INCLUDED_DELIVERY.md", + "present": true, + "sha256": "7b1d6bbdea15845cf7f4fe104057a4053be1c0f0edb2186da26d11dded45b690", + "not_reexecuted": true + }, + { + "file": "docs/SGF_OPERATIONAL_INFORMATION_DELIVERY.md", + "present": true, + "sha256": "a4c82f636f5c78874a6cc89bbc42e115c8cc48df0fb957f4a8bc81472d9738f0", + "not_reexecuted": true + }, + { + "file": "docs/SGF_ACCOUNTING_REPORTS_DELIVERY.md", + "present": true, + "sha256": "f910d6d11fd7f4e18a675fea9f07a50137bbed72d4718bb7ec6e022bdd1959e5", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FISCAL_OBLIGATIONS_DELIVERY.md", + "present": true, + "sha256": "028e3172128435924dc9594b68b25560cb0b30495f27c522864a94a1f8cc9504", + "not_reexecuted": true + }, + { + "file": "docs/SGF_FINANCIAL_STATEMENTS_DELIVERY.md", + "present": true, + "sha256": "53a07e2c0517de5b6881584c598b2f1d1d5665422ac2f8f5932626822daf3359", + "not_reexecuted": true + }, + { + "file": "docs/SGF_COVERAGE_RECONCILIATION.md", + "present": true, + "sha256": "b1fb3722b5a9444699f713f7c8a6535eceb05977dacb194ce7dace0e1a4afa14", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-fixed-assets-20261111.json", + "present": true, + "sha256": "64c88d287dc75c14b2c4194fd862984fd3fe6765aa72fae599bfefc84959a695", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conferência visual restante e aceitação explícita do conjunto; ver SGF_SIX_MODULE_CLOSURE_20261110.md", + "Backlog fiscal/financeiro amplo pendente; tabela de cobertura não implementa motores ou modelos", + "Activos gerais e tratamento fiscal/legal, alienação/contraprestação, correcções de custo já admitido e cadeias adicionais fora do recorte QA" + ] + }, + { + "id": "human_resources", + "label": "Recursos Humanos", + "features": [ + "Módulo autónomo com perfis QA próprios separados do Financeiro", + "Cadastro pseudónimo individual preparado e revisto", + "Regras artificiais versionadas com vigência e fonte explícitas", + "Processamento exacto por trabalhador/período com escolha de fontes e revisão distinta", + "Correcção/cancelamento versionados, prévia, histórico, recibos e recuperação", + "Mapa individual revisto conservado com SHA e exportação autenticada; filtros SQL25/100", + "Contratos internos QA revistos com correcção/terminação/cancelamento e dependências", + "Plano e assiduidade por dia com intervalos explícitos, dados desconhecidos e revisão distinta", + "Condição de férias QA, reserva/gozo/cancelamento individuais, saldo efectivo sem duplicação", + "Conferência parcial do período por pessoa, fontes/versões e CSV/SHA conservado", + "Ligação contratual explícita opcional em novas preparações salariais, provada na regressão sem alterar salários8085", + "Resumo CSV legível derivado de versão revista, com SHA próprio/original, sem alterar o artefacto conservado" + ], + "sources": [ + { + "file": "app/Modules/HumanResources/Remunerations.php", + "present": true, + "sha256": "950333ced35d6949487bdf78ef109fad608338525acfe60b05b9a320fd3aac01" + }, + { + "file": "app/Modules/HumanResources/Http/RemunerationsController.php", + "present": true, + "sha256": "3725145a623791a6c2825d8aaa4f122ba7b80908c08603211d6d9e74df76e801" + }, + { + "file": "app/Shared/Installation/HumanResourcesProfiles.php", + "present": true, + "sha256": "f908b8cf9f7121bb9b43733f507771a60aa34eb553df8342c250e5b39940ae93" + }, + { + "file": "resources/js/RemunerationsWorkspace.jsx", + "present": true, + "sha256": "edb225504bff359d5c53c040421e322bb4cd887ac946b3316852ecd1ed25b76e" + }, + { + "file": "resources/js/statement-download.js", + "present": true, + "sha256": "2dcbea8a6523970211024dbac28743ce4a128b98808fd6f0d39b73cbf48457ff" + }, + { + "file": "app/Modules/HumanResources/Personnel.php", + "present": true, + "sha256": "da109cf709a6306f7c7a8a3f111514cb1e5c4d1dc0554e15e36022bf1fbb829b" + }, + { + "file": "app/Modules/HumanResources/PersonnelTime.php", + "present": true, + "sha256": "e0fc1bdecbbd24c1ac21a426d8547fc049c00fd18ae6faa7ded64128252bbfd6" + }, + { + "file": "app/Modules/HumanResources/Http/PersonnelController.php", + "present": true, + "sha256": "539fb7ce74dd3b87263d1610fee7ddac2028c4536bc8d549be6d0617c3f947a2" + }, + { + "file": "resources/js/PersonnelWorkspace.jsx", + "present": true, + "sha256": "3bff1b58a1f08c8065abaa68fd7547156f3537f573e0a8d4b922cd3c2cf7ae06" + }, + { + "file": "app/Modules/HumanResources/PersonnelReadableCsv.php", + "present": true, + "sha256": "5e31d7de8aa243b9f9578d859e05ab9071c06dc39bb8616f35eed085856c493c" + } + ], + "existing_evidence": [ + { + "file": "docs/SGF_HUMAN_RESOURCES_DELIVERY.md", + "present": true, + "sha256": "1494d29938565bb6f5d552385d7b32dd0a8f22856755bf638d81cf9a41a65f60", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-human-resources-20261111.json", + "present": true, + "sha256": "95d0c3fcd407cfbb03226288b94570478b906fdf3cb50c26a9960956f536cf29", + "not_reexecuted": true + }, + { + "file": "docs/SGF_HUMAN_RESOURCES_PERSONNEL_DELIVERY.md", + "present": true, + "sha256": "045d111cd08ad58c22a802d6d728c7f300d91b4078b65c3ac61683791747a02e", + "not_reexecuted": true + }, + { + "file": "docs/proofs/sgf-hr-personnel-20261111.json", + "present": true, + "sha256": "b4ce53d6099a13acbe6bd2d7431f9109b8a318000f9d69813353349cd823cdc7", + "not_reexecuted": true + }, + { + "file": "docs/SGF_HUMAN_RESOURCES_READABLE_DELIVERY.md", + "present": true, + "sha256": "18275d4fda727e0082379bcd17804397a13dc3c5f5a88575c48c4e6c8abf95b2", + "not_reexecuted": true + } + ], + "missing_host_tables": [], + "missing_native_tables": [], + "integrated_release_verified": false, + "client_presentable": false, + "remaining": [ + "Conformidade e aplicabilidade laboral real: contratos legais, turnos/calendário, férias legais, benefícios e modelos", + "Cálculo legal IRPS/INSS e ligação a dívida/pagamento/Contabilidade sem efeitos implícitos", + "Revisão visual, carga sustentada, armazenamento e capacidade de implantação" + ] + } + ], + "release_gates": [ + { + "id": "single_installation", + "criterion": "Os seis módulos operam no mesmo endereço e empresa/unidade, sem mudar de demonstração", + "passed": false + }, + { + "id": "functional_modules", + "criterion": "Cada funcionalidade obrigatória tem ecrã/comando/consulta executável; menus e fontes isoladas não bastam", + "passed": false + }, + { + "id": "cross_module_flows", + "criterion": "Os documentos atravessam as fronteiras mantendo origem, versão, autoridade e efeito único", + "passed": false + }, + { + "id": "access_isolation", + "criterion": "Perfis explícitos e recusas de empresa/unidade/módulo/permissão verificadas", + "passed": false + }, + { + "id": "presentation", + "criterion": "Percurso de apresentação coerente, dados fictícios identificados, linguagem e navegação revistas", + "passed": false + }, + { + "id": "operations", + "criterion": "Arranque e recuperação da instalação entregues e ensaiados, sem depender do SGC", + "passed": false + }, + { + "id": "commercial_honesty", + "criterion": "Documentos de teste não são emissão fiscal; capacidade de implantação exige prova própria", + "passed": false + }, + { + "id": "preservation", + "criterion": "Seis registos, cache, dados aprovados e trabalho alheio preservados", + "passed": false + } + ], + "client_presentable": false, + "limits": [ + "Host-table footprint is a minimum projection, not a full migration/dependency validation.", + "Native purchasing/stock tables live in their existing separate native book; this scan does not call that missing.", + "Source/evidence presence does not mean a feature is integrated, visually accepted or deployment-ready." + ], + "database_writes": 0, + "application_bootstraps": 0, + "cache_cleared": false, + "new_demo": false, + "new_rights": false +} diff --git a/packages/general-ledger/bootstrap.php b/packages/general-ledger/bootstrap.php index 0eb666c..0f30de9 100644 --- a/packages/general-ledger/bootstrap.php +++ b/packages/general-ledger/bootstrap.php @@ -10,6 +10,7 @@ require_once __DIR__.'/src/IndustrialSchema.php'; require_once __DIR__.'/src/IndustrialCorrectionSchema.php'; require_once __DIR__.'/src/CostCorrectionSchema.php'; require_once __DIR__.'/src/CycleSchema.php'; +require_once __DIR__.'/src/LedgerAssets.php'; require_once __DIR__.'/src/PayableJournal.php'; require_once __DIR__.'/src/StockSchema.php'; require_once __DIR__.'/src/StockValuation.php'; diff --git a/packages/general-ledger/src/LedgerAssets.php b/packages/general-ledger/src/LedgerAssets.php new file mode 100644 index 0000000..ccd6872 --- /dev/null +++ b/packages/general-ledger/src/LedgerAssets.php @@ -0,0 +1,14 @@ +db->table('gl_asset_books')->where($this->scope)->first();$kind=$s['effect_type'];if(!$meta||$meta->policy!=='QA-ASSET-JOURNAL-v1'||$meta->host_database!==($s['host_database']??null)||$rule->policy!=='QA-ASSET-JOURNAL-v1'||$rule->effect_type!==$kind||$rule->currency!=='MZN'||$date<$rule->valid_from||$date>$rule->valid_to||$s['permitted_period_key']!==$period->period_key)throw new \DomainException('Origem de activos fora da composição ou regra revista.'); + $correction=in_array($kind,['assets.depreciation.reversal','assets.depreciation.replacement'],true);if((!$correction&&$date!==$s['effect_on'])||($correction&&$date<$s['original_journal_on']))throw new \DomainException('Data da origem de activos incompatível.'); + $parts=match($kind){'assets.admitted'=>[['QA-ACTIVO-CUSTO',$amount,0],['QA-ACTIVO-ORIGEM-QA',0,$amount]],'assets.depreciation','assets.depreciation.replacement'=>[['QA-ACTIVO-GASTO-DEP',$amount,0],['QA-ACTIVO-DEP-ACUM',0,$amount]],'assets.depreciation.reversal'=>[['QA-ACTIVO-DEP-ACUM',$amount,0],['QA-ACTIVO-GASTO-DEP',0,$amount]],'assets.writeoff'=>[['QA-ACTIVO-DEP-ACUM',$s['accumulated_minor'],0],['QA-ACTIVO-GASTO-ABATE',$s['carrying_minor'],0],['QA-ACTIVO-CUSTO',0,$s['cost_minor']]],default=>throw new \DomainException('Tipo de activo não suportado.')}; + $parts=array_values(array_filter($parts,fn($p)=>$p[1]+$p[2]>0));$accounts=$this->q('accounts')->whereIn('code',array_column($parts,0))->get()->keyBy('code');$lines=[];foreach($parts as[$code,$debit,$credit]){$a=$accounts->get($code);if(!$a||!$a->is_movement||$a->currency!=='MZN')throw new \DomainException('Conta de activo incompatível.');$lines[]=['account_id'=>$a->id,'account_code'=>$code,'debit_minor'=>$debit,'credit_minor'=>$credit];} + $debit=array_sum(array_column($lines,'debit_minor'));$credit=array_sum(array_column($lines,'credit_minor'));if($debit!==$amount||$credit!==$amount||count($lines)<2||count($lines)>3||$rule->debit_account_id!==$lines[0]['account_id']||$rule->credit_account_id!==$lines[count($lines)-1]['account_id'])throw new \DomainException('Plano do activo não concilia com regra ou montante.');return['contract'=>'SGF-FIXED-ASSETS-INTEGRATED-QA-v1','source'=>$s,'journal_on'=>$date,'currency'=>'MZN','period'=>(array)$period,'rule'=>(array)$rule,'lines'=>$lines,'impact'=>['debit_minor'=>$amount,'credit_minor'=>$amount,'financial_balance_changed'=>false,'payment_created'=>false]]; + } +} diff --git a/packages/general-ledger/src/LedgerCycle.php b/packages/general-ledger/src/LedgerCycle.php index 04e15cf..4216dde 100644 --- a/packages/general-ledger/src/LedgerCycle.php +++ b/packages/general-ledger/src/LedgerCycle.php @@ -51,7 +51,7 @@ trait LedgerCycle $hash=$this->hash([$action,$d]);$old=$this->q('commands')->where(['actor_id'=>$this->actor,'command_key'=>$key])->first(); if($old){if(!hash_equals($old->request_hash,$hash))throw new \DomainException('Chave repetida com conteúdo diferente.');return json_decode($old->result,true,128,JSON_THROW_ON_ERROR)+['replayed'=>true];} if($action==='effect-prepare'){ - if(!in_array($d['effect_type'],['allocation.confirmed','allocation.reversed','sales.invoice','receivable.allocated','receivable.reversed','receivable.reduced','receivable.reduction_reversed','cash.receipt_classified','cash.expense_confirmed','workshop.direct_cost.validated','industry.cost.validated','stock.receipt','stock.issue','stock.return','stock.adjustment','stock.supplier_return'],true))throw new \InvalidArgumentException('Seleccione uma origem contabilística suportada.'); + if(!in_array($d['effect_type'],['allocation.confirmed','allocation.reversed','sales.invoice','receivable.allocated','receivable.reversed','receivable.reduced','receivable.reduction_reversed','cash.receipt_classified','cash.expense_confirmed','workshop.direct_cost.validated','industry.cost.validated','stock.receipt','stock.issue','stock.return','stock.adjustment','stock.supplier_return','assets.admitted','assets.depreciation','assets.writeoff'],true))throw new \InvalidArgumentException('Seleccione uma origem contabilística suportada.'); $result=$this->prepare(['obligation_id'=>$d['source_id']]+$d,$reason,$d['effect_type']); }elseif(str_starts_with($action,'correct-'))$result=$this->correction($action,$d,$reason); else $result=$this->periodCommand($action,$d,$reason); @@ -65,8 +65,9 @@ trait LedgerCycle $id=$this->id($d['journal_id']); if($this->q('correction_results')->where('original_journal_id',$id)->exists())throw new \DomainException('Correcção já aplicada; consulte o par existente.'); $original=$this->q('journals')->where('id',$id)->first()??throw new \DomainException('Original fora do âmbito.');$isCost=json_decode($original->snapshot,true)['source']['effect_type']==='workshop.direct_cost.validated';$isIndustrial=json_decode($original->snapshot,true)['source']['effect_type']==='industry.cost.validated'; + $isAsset=json_decode($original->snapshot,true)['source']['effect_type']==='assets.depreciation'; $parts=[]; - foreach($isIndustrial?['industry.cost.reversal'=>'QA-INDUSTRIAL-CORRECTION-REVERSE','industry.cost.replacement'=>'QA-INDUSTRIAL-CORRECTION-REPLACE']:($isCost?['workshop.direct_cost.reversal'=>'QA-COST-CORRECTION-REVERSE','workshop.direct_cost.replacement'=>'QA-COST-CORRECTION-REPLACE']:['journal.reversal'=>'QA-CORRECTION-REVERSE','journal.replacement'=>'QA-CORRECTION-REPLACE']) as $kind=>$rule){ + foreach($isAsset?['assets.depreciation.reversal'=>'QA-ASSET-CORRECTION-REVERSE','assets.depreciation.replacement'=>'QA-ASSET-CORRECTION-REPLACE']:($isIndustrial?['industry.cost.reversal'=>'QA-INDUSTRIAL-CORRECTION-REVERSE','industry.cost.replacement'=>'QA-INDUSTRIAL-CORRECTION-REPLACE']:($isCost?['workshop.direct_cost.reversal'=>'QA-COST-CORRECTION-REVERSE','workshop.direct_cost.replacement'=>'QA-COST-CORRECTION-REPLACE']:['journal.reversal'=>'QA-CORRECTION-REVERSE','journal.replacement'=>'QA-CORRECTION-REPLACE'])) as $kind=>$rule){ $parts[]=$this->prepare(['obligation_id'=>$id,'journal_on'=>$d['journal_on'],'period_key'=>$d['period_key'],'rule_key'=>$rule],$reason,$kind); } $revision=$this->hash($parts); diff --git a/packages/general-ledger/src/PayableJournal.php b/packages/general-ledger/src/PayableJournal.php index a499278..e9b63f6 100644 --- a/packages/general-ledger/src/PayableJournal.php +++ b/packages/general-ledger/src/PayableJournal.php @@ -8,6 +8,7 @@ use Tempo\Finance\Payables\Payables; final class PayableJournal { use LedgerCycle; + use LedgerAssets; use LedgerSales; use LedgerReports; public const POLICY='QA-PAYABLE-JOURNAL-v1'; @@ -37,8 +38,9 @@ final class PayableJournal if(!preg_match('/^(\d{4})-(\d{2})-(\d{2})$/D',$date,$m)||!checkdate((int)$m[2],(int)$m[3],(int)$m[1]))throw new \InvalidArgumentException('Data de lançamento explícita inválida.'); $sales=in_array($kind,['sales.invoice','receivable.allocated','receivable.reversed','receivable.reduced','receivable.reduction_reversed','cash.receipt_classified','cash.expense_confirmed'],true);$cost=in_array($kind,['workshop.direct_cost.validated','workshop.direct_cost.reversal','workshop.direct_cost.replacement'],true); $industrial=in_array($kind,['industry.cost.validated','industry.cost.reversal','industry.cost.replacement'],true); + $asset=in_array($kind,['assets.admitted','assets.depreciation','assets.writeoff','assets.depreciation.reversal','assets.depreciation.replacement'],true); $stock=in_array($kind,['stock.receipt','stock.issue','stock.return','stock.adjustment','stock.supplier_return'],true); - $s=$sales||$cost||$industrial||$stock?($this->additionalSource!==null?($this->additionalSource)($kind,$obligation):throw new \DomainException('Origem adicional não composta neste host.')):($kind==='supplier_payable.recognised'?($this->source)($obligation):$this->cycleSource($kind,$obligation)); + $s=$sales||$cost||$industrial||$stock||$asset?($this->additionalSource!==null?($this->additionalSource)($kind,$obligation):throw new \DomainException('Origem adicional não composta neste host.')):($kind==='supplier_payable.recognised'?($this->source)($obligation):$this->cycleSource($kind,$obligation)); $amount=$s['amount_minor']??$s['principal_minor']??null; if(($s['effect_type']??'')!==$kind||!($s['compatible']??false)||($s['currency']??'')!=='MZN'||!is_int($amount)||$amount<=0||(!$sales&&($s['tax_minor']??null)!==0)||($s['history_imported']??true))throw new \DomainException('Fonte incompatível com o ensaio contabilístico.'); $period=$this->latest('periods','period_key',$periodKey);$rule=$this->latest('rules','rule_key',$ruleKey); @@ -47,6 +49,7 @@ final class PayableJournal if($stock){if(!$this->db->getSchemaBuilder()->hasTable('gl_stock_books')||($s['stock_movement_on']??'')!==$date)throw new \DomainException('Valorização não composta ou data física incompatível.');} if($industrial&&($s['period_start']<$period->starts_on||$s['period_end']>$period->ends_on))throw new \DomainException('Período não contém o lote seleccionado.'); if($period->status!=='open'||$date<$period->starts_on||$date>$period->ends_on)throw new \DomainException('Período fechado, incompatível ou alterado.'); + if($asset)return $this->captureAssets($s,$date,$period,$rule,$amount); if($rule->policy!==(str_starts_with($kind,'cash.')?'QA-CASH-JOURNAL-v1':($stock?'QA-STOCK-VALUATION-v1':($sales?'QA-SALES-JOURNAL-v1':($cost?'QA-DIRECT-COST-JOURNAL-v1':($industrial?'QA-INDUSTRIAL-JOURNAL-v1':self::POLICY)))))||$rule->effect_type!==$s['effect_type']||$rule->currency!=='MZN'||$date<$rule->valid_from||$date>$rule->valid_to)throw new \DomainException('Regra contabilística ausente, incompatível ou fora de vigência.'); if(str_starts_with($kind,'cash.')&&($s['permitted_period_key']!==$periodKey||$s['cash_effect_on']!==$date))throw new \DomainException('Data ou período de Caixa incompatível.');if($sales)return $this->captureSales($s,$date,$period,$rule,$amount); $accounts=$this->q('accounts')->whereIn('id',[$rule->debit_account_id,$rule->credit_account_id])->get()->keyBy('id'); @@ -83,7 +86,7 @@ final class PayableJournal if($old){if(!hash_equals($old->request_hash,$hash))throw new \DomainException('Chave repetida com conteúdo diferente.');return json_decode($old->result,true,64,JSON_THROW_ON_ERROR)+['replayed'=>true];} if(in_array($action,['validate','post'],true)){ $candidate=$this->proposal($this->id($data['proposal_id'])); - if(in_array(json_decode($candidate->snapshot,true)['source']['effect_type'],['journal.reversal','journal.replacement','workshop.direct_cost.reversal','workshop.direct_cost.replacement','industry.cost.reversal','industry.cost.replacement'],true))throw new \DomainException('Use a decisão/aplicação do par de correcção completo.'); + if(in_array(json_decode($candidate->snapshot,true)['source']['effect_type'],['journal.reversal','journal.replacement','workshop.direct_cost.reversal','workshop.direct_cost.replacement','industry.cost.reversal','industry.cost.replacement','assets.depreciation.reversal','assets.depreciation.replacement'],true))throw new \DomainException('Use a decisão/aplicação do par de correcção completo.'); } if($action==='prepare')$result=$this->prepare($data,$reason); elseif($action==='validate')$result=$this->review($data,$reason); @@ -107,7 +110,7 @@ final class PayableJournal { if(!is_string($d['revision'])||!in_array($d['decision'],['approved','rejected'],true))throw new \InvalidArgumentException('Decisão/revisão explícita inválida.'); $p=$this->proposal($this->id($d['proposal_id'])); - if(in_array(json_decode($p->snapshot,true)['source']['effect_type'],['journal.reversal','journal.replacement','workshop.direct_cost.reversal','workshop.direct_cost.replacement','industry.cost.reversal','industry.cost.replacement'],true))($this->authorize)('correct_validate'); + if(in_array(json_decode($p->snapshot,true)['source']['effect_type'],['journal.reversal','journal.replacement','workshop.direct_cost.reversal','workshop.direct_cost.replacement','industry.cost.reversal','industry.cost.replacement','assets.depreciation.reversal','assets.depreciation.replacement'],true))($this->authorize)('correct_validate'); if((int)$p->prepared_by===$this->actor||$p->prepared_person===$this->person)throw new \DomainException('Revisão exige outra identidade e pessoa.'); $this->current($p,$d['revision']);$old=$this->q('reviews')->where('proposal_id',$p->id)->first(); if($old){if($old->decision!==$d['decision']||$old->reason!==$reason)throw new \DomainException('Decisão já registada; não pode ser alterada.');return ['state'=>$old->decision,'proposal_id'=>$p->id,'review_id'=>$old->id];} @@ -118,7 +121,7 @@ final class PayableJournal private function post(array $d,string $reason):array { if(!is_string($d['revision']))throw new \InvalidArgumentException('Revisão inválida.');$p=$this->proposal($this->id($d['proposal_id'])); - if(in_array(json_decode($p->snapshot,true)['source']['effect_type'],['journal.reversal','journal.replacement','workshop.direct_cost.reversal','workshop.direct_cost.replacement','industry.cost.reversal','industry.cost.replacement'],true))($this->authorize)('correct_post'); + if(in_array(json_decode($p->snapshot,true)['source']['effect_type'],['journal.reversal','journal.replacement','workshop.direct_cost.reversal','workshop.direct_cost.replacement','industry.cost.reversal','industry.cost.replacement','assets.depreciation.reversal','assets.depreciation.replacement'],true))($this->authorize)('correct_post'); if((int)$p->prepared_by!==$this->actor||$p->prepared_person!==$this->person)throw new \DomainException('Só o preparador autorizado aplica esta versão.'); $existing=$this->q('journals')->where('effect_key',$p->effect_key)->first(); if($existing){if((int)$existing->proposal_id!==(int)$p->id||!hash_equals($p->revision,$d['revision'])||$existing->reason!==$reason)throw new \DomainException('Efeito já lançado com conteúdo diferente.');return ['state'=>'posted','proposal_id'=>$p->id,'journal_id'=>$existing->id];} @@ -153,12 +156,12 @@ final class PayableJournal $lines=$this->q('lines')->whereIn('journal_id',$ids)->orderBy('line_no')->get(); $totals=$this->q('lines')->whereIn('journal_id',(clone $journals)->select('id'));if($account)$totals->where('account_id',$account->id); $sum=$totals->selectRaw('coalesce(sum(debit_minor),0) AS debit_minor,coalesce(sum(credit_minor),0) AS credit_minor')->first(); - $proposalQuery=$this->q('proposals');if($industrialLot)$proposalQuery->where('snapshot->source->industrial_lot_id',$industrialLot)->whereIn('snapshot->source->effect_type',['industry.cost.validated','industry.cost.reversal','industry.cost.replacement']);if($costOrder)$proposalQuery->where('snapshot->source->cost_order_id',$costOrder);if($salesInvoice)$proposalQuery->where('snapshot->source->invoice_id',$salesInvoice);if($focus)$proposalQuery->where('obligation_id',$focus)->whereIn('snapshot->source->effect_type',self::SUPPLIER_EFFECTS);$proposals=$proposalQuery->orderByDesc('id')->paginate(25,['*'],'page',$page);$proposalIds=array_map(fn($p)=>$p->id,$proposals->items());$reviews=$this->q('reviews')->whereIn('proposal_id',$proposalIds)->get(); + $proposalQuery=$this->q('proposals');if($industrialLot)$proposalQuery->where('snapshot->source->industrial_lot_id',$industrialLot)->whereIn('snapshot->source->effect_type',['industry.cost.validated','industry.cost.reversal','industry.cost.replacement','assets.depreciation.reversal','assets.depreciation.replacement']);if($costOrder)$proposalQuery->where('snapshot->source->cost_order_id',$costOrder);if($salesInvoice)$proposalQuery->where('snapshot->source->invoice_id',$salesInvoice);if($focus)$proposalQuery->where('obligation_id',$focus)->whereIn('snapshot->source->effect_type',self::SUPPLIER_EFFECTS);$proposals=$proposalQuery->orderByDesc('id')->paginate(25,['*'],'page',$page);$proposalIds=array_map(fn($p)=>$p->id,$proposals->items());$reviews=$this->q('reviews')->whereIn('proposal_id',$proposalIds)->get(); $posted=$this->q('journals')->whereIn('proposal_id',$proposalIds)->pluck('id','proposal_id');foreach($proposals->items() as $p)$p->journal_id=$posted->get($p->id); $eventPager=$this->q('events')->when($focus||$salesInvoice||$costOrder||$industrialLot,fn($q)=>$q->where(function($events)use($focus,$salesInvoice,$costOrder,$industrialLot){ - $events->where(fn($e)=>$e->where('kind','journal.prepared')->whereIn('entity_id',$this->q('proposals')->when($focus,fn($q)=>$q->where('obligation_id',$focus)->whereIn('snapshot->source->effect_type',self::SUPPLIER_EFFECTS))->when($salesInvoice,fn($q)=>$q->where('snapshot->source->invoice_id',$salesInvoice))->when($costOrder,fn($q)=>$q->where('snapshot->source->cost_order_id',$costOrder))->when($industrialLot,fn($q)=>$q->where('snapshot->source->industrial_lot_id',$industrialLot)->whereIn('snapshot->source->effect_type',['industry.cost.validated','industry.cost.reversal','industry.cost.replacement']))->select('id'))) - ->orWhere(fn($e)=>$e->where('kind','journal.reviewed')->whereIn('entity_id',$this->q('reviews')->whereIn('proposal_id',$this->q('proposals')->when($focus,fn($q)=>$q->where('obligation_id',$focus)->whereIn('snapshot->source->effect_type',self::SUPPLIER_EFFECTS))->when($salesInvoice,fn($q)=>$q->where('snapshot->source->invoice_id',$salesInvoice))->when($costOrder,fn($q)=>$q->where('snapshot->source->cost_order_id',$costOrder))->when($industrialLot,fn($q)=>$q->where('snapshot->source->industrial_lot_id',$industrialLot)->whereIn('snapshot->source->effect_type',['industry.cost.validated','industry.cost.reversal','industry.cost.replacement']))->select('id'))->select('id'))) - ->orWhere(fn($e)=>$e->where('kind','journal.posted')->whereIn('entity_id',$this->q('journals')->when($focus,fn($q)=>$q->where('obligation_id',$focus)->whereIn('snapshot->source->effect_type',self::SUPPLIER_EFFECTS))->when($salesInvoice,fn($q)=>$q->where('snapshot->source->invoice_id',$salesInvoice))->when($costOrder,fn($q)=>$q->where('snapshot->source->cost_order_id',$costOrder))->when($industrialLot,fn($q)=>$q->where('snapshot->source->industrial_lot_id',$industrialLot)->whereIn('snapshot->source->effect_type',['industry.cost.validated','industry.cost.reversal','industry.cost.replacement']))->select('id'))); + $events->where(fn($e)=>$e->where('kind','journal.prepared')->whereIn('entity_id',$this->q('proposals')->when($focus,fn($q)=>$q->where('obligation_id',$focus)->whereIn('snapshot->source->effect_type',self::SUPPLIER_EFFECTS))->when($salesInvoice,fn($q)=>$q->where('snapshot->source->invoice_id',$salesInvoice))->when($costOrder,fn($q)=>$q->where('snapshot->source->cost_order_id',$costOrder))->when($industrialLot,fn($q)=>$q->where('snapshot->source->industrial_lot_id',$industrialLot)->whereIn('snapshot->source->effect_type',['industry.cost.validated','industry.cost.reversal','industry.cost.replacement','assets.depreciation.reversal','assets.depreciation.replacement']))->select('id'))) + ->orWhere(fn($e)=>$e->where('kind','journal.reviewed')->whereIn('entity_id',$this->q('reviews')->whereIn('proposal_id',$this->q('proposals')->when($focus,fn($q)=>$q->where('obligation_id',$focus)->whereIn('snapshot->source->effect_type',self::SUPPLIER_EFFECTS))->when($salesInvoice,fn($q)=>$q->where('snapshot->source->invoice_id',$salesInvoice))->when($costOrder,fn($q)=>$q->where('snapshot->source->cost_order_id',$costOrder))->when($industrialLot,fn($q)=>$q->where('snapshot->source->industrial_lot_id',$industrialLot)->whereIn('snapshot->source->effect_type',['industry.cost.validated','industry.cost.reversal','industry.cost.replacement','assets.depreciation.reversal','assets.depreciation.replacement']))->select('id'))->select('id'))) + ->orWhere(fn($e)=>$e->where('kind','journal.posted')->whereIn('entity_id',$this->q('journals')->when($focus,fn($q)=>$q->where('obligation_id',$focus)->whereIn('snapshot->source->effect_type',self::SUPPLIER_EFFECTS))->when($salesInvoice,fn($q)=>$q->where('snapshot->source->invoice_id',$salesInvoice))->when($costOrder,fn($q)=>$q->where('snapshot->source->cost_order_id',$costOrder))->when($industrialLot,fn($q)=>$q->where('snapshot->source->industrial_lot_id',$industrialLot)->whereIn('snapshot->source->effect_type',['industry.cost.validated','industry.cost.reversal','industry.cost.replacement','assets.depreciation.reversal','assets.depreciation.replacement']))->select('id'))); }))->orderByDesc('id')->paginate(25,['*'],'page',$page); return ['synthetic_only'=>true,'journals'=>$pager->items(),'lines'=>$lines->all(),'proposals'=>$proposals->items(),'reviews'=>$reviews->all(),'accounts'=>$withAccounts?$this->q('accounts')->orderBy('id')->get()->all():[], 'events'=>$eventPager->items(),'totals'=>['debit_minor'=>(int)$sum->debit_minor,'credit_minor'=>(int)$sum->credit_minor,'net_minor'=>(int)$sum->debit_minor-(int)$sum->credit_minor], diff --git a/resources/css/multiline-invoice.css b/resources/css/multiline-invoice.css new file mode 100644 index 0000000..a6ca946 --- /dev/null +++ b/resources/css/multiline-invoice.css @@ -0,0 +1 @@ +.multi-invoice-layout{display:grid;grid-template-columns:minmax(0,1fr) 280px;gap:20px;align-items:start}.multi-invoice-totals{position:sticky;top:12px;margin-top:0}.multi-invoice-totals dl{display:grid;grid-template-columns:1fr auto;gap:12px}.multi-invoice-totals dd{margin:0;text-align:right;font-variant-numeric:tabular-nums}.multi-total{font-size:1.35rem;font-weight:700}.multi-line-fields{display:grid;grid-template-columns:minmax(160px,2fr) 90px minmax(100px,1fr) minmax(120px,1fr) auto;gap:12px;align-items:end}.multi-reference{min-width:0;margin:12px 0}.multi-reference .actions{display:flex;flex-wrap:wrap;gap:8px}.multi-reference select{width:100%;max-width:100%}.multi-invoice-workspace fieldset{min-width:0}.multi-invoice-workspace nav{display:flex;gap:12px;align-items:center;margin-top:14px}.multi-invoice-workspace .multi-line-fields button{margin-bottom:0}.multi-invoice-workspace .multi-invoice-totals{overflow-wrap:anywhere}@media(max-width:1000px){.multi-invoice-layout{grid-template-columns:minmax(0,1fr)}.multi-invoice-totals{position:static}.multi-line-fields{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:560px){.multi-line-fields{grid-template-columns:minmax(0,1fr)}} diff --git a/resources/js/FixedAssetsWorkspace.jsx b/resources/js/FixedAssetsWorkspace.jsx new file mode 100644 index 0000000..66098da --- /dev/null +++ b/resources/js/FixedAssetsWorkspace.jsx @@ -0,0 +1,38 @@ +import React,{useEffect,useState,useRef} from 'react'; +const kinds={asset:'Cadastro e custódia',plan:'Planos mensais',charge:'Quotas por competência',disposal:'Abate sem contraprestação'}; +const fields={asset:{code:'Código QA',description:'Descrição',origin_reference:'Referência da abertura artificial',cost:'Custo MZN (desconhecido se vazio)',residual:'Residual MZN (desconhecido se vazio)',available_on:'Entrada em uso',location:'Localização interna',custodian:'Custódia interna'},plan:{asset_key:'Activo',life_months:'Vida artificial em meses',status:'Estado do plano'},charge:{asset_key:'Activo',plan_key:'Plano escolhido',month:'Competência'},disposal:{asset_key:'Activo',effect_on:'Data do abate'}}; +const money=v=>v===null||v===undefined?'Indeterminado':`${BigInt(v)/100n},${String(BigInt(v)%100n).padStart(2,'0')} MZN`; +const label=r=>r?.data?.input?.code??r?.data?.input?.month??`${kinds[r?.kind]??'Registo'} · v${r?.version??''}`; +export default function FixedAssetsWorkspace({api,context,can,perform,busy}){ + const[kind,setKind]=useState('asset'),[page,setPage]=useState(1),[size,setSize]=useState(25),[search,setSearch]=useState(''),[list,setList]=useState(null),[selected,setSelected]=useState(null),[form,setForm]=useState({}),[reason,setReason]=useState(''),[cut,setCut]=useState(null),[key,setKey]=useState(crypto.randomUUID()),[domain,setDomain]=useState(''),[result,setResult]=useState(null),[error,setError]=useState(''),[ledger,setLedger]=useState(null),[ledgerPage,setLedgerPage]=useState(1),[history,setHistory]=useState(false); + const reads=useRef({list:0,ledger:0}); + const locked=busy||Boolean(cut);const unit=context.unit?.id;const chosenAsset=selected?(selected.kind==='asset'?selected.record_key:selected.data.input.asset_key):null; + async function load(){const q=new URLSearchParams({kind,page,per_page:size,...(search?{search}:{}),...(history&&selected?{record_key:selected.record_key}:{})});const n=++reads.current.list;const out=await api('/api/fixed-assets?'+q);if(n===reads.current.list){if(out.kind!==kind||out.records.per_page!==Number(size)||!Array.isArray(out.records.data)||out.records.data.length>size)throw Error('Resposta de consulta incompatível; actualize.');setList(out);}} + async function loadLedger(){const n=++reads.current.ledger;if(chosenAsset){const out=await api('/api/fixed-assets/ledger?'+new URLSearchParams({asset_key:chosenAsset,page:ledgerPage,per_page:25}));if(n===reads.current.ledger)setLedger(out);}else setLedger(null);} + useEffect(()=>{setList(null);load().catch(e=>setError(e.message));},[context.token,kind,page,size,search,history]); + useEffect(()=>{loadLedger().catch(e=>setError(e.message));},[chosenAsset,ledgerPage]); + function reset(k=kind){reads.current.list++;reads.current.ledger++;setKind(k);setSelected(null);setForm({operation:'establish',status:'active'});setHistory(false);setPage(1);setCut(null);setResult(null);setError('');setLedger(null);setKey(crypto.randomUUID());} + function choose(r){reads.current.ledger++;setLedger(null);setSelected(r);setForm({...((r.effective??r).data.input),operation:'amend'});setCut(null);setResult(null);setLedgerPage(1);setKey(crypto.randomUUID());} + async function preview(data,type='admin'){await perform(async()=>{setError('');setResult(null);setDomain(type);const out=await api('/api/fixed-assets'+(type==='ledger'?'/ledger':'')+'/preview','POST',data,context.token,key);setCut(out);},'Confira o impacto individual antes de confirmar.');} + async function confirm(){await perform(async()=>{try{const out=await api('/api/fixed-assets'+(domain==='ledger'?'/ledger':'')+'/confirm','POST',{preview:cut.preview});setResult({...out,command_key:key});setCut(null);setKey(crypto.randomUUID());await load();await loadLedger();setSelected(null);setForm({});}catch(e){setError(e.message);throw e;}},'Decisão registada e auditada.');} + async function recover(){await perform(async()=>{const out=await api(domain==='ledger'?'/api/general-ledger/receipts/'+key:'/api/fixed-assets/receipts/'+key);setResult(out.result??out);setCut(null);await load();await loadLedger();},'Comprovativo consultado sem repetir a operação.');} + const action=data=>preview({action:'effect-prepare',data},'ledger'); + const source=selected?.effective??selected;const input=source?.data.input;const proposedAsset=source?.kind==='asset'?source.record_key:input?.asset_key; + return

CONTABILIDADE

Activos e depreciações internas QA

Exercício artificial. Cadastro, plano, quota e lançamento exigem decisões próprias. Sem aquisição, pagamento ou tratamento fiscal legal presumidos.

{error&&

{error}

} +
{Object.entries(kinds).map(([k,n])=>)}
+

{kinds[kind]}

+ {list?.records.data.map(r=>

{r.data.input.description??r.data.input.evidence}

{r.effective&&

Versão efectiva: {r.effective.version}

}
)} + {list&&}
+ {selected&&

Versão seleccionada

{label(selected)} · versão {selected.version} · autor #{selected.actor_id} · {selected.created_at}

{selected.reason}

{Object.entries(selected.data.input).filter(([k])=>fields[kind][k]).map(([k,v])=>
{fields[kind][k]}
{v===null?'Indeterminado':String(v)}
)}
{selected.data.calculation?.complete&&

Quota: {money(selected.data.calculation.quota_cents)} · líquido previsto: {money(selected.data.calculation.carrying_cents)}

}
} + {can('fixed_assets.prepare',unit)&&

{selected?'Preparar nova versão':'Preparar registo'}

{e.preventDefault();const d={operation:form.operation??'establish',evidence:form.evidence??''};for(const f of Object.keys(fields[kind]))d[f]=form[f]===''||form[f]===undefined?null:f==='life_months'?Number(form[f]):form[f];const recordKey=selected?.record_key??crypto.randomUUID();preview({action:'prepare',kind,record_key:recordKey,version:selected?.version??0,reason,data:d});}}>
+ + {Object.entries(fields[kind]).map(([f,n])=>)}