Files
SGF/scripts/sgf-native-ledger-setup.php
T
2026-11-11 09:00:17 +02:00

55 lines
11 KiB
PHP

<?php
use Illuminate\Support\Facades\{DB,Schema};
use Tempo\Finance\GeneralLedger\{LedgerSchema,CycleSchema,SalesSchema,CostCorrectionSchema,IndustrialCorrectionSchema,StockSchema};
use Tempo\Finance\Payables\{SettlementSchema,ReversalSchema};
$book=$argv[1]??'regression';require __DIR__.'/sgf-single-bootstrap.php';
$profiles=json_decode(file_get_contents($s['root'].'/single-profiles.json'),true);$policy=App\Shared\Installation\SyntheticProfiles::policy();$pin=App\Shared\Installation\SyntheticProfiles::fingerprint($policy);
LedgerSchema::assertSynthetic($native);$hostIdentity=App\Shared\Installation\CompositionRuntime::physical();
if($native->getDatabaseName()!=='compras_receipts_test_'.$tag||$native->getConfig('username')!=='payable_history_native_'.$tag)throw new LogicException('Preserve another native book');
if(!$native->getSchemaBuilder()->hasTable('gl_sgf_composition')){
foreach(['host'=>['database'=>$s['env']['DB_DATABASE'],'username'=>$s['env']['DB_USERNAME'],'password'=>$s['env']['DB_PASSWORD']],'native'=>$s['nativeConnection']] as $name=>$c){
$dump=new Symfony\Component\Process\Process(['C:/Program Files/PostgreSQL/17/bin/pg_dump.exe','-h','127.0.0.1','-p','55439','-U',$c['username'],'-d',$c['database'],'--format=custom','--no-owner','--no-privileges','--file',$work.'/'.$name.'-before-ledger.dump'],$repo,['PGPASSWORD'=>$c['password']]);$dump->setTimeout(120);$dump->mustRun();
}
}
$oldDefault=DB::getDefaultConnection();DB::setDefaultConnection('receipt_native');Schema::clearResolvedInstance('db.schema');app()->forgetInstance('db.schema');
$oldEnvironment=$app->environment();$app->instance('env','payable-testing');
try{
$result=$native->transaction(function()use($native,$hostIdentity,$f,$nf,$pin,$tag){
$native->statement("SET LOCAL lock_timeout='5s'");$native->selectOne("SELECT pg_advisory_xact_lock(hashtext('sgf-native-integrated-ledger'))");
if($native->getSchemaBuilder()->hasTable('gl_sgf_composition')){
$r=$native->table('gl_sgf_composition')->first();if($r->host_database!==$hostIdentity['database']||$r->approval_hash!==$pin)throw new LogicException('Preserve existing financial binding');
return ['replayed'=>true,'warehouse_id'=>(int)$r->warehouse_id,'stock_item_id'=>(int)$r->stock_item_id,'scope'=>['tenant_id'=>(int)$r->tenant_id,'company_id'=>(int)$r->company_id,'unit_id'=>(int)$r->unit_id]];
}
if($native->getSchemaBuilder()->hasTable('gl_journals'))throw new LogicException('Existing ledger needs a reviewed upgrade; never replace it');
$tablesBefore=array_column($native->select("SELECT tablename FROM pg_tables WHERE schemaname='public'"),'tablename');$functionsBefore=array_column($native->select("SELECT oid FROM pg_proc WHERE pronamespace='public'::regnamespace"),'oid');
$scope=['tenant_id'=>$nf['tenant'],'company_id'=>$f['company'],'unit_id'=>$f['unit']];
$warehouse=(array)$native->table('warehouses')->where('id',$nf['warehouse'])->first();unset($warehouse['id']);$warehouse['code']='QA-INTEGRADO-'.$tag;$warehouse['name']='Armazém integrado QA';$warehouseId=$native->table('warehouses')->insertGetId($warehouse);
$stock=(array)$native->table('stock_items')->where('id',$nf['stock'])->first();unset($stock['id']);$stock['code']='QA-INTEGRADO-'.$tag;$stock['name']='Artigo integrado exclusivamente QA';if(array_key_exists('barcode',$stock))$stock['barcode']=null;$stockId=$native->table('stock_items')->insertGetId($stock);
$native->unprepared('CREATE TABLE gl_sgf_composition(tenant_id bigint PRIMARY KEY,company_id bigint NOT NULL,unit_id bigint NOT NULL,financial_company_id bigint NOT NULL CHECK(financial_company_id=0),financial_unit_id bigint NOT NULL CHECK(financial_unit_id=0),host_database text NOT NULL UNIQUE,approval_hash char(64) NOT NULL,contract text NOT NULL,warehouse_id bigint NOT NULL REFERENCES warehouses(id),stock_item_id bigint NOT NULL REFERENCES stock_items(id),created_at timestamptz NOT NULL DEFAULT now())');
$native->table('gl_sgf_composition')->insert($scope+['financial_company_id'=>0,'financial_unit_id'=>0,'host_database'=>$hostIdentity['database'],'approval_hash'=>$pin,'contract'=>'SGF-8085-NATIVE-COMPOSITION-v1','warehouse_id'=>$warehouseId,'stock_item_id'=>$stockId]);
LedgerSchema::install($native);CycleSchema::install($native);SalesSchema::install($native);
$native->unprepared('CREATE TABLE gl_cost_sources(id bigserial PRIMARY KEY,tenant_id bigint NOT NULL,company_id bigint NOT NULL,unit_id bigint NOT NULL,order_id bigint NOT NULL,material_origin text NOT NULL,time_origin text NOT NULL,period_key varchar(80) NOT NULL,created_by bigint NOT NULL,reason text NOT NULL CHECK(length(reason)>=3),created_at timestamp NOT NULL,UNIQUE(tenant_id,company_id,unit_id,order_id))');
$native->unprepared('CREATE TABLE gl_industrial_sources(id bigserial PRIMARY KEY,tenant_id bigint NOT NULL,company_id bigint NOT NULL,unit_id bigint NOT NULL,lot_id bigint NOT NULL,origins jsonb NOT NULL,period_key varchar(80) NOT NULL,created_by bigint NOT NULL,reason text NOT NULL CHECK(length(reason)>=3),created_at timestamp NOT NULL,UNIQUE(tenant_id,company_id,unit_id,lot_id))');
foreach(['gl_sgf_composition','gl_cost_sources','gl_industrial_sources'] as $table)$native->unprepared('CREATE TRIGGER gl_immutable BEFORE UPDATE OR DELETE ON '.$table.' FOR EACH ROW EXECUTE FUNCTION gl_append_only()');
CostCorrectionSchema::install($native);IndustrialCorrectionSchema::install($native);StockSchema::install($native);
if(!$native->getSchemaBuilder()->hasTable('ps_payments'))SettlementSchema::install();
if(!$native->getSchemaBuilder()->hasTable('ps_reversals'))ReversalSchema::install();
foreach([$nf['finance']=>['view','prepare','post','correct_prepare','correct_post','period_prepare','period_close'],$nf['finance_reviewer']=>['view','validate','correct_validate','period_validate'],$nf['finance_auditor']=>['view']] as $actor=>$actions){
$person=$native->table('pa_access')->where(['tenant_id'=>$nf['tenant'],'company_id'=>0,'unit_id'=>0,'user_id'=>$actor,'action'=>'view'])->value('person');if(!is_string($person)||$person==='')throw new LogicException('Financial person binding absent');
foreach($actions as $action)$native->table('pa_access')->insert($scope+['user_id'=>$actor,'person'=>$person,'action'=>'general_ledger.'.$action]);
}
$meta=['created_by'=>$nf['finance'],'reason'=>'Preparação fictícia do recorte integrado expressamente autorizado em10/11/2026.','created_at'=>now()];$accounts=[];
foreach(['QA-CONTRAPARTIDA','QA-FORNECEDOR','QA-LIQUIDACAO','QA-CLIENTES','QA-VENDAS','QA-IMPOSTO-TESTE','QA-LIQUIDACAO-CLIENTE','QA-CUSTO-DIRECTO','QA-COMPENSACAO-ENSAIO','QA-CUSTO-INDUSTRIAL','QA-COMPENSACAO-INDUSTRIAL','QA-STOCK','QA-COMPENSACAO-STOCK','QA-CONSUMO-STOCK'] as $code)$accounts[$code]=$native->table('gl_accounts')->insertGetId($scope+['code'=>$code,'label'=>'Conta artificial '.$code,'is_movement'=>true,'currency'=>'MZN']);
$native->table('gl_periods')->insert($scope+$meta+['period_key'=>'QA-2026-INTEGRADO','version'=>1,'starts_on'=>'2026-01-01','ends_on'=>'2026-12-31','status'=>'open']);
$rules=['supplier_payable.recognised'=>['QA-CONTRAPARTIDA','QA-FORNECEDOR'],'allocation.confirmed'=>['QA-FORNECEDOR','QA-LIQUIDACAO'],'allocation.reversed'=>['QA-LIQUIDACAO','QA-FORNECEDOR'],'journal.reversal'=>['QA-FORNECEDOR','QA-CONTRAPARTIDA'],'journal.replacement'=>['QA-CONTRAPARTIDA','QA-FORNECEDOR'],'sales.invoice'=>['QA-CLIENTES','QA-VENDAS'],'receivable.allocated'=>['QA-LIQUIDACAO-CLIENTE','QA-CLIENTES'],'receivable.reversed'=>['QA-CLIENTES','QA-LIQUIDACAO-CLIENTE'],'workshop.direct_cost.validated'=>['QA-CUSTO-DIRECTO','QA-COMPENSACAO-ENSAIO'],'workshop.direct_cost.reversal'=>['QA-COMPENSACAO-ENSAIO','QA-CUSTO-DIRECTO'],'workshop.direct_cost.replacement'=>['QA-CUSTO-DIRECTO','QA-COMPENSACAO-ENSAIO'],'industry.cost.validated'=>['QA-CUSTO-INDUSTRIAL','QA-COMPENSACAO-INDUSTRIAL'],'industry.cost.reversal'=>['QA-COMPENSACAO-INDUSTRIAL','QA-CUSTO-INDUSTRIAL'],'industry.cost.replacement'=>['QA-CUSTO-INDUSTRIAL','QA-COMPENSACAO-INDUSTRIAL'],'stock.receipt'=>['QA-STOCK','QA-COMPENSACAO-STOCK'],'stock.issue'=>['QA-CONSUMO-STOCK','QA-STOCK'],'stock.return'=>['QA-STOCK','QA-CONSUMO-STOCK']];
foreach($rules as $kind=>[$debit,$credit]){$policy=str_starts_with($kind,'stock.')?'QA-STOCK-VALUATION-v1':(str_starts_with($kind,'sales.')||str_starts_with($kind,'receivable.')?'QA-SALES-JOURNAL-v1':(str_starts_with($kind,'workshop.')?'QA-DIRECT-COST-JOURNAL-v1':(str_starts_with($kind,'industry.')?'QA-INDUSTRIAL-JOURNAL-v1':'QA-PAYABLE-JOURNAL-v1')));$native->table('gl_rules')->insert($scope+$meta+['rule_key'=>$kind,'version'=>1,'policy'=>$policy,'effect_type'=>$kind,'currency'=>'MZN','valid_from'=>'2026-01-01','valid_to'=>'2026-12-31','debit_account_id'=>$accounts[$debit],'credit_account_id'=>$accounts[$credit],'tax_account_id'=>$kind==='sales.invoice'?$accounts['QA-IMPOSTO-TESTE']:null]);}
$native->table('gl_stock_books')->insert($scope+$meta+['stock_item_id'=>$stockId,'warehouse_id'=>$warehouseId,'unit_code'=>'UN','period_key'=>'QA-2026-INTEGRADO']);
foreach($native->select("SELECT oid,oid::regprocedure::text signature FROM pg_proc WHERE pronamespace='public'::regnamespace") as $fn)if(!in_array($fn->oid,$functionsBefore,true))$native->statement('REVOKE ALL ON FUNCTION '.$fn->signature.' FROM PUBLIC');
foreach($native->select("SELECT tablename FROM pg_tables WHERE schemaname='public'") as $t)if(!in_array($t->tablename,$tablesBefore,true))$native->statement('REVOKE ALL ON TABLE public."'.$t->tablename.'" FROM PUBLIC');
foreach($native->select("SELECT sequencename FROM pg_sequences WHERE schemaname='public' AND sequencename LIKE 'gl_%'") as $seq)$native->statement('REVOKE ALL ON SEQUENCE public."'.$seq->sequencename.'" FROM PUBLIC');
return ['replayed'=>false,'warehouse_id'=>$warehouseId,'stock_item_id'=>$stockId,'scope'=>$scope,'initial_journals'=>0,'existing_obligations_not_copied'=>true];
});
$native->transaction(function()use($native,$result){foreach(['QA-CORRECTION-REVERSE'=>'journal.reversal','QA-CORRECTION-REPLACE'=>'journal.replacement','QA-COST-CORRECTION-REVERSE'=>'workshop.direct_cost.reversal','QA-COST-CORRECTION-REPLACE'=>'workshop.direct_cost.replacement','QA-INDUSTRIAL-CORRECTION-REVERSE'=>'industry.cost.reversal','QA-INDUSTRIAL-CORRECTION-REPLACE'=>'industry.cost.replacement'] as $alias=>$kind){if($native->table('gl_rules')->where($result['scope'])->where('rule_key',$alias)->exists())continue;$rule=(array)$native->table('gl_rules')->where($result['scope'])->where('rule_key',$kind)->first();unset($rule['id']);$rule['rule_key']=$alias;$native->table('gl_rules')->insert($rule);}});
}finally{DB::setDefaultConnection($oldDefault);Schema::clearResolvedInstance('db.schema');app()->forgetInstance('db.schema');$app->instance('env',$oldEnvironment);}
file_put_contents($s['root'].'/single-native-ledger.json',json_encode($result,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));file_put_contents($work.'/native-setup-result.json',json_encode(['passed'=>true]+$result,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo json_encode(['passed'=>true,'book'=>$book,'replayed'=>$result['replayed'],'proof'=>$work.'/native-setup-result.json']).PHP_EOL;