@@ -7,11 +7,17 @@ use App\Models\CriticalityLevel;
|
||||
use App\Models\Department;
|
||||
use App\Models\Equipment;
|
||||
use App\Models\EquipmentSparePart;
|
||||
use App\Models\Requisition;
|
||||
use App\Models\SparePart;
|
||||
use App\Models\StockItem;
|
||||
use App\Models\StockMovement;
|
||||
use App\Models\StockOperation;
|
||||
use App\Models\User;
|
||||
use App\Services\AdministrativeGeography;
|
||||
use App\Services\ConflictGuard;
|
||||
use App\Services\CurrentActor;
|
||||
use App\Services\EquipmentWarehouseRequests;
|
||||
use App\Services\OrganizationAccess;
|
||||
use App\Services\ReceiptWorkflow;
|
||||
use App\Services\RequisitionWorkflow;
|
||||
use Brick\Math\BigDecimal;
|
||||
@@ -26,17 +32,66 @@ use Inertia\Response;
|
||||
|
||||
class EquipmentController extends Controller
|
||||
{
|
||||
private function actor(Request $request): User
|
||||
{
|
||||
$actor = $request->user('web');
|
||||
abort_unless($actor instanceof User, 403);
|
||||
|
||||
return $actor;
|
||||
}
|
||||
|
||||
public function show(Request $request, int $equipment): \Symfony\Component\HttpFoundation\Response
|
||||
{
|
||||
$actor = $request->user('web');
|
||||
abort_unless($actor instanceof User, 403);
|
||||
$actor = User::query()->where('tenant_id', $actor->tenant_id)->findOrFail($actor->id);
|
||||
abort_unless($actor->hasFunctions([PurchasingRole::Admin, PurchasingRole::Warehouse, PurchasingRole::Procurement, PurchasingRole::Finance, PurchasingRole::DepartmentHead]), 403);
|
||||
$request->validate(['purchases_page' => 'nullable|integer|min:1|max:1000000', 'movements_page' => 'nullable|integer|min:1|max:1000000', 'requests_page' => 'nullable|integer|min:1|max:1000000']);
|
||||
|
||||
return DB::transaction(function () use ($request, $actor, $equipment) {
|
||||
if (DB::getDriverName() === 'pgsql' && DB::transactionLevel() === 1) {
|
||||
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
}
|
||||
$asset = Equipment::query()->when($actor->departmentScoped(), fn ($q) => $q->where('department_id', $actor->department_id))->findOrFail($equipment);
|
||||
$purchases = Requisition::query()->visibleTo($actor)->whereHas('items', fn ($q) => $q->where('equipment_id', $asset->id))
|
||||
->with(['items' => fn ($q) => $q->where('equipment_id', $asset->id)->select(['id', 'requisition_id', 'description', 'quantity', 'unit'])])
|
||||
->orderBy('id')->paginate(15, ['id', 'number', 'status', 'needed_by'], 'purchases_page')->withQueryString();
|
||||
$movements = StockOperation::query()->where('equipment_id', $asset->id)->orderBy('id')
|
||||
->paginate(15, ['id', 'kind', 'reference', 'quantity_milli', 'recorded_at', 'snapshot'], 'movements_page')->withQueryString()
|
||||
->through(fn (StockOperation $operation) => [
|
||||
'id' => $operation->id, 'kind' => $operation->kind, 'reference' => $operation->reference,
|
||||
'quantity_milli' => $operation->quantity_milli, 'recorded_at' => $operation->recorded_at,
|
||||
'item' => $operation->snapshot['stock_item'] ?? null, 'warehouse' => $operation->snapshot['warehouse'] ?? null,
|
||||
]);
|
||||
Inertia::encryptHistory();
|
||||
|
||||
$response = Inertia::render('equipment/show', ['equipment' => $asset->only(['id', 'code', 'description', 'area', 'status']),
|
||||
'geography' => app(AdministrativeGeography::class)->describe($asset->geography_reference),
|
||||
'warehouseRequests' => app(EquipmentWarehouseRequests::class)->page($actor, $asset),
|
||||
'purchases' => $purchases, 'movements' => $movements])->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
||||
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
abort_unless($request->user()->hasFunctions([PurchasingRole::Admin, PurchasingRole::Warehouse, PurchasingRole::Procurement, PurchasingRole::Finance, PurchasingRole::DepartmentHead]), 403);
|
||||
abort_unless($this->actor($request)->hasFunctions([PurchasingRole::Admin, PurchasingRole::Warehouse, PurchasingRole::Procurement, PurchasingRole::Finance, PurchasingRole::DepartmentHead]), 403);
|
||||
$filters = $request->validate(['location' => 'nullable|string|regex:/^[0-9]+$/D|max:20']);
|
||||
$geography = app(AdministrativeGeography::class);
|
||||
$assets = Equipment::query();
|
||||
if ($request->user()->departmentScoped()) {
|
||||
$assets->where('department_id', $request->user()->department_id);
|
||||
if (! empty($filters['location'])) {
|
||||
abort_unless($geography->availableFor('equipment'), 503);
|
||||
$assets->where('geography_reference->catalogue', config('geography.source_sha256'))->whereIn('geography_reference->record_id', $geography->descendants($filters['location']));
|
||||
}
|
||||
if ($this->actor($request)->departmentScoped()) {
|
||||
$assets->where('department_id', $this->actor($request)->department_id);
|
||||
}
|
||||
$equipment = $assets->orderBy('code')->get();
|
||||
$links = EquipmentSparePart::query()->whereIn('equipment_id', $equipment->pluck('id'))->get();
|
||||
$parts = SparePart::query();
|
||||
if ($request->user()->departmentScoped()) {
|
||||
if ($this->actor($request)->departmentScoped() || ! empty($filters['location'])) {
|
||||
$parts->whereIn('id', $links->pluck('spare_part_id'));
|
||||
}
|
||||
$parts = $parts->get()->map(function (SparePart $part) {
|
||||
@@ -47,24 +102,28 @@ class EquipmentController extends Controller
|
||||
});
|
||||
$items = StockItem::query();
|
||||
$departments = Department::query();
|
||||
if ($request->user()->departmentScoped()) {
|
||||
if ($this->actor($request)->departmentScoped() || ! empty($filters['location'])) {
|
||||
$items->whereIn('id', $parts->pluck('stock_item_id')->merge($equipment->pluck('stock_item_id')->filter()));
|
||||
$departments->whereKey($request->user()->department_id);
|
||||
if ($this->actor($request)->departmentScoped()) {
|
||||
$departments->whereKey($this->actor($request)->department_id);
|
||||
}
|
||||
}
|
||||
|
||||
return Inertia::render('equipment/index', [
|
||||
'geographyEnabled' => $geography->availableFor('equipment'), 'equipmentGeography' => $equipment->mapWithKeys(fn (Equipment $asset) => [$asset->id => $geography->describe($asset->geography_reference)])->all(),
|
||||
'filters' => $filters, 'geographyFilter' => empty($filters['location']) ? null : $geography->describe(['catalogue' => config('geography.source_sha256'), 'record_id' => $filters['location'], 'country' => 'MZ']),
|
||||
'equipment' => $equipment, 'parts' => $parts, 'links' => $links, 'levels' => CriticalityLevel::query()->orderBy('code')->get(),
|
||||
'items' => $items->orderBy('code')->get(['id', 'code', 'name', 'unit']),
|
||||
'departments' => $departments->orderBy('name')->get(['id', 'name']),
|
||||
'history' => StockOperation::query()->whereNotNull('equipment_id')->whereIn('equipment_id', $equipment->pluck('id'))->latest('id')->paginate(15),
|
||||
'canManage' => $request->user()->hasFunctions([PurchasingRole::Admin, PurchasingRole::Warehouse, PurchasingRole::Procurement]),
|
||||
'canConfigure' => $request->user()->hasFunctions([PurchasingRole::Admin]),
|
||||
'canManage' => $this->actor($request)->hasFunctions([PurchasingRole::Admin, PurchasingRole::Warehouse, PurchasingRole::Procurement]),
|
||||
'canConfigure' => $this->actor($request)->hasFunctions([PurchasingRole::Admin]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request, string $kind): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$user = $this->actor($request);
|
||||
abort_unless(in_array($kind, ['categorias', 'activos', 'pecas', 'associacoes']), 404);
|
||||
abort_unless($kind === 'categorias' ? $user->hasFunctions([PurchasingRole::Admin]) : $user->hasFunctions([PurchasingRole::Admin, PurchasingRole::Warehouse, PurchasingRole::Procurement]), 403);
|
||||
$tenant = $user->tenant_id;
|
||||
@@ -76,9 +135,17 @@ class EquipmentController extends Controller
|
||||
'pecas' => ['id' => ['nullable', 'integer', $exists('spare_parts')], 'version' => 'nullable|required_with:id|integer|min:1', 'stock_item_id' => ['required', 'integer', $exists('stock_items'), Rule::unique('spare_parts')->ignore($request->input('id'))], 'criticality_level_id' => ['required', 'integer', $exists('criticality_levels')], 'minimum_quantity' => ['required', 'regex:/^\d{1,9}(\.\d{1,3})?$/']],
|
||||
'associacoes' => ['equipment_id' => ['required', 'integer', $exists('equipment')], 'spare_part_id' => ['required', 'integer', $exists('spare_parts')]],
|
||||
};
|
||||
if ($kind === 'activos') {
|
||||
$rules += app(AdministrativeGeography::class)->referenceRules('equipment');
|
||||
}
|
||||
$data = $request->validate($rules);
|
||||
DB::transaction(function () use ($data, $kind, $user) {
|
||||
app(ConflictGuard::class)->lockCompany($user);
|
||||
$user = app(CurrentActor::class)->lock($user, $kind === 'categorias' ? [PurchasingRole::Admin] : [PurchasingRole::Admin, PurchasingRole::Warehouse, PurchasingRole::Procurement]);
|
||||
abort_if(app(OrganizationAccess::class)->maintenanceOnly($user), 403);
|
||||
if ($kind === 'activos' && array_key_exists('geography_reference', $data)) {
|
||||
$data['geography_reference'] = app(AdministrativeGeography::class)->canonicalFor('equipment', $data['geography_reference']);
|
||||
}
|
||||
$model = match ($kind) {
|
||||
'categorias' => new CriticalityLevel,'activos' => new Equipment,'pecas' => new SparePart,'associacoes' => new EquipmentSparePart
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user