Files
SGF/app/Modules/Purchasing/PayablePositionReadController.php
T
2026-11-11 09:00:17 +02:00

34 lines
5.5 KiB
PHP

<?php
namespace App\Modules\Purchasing;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Tempo\Purchasing\Embedded\Models\Invoice;
/** Paged source window; positions come exclusively from the original payable engine. */
final class PayablePositionReadController
{
private function reviewedTerms(array $scope,array $ids):array
{
if(config('payable_due.enabled')!==true||$ids===[])return [];
$meta=DB::table('pd_due_meta')->where('policy',PayableDueController::POLICY)->first();abort_unless($meta&&DB::connection()->getDatabaseName()==='compras_receipts_test_'.$meta->composition&&hash_equals($meta->source_hash,(string)config('payable_due.schema_sha256')),503,'Condições administrativas não conferidas.');
$q=DB::table('pd_due_versions')->where($scope)->whereIn('obligation_id',$ids)->where('state','approved')->whereNotExists(function($next){$next->selectRaw('1')->from('pd_due_versions as newer')->where('newer.state','approved');foreach(['tenant_id','company_id','unit_id','obligation_id']as$field)$next->whereColumn('newer.'.$field,'pd_due_versions.'.$field);$next->whereColumn('newer.version','>','pd_due_versions.version');});
$out=[];foreach($q->get(['obligation_id','version','data','created_at'])as$row){$d=json_decode($row->data,true);$out[$row->obligation_id]=['version'=>(int)$row->version,'due_on'=>$d['due_on'],'evidence'=>$d['evidence'],'reviewed_at'=>$row->created_at];}return$out;
}
public function index(Request $r,ComposedFinance $finance)
{
$d=$r->validate(['page'=>'sometimes|integer|min:1|max:100000','per_page'=>'sometimes|integer|in:25,100','search'=>'sometimes|nullable|string|max:100','document_state'=>'sometimes|in:pending,validated','reference_on'=>'sometimes|date_format:Y-m-d']);abort_if(array_diff(array_keys($r->query()),array_keys($d)),422);
try{return $finance->run('view',function($engine,$settlements,$actor,$origin,$orders)use($d){
$q=Invoice::query()->where('invoices.tenant_id',$actor->tenant_id)->whereIn('invoices.purchase_order_id',$orders->select('id'))
->join('purchase_orders as po',fn($j)=>$j->on('po.id','=','invoices.purchase_order_id')->on('po.tenant_id','=','invoices.tenant_id'))
->leftJoin('suppliers as supplier',fn($j)=>$j->on('supplier.id','=','po.supplier_id')->on('supplier.tenant_id','=','po.tenant_id'));
if(isset($d['document_state']))$q->where('invoices.status',$d['document_state']);
if(($d['search']??'')!==''){$needle='%'.str_replace(['!','%','_'],['!!','!%','!_'],$d['search']).'%';$q->where(fn($x)=>$x->whereRaw("invoices.number ILIKE ? ESCAPE '!'",[$needle])->orWhereRaw("supplier.name ILIKE ? ESCAPE '!'",[$needle]));}
$p=$q->orderByDesc('invoices.id')->paginate((int)($d['per_page']??25),['invoices.id'],'page',(int)($d['page']??1));$ids=$p->pluck('id')->map(fn($id)=>(int)$id)->all();$book=$engine->positionsPage($ids);$positions=collect($book['obligations'])->keyBy('source_id');$sources=DB::table('pa_sources')->where('tenant_id',$actor->tenant_id)->whereIn('local_source_id',$ids)->where(['company_id'=>0,'unit_id'=>0])->get(['id','local_source_id'])->groupBy('local_source_id');$documents=collect($book['documents'])->keyBy('local_id');$rows=[];
foreach($ids as$id){$source=$documents->get($id);abort_unless($source,409,'Documento de origem alterado durante a consulta.');$identities=$sources->get($id,collect());abort_if($identities->count()>1,409,'Há mais de uma identidade financeira para este documento; exige conferência.');$position=$positions->get($identities->first()?->id);$money=[];foreach(['principal_minor','balance_minor','confirmed_payments_minor','allocated_payments_minor']as$field)$money[$field]=$position&&$position[$field]!==null?(string)$position[$field]:null;$rows[]=['document_id'=>$id,'reference'=>$source['number'],'supplier'=>$source['supplier_name'],'supplier_id'=>$source['supplier_id'],'document_state'=>$source['status'],'currency'=>$position['currency']??$source['currency'],'document_currency'=>$source['currency'],'obligation_id'=>$position['id']??null,'recognition_state'=>$position?'recognised':'not_recognised','payment_state'=>$position['payment_state']??'not_recognised','source_conflict'=>$position['source_conflict']??false,'source_revision'=>$source['revision'],'due_on'=>$position['due_on']??null,'due_status'=>$position['due_status']??'not_recognised','days_overdue'=>$position['days_overdue']??null,'pending'=>$position['documentary_pending']??$source['missing']]+$money;}
$terms=$this->reviewedTerms($origin['sgf_host_scope'],array_values(array_filter(array_column($rows,'obligation_id'))));foreach($rows as&$row){$row['reviewed_terms']=$terms[$row['obligation_id']]??null;$row+=PayableAge::classify($row,$d['reference_on']??null);}unset($row);
return response()->json(['positions'=>$rows,'aging_reference_on'=>$d['reference_on']??null,'aging_basis'=>'reviewed_administrative_terms_current_balance','pagination'=>['page'=>$p->currentPage(),'last_page'=>$p->lastPage(),'per_page'=>$p->perPage(),'total'=>$p->total()],'observed_at'=>now()->utc()->toIso8601String(),'balance_basis'=>'original_current_payable_engine','totals_basis'=>'visible_page_only','synthetic_only'=>true,'notice'=>'Documentos sem obrigação reconhecida e saldos indeterminados não são apresentados como dívida zero. Cada página é uma leitura actual; não é um relatório histórico.'])->header('Cache-Control','no-store, private');
});}catch(\InvalidArgumentException$e){abort(422,$e->getMessage());}catch(\DomainException$e){abort(403,$e->getMessage());}
}
}