63 lines
3.9 KiB
PHP
63 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Enums\PurchasingRole;
|
|
use App\Models\Invoice;
|
|
use App\Models\PurchaseOrder;
|
|
use App\Models\User;
|
|
use App\Services\InventoryDispositions;
|
|
use App\Services\InventoryReversals;
|
|
use App\Services\InvoiceWorkflow;
|
|
use App\Services\OrganizationAccess;
|
|
use App\Services\SupplierPayables;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Validation\Rule;
|
|
use Inertia\Inertia;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class InvoiceController extends Controller
|
|
{
|
|
public function index(Request $request): Response
|
|
{
|
|
$actor = $request->user('web');
|
|
abort_unless($actor instanceof User, 403);
|
|
abort_unless($actor->hasFunctions([PurchasingRole::Admin, PurchasingRole::Finance]), 403);
|
|
$filters = $request->validate(['invoice_id' => ['nullable', 'integer', 'min:1', Rule::exists('invoices', 'id')->where('tenant_id', $actor->tenant_id)],
|
|
'purchase_order_id' => ['nullable', 'integer', 'min:1', Rule::exists('purchase_orders', 'id')->where('tenant_id', $actor->tenant_id)]]);
|
|
$focus = isset($filters['invoice_id']) ? (int) $filters['invoice_id'] : null;
|
|
$focusOrder = isset($filters['purchase_order_id']) ? (int) $filters['purchase_order_id'] : null;
|
|
Inertia::encryptHistory();
|
|
|
|
$response = Inertia::render('invoices/index', ['focusInvoiceId' => $focus, 'focusOrderId' => $focusOrder, 'stockReversals' => app(InventoryReversals::class)->financialNotices(), 'stockReturns' => app(InventoryDispositions::class)->financialNotices(),
|
|
'invoices' => Invoice::query()->whereIn('purchase_order_id', PurchaseOrder::query()->visibleTo($actor)->select('id'))->when($focus !== null, fn ($q) => $q->whereKey($focus))->when($focusOrder !== null, fn ($q) => $q->where('purchase_order_id', $focusOrder))->with('items')->latest('id')->paginate(15)->withQueryString()
|
|
->through(fn (Invoice $invoice) => [...$invoice->toArray(), 'canValidate' => app(InvoiceWorkflow::class)->reviewerEligible($actor, $invoice)]),
|
|
'orders' => PurchaseOrder::query()->visibleTo($actor)->with('items')->latest('id')->get()->map(fn ($order) => ['id' => $order->id, 'number' => $order->number, 'supplier' => $order->snapshot['supplier']['name'], 'items' => $order->items]),
|
|
'payablesAvailable' => SupplierPayables::enabled() && app(SupplierPayables::class)->can($actor, 'view'), 'canManage' => $actor->hasFunctions([PurchasingRole::Finance]) && ! app(OrganizationAccess::class)->maintenanceOnly($actor)])->toResponse($request);
|
|
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
|
|
|
return $response;
|
|
}
|
|
|
|
public function store(Request $request, PurchaseOrder $order, InvoiceWorkflow $workflow): RedirectResponse
|
|
{
|
|
$actor = $request->user('web');
|
|
abort_unless($actor instanceof User && $actor->hasFunctions([PurchasingRole::Finance]), 403);
|
|
$decimal = ['required', 'regex:/^\d{1,12}(\.\d{1,2})?$/'];
|
|
$data = $request->validate(['number' => 'required|string|max:100', 'issued_on' => 'required|date_format:Y-m-d', 'subtotal' => $decimal, 'tax' => $decimal, 'total' => $decimal, 'items' => 'required|array|min:1|max:50', 'items.*.purchase_order_item_id' => 'required|integer|distinct', 'items.*.quantity' => ['required', 'regex:/^\d{1,9}(\.\d{1,3})?$/'], 'items.*.unit' => 'sometimes|required|string|max:30', 'items.*.unit_price' => ['sometimes', ...$decimal], 'items.*.tax_rate' => ['sometimes', 'required', 'regex:/^\d{1,3}(\.\d{1,2})?$/']]);
|
|
$workflow->register($actor, $order, $data);
|
|
|
|
return to_route('invoices.index');
|
|
}
|
|
|
|
public function validateInvoice(Request $request, Invoice $invoice, InvoiceWorkflow $workflow): RedirectResponse
|
|
{
|
|
$actor = $request->user('web');
|
|
abort_unless($actor instanceof User, 403);
|
|
$workflow->validate($actor, $invoice);
|
|
|
|
return back();
|
|
}
|
|
}
|