35 lines
3.6 KiB
PHP
35 lines
3.6 KiB
PHP
<?php
|
|
namespace App\Modules\Accounting;
|
|
|
|
use App\Shared\Installation\CompositionRuntime;
|
|
use Tempo\Finance\Payables\Payables;
|
|
|
|
/** Explicit, fixed synthetic material perimeter. Unknown or shared origins fail closed. */
|
|
final class ComposedMaterialIsolation
|
|
{
|
|
public function check($host,$native,array $scope):void
|
|
{
|
|
$physical=CompositionRuntime::physical($host);$path='C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/receipt-composition-'.$physical['composition'].'/single-material-perimeter.json';
|
|
$pin=config('sgf_single_installation.material_perimeter_sha256');
|
|
if(!is_string($pin)||!preg_match('/^[a-f0-9]{64}$/D',$pin)||!is_file($path)||is_link($path)||!hash_equals($pin,hash_file('sha256',$path)))throw new \DomainException('Perímetro de materiais não conferido; não combinar custos e valorização.');
|
|
$p=json_decode(file_get_contents($path),true,32,JSON_THROW_ON_ERROR);
|
|
if(($p['contract']??null)!=='SGF-QA-DISJOINT-MATERIALS-v1'||($p['scope']??null)!==$scope||($p['host_database']??null)!==$host->getDatabaseName()||($p['composition']??null)!==$physical['composition'])throw new \DomainException('Perímetro pertence a outro contexto.');
|
|
$stocks=$native->table('gl_stock_books')->where($scope)->orderBy('id')->get(['stock_item_id','warehouse_id']);
|
|
$actual=$stocks->map(fn($r)=>(array)$r)->all();if(Payables::canonical($actual)!==Payables::canonical($p['valued_stock']))throw new \DomainException('Artigo/armazém de valorização mudou; nova conferência obrigatória.');
|
|
$codes=$native->table('stock_items')->where('tenant_id',$scope['tenant_id'])->whereIn('id',$stocks->pluck('stock_item_id'))->pluck('code')->all();
|
|
foreach($native->table('gl_cost_sources')->where($scope)->orderBy('id')->cursor() as $map){
|
|
$claim=$p['direct_costs'][(string)$map->order_id]??null;
|
|
if(!$claim||$claim['material_origin']!==$map->material_origin)throw new \DomainException('Custo sem declaração individual de exclusão do Stock valorizado.');
|
|
if(!$host->table('wo_orders')->where($scope)->where('id',$map->order_id)->exists())throw new \DomainException('Ordem de custo fora da empresa/unidade.');
|
|
$entry=$host->table('wo_entries')->where('order_id',$map->order_id)->where('origin',$map->material_origin)->orderByDesc('version')->first();$data=$entry?json_decode($entry->data,true):null;
|
|
if(!$entry||$entry->kind!=='material'||($data['resource']??null)!==$claim['resource']||in_array($claim['resource'],$codes,true)||!str_starts_with($claim['resource'],'QA-MATERIAL-DIRECTO-')||isset($data['stock_item_id'])||isset($data['delivery_movement_id']))throw new \DomainException('Material directo partilhado, alterado ou sem fronteira física explícita.');
|
|
}
|
|
foreach($native->table('gl_industrial_sources')->where($scope)->orderBy('id')->cursor() as $map){
|
|
$claim=$p['industrial_costs'][(string)$map->lot_id]??null;$origins=json_decode($map->origins,true);
|
|
if(!$claim||$claim['material_origin']!==($origins['material']??null)||in_array($claim['material_origin'],$codes,true))throw new \DomainException('Material industrial sem exclusão individual do Stock valorizado.');
|
|
$entry=$host->table('ic_inputs')->where($scope)->where('lot_id',$map->lot_id)->where('kind','material')->where('source_ref',$claim['material_origin'])->orderByDesc('version')->first();
|
|
if(!$entry||!str_starts_with($entry->source_ref,'IND-TEST-MATERIAL-'))throw new \DomainException('Origem industrial fora do perímetro sintético seleccionado.');
|
|
}
|
|
}
|
|
}
|