Commit 55e34c49 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add Invoice Correction Wizard for superadmins

4-step wizard: search participant → select invoice → enter correction
(new amount, description, reason) → creates remainder invoice if needed.
Full audit log trace via Log::channel('audit') and invoice metadata.
Accessible from SuperAdmin panel, gated by super_admin.access permission.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 98d2b625
<?php
namespace App\Livewire\Admin;
use App\Domain\Financial\Enums\InvoiceStatus;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Financial\Models\Payment;
use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Financial\Services\PaymentService;
use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Exceptions\DomainException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('تصحيح فاتورة')]
class InvoiceCorrectionWizard extends Component
{
public int $currentStep = 1;
// Step 1: Find participant
public string $search = '';
public ?int $selectedParticipantId = null;
public string $selectedParticipantName = '';
// Step 2: Select invoice
public ?int $selectedInvoiceId = null;
public array $invoiceDetails = [];
// Step 3: Correction details
public string $correctionType = 'adjust_amount'; // adjust_amount | split_installment | change_description
public string $newAmountInput = '';
public string $newDescription = '';
public string $correctionReason = '';
public bool $createRemainderInvoice = true;
public string $remainderDueDate = '';
public string $remainderDescription = '';
// Step 4: Results
public bool $completed = false;
public array $correctionResult = [];
public function mount(): void
{
$this->authorize('super_admin.access');
$this->remainderDueDate = now()->addMonth()->startOfMonth()->toDateString();
}
public function selectParticipant(int $id, string $name): void
{
$this->selectedParticipantId = $id;
$this->selectedParticipantName = $name;
$this->search = '';
$this->currentStep = 2;
}
public function selectInvoice(int $invoiceId): void
{
$invoice = Invoice::with('items', 'payments')->find($invoiceId);
if (!$invoice) {
return;
}
$this->selectedInvoiceId = $invoiceId;
$this->invoiceDetails = [
'id' => $invoice->id,
'number' => $invoice->number,
'status' => $invoice->status->value,
'total_amount' => $invoice->total_amount,
'paid_amount' => $invoice->paid_amount,
'due_amount' => $invoice->due_amount,
'notes' => $invoice->notes,
'issue_date' => $invoice->issue_date?->format('Y-m-d'),
'items' => $invoice->items->map(fn ($item) => [
'id' => $item->id,
'description' => $item->description,
'unit_price' => $item->unit_price,
'quantity' => $item->quantity,
'total_amount' => $item->total_amount,
])->toArray(),
'payments' => $invoice->payments->map(fn ($p) => [
'id' => $p->id,
'amount' => $p->amount,
'method' => $p->method,
'payment_date' => $p->payment_date,
'status' => $p->status,
])->toArray(),
];
$this->newAmountInput = number_format($invoice->total_amount / 100, 2, '.', '');
$this->newDescription = $invoice->items->first()?->description ?? '';
$this->currentStep = 3;
}
public function previousStep(): void
{
if ($this->currentStep === 3) {
$this->selectedInvoiceId = null;
$this->invoiceDetails = [];
$this->currentStep = 2;
} elseif ($this->currentStep === 2) {
$this->selectedParticipantId = null;
$this->selectedParticipantName = '';
$this->currentStep = 1;
}
}
public function getNewAmountPiastersProperty(): int
{
return max(0, (int) round((float) $this->newAmountInput * 100));
}
public function getOriginalAmountProperty(): int
{
return $this->invoiceDetails['total_amount'] ?? 0;
}
public function getDifferenceProperty(): int
{
return $this->originalAmount - $this->newAmountPiasters;
}
public function applyCorrection(): void
{
$this->authorize('super_admin.access');
$this->validate([
'correctionReason' => 'required|min:10',
'newAmountInput' => 'required|numeric|min:0',
], [
'correctionReason.required' => 'سبب التصحيح مطلوب',
'correctionReason.min' => 'سبب التصحيح يجب أن يكون 10 أحرف على الأقل',
'newAmountInput.required' => 'المبلغ الجديد مطلوب',
'newAmountInput.numeric' => 'المبلغ يجب أن يكون رقم',
'newAmountInput.min' => 'المبلغ لا يمكن أن يكون سالب',
]);
$actor = auth()->user();
$invoice = Invoice::with('items', 'payments')->find($this->selectedInvoiceId);
if (!$invoice) {
session()->flash('error', 'الفاتورة غير موجودة');
return;
}
$newAmount = $this->newAmountPiasters;
$originalAmount = $invoice->total_amount;
$difference = $originalAmount - $newAmount;
if ($newAmount === $originalAmount && $this->correctionType !== 'change_description') {
session()->flash('error', 'المبلغ الجديد مطابق للمبلغ الأصلي');
return;
}
try {
$result = DB::transaction(function () use ($invoice, $actor, $newAmount, $originalAmount, $difference) {
$oldData = [
'total_amount' => $invoice->total_amount,
'subtotal_amount' => $invoice->subtotal_amount,
'paid_amount' => $invoice->paid_amount,
'due_amount' => $invoice->due_amount,
'status' => $invoice->status->value,
'notes' => $invoice->notes,
'items' => $invoice->items->toArray(),
];
$invoiceService = app(InvoiceService::class);
$remainderInvoice = null;
// Adjust the invoice amount
$invoice->subtotal_amount = $newAmount;
$invoice->total_amount = $newAmount;
// Adjust payments if they exceed new amount
if ($invoice->paid_amount > $newAmount) {
$invoice->paid_amount = $newAmount;
}
$invoice->due_amount = max(0, $newAmount - $invoice->paid_amount);
// Update status based on new amounts
if ($invoice->due_amount === 0 && $newAmount > 0) {
$invoice->status = InvoiceStatus::Paid;
$invoice->paid_at = $invoice->paid_at ?? now();
} elseif ($invoice->paid_amount > 0 && $invoice->due_amount > 0) {
$invoice->status = InvoiceStatus::PartiallyPaid;
} elseif ($newAmount === 0) {
$invoice->status = InvoiceStatus::Cancelled;
$invoice->cancelled_at = now();
}
// Update notes
$correctionNote = 'تصحيح: ' . $this->correctionReason;
$invoice->notes = $correctionNote;
// Update metadata with audit trail
$invoice->metadata = array_merge($invoice->metadata ?? [], [
'correction' => [
'corrected_at' => now()->toIso8601String(),
'corrected_by' => $actor->id,
'corrected_by_name' => $actor->name,
'original_amount' => $originalAmount,
'new_amount' => $newAmount,
'difference' => $difference,
'reason' => $this->correctionReason,
'type' => $this->correctionType,
],
]);
$invoice->save();
// Update invoice items
$mainItem = $invoice->items->first();
if ($mainItem) {
$mainItem->update([
'unit_price' => $newAmount,
'total_amount' => $newAmount,
'description' => $this->newDescription ?: $mainItem->description,
]);
}
// Fix payment amounts if they exceeded the new total
foreach ($invoice->payments as $payment) {
if ($payment->amount > $newAmount) {
$payment->update(['amount' => $newAmount]);
}
}
// Create remainder invoice if needed
if ($this->createRemainderInvoice && $difference > 0) {
$participant = Participant::with('person')->find($this->selectedParticipantId);
$remainderInvoice = $invoiceService->create([
'academy_id' => $invoice->academy_id,
'billable_type' => $invoice->billable_type,
'billable_id' => $invoice->billable_id,
'total_amount' => $difference,
'subtotal_amount' => $difference,
'discount_amount' => 0,
'tax_amount' => 0,
'service_fee_amount' => 0,
'due_date' => $this->remainderDueDate,
'contact_name' => $participant?->person?->name_ar ?? $invoice->contact_name,
'notes' => $this->remainderDescription ?: ('أقساط متبقية — تصحيح من ' . $invoice->number),
'metadata' => [
'correction_remainder' => true,
'original_invoice_id' => $invoice->id,
'original_invoice_number' => $invoice->number,
'corrected_at' => now()->toIso8601String(),
],
], [
[
'description' => $this->remainderDescription ?: ('المبلغ المتبقي من ' . $invoice->number),
'quantity' => 1,
'unit_price' => $difference,
'discount_amount' => 0,
'tax_amount' => 0,
],
], $actor);
$remainderInvoice->update(['status' => InvoiceStatus::Sent]);
}
// Audit log
Log::channel('audit')->info('invoice_correction', [
'actor_id' => $actor->id,
'actor_name' => $actor->name,
'invoice_id' => $invoice->id,
'invoice_number' => $invoice->number,
'participant_id' => $this->selectedParticipantId,
'participant_name' => $this->selectedParticipantName,
'correction_type' => $this->correctionType,
'original_amount' => $originalAmount,
'new_amount' => $newAmount,
'difference' => $difference,
'reason' => $this->correctionReason,
'remainder_invoice_id' => $remainderInvoice?->id,
'remainder_invoice_number' => $remainderInvoice?->number,
'old_data' => $oldData,
'timestamp' => now()->toIso8601String(),
]);
return [
'invoice_number' => $invoice->number,
'original_amount' => $originalAmount,
'new_amount' => $newAmount,
'new_status' => $invoice->status->value,
'remainder_invoice' => $remainderInvoice ? [
'number' => $remainderInvoice->number,
'amount' => $difference,
] : null,
];
});
$this->correctionResult = $result;
$this->completed = true;
$this->currentStep = 4;
} catch (DomainException $e) {
session()->flash('error', $e->getMessage());
} catch (\Throwable $e) {
Log::error('Invoice correction failed', [
'invoice_id' => $this->selectedInvoiceId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
session()->flash('error', 'حدث خطأ: ' . $e->getMessage());
}
}
public function startOver(): void
{
$this->reset();
$this->remainderDueDate = now()->addMonth()->startOfMonth()->toDateString();
$this->currentStep = 1;
}
public function render()
{
$searchResults = collect();
if (strlen($this->search) >= 2 && !$this->selectedParticipantId) {
$searchResults = Participant::query()
->with('person')
->where(function ($q) {
$search = $this->search;
$q->where('participant_number', 'ilike', "%{$search}%")
->orWhereHas('person', function ($pq) use ($search) {
$pq->where('name_ar', 'ilike', "%{$search}%")
->orWhere('name', 'ilike', "%{$search}%")
->orWhere('phone', 'like', "%{$search}%");
});
})
->limit(10)
->get();
}
$invoices = collect();
if ($this->selectedParticipantId && $this->currentStep === 2) {
$invoices = Invoice::where('billable_id', $this->selectedParticipantId)
->where('billable_type', 'App\\Domain\\Participant\\Models\\Participant')
->with('items')
->orderByDesc('created_at')
->get();
}
return view('livewire.admin.invoice-correction-wizard', [
'searchResults' => $searchResults,
'invoices' => $invoices,
]);
}
}
<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('admin.panel') }}" wire:navigate
class="inline-flex items-center gap-2 px-4 py-2 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 text-sm font-medium">
<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="M6 18L18 6M6 6l12 12"/>
</svg>
{{ __('رجوع') }}
</a>
</div>
{{-- Flash --}}
@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
{{-- Warning Banner --}}
<div class="mb-6 p-4 bg-amber-50 border border-amber-200 rounded-xl">
<div class="flex items-start gap-3">
<svg class="w-5 h-5 text-amber-600 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/>
</svg>
<div>
<p class="text-sm font-medium text-amber-800">{{ __('هذه الأداة للمديرين فقط') }}</p>
<p class="text-xs text-amber-600 mt-0.5">{{ __('كل تعديل يتم تسجيله في سجل المراجعة ولا يمكن حذفه') }}</p>
</div>
</div>
</div>
{{-- Step Indicator --}}
@if(!$completed)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 mb-6">
<div class="flex items-center justify-between">
@php
$steps = [
1 => 'اختيار المشترك',
2 => 'اختيار الفاتورة',
3 => 'تفاصيل التصحيح',
4 => 'النتيجة',
];
@endphp
@foreach($steps as $num => $label)
<div class="flex items-center {{ !$loop->last ? 'flex-1' : '' }}">
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold
{{ $num === $currentStep ? 'bg-red-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="text-xs sm:text-sm font-medium {{ $num === $currentStep ? 'text-gray-800' : 'text-gray-500' }} hidden sm:inline">
{{ __($label) }}
</span>
</div>
@if(!$loop->last)
<div class="flex-1 h-0.5 mx-3 {{ $num < $currentStep ? 'bg-green-300' : 'bg-gray-200' }}"></div>
@endif
</div>
@endforeach
</div>
</div>
@endif
{{-- Step 1: Search Participant --}}
@if($currentStep === 1)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ __('البحث عن المشترك') }}</h2>
<input type="text" wire:model.live.debounce.300ms="search"
placeholder="{{ __('ابحث بالاسم أو رقم الهاتف أو رقم المشترك...') }}"
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-red-500 focus:border-red-500 text-sm">
@if($searchResults->isNotEmpty())
<div class="mt-4 space-y-2 max-h-80 overflow-y-auto">
@foreach($searchResults as $participant)
<button wire:click="selectParticipant({{ $participant->id }}, '{{ addslashes($participant->person?->name_ar) }}')"
class="w-full flex items-center gap-3 p-3 border border-gray-200 rounded-xl hover:bg-gray-50 hover:border-red-300 transition-colors text-start">
<div class="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center shrink-0">
<span class="text-sm font-bold text-red-700">{{ mb_substr($participant->person?->name_ar ?? '?', 0, 1) }}</span>
</div>
<div class="min-w-0 flex-1">
<p class="font-medium text-gray-800 text-sm truncate">{{ $participant->person?->name_ar }}</p>
<p class="text-xs text-gray-500">{{ $participant->participant_number }} — {{ $participant->person?->phone }}</p>
</div>
</button>
@endforeach
</div>
@elseif(strlen($search) >= 2)
<p class="mt-4 text-sm text-gray-500 text-center py-4">{{ __('لا توجد نتائج') }}</p>
@endif
</div>
@endif
{{-- Step 2: Select Invoice --}}
@if($currentStep === 2)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-semibold text-gray-800">{{ __('فواتير') }}: {{ $selectedParticipantName }}</h2>
<button wire:click="previousStep" class="text-sm text-gray-500 hover:text-gray-700">{{ __('تغيير المشترك') }}</button>
</div>
@if($invoices->isEmpty())
<p class="text-sm text-gray-500 text-center py-8">{{ __('لا توجد فواتير لهذا المشترك') }}</p>
@else
<div class="space-y-3 max-h-[500px] overflow-y-auto">
@foreach($invoices as $inv)
<button wire:click="selectInvoice({{ $inv->id }})"
class="w-full text-start p-4 border border-gray-200 rounded-xl hover:bg-gray-50 hover:border-red-300 transition-colors">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-mono font-medium text-gray-800">{{ $inv->number }}</span>
<span class="px-2 py-0.5 text-xs rounded-full
{{ $inv->status === \App\Domain\Financial\Enums\InvoiceStatus::Paid ? 'bg-green-100 text-green-700' : '' }}
{{ $inv->status === \App\Domain\Financial\Enums\InvoiceStatus::Sent ? 'bg-blue-100 text-blue-700' : '' }}
{{ $inv->status === \App\Domain\Financial\Enums\InvoiceStatus::PartiallyPaid ? 'bg-amber-100 text-amber-700' : '' }}
{{ $inv->status === \App\Domain\Financial\Enums\InvoiceStatus::Cancelled ? 'bg-gray-100 text-gray-500' : '' }}
{{ $inv->status === \App\Domain\Financial\Enums\InvoiceStatus::Overdue ? 'bg-red-100 text-red-700' : '' }}
{{ $inv->status === \App\Domain\Financial\Enums\InvoiceStatus::Draft ? 'bg-gray-100 text-gray-600' : '' }}
">{{ $inv->status->label() }}</span>
</div>
<div class="flex items-center justify-between text-sm">
<span class="text-gray-600 truncate max-w-[60%]">{{ $inv->items->first()?->description ?? $inv->notes }}</span>
<span class="font-bold text-gray-800" dir="ltr">{{ number_format($inv->total_amount / 100, 2) }} {{ __('ج.م') }}</span>
</div>
<div class="flex items-center justify-between mt-1 text-xs text-gray-400">
<span>{{ $inv->issue_date?->format('Y-m-d') }}</span>
@if($inv->paid_amount > 0 && $inv->paid_amount < $inv->total_amount)
<span class="text-amber-600">{{ __('مدفوع') }}: {{ number_format($inv->paid_amount / 100, 2) }}</span>
@endif
</div>
</button>
@endforeach
</div>
@endif
</div>
@endif
{{-- Step 3: Correction Details --}}
@if($currentStep === 3)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-lg font-semibold text-gray-800">{{ __('تصحيح الفاتورة') }}: {{ $invoiceDetails['number'] ?? '' }}</h2>
<button wire:click="previousStep" class="text-sm text-gray-500 hover:text-gray-700">{{ __('اختيار فاتورة أخرى') }}</button>
</div>
{{-- Current Invoice Info --}}
<div class="mb-6 p-4 bg-gray-50 border border-gray-200 rounded-xl space-y-2">
<div class="flex justify-between text-sm">
<span class="text-gray-600">{{ __('المبلغ الحالي') }}</span>
<span class="font-bold text-gray-800" dir="ltr">{{ number_format(($invoiceDetails['total_amount'] ?? 0) / 100, 2) }} {{ __('ج.م') }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">{{ __('المدفوع') }}</span>
<span class="font-medium" dir="ltr">{{ number_format(($invoiceDetails['paid_amount'] ?? 0) / 100, 2) }} {{ __('ج.م') }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-600">{{ __('الحالة') }}</span>
<span class="font-medium">{{ $invoiceDetails['status'] ?? '' }}</span>
</div>
@if(!empty($invoiceDetails['items']))
<div class="border-t border-gray-200 pt-2 mt-2">
@foreach($invoiceDetails['items'] as $item)
<div class="flex justify-between text-xs text-gray-500">
<span>{{ $item['description'] }}</span>
<span dir="ltr">{{ number_format($item['unit_price'] / 100, 2) }}</span>
</div>
@endforeach
</div>
@endif
@if(!empty($invoiceDetails['payments']))
<div class="border-t border-gray-200 pt-2 mt-2">
<p class="text-xs font-medium text-gray-600 mb-1">{{ __('المدفوعات:') }}</p>
@foreach($invoiceDetails['payments'] as $pay)
<div class="flex justify-between text-xs text-gray-500">
<span>{{ $pay['method'] }} — {{ $pay['payment_date'] }}</span>
<span dir="ltr">{{ number_format($pay['amount'] / 100, 2) }}</span>
</div>
@endforeach
</div>
@endif
</div>
{{-- Correction Form --}}
<div class="space-y-5">
{{-- New Amount --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __('المبلغ الصحيح (ج.م)') }}</label>
<input type="number" wire:model.live="newAmountInput" step="0.01" min="0"
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-red-500 focus:border-red-500 text-lg font-bold"
dir="ltr">
@if($this->difference > 0)
<p class="mt-1 text-sm text-amber-600">
{{ __('فرق:') }} <span dir="ltr">{{ number_format($this->difference / 100, 2) }} {{ __('ج.م') }}</span> {{ __('سيتم خصمه') }}
</p>
@elseif($this->difference < 0)
<p class="mt-1 text-sm text-red-600">
{{ __('زيادة:') }} <span dir="ltr">{{ number_format(abs($this->difference) / 100, 2) }} {{ __('ج.م') }}</span>
</p>
@endif
</div>
{{-- New Description --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">{{ __('الوصف الصحيح') }}</label>
<input type="text" wire:model="newDescription"
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-red-500 focus:border-red-500 text-sm">
</div>
{{-- Remainder Invoice --}}
@if($this->difference > 0)
<div class="p-4 border border-dashed border-amber-300 rounded-xl bg-amber-50">
<label class="flex items-center gap-3 cursor-pointer mb-3">
<input type="checkbox" wire:model.live="createRemainderInvoice"
class="w-5 h-5 rounded border-gray-300 text-red-600 focus:ring-red-500">
<span class="text-sm font-medium text-gray-800">{{ __('إنشاء فاتورة بالمبلغ المتبقي') }} ({{ number_format($this->difference / 100, 2) }} {{ __('ج.م') }})</span>
</label>
@if($createRemainderInvoice)
<div class="space-y-3 ms-8">
<div>
<label class="block text-xs text-gray-600 mb-1">{{ __('تاريخ الاستحقاق') }}</label>
<input type="date" wire:model="remainderDueDate"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm" dir="ltr">
</div>
<div>
<label class="block text-xs text-gray-600 mb-1">{{ __('وصف الفاتورة الجديدة') }}</label>
<input type="text" wire:model="remainderDescription"
placeholder="{{ __('أقساط متبقية...') }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
</div>
</div>
@endif
</div>
@endif
{{-- Reason (required) --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
{{ __('سبب التصحيح') }} <span class="text-red-500">*</span>
</label>
<textarea wire:model="correctionReason" rows="3"
placeholder="{{ __('اشرح سبب التعديل بالتفصيل — مثال: الموظف سجّل القيد كاملاً بدلاً من القسط الأول فقط') }}"
class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-red-500 focus:border-red-500 text-sm"></textarea>
@error('correctionReason')
<p class="mt-1 text-xs text-red-600">{{ $message }}</p>
@enderror
</div>
</div>
{{-- Actions --}}
<div class="flex justify-between mt-8">
<button wire:click="previousStep"
class="inline-flex items-center gap-2 px-6 py-3 text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 font-medium">
<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="M15 19l-7-7 7-7"/>
</svg>
{{ __('السابق') }}
</button>
<button wire:click="applyCorrection" wire:loading.attr="disabled"
class="inline-flex items-center gap-2 px-8 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 font-bold transition-colors disabled:opacity-50">
<span wire:loading.remove wire:target="applyCorrection">{{ __('تطبيق التصحيح') }}</span>
<span wire:loading wire:target="applyCorrection" class="inline-flex items-center gap-2">
<svg class="animate-spin w-5 h-5" 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>
</button>
</div>
</div>
@endif
{{-- Step 4: Results --}}
@if($currentStep === 4 && $completed)
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6 text-center">
<div class="w-20 h-20 mx-auto bg-green-100 rounded-full flex items-center justify-center mb-6">
<svg class="w-10 h-10 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-2xl font-bold text-green-700 mb-2">{{ __('تم التصحيح بنجاح') }}</h2>
<p class="text-gray-600 mb-6">{{ __('تم تعديل الفاتورة وتسجيل التغيير في سجل المراجعة') }}</p>
<div class="inline-block bg-gray-50 border border-gray-200 rounded-xl p-6 mb-6 text-start max-w-md mx-auto">
<div class="space-y-3">
<div class="flex justify-between">
<span class="text-sm text-gray-600">{{ __('الفاتورة') }}</span>
<span class="font-mono font-medium">{{ $correctionResult['invoice_number'] ?? '' }}</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-gray-600">{{ __('المبلغ الأصلي') }}</span>
<span class="font-medium line-through text-gray-400" dir="ltr">{{ number_format(($correctionResult['original_amount'] ?? 0) / 100, 2) }} {{ __('ج.م') }}</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-gray-600">{{ __('المبلغ الجديد') }}</span>
<span class="font-bold text-green-700" dir="ltr">{{ number_format(($correctionResult['new_amount'] ?? 0) / 100, 2) }} {{ __('ج.م') }}</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-gray-600">{{ __('الحالة الجديدة') }}</span>
<span class="font-medium">{{ $correctionResult['new_status'] ?? '' }}</span>
</div>
@if(!empty($correctionResult['remainder_invoice']))
<div class="border-t border-gray-200 pt-3">
<div class="flex justify-between">
<span class="text-sm text-gray-600">{{ __('فاتورة المتبقي') }}</span>
<span class="font-mono font-medium">{{ $correctionResult['remainder_invoice']['number'] }}</span>
</div>
<div class="flex justify-between">
<span class="text-sm text-gray-600">{{ __('المبلغ المتبقي') }}</span>
<span class="font-bold text-amber-700" dir="ltr">{{ number_format($correctionResult['remainder_invoice']['amount'] / 100, 2) }} {{ __('ج.م') }}</span>
</div>
</div>
@endif
</div>
</div>
<div class="flex justify-center gap-3">
<button wire:click="startOver"
class="inline-flex items-center gap-2 px-6 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 font-medium">
{{ __('تصحيح فاتورة أخرى') }}
</button>
<a href="{{ route('admin.panel') }}" 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">
{{ __('لوحة التحكم') }}
</a>
</div>
</div>
@endif
</div>
...@@ -11,6 +11,10 @@ ...@@ -11,6 +11,10 @@
<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 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"/></svg> <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 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"/></svg>
{{ __('تسوية المشتركين') }} {{ __('تسوية المشتركين') }}
</a> </a>
<a href="{{ route('admin.invoice-correction') }}" class="inline-flex items-center gap-2 px-4 py-2 bg-red-50 border border-red-200 text-red-800 text-sm font-medium rounded-lg hover:bg-red-100 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="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
{{ __('تصحيح فاتورة') }}
</a>
</div> </div>
<!-- Stats Cards --> <!-- Stats Cards -->
......
...@@ -494,6 +494,8 @@ ...@@ -494,6 +494,8 @@
->middleware('permission:super_admin.access'); ->middleware('permission:super_admin.access');
Route::get('/admin/reconciliation', \App\Livewire\Admin\ReconciliationWizard::class)->name('admin.reconciliation') Route::get('/admin/reconciliation', \App\Livewire\Admin\ReconciliationWizard::class)->name('admin.reconciliation')
->middleware('permission:super_admin.access'); ->middleware('permission:super_admin.access');
Route::get('/admin/invoice-correction', \App\Livewire\Admin\InvoiceCorrectionWizard::class)->name('admin.invoice-correction')
->middleware('permission:super_admin.access');
// Exports // Exports
Route::get('/export/report', [\App\Http\Controllers\ExportController::class, 'report']) Route::get('/export/report', [\App\Http\Controllers\ExportController::class, 'report'])
......
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