identifiers();$out=[]; $permissions->require($context->userId(),$s['tenant_id'],$s['company_id'],'access.manage'); foreach(ModuleRecords::AREAS as $area){ $unit=str_ends_with($area,'_drafts')?$context->unitId():null; if(str_ends_with($area,'_drafts')&&!$unit)continue; if(!$permissions->has($context->userId(),$s['tenant_id'],$s['company_id'],"$area.view",$unit))continue; $rows=DB::table('a_'.$area)->where($s)->where('unit_id',$unit)->orderBy('id')->get(['id','name','version']); foreach($rows as $row){ $row->modules=DB::table('identity_audit')->where($s)->whereIn('action',['module.record.associated','module.record.disassociated']) ->whereRaw("details::jsonb->>'area' = ?",[$area])->whereRaw("details::jsonb->>'record_id' = ?",[(string)$row->id]) ->orderByDesc('id')->get(['details','action'])->unique(fn($a)=>json_decode($a->details,true)['module_id']) ->filter(fn($a)=>$a->action==='module.record.associated')->map(fn($a)=>json_decode($a->details,true)['module_id'])->values()->all(); } $out[$area]=$rows; } return response()->json(['records'=>$out,'modules'=>app(ModuleNavigation::class)->available($context), 'notice'=>'Registos sem associação explícita permanecem aqui visíveis pelos direitos originais. Associar não altera o registo, a origem ou as permissões.'])->header('Cache-Control','no-store'); } public function associate(Request $r,CompanyContext $context,Permissions $permissions) { $d=$r->validate(['area'=>'required|in:'.implode(',',ModuleRecords::AREAS),'id'=>'required|integer|min:1','version'=>'required|integer|min:1','module_id'=>'required|string','reason'=>'required|string|min:3|max:500','operation'=>'sometimes|in:associate,remove']); abort_if(array_diff(array_keys($r->all()),array_keys($d))!==[],422); $s=$context->identifiers();$unit=str_ends_with($d['area'],'_drafts')?$context->unitId():null; abort_if(str_ends_with($d['area'],'_drafts')&&!$unit,403); DB::transaction(function()use($d,$s,$unit,$context,$permissions){ DB::table('companies')->where('tenant_id',$s['tenant_id'])->where('id',$s['company_id'])->lockForUpdate()->firstOrFail(); $permissions->require($context->userId(),$s['tenant_id'],$s['company_id'],'access.manage'); $permissions->require($context->userId(),$s['tenant_id'],$s['company_id'],$d['area'].'.view',$unit); $permissions->require($context->userId(),$s['tenant_id'],$s['company_id'],$d['area'].'.edit',$unit); abort_unless(in_array($d['module_id'],array_column(app(ModuleNavigation::class)->available($context),'id'),true),403); $row=DB::table('a_'.$d['area'])->where($s)->where('unit_id',$unit)->where('id',$d['id'])->first(['id','version']); abort_unless($row,403); abort_unless((int)$row->version===(int)$d['version'],409,'O registo mudou. Reabra a consulta antes de decidir a associação.'); $previous=DB::table('identity_audit')->where($s)->whereIn('action',['module.record.associated','module.record.disassociated']) ->whereRaw("details::jsonb->>'area' = ?",[$d['area']])->whereRaw("details::jsonb->>'record_id' = ?",[(string)$d['id']])->whereRaw("details::jsonb->>'module_id' = ?",[$d['module_id']])->orderByDesc('id')->value('action'); $remove=($d['operation']??'associate')==='remove'; if(($remove&&$previous==='module.record.associated')||(!$remove&&$previous!=='module.record.associated')) ModuleRecords::associate($d['area'],$d['id'],$context->userId(),$s+['unit_id'=>$unit],$d['module_id'],$d['reason'],$remove,(int)$row->version); }); return response()->json(['ok'=>true]); } }