Files
SGF/app/Shared/Context/ModuleRecords.php
T
2026-11-02 08:06:45 +02:00

50 lines
2.7 KiB
PHP

<?php
namespace App\Shared\Context;
use App\Modules\Identity\Services\Audit;
use Illuminate\Support\Facades\DB;
/** Explicit, append-only associations reuse the existing immutable audit store.
* Legacy rows are never assigned from their names, creators or absence of a link.
*/
final class ModuleRecords
{
public const AREAS = ['clients','services','service_drafts','billing_drafts'];
public static function active(): ?string { return request()->attributes->get('sgf_module'); }
public static function query(string $table)
{
$query=DB::table($table);
$module=self::active();
if (!$module) return $query; // CLI domain proofs have no HTTP module selection.
if (str_starts_with($table,'a_') && in_array(substr($table,2),self::AREAS,true)) {
self::constrain($query,$table,substr($table,2),$module);
} elseif ($table==='si_invoices') {
$query->whereIn('draft_id',self::query('a_billing_drafts')->select('id'));
}
return $query;
}
public static function constrain($query,string $table,string $area,string $module): void
{
$query->whereExists(function($q)use($table,$area,$module){
$q->selectRaw('1')->from('identity_audit as ma')->where('ma.action','module.record.associated')
->whereColumn('ma.tenant_id',$table.'.tenant_id')->whereColumn('ma.company_id',$table.'.company_id')
->whereRaw("ma.details::jsonb->>'area' = ?",[$area])
->whereRaw("ma.details::jsonb->>'record_id' = {$table}.id::text")
->whereRaw("ma.details::jsonb->>'module_id' = ?",[$module])
->whereNotExists(function($revoked){
$revoked->selectRaw('1')->from('identity_audit as mr')->where('mr.action','module.record.disassociated')
->whereColumn('mr.tenant_id','ma.tenant_id')->whereColumn('mr.company_id','ma.company_id')->whereColumn('mr.id','>','ma.id')
->whereRaw("mr.details::jsonb->>'area' = ma.details::jsonb->>'area'")
->whereRaw("mr.details::jsonb->>'record_id' = ma.details::jsonb->>'record_id'")
->whereRaw("mr.details::jsonb->>'module_id' = ma.details::jsonb->>'module_id'");
});
});
}
public static function associate(string $area,int $id,int $actor,array $scope,string $module,string $reason,bool $remove=false,?int $version=null): void
{
Audit::record($remove?'module.record.disassociated':'module.record.associated',$actor,$scope['tenant_id'],$scope['company_id'],$scope['unit_id']??null,
details:['area'=>$area,'record_id'=>$id,'module_id'=>$module,'reason'=>$reason,'version'=>$version]);
}
}