Commit 280d6a36 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Fix stock count not updating inventory + add purchase order wizard

1. Stock Count fix: dispatch StockCountCompleted event after completing
   a count so the ProcessCountAdjustments listener creates inventory
   movements. Made listener synchronous for immediate feedback.

2. Stock Adjustment: route now points to the existing wizard component
   instead of the plain form.

3. Purchase Orders: new CreatePurchaseOrderWizard with 4-step flow
   (supplier → warehouse/dates → items → review & confirm).
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 9bf95b08
...@@ -6,10 +6,9 @@ ...@@ -6,10 +6,9 @@
use App\Domain\Inventory\Events\StockCountCompleted; use App\Domain\Inventory\Events\StockCountCompleted;
use App\Domain\Inventory\Models\Warehouse; use App\Domain\Inventory\Models\Warehouse;
use App\Domain\Inventory\Services\InventoryService; use App\Domain\Inventory\Services\InventoryService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class ProcessCountAdjustments implements ShouldQueue class ProcessCountAdjustments
{ {
public function __construct( public function __construct(
private InventoryService $inventoryService, private InventoryService $inventoryService,
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
use App\Domain\Inventory\Enums\MovementType; use App\Domain\Inventory\Enums\MovementType;
use App\Domain\Inventory\Enums\StockCountItemStatus; use App\Domain\Inventory\Enums\StockCountItemStatus;
use App\Domain\Inventory\Enums\StockCountStatus; use App\Domain\Inventory\Enums\StockCountStatus;
use App\Domain\Inventory\Events\StockCountCompleted;
use App\Domain\Inventory\Models\InventoryLevel; use App\Domain\Inventory\Models\InventoryLevel;
use App\Domain\Inventory\Models\Product; use App\Domain\Inventory\Models\Product;
use App\Domain\Inventory\Models\StockCount; use App\Domain\Inventory\Models\StockCount;
...@@ -145,6 +146,8 @@ public function completeCount(StockCount $stockCount, User $actor): StockCount ...@@ -145,6 +146,8 @@ public function completeCount(StockCount $stockCount, User $actor): StockCount
'completed_by' => $actor->id, 'completed_by' => $actor->id,
]); ]);
StockCountCompleted::dispatch($stockCount, $actor);
return $stockCount->fresh('items'); return $stockCount->fresh('items');
}); });
} }
......
<?php
namespace App\Livewire\Inventory;
use App\Domain\Inventory\Models\Product;
use App\Domain\Inventory\Models\PurchaseOrder;
use App\Domain\Inventory\Models\Warehouse;
use App\Domain\Inventory\Services\PurchaseOrderService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Traits\UsesBranchScope;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('أمر شراء جديد')]
class CreatePurchaseOrderWizard extends Component
{
use UsesBranchScope;
public int $currentStep = 1;
public int $totalSteps = 4;
public bool $completed = false;
// Step 1: Supplier
public string $supplier_name = '';
public string $supplier_contact = '';
// Step 2: Warehouse & Dates
public ?int $warehouseId = null;
public ?array $selectedWarehouse = null;
public string $order_date = '';
public string $expected_delivery_date = '';
// Step 3: Items
public array $items = [];
// Step 4: Additional costs
public string $tax_amount = '0';
public string $shipping_cost = '0';
public string $notes = '';
// Result
public ?string $orderNumber = null;
public ?string $orderUuid = null;
public function mount(): void
{
$this->authorize('inventory.manage');
$this->order_date = now()->format('Y-m-d');
$this->items[] = ['product_id' => '', 'quantity_ordered' => '1', 'unit_cost' => '', 'notes' => ''];
}
public function getStepLabels(): array
{
return [
1 => 'المورد',
2 => 'المستودع والتاريخ',
3 => 'الأصناف',
4 => 'مراجعة وتأكيد',
];
}
public function selectWarehouse(int $id): void
{
$warehouse = Warehouse::findOrFail($id);
$this->warehouseId = $id;
$this->selectedWarehouse = [
'id' => $warehouse->id,
'name_ar' => $warehouse->name_ar,
];
}
public function addItem(): void
{
$this->items[] = ['product_id' => '', 'quantity_ordered' => '1', 'unit_cost' => '', 'notes' => ''];
}
public function removeItem(int $index): void
{
unset($this->items[$index]);
$this->items = array_values($this->items);
if (empty($this->items)) {
$this->addItem();
}
}
public function getItemsTotalProperty(): int
{
return (int) collect($this->items)->sum(function ($item) {
return ((int) ($item['quantity_ordered'] ?? 0)) * (int) round((float) ($item['unit_cost'] ?? 0) * 100);
});
}
public function getGrandTotalProperty(): int
{
return $this->getItemsTotalProperty()
+ (int) round((float) $this->tax_amount * 100)
+ (int) round((float) $this->shipping_cost * 100);
}
public function nextStep(): void
{
$this->validate($this->rulesForStep($this->currentStep));
if ($this->currentStep < $this->totalSteps) {
$this->currentStep++;
}
}
public function previousStep(): void
{
if ($this->currentStep > 1) {
$this->currentStep--;
}
}
public function goToStep(int $step): void
{
if ($step < $this->currentStep) {
$this->currentStep = $step;
}
}
public function confirm(): void
{
try {
$branchId = $this->getActiveBranchId() ?? auth()->user()->branch_id;
$orderNumber = 'PO-' . now()->format('Ymd') . '-' . str_pad(
PurchaseOrder::whereDate('created_at', today())->count() + 1,
3, '0', STR_PAD_LEFT
);
$data = [
'academy_id' => app('current_academy')->id,
'branch_id' => $branchId,
'warehouse_id' => $this->warehouseId,
'order_number' => $orderNumber,
'supplier_name' => $this->supplier_name,
'supplier_contact' => $this->supplier_contact ?: null,
'order_date' => $this->order_date,
'expected_delivery_date' => $this->expected_delivery_date ?: null,
'tax_amount' => (int) round((float) $this->tax_amount * 100),
'shipping_cost' => (int) round((float) $this->shipping_cost * 100),
'notes' => $this->notes ?: null,
'metadata' => [],
];
$items = collect($this->items)->map(fn ($item) => [
'product_id' => (int) $item['product_id'],
'quantity_ordered' => (int) $item['quantity_ordered'],
'unit_cost' => (int) round((float) $item['unit_cost'] * 100),
'notes' => $item['notes'] ?? null,
])->toArray();
$service = app(PurchaseOrderService::class);
$po = $service->create($data, $items, auth()->user());
$this->completed = true;
$this->orderNumber = $po->order_number;
$this->orderUuid = $po->uuid ?? $po->id;
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
}
}
private function rulesForStep(int $step): array
{
return match ($step) {
1 => [
'supplier_name' => 'required|string|max:255',
'supplier_contact' => 'nullable|string|max:255',
],
2 => [
'warehouseId' => 'required|integer|exists:warehouses,id',
'order_date' => 'required|date',
'expected_delivery_date' => 'nullable|date|after_or_equal:order_date',
],
3 => [
'items' => 'required|array|min:1',
'items.*.product_id' => 'required|exists:products,id',
'items.*.quantity_ordered' => 'required|integer|min:1',
'items.*.unit_cost' => 'required|numeric|min:0.01',
],
default => [],
};
}
public function messages(): array
{
return [
'supplier_name.required' => 'اسم المورد مطلوب',
'warehouseId.required' => 'يجب اختيار المستودع',
'warehouseId.exists' => 'المستودع غير موجود',
'order_date.required' => 'تاريخ الطلب مطلوب',
'expected_delivery_date.after_or_equal' => 'تاريخ التسليم يجب أن يكون بعد تاريخ الطلب',
'items.required' => 'يجب إضافة صنف واحد على الأقل',
'items.min' => 'يجب إضافة صنف واحد على الأقل',
'items.*.product_id.required' => 'اختر المنتج',
'items.*.product_id.exists' => 'المنتج غير موجود',
'items.*.quantity_ordered.required' => 'الكمية مطلوبة',
'items.*.quantity_ordered.min' => 'الكمية يجب أن تكون 1 على الأقل',
'items.*.unit_cost.required' => 'سعر الوحدة مطلوب',
'items.*.unit_cost.min' => 'سعر الوحدة يجب أن يكون أكبر من صفر',
];
}
public function render()
{
$branchId = $this->getActiveBranchId();
return view('livewire.inventory.create-purchase-order-wizard', [
'warehouses' => Warehouse::query()
->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
->where('is_active', true)
->orderBy('name_ar')
->get(['id', 'name_ar', 'code']),
'products' => Product::query()
->where('is_active', true)
->select('id', 'name_ar', 'sku')
->orderBy('name_ar')
->get(),
]);
}
}
<div>
{{-- Header --}}
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-xl sm:text-2xl font-bold text-gray-800">{{ __('أمر شراء جديد') }}</h1>
<p class="text-sm text-gray-500 mt-1">{{ __('إنشاء أمر شراء من المورد') }}</p>
</div>
<a href="{{ route('inventory.purchase-orders') }}" wire:navigate
class="text-sm text-gray-500 hover:text-gray-700">
&larr; {{ __('العودة للقائمة') }}
</a>
</div>
{{-- Flash Messages --}}
@if(session('error'))
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-xl text-red-700 text-sm">
{{ session('error') }}
</div>
@endif
{{-- Success State --}}
@if($completed)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-8 text-center">
<div class="w-16 h-16 mx-auto mb-4 bg-green-100 rounded-full flex items-center justify-center">
<svg class="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
</div>
<h2 class="text-xl font-bold text-gray-800 mb-2">{{ __('تم إنشاء أمر الشراء بنجاح') }}</h2>
<p class="text-gray-500 mb-2">{{ __('رقم الأمر') }}: <span class="font-mono font-bold" dir="ltr">{{ $orderNumber }}</span></p>
<div class="flex items-center justify-center gap-3 mt-6">
@if($orderUuid)
<a href="{{ route('inventory.purchase-orders.show', $orderUuid) }}" wire:navigate
class="inline-flex items-center gap-2 px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium transition-colors">
{{ __('عرض أمر الشراء') }}
</a>
@endif
<a href="{{ route('inventory.purchase-orders.create') }}" wire:navigate
class="inline-flex items-center gap-2 px-6 py-3 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 font-medium transition-colors">
{{ __('إنشاء أمر آخر') }}
</a>
<a href="{{ route('inventory.purchase-orders') }}" wire:navigate
class="inline-flex items-center gap-2 px-6 py-3 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 font-medium transition-colors">
{{ __('العودة للقائمة') }}
</a>
</div>
</div>
@else
{{-- Step Indicator --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6">
<div class="flex items-center justify-between">
@foreach($this->getStepLabels() as $num => $label)
<div class="flex items-center {{ !$loop->last ? 'flex-1' : '' }}">
<button wire:click="goToStep({{ $num }})"
@if($num >= $currentStep) disabled @endif
class="flex items-center gap-2 {{ $num < $currentStep ? 'cursor-pointer' : 'cursor-default' }}">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold transition-colors
{{ $num === $currentStep ? 'bg-blue-600 text-white' : '' }}
{{ $num < $currentStep ? 'bg-green-500 text-white' : '' }}
{{ $num > $currentStep ? 'bg-gray-200 text-gray-500' : '' }}">
@if($num < $currentStep)
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
@else
{{ $num }}
@endif
</div>
<span class="hidden sm:inline text-sm {{ $num === $currentStep ? 'text-blue-700 font-semibold' : 'text-gray-500' }}">
{{ __($label) }}
</span>
</button>
@if(!$loop->last)
<div class="flex-1 mx-3 h-0.5 {{ $num < $currentStep ? 'bg-green-400' : 'bg-gray-200' }}"></div>
@endif
</div>
@endforeach
</div>
</div>
{{-- Step Content --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
{{-- Step 1: Supplier --}}
@if($currentStep === 1)
<div>
<h2 class="text-lg font-bold text-gray-800 mb-4">{{ __('معلومات المورد') }}</h2>
<div class="space-y-4 max-w-lg">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('اسم المورد') }} <span class="text-red-500">*</span></label>
<input type="text" wire:model="supplier_name"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-base"
placeholder="{{ __('أدخل اسم المورد') }}">
@error('supplier_name') <span class="text-red-500 text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('جهة الاتصال') }}</label>
<input type="text" wire:model="supplier_contact"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-base"
placeholder="{{ __('رقم هاتف أو بريد إلكتروني') }}">
@error('supplier_contact') <span class="text-red-500 text-xs mt-1">{{ $message }}</span> @enderror
</div>
</div>
</div>
@endif
{{-- Step 2: Warehouse & Dates --}}
@if($currentStep === 2)
<div>
<h2 class="text-lg font-bold text-gray-800 mb-4">{{ __('المستودع وتاريخ الطلب') }}</h2>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __('المستودع') }} <span class="text-red-500">*</span></label>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
@foreach($warehouses as $warehouse)
<button type="button" wire:click="selectWarehouse({{ $warehouse->id }})"
class="p-4 rounded-xl border-2 text-start transition-all
{{ $warehouseId === $warehouse->id ? 'border-blue-500 bg-blue-50 ring-2 ring-blue-200' : 'border-gray-200 hover:border-gray-300 hover:bg-gray-50' }}">
<p class="font-semibold text-gray-800">{{ $warehouse->name_ar }}</p>
@if($warehouse->code)
<p class="text-xs text-gray-500 mt-1" dir="ltr">{{ $warehouse->code }}</p>
@endif
</button>
@endforeach
</div>
@error('warehouseId') <span class="text-red-500 text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-lg">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ الطلب') }} <span class="text-red-500">*</span></label>
<input type="date" wire:model="order_date" dir="ltr"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
@error('order_date') <span class="text-red-500 text-xs mt-1">{{ $message }}</span> @enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('تاريخ التسليم المتوقع') }}</label>
<input type="date" wire:model="expected_delivery_date" dir="ltr"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
@error('expected_delivery_date') <span class="text-red-500 text-xs mt-1">{{ $message }}</span> @enderror
</div>
</div>
</div>
@endif
{{-- Step 3: Items --}}
@if($currentStep === 3)
<div>
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-bold text-gray-800">{{ __('أصناف أمر الشراء') }}</h2>
<button type="button" wire:click="addItem"
class="inline-flex items-center gap-1 px-4 py-2 bg-blue-50 hover:bg-blue-100 text-blue-700 rounded-lg text-sm font-medium transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
{{ __('إضافة صنف') }}
</button>
</div>
<div class="space-y-3">
@foreach($items as $index => $item)
<div class="p-4 bg-gray-50 rounded-xl border border-gray-200">
<div class="grid grid-cols-12 gap-3 items-start">
<div class="col-span-12 md:col-span-5">
<label class="block text-xs text-gray-500 mb-1">{{ __('المنتج') }} <span class="text-red-500">*</span></label>
<select wire:model="items.{{ $index }}.product_id"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500">
<option value="">{{ __('اختر المنتج') }}</option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name_ar }} ({{ $product->sku }})</option>
@endforeach
</select>
@error("items.{$index}.product_id") <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div class="col-span-4 md:col-span-2">
<label class="block text-xs text-gray-500 mb-1">{{ __('الكمية') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="items.{{ $index }}.quantity_ordered" min="1" dir="ltr"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500">
@error("items.{$index}.quantity_ordered") <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div class="col-span-4 md:col-span-2">
<label class="block text-xs text-gray-500 mb-1">{{ __('سعر الوحدة (ج.م)') }} <span class="text-red-500">*</span></label>
<input type="number" wire:model="items.{{ $index }}.unit_cost" step="0.01" min="0" dir="ltr"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500">
@error("items.{$index}.unit_cost") <span class="text-red-500 text-xs">{{ $message }}</span> @enderror
</div>
<div class="col-span-3 md:col-span-2">
<label class="block text-xs text-gray-500 mb-1">{{ __('الإجمالي') }}</label>
<div class="px-3 py-2.5 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 font-mono" dir="ltr">
{{ number_format(((float)($item['quantity_ordered'] ?? 0)) * ((float)($item['unit_cost'] ?? 0)), 2) }}
</div>
</div>
<div class="col-span-1 flex items-end pb-1">
@if(count($items) > 1)
<button type="button" wire:click="removeItem({{ $index }})"
class="p-2 text-red-500 hover:text-red-700 hover:bg-red-50 rounded-lg transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</button>
@endif
</div>
</div>
<div class="mt-2 col-span-12">
<input type="text" wire:model="items.{{ $index }}.notes"
class="w-full px-3 py-2 border border-gray-200 rounded-lg text-xs focus:ring-2 focus:ring-blue-500"
placeholder="{{ __('ملاحظات على الصنف (اختياري)') }}">
</div>
</div>
@endforeach
</div>
@error('items') <span class="text-red-500 text-xs mt-2 block">{{ $message }}</span> @enderror
{{-- Additional costs inline --}}
<div class="mt-6 pt-4 border-t border-gray-200">
<h3 class="text-sm font-semibold text-gray-700 mb-3">{{ __('تكاليف إضافية') }}</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('الضريبة (ج.م)') }}</label>
<input type="number" wire:model="tax_amount" step="0.01" min="0" dir="ltr"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('تكلفة الشحن (ج.م)') }}</label>
<input type="number" wire:model="shipping_cost" step="0.01" min="0" dir="ltr"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">{{ __('ملاحظات') }}</label>
<input type="text" wire:model="notes"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500"
placeholder="{{ __('ملاحظات عامة') }}">
</div>
</div>
</div>
</div>
@endif
{{-- Step 4: Review --}}
@if($currentStep === 4)
<div>
<h2 class="text-lg font-bold text-gray-800 mb-4">{{ __('مراجعة أمر الشراء') }}</h2>
<div class="space-y-4">
{{-- Supplier Info --}}
<div class="p-4 bg-gray-50 rounded-xl">
<h3 class="text-sm font-semibold text-gray-600 mb-2">{{ __('المورد') }}</h3>
<p class="font-bold text-gray-800">{{ $supplier_name }}</p>
@if($supplier_contact)
<p class="text-sm text-gray-500 mt-1">{{ $supplier_contact }}</p>
@endif
</div>
{{-- Warehouse & Dates --}}
<div class="p-4 bg-gray-50 rounded-xl">
<h3 class="text-sm font-semibold text-gray-600 mb-2">{{ __('المستودع والتاريخ') }}</h3>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3 text-sm">
<div>
<span class="text-gray-500">{{ __('المستودع') }}:</span>
<span class="font-medium text-gray-800">{{ $selectedWarehouse['name_ar'] ?? '-' }}</span>
</div>
<div>
<span class="text-gray-500">{{ __('تاريخ الطلب') }}:</span>
<span class="font-medium text-gray-800" dir="ltr">{{ $order_date }}</span>
</div>
@if($expected_delivery_date)
<div>
<span class="text-gray-500">{{ __('التسليم المتوقع') }}:</span>
<span class="font-medium text-gray-800" dir="ltr">{{ $expected_delivery_date }}</span>
</div>
@endif
</div>
</div>
{{-- Items Summary --}}
<div class="p-4 bg-gray-50 rounded-xl">
<h3 class="text-sm font-semibold text-gray-600 mb-2">{{ __('الأصناف') }} ({{ count($items) }})</h3>
<table class="w-full text-sm">
<thead>
<tr class="border-b border-gray-200">
<th class="text-start py-2 text-gray-600 font-medium">{{ __('المنتج') }}</th>
<th class="text-center py-2 text-gray-600 font-medium w-20">{{ __('الكمية') }}</th>
<th class="text-end py-2 text-gray-600 font-medium w-28">{{ __('سعر الوحدة') }}</th>
<th class="text-end py-2 text-gray-600 font-medium w-28">{{ __('الإجمالي') }}</th>
</tr>
</thead>
<tbody>
@foreach($items as $item)
@php
$product = $products->firstWhere('id', $item['product_id']);
$lineTotal = ((float)($item['quantity_ordered'] ?? 0)) * ((float)($item['unit_cost'] ?? 0));
@endphp
<tr class="border-b border-gray-100">
<td class="py-2">{{ $product?->name_ar ?? '-' }}</td>
<td class="py-2 text-center" dir="ltr">{{ $item['quantity_ordered'] }}</td>
<td class="py-2 text-end" dir="ltr">{{ number_format((float)($item['unit_cost'] ?? 0), 2) }}</td>
<td class="py-2 text-end font-medium" dir="ltr">{{ number_format($lineTotal, 2) }}</td>
</tr>
@endforeach
</tbody>
</table>
{{-- Totals --}}
<div class="border-t border-gray-300 mt-3 pt-3 space-y-1 text-sm max-w-xs ms-auto">
@if((float)$tax_amount > 0)
<div class="flex justify-between">
<span class="text-gray-500">{{ __('الضريبة') }}</span>
<span dir="ltr">{{ number_format((float)$tax_amount, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
@if((float)$shipping_cost > 0)
<div class="flex justify-between">
<span class="text-gray-500">{{ __('الشحن') }}</span>
<span dir="ltr">{{ number_format((float)$shipping_cost, 2) }} {{ __('ج.م') }}</span>
</div>
@endif
<div class="flex justify-between font-bold text-base pt-2 border-t border-gray-200">
<span>{{ __('الإجمالي الكلي') }}</span>
@php
$grandTotal = collect($items)->sum(fn($i) => ((float)($i['quantity_ordered'] ?? 0)) * ((float)($i['unit_cost'] ?? 0)))
+ (float)$tax_amount + (float)$shipping_cost;
@endphp
<span dir="ltr">{{ number_format($grandTotal, 2) }} {{ __('ج.م') }}</span>
</div>
</div>
</div>
@if($notes)
<div class="p-4 bg-gray-50 rounded-xl">
<h3 class="text-sm font-semibold text-gray-600 mb-1">{{ __('ملاحظات') }}</h3>
<p class="text-sm text-gray-700">{{ $notes }}</p>
</div>
@endif
</div>
</div>
@endif
</div>
{{-- Navigation Buttons --}}
<div class="flex items-center justify-between mt-6">
<div>
@if($currentStep > 1)
<button type="button" wire:click="previousStep"
class="inline-flex items-center gap-2 px-5 py-2.5 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 font-medium text-sm transition">
<svg class="w-4 h-4 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
{{ __('السابق') }}
</button>
@endif
</div>
<div>
@if($currentStep < $totalSteps)
<button type="button" wire:click="nextStep"
class="inline-flex items-center gap-2 px-5 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium text-sm transition">
{{ __('التالي') }}
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
</button>
@else
<button type="button" wire:click="confirm"
wire:loading.attr="disabled"
wire:target="confirm"
class="inline-flex items-center gap-2 px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 font-medium text-sm transition disabled:opacity-50">
<span wire:loading.remove wire:target="confirm">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
</span>
<span wire:loading wire:target="confirm">
<svg class="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
</span>
<span wire:loading.remove wire:target="confirm">{{ __('تأكيد أمر الشراء') }}</span>
<span wire:loading wire:target="confirm">{{ __('جارٍ الحفظ...') }}</span>
</button>
@endif
</div>
</div>
@endif
</div>
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
use App\Livewire\Inventory\MovementList; use App\Livewire\Inventory\MovementList;
use App\Livewire\Inventory\ProductForm as InventoryProductForm; use App\Livewire\Inventory\ProductForm as InventoryProductForm;
use App\Livewire\Inventory\ProductList as InventoryProductList; use App\Livewire\Inventory\ProductList as InventoryProductList;
use App\Livewire\Inventory\StockAdjustment; use App\Livewire\Inventory\StockAdjustmentWizard;
use App\Livewire\Inventory\WarehouseForm as InventoryWarehouseForm; use App\Livewire\Inventory\WarehouseForm as InventoryWarehouseForm;
use App\Livewire\Inventory\WarehouseList as InventoryWarehouseList; use App\Livewire\Inventory\WarehouseList as InventoryWarehouseList;
use App\Livewire\Wallets\WalletList; use App\Livewire\Wallets\WalletList;
...@@ -355,9 +355,7 @@ ...@@ -355,9 +355,7 @@
->middleware('permission:inventory.update'); ->middleware('permission:inventory.update');
Route::get('/inventory/movements', MovementList::class)->name('inventory.movements') Route::get('/inventory/movements', MovementList::class)->name('inventory.movements')
->middleware('permission:inventory.list'); ->middleware('permission:inventory.list');
Route::get('/inventory/adjustments', StockAdjustment::class)->name('inventory.adjustments') Route::get('/inventory/adjustments', StockAdjustmentWizard::class)->name('inventory.adjustments')
->middleware('permission:inventory.adjust');
Route::get('/inventory/adjustments/wizard', \App\Livewire\Inventory\StockAdjustmentWizard::class)->name('inventory.adjustments.wizard')
->middleware('permission:inventory.adjust'); ->middleware('permission:inventory.adjust');
Route::get('/inventory/kits', \App\Livewire\Inventory\KitList::class)->name('inventory.kits') Route::get('/inventory/kits', \App\Livewire\Inventory\KitList::class)->name('inventory.kits')
->middleware('permission:inventory.manage'); ->middleware('permission:inventory.manage');
...@@ -373,7 +371,7 @@ ...@@ -373,7 +371,7 @@
->middleware('permission:inventory.manage'); ->middleware('permission:inventory.manage');
Route::get('/inventory/purchase-orders', \App\Livewire\Inventory\PurchaseOrderList::class)->name('inventory.purchase-orders') Route::get('/inventory/purchase-orders', \App\Livewire\Inventory\PurchaseOrderList::class)->name('inventory.purchase-orders')
->middleware('permission:inventory.manage'); ->middleware('permission:inventory.manage');
Route::get('/inventory/purchase-orders/create', \App\Livewire\Inventory\PurchaseOrderForm::class)->name('inventory.purchase-orders.create') Route::get('/inventory/purchase-orders/create', \App\Livewire\Inventory\CreatePurchaseOrderWizard::class)->name('inventory.purchase-orders.create')
->middleware('permission:inventory.manage'); ->middleware('permission:inventory.manage');
Route::get('/inventory/purchase-orders/{purchaseOrder}', \App\Livewire\Inventory\PurchaseOrderShow::class)->name('inventory.purchase-orders.show') Route::get('/inventory/purchase-orders/{purchaseOrder}', \App\Livewire\Inventory\PurchaseOrderShow::class)->name('inventory.purchase-orders.show')
->middleware('permission:inventory.list'); ->middleware('permission:inventory.list');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment