commit melhorias sgf

This commit is contained in:
2026-11-11 09:00:17 +02:00
parent 0b05a1192c
commit 1d11a5e57b
736 changed files with 40746 additions and 210 deletions
@@ -0,0 +1,175 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\Sgc\StoreInvoiceRequest;
use App\Http\Requests\Sgc\StorePaymentRequest;
use App\Services\Sgc\IntegrationService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class SgcIntegrationController extends Controller
{
private IntegrationService $integrationService;
public function __construct(IntegrationService $integrationService)
{
$this->integrationService = $integrationService;
}
/**
* POST /api/v1/integration/sgc/invoices
* Recebe fatura do SGC
*/
public function storeInvoice(StoreInvoiceRequest $request): JsonResponse
{
$idempotencyKey = $request->header('X-Idempotency-Key');
if (!$idempotencyKey) {
return response()->json([
'status' => 'REJECTED',
'error_code' => 'MISSING_IDEMPOTENCY_KEY',
'message' => 'Header X-Idempotency-Key é obrigatório',
], 400);
}
// Verificar assinatura HMAC (em produção)
// $this->verificarAssinatura($request);
$resultado = $this->integrationService->receberFatura(
$request->validated(),
$idempotencyKey
);
$statusCode = match($resultado['status']) {
'ACCEPTED' => 201,
'DUPLICATE' => 409,
'REJECTED' => 400,
default => 500,
};
return response()->json($resultado, $statusCode);
}
/**
* POST /api/v1/integration/sgc/payments
* Recebe confirmação de pagamento do SGC
*/
public function storePayment(StorePaymentRequest $request): JsonResponse
{
$idempotencyKey = $request->header('X-Idempotency-Key');
if (!$idempotencyKey) {
return response()->json([
'status' => 'REJECTED',
'error_code' => 'MISSING_IDEMPOTENCY_KEY',
'message' => 'Header X-Idempotency-Key é obrigatório',
], 400);
}
$resultado = $this->integrationService->receberPagamento(
$request->validated(),
$idempotencyKey
);
$statusCode = match($resultado['status']) {
'ACCEPTED' => 201,
'DUPLICATE' => 409,
'REJECTED' => 400,
default => 500,
};
return response()->json($resultado, $statusCode);
}
/**
* GET /api/v1/integration/sgc/invoices/{sgfDocNumber}/status
* Consulta estado de uma fatura
*/
public function getInvoiceStatus(string $sgfDocNumber): JsonResponse
{
$fatura = \App\Models\Sgc\Fatura::where('sgf_doc_number', $sgfDocNumber)
->with(['ordemCompra', 'fornecedor'])
->first();
if (!$fatura) {
return response()->json([
'status' => 'NOT_FOUND',
'message' => 'Fatura não encontrada',
], 404);
}
// Construir lifecycle
$lifecycle = [
['event' => 'RECEBIDA', 'at' => $fatura->created_at],
];
if ($fatura->status_3_way_match !== 'Pendente') {
$lifecycle[] = [
'event' => '3WAY_MATCH',
'at' => $fatura->updated_at,
'result' => $fatura->status_3_way_match,
];
}
if ($fatura->status_pagamento === 'Pago') {
$lifecycle[] = [
'event' => 'PAGA',
'at' => $fatura->data_pagamento,
'reference' => $fatura->numero_comprovativo,
];
}
return response()->json([
'sgf_doc_number' => $fatura->sgf_doc_number,
'current_status' => $fatura->status_pagamento,
'lifecycle' => $lifecycle,
]);
}
/**
* GET /api/v1/integration/sgc/stats
* Estatísticas de integração
*/
public function getStats(): JsonResponse
{
$stats = [
'faturas' => [
'total' => \App\Models\Sgc\Fatura::count(),
'conformes' => \App\Models\Sgc\Fatura::conformes()->count(),
'com_excecao' => \App\Models\Sgc\Fatura::comExcecao()->count(),
'pagas' => \App\Models\Sgc\Fatura::pagas()->count(),
],
'ordens_compra' => [
'total' => \App\Models\Sgc\OrdemCompra::count(),
'cabimentadas' => \App\Models\Sgc\OrdemCompra::cabimentadas()->count(),
],
'integracao' => [
'aceites' => DB::table('sgc_integration_log')
->where('status', 'ACCEPTED')->count(),
'rejeitadas' => DB::table('sgc_integration_log')
->where('status', 'REJECTED')->count(),
'duplicadas' => DB::table('sgc_integration_log')
->where('status', 'DUPLICATE')->count(),
],
];
return response()->json($stats);
}
/**
* Verifica assinatura HMAC (em produção)
*/
private function verificarAssinatura(Request $request): bool
{
$signature = $request->header('X-SGC-Signature');
$secret = config('services.sgc.webhook_secret');
$payload = json_encode($request->all(), JSON_UNESCAPED_SLASHES);
$expectedSignature = hash_hmac('sha256', $payload, $secret);
return hash_equals($expectedSignature, $signature);
}
}
@@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Requests\Sgc\StoreInvoiceRequest;
use App\Modules\Purchasing\SgcInvoiceIngress;
use Illuminate\Database\QueryException;
final class SgcInvoiceIngressController
{
public function context(int $order, \App\Modules\Purchasing\InvoiceController $controller, \App\Modules\Purchasing\ReceiptRuntime $runtime)
{
$state = $controller->show($order, $runtime)->getData(true);
return response()->json(['order'=>$state['order'], 'revision'=>$state['revision'], 'receipt_progress'=>$state['receipt_progress']]);
}
public function __invoke(StoreInvoiceRequest $request, SgcInvoiceIngress $ingress)
{
try { return $ingress->receive($request); }
catch (QueryException $error) {
if (in_array($error->getCode(), ['55P03','40P01','40001'], true)) {
return response()->json(['error'=>'RETRY_SAME_KEY', 'message'=>'Operação concorrente; recuperar com a mesma chave e conteúdo.'], 503)->header('Retry-After', '1');
}
if ($error->getCode() === '23505') return response()->json(['error'=>'DOCUMENT_CONFLICT'], 409);
throw $error;
}
}
}