285 lines
9.4 KiB
PHP
285 lines
9.4 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\App;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\App\InvoiceRequestProcessRequest;
|
|
use App\Http\Requests\App\InvoiceRequestRejectRequest;
|
|
use App\Http\Requests\App\InvoiceRequestUploadRequest;
|
|
use App\Models\InvoiceRequest;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
|
|
|
/**
|
|
* Controlador para gestión administrativa de solicitudes de factura
|
|
*/
|
|
class InvoiceRequestController extends Controller
|
|
{
|
|
/**
|
|
* Listar todas las solicitudes de factura con filtros y paginación
|
|
*/
|
|
public function index(Request $request)
|
|
{
|
|
$query = InvoiceRequest::with([
|
|
'sale:id,invoice_number,subtotal,tax,total,payment_method,status,created_at',
|
|
'sale.user:id,name,email',
|
|
'client:id,name,client_number,email,phone,rfc,razon_social',
|
|
'processedBy:id,name,email'
|
|
]);
|
|
|
|
// Filtro por estado
|
|
if ($request->has('status') && $request->status !== '') {
|
|
$query->where('status', $request->status);
|
|
}
|
|
|
|
// Filtro por rango de fechas
|
|
if ($request->has('date_from')) {
|
|
$query->whereDate('requested_at', '>=', $request->date_from);
|
|
}
|
|
|
|
if ($request->has('date_to')) {
|
|
$query->whereDate('requested_at', '<=', $request->date_to);
|
|
}
|
|
|
|
// Búsqueda por folio de venta
|
|
if ($request->has('invoice_number') && $request->invoice_number !== '') {
|
|
$query->whereHas('sale', function ($q) use ($request) {
|
|
$q->where('invoice_number', 'like', '%' . $request->invoice_number . '%');
|
|
});
|
|
}
|
|
|
|
// Búsqueda por cliente
|
|
if ($request->has('client_search') && $request->client_search !== '') {
|
|
$query->whereHas('client', function ($q) use ($request) {
|
|
$q->where('name', 'like', '%' . $request->client_search . '%')
|
|
->orWhere('rfc', 'like', '%' . $request->client_search . '%')
|
|
->orWhere('email', 'like', '%' . $request->client_search . '%');
|
|
});
|
|
}
|
|
|
|
// Ordenamiento
|
|
$sortBy = $request->get('sort_by', 'requested_at');
|
|
$sortOrder = $request->get('sort_order', 'desc');
|
|
$query->orderBy($sortBy, $sortOrder);
|
|
|
|
// Paginación
|
|
$perPage = $request->get('per_page', 15);
|
|
$invoiceRequests = $query->paginate($perPage);
|
|
|
|
return ApiResponse::OK->response([
|
|
'invoice_requests' => $invoiceRequests
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Mostrar detalles de una solicitud específica
|
|
*/
|
|
public function show($id)
|
|
{
|
|
$invoiceRequest = InvoiceRequest::with([
|
|
'sale.user:id,name,email',
|
|
'sale.details:id,sale_id,inventory_id,product_name,quantity,unit_price,subtotal,discount_percentage,discount_amount',
|
|
'sale.details:inventory_id.category_id,name',
|
|
'sale.details.inventory:id,category_id,name,sku',
|
|
'sale.details.inventory.category:id,name',
|
|
'sale.details.serials:id,inventory_id,sale_detail_id,serial_number,status',
|
|
'client',
|
|
'processedBy:id,name,email'
|
|
])->find($id);
|
|
|
|
if (!$invoiceRequest) {
|
|
return ApiResponse::NOT_FOUND->response([
|
|
'message' => 'Solicitud de factura no encontrada'
|
|
]);
|
|
}
|
|
|
|
return ApiResponse::OK->response([
|
|
'invoice_request' => $invoiceRequest
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Marcar una solicitud como procesada
|
|
*
|
|
*/
|
|
public function process(InvoiceRequestProcessRequest $request, $id)
|
|
{
|
|
$invoiceRequest = InvoiceRequest::find($id);
|
|
|
|
if (!$invoiceRequest) {
|
|
return ApiResponse::NOT_FOUND->response([
|
|
'message' => 'Solicitud de factura no encontrada'
|
|
]);
|
|
}
|
|
|
|
if ($invoiceRequest->status !== InvoiceRequest::STATUS_PENDING) {
|
|
return ApiResponse::BAD_REQUEST->response([
|
|
'message' => 'Solo se pueden procesar solicitudes pendientes',
|
|
'current_status' => $invoiceRequest->status
|
|
]);
|
|
}
|
|
|
|
$invoiceRequest->markAsProcessed(
|
|
$request->user()->id,
|
|
$request->notes
|
|
);
|
|
|
|
$invoiceRequest->load([
|
|
'sale:id,invoice_number,total',
|
|
'client:id,name,rfc,email',
|
|
'processedBy:id,name'
|
|
]);
|
|
|
|
return ApiResponse::OK->response([
|
|
'message' => 'Solicitud marcada como procesada correctamente',
|
|
'invoice_request' => $invoiceRequest
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Rechazar una solicitud
|
|
*
|
|
*/
|
|
public function reject(InvoiceRequestRejectRequest $request, $id)
|
|
{
|
|
$invoiceRequest = InvoiceRequest::find($id);
|
|
|
|
if (!$invoiceRequest) {
|
|
return ApiResponse::NOT_FOUND->response([
|
|
'message' => 'Solicitud de factura no encontrada'
|
|
]);
|
|
}
|
|
|
|
if ($invoiceRequest->status !== InvoiceRequest::STATUS_PENDING) {
|
|
return ApiResponse::BAD_REQUEST->response([
|
|
'message' => 'Solo se pueden rechazar solicitudes pendientes',
|
|
'current_status' => $invoiceRequest->status
|
|
]);
|
|
}
|
|
|
|
$invoiceRequest->markAsRejected(
|
|
$request->user()->id,
|
|
$request->notes
|
|
);
|
|
|
|
$invoiceRequest->load([
|
|
'sale:id,invoice_number,total',
|
|
'client:id,name,rfc,email',
|
|
'processedBy:id,name'
|
|
]);
|
|
|
|
return ApiResponse::OK->response([
|
|
'message' => 'Solicitud rechazada correctamente',
|
|
'invoice_request' => $invoiceRequest
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Obtener estadísticas de solicitudes de factura
|
|
*/
|
|
public function stats()
|
|
{
|
|
$stats = [
|
|
'pending' => InvoiceRequest::where('status', InvoiceRequest::STATUS_PENDING)->count(),
|
|
'processed' => InvoiceRequest::where('status', InvoiceRequest::STATUS_PROCESSED)->count(),
|
|
'rejected' => InvoiceRequest::where('status', InvoiceRequest::STATUS_REJECTED)->count(),
|
|
'total' => InvoiceRequest::count(),
|
|
'today_pending' => InvoiceRequest::where('status', InvoiceRequest::STATUS_PENDING)
|
|
->whereDate('requested_at', today())
|
|
->count(),
|
|
'this_month' => InvoiceRequest::whereMonth('requested_at', now()->month)
|
|
->whereYear('requested_at', now()->year)
|
|
->count(),
|
|
];
|
|
|
|
return ApiResponse::OK->response([
|
|
'stats' => $stats
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Subir archivos de factura (XML y PDF) y guardar UUID del CFDI
|
|
*/
|
|
public function uploadInvoiceFile(InvoiceRequestUploadRequest $request, $id)
|
|
{
|
|
$invoiceRequest = InvoiceRequest::find($id);
|
|
|
|
if (!$invoiceRequest) {
|
|
return ApiResponse::NOT_FOUND->response([
|
|
'message' => 'Solicitud de factura no encontrada'
|
|
]);
|
|
}
|
|
|
|
// Validar que la solicitud esté pendiente o procesada (permitir actualizar facturas)
|
|
if (!in_array($invoiceRequest->status, [InvoiceRequest::STATUS_PENDING, InvoiceRequest::STATUS_PROCESSED])) {
|
|
return ApiResponse::BAD_REQUEST->response([
|
|
'message' => 'No se pueden subir archivos a solicitudes rechazadas',
|
|
'current_status' => $invoiceRequest->status
|
|
]);
|
|
}
|
|
|
|
// Generar timestamp para nombres de archivo
|
|
$timestamp = now()->format('YmdHis');
|
|
$storagePath = 'invoices/' . now()->format('Y/m');
|
|
|
|
$updateData = [
|
|
'cfdi_uuid' => $request->cfdi_uuid,
|
|
];
|
|
|
|
// Procesar XML si se envió
|
|
if ($request->hasFile('invoice_xml')) {
|
|
// Eliminar XML anterior si existe
|
|
if ($invoiceRequest->invoice_xml_path) {
|
|
Storage::disk('public')->delete($invoiceRequest->invoice_xml_path);
|
|
}
|
|
|
|
$xmlFileName = "invoice-{$id}-{$timestamp}.xml";
|
|
$xmlPath = $request->file('invoice_xml')->storeAs(
|
|
$storagePath,
|
|
$xmlFileName,
|
|
'public'
|
|
);
|
|
$updateData['invoice_xml_path'] = $xmlPath;
|
|
}
|
|
|
|
// Procesar PDF (siempre requerido)
|
|
if ($request->hasFile('invoice_pdf')) {
|
|
// Eliminar PDF anterior si existe
|
|
if ($invoiceRequest->invoice_pdf_path) {
|
|
Storage::disk('public')->delete($invoiceRequest->invoice_pdf_path);
|
|
}
|
|
|
|
$pdfFileName = "invoice-{$id}-{$timestamp}.pdf";
|
|
$pdfPath = $request->file('invoice_pdf')->storeAs(
|
|
$storagePath,
|
|
$pdfFileName,
|
|
'public'
|
|
);
|
|
$updateData['invoice_pdf_path'] = $pdfPath;
|
|
}
|
|
|
|
// Actualizar registro
|
|
$invoiceRequest->update($updateData);
|
|
|
|
$invoiceRequest->load([
|
|
'sale:id,invoice_number',
|
|
'client:id,name,rfc',
|
|
]);
|
|
|
|
$files = [];
|
|
if (isset($xmlPath)) {
|
|
$files['xml_url'] = Storage::url($xmlPath);
|
|
}
|
|
if (isset($pdfPath)) {
|
|
$files['pdf_url'] = Storage::url($pdfPath);
|
|
}
|
|
|
|
return ApiResponse::OK->response([
|
|
'message' => 'Archivos de factura subidos correctamente',
|
|
'invoice_request' => $invoiceRequest,
|
|
'files' => $files
|
|
]);
|
|
}
|
|
}
|