Commit 2be39175 authored by Mahmoud Aglan's avatar Mahmoud Aglan

Unify receipt/invoice settings: 3-tab template system + fix seeder

- EnrollmentSettingsSeeder: fix Organization → Academy class name
- ReceiptTemplate: add defaultInvoiceFields/Settings + defaultPaymentFields/Settings
- ReceiptSettings: rewritten with pos/invoice/payment tabs; each tab loads
  its own ReceiptTemplate (type='invoice'|'payment') and saves independently
- receipt-settings.blade.php: 3-tab UI with field toggles + appearance settings
  per document type; link to branding page for logo/colors
- print/invoice.blade.php: all 15 fields now gated by invoice template flags
  (logo, academy name, branch, tax number, client details, items table,
  subtotal, discount, tax, service fee, paid amount, payments history,
  signature, terms & conditions, footer text)
- print/payment-receipt.blade.php: all 11 fields gated by payment template;
  footer text pulled from template settings instead of hardcoded string
Co-Authored-By: 's avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent 20284ee2
......@@ -162,4 +162,58 @@ public static function defaultSettings(): array
'time_format' => 'H:i',
];
}
public static function defaultInvoiceFields(): array
{
return [
'header_logo' => true,
'header_academy_name' => true,
'header_branch' => true,
'header_tax_number' => false,
'client_details' => true,
'items_table' => true,
'subtotal' => true,
'discount' => true,
'tax_amount' => true,
'service_fee' => true,
'paid_amount' => true,
'payments_history' => true,
'signature' => false,
'terms_conditions' => false,
'footer_text' => true,
];
}
public static function defaultInvoiceSettings(): array
{
return [
'footer_text' => '',
'currency_symbol' => 'ج.م',
];
}
public static function defaultPaymentFields(): array
{
return [
'header_logo' => true,
'header_academy_name' => true,
'reference_number' => true,
'date' => true,
'payment_method' => true,
'invoice_number' => true,
'payer_name' => true,
'received_by' => true,
'status_badge' => true,
'notes' => true,
'footer_text' => true,
];
}
public static function defaultPaymentSettings(): array
{
return [
'footer_text' => 'تم إنشاء هذا المستند إلكترونياً ولا يحتاج إلى توقيع',
'currency_symbol' => 'ج.م',
];
}
}
......@@ -9,71 +9,113 @@
use Livewire\Component;
#[Layout('layouts.app')]
#[Title('إعدادات الإيصال')]
#[Title('إعدادات الإيصالات والفواتير')]
class ReceiptSettings extends Component
{
public string $activeTab = 'pos';
public ?int $selectedBranchId = null;
public array $visibleFields = [];
public array $sections = [];
public array $settings = [];
public bool $saved = false;
// POS Receipt
public array $posFields = [];
public array $posSections = [];
public array $posSettings = [];
// Invoice
public array $invoiceFields = [];
public array $invoiceSettings = [];
// Payment Receipt
public array $paymentFields = [];
public array $paymentSettings = [];
public function mount(): void
{
$this->authorize('settings.manage');
$this->selectedBranchId = session('active_branch_id', auth()->user()->branch_id);
$this->loadTemplate();
$this->loadAll();
}
public function updatedSelectedBranchId(): void
{
$this->loadTemplate();
$this->loadAll();
}
public function updatedActiveTab(): void
{
$this->saved = false;
}
private function loadAll(): void
{
$this->loadPos();
$this->loadInvoice();
$this->loadPayment();
$this->saved = false;
}
public function loadTemplate(): void
private function loadPos(): void
{
$template = $this->selectedBranchId
$tpl = $this->selectedBranchId
? ReceiptTemplate::resolveFor($this->selectedBranchId, 'pos')
: null;
$this->posFields = $tpl?->visible_fields ?? ReceiptTemplate::defaultPosFields();
$this->posSections = $tpl?->sections ?? ReceiptTemplate::defaultPosSections();
$this->posSettings = $tpl?->settings ?? ReceiptTemplate::defaultSettings();
}
$this->visibleFields = $template?->visible_fields ?? ReceiptTemplate::defaultPosFields();
$this->sections = $template?->sections ?? ReceiptTemplate::defaultPosSections();
$this->settings = $template?->settings ?? ReceiptTemplate::defaultSettings();
$this->saved = false;
private function loadInvoice(): void
{
$tpl = $this->selectedBranchId
? ReceiptTemplate::resolveFor($this->selectedBranchId, 'invoice')
: null;
$this->invoiceFields = $tpl?->visible_fields ?? ReceiptTemplate::defaultInvoiceFields();
$this->invoiceSettings = $tpl?->settings ?? ReceiptTemplate::defaultInvoiceSettings();
}
private function loadPayment(): void
{
$tpl = $this->selectedBranchId
? ReceiptTemplate::resolveFor($this->selectedBranchId, 'payment')
: null;
$this->paymentFields = $tpl?->visible_fields ?? ReceiptTemplate::defaultPaymentFields();
$this->paymentSettings = $tpl?->settings ?? ReceiptTemplate::defaultPaymentSettings();
}
public function toggleField(string $key): void
{
$this->visibleFields[$key] = !($this->visibleFields[$key] ?? true);
match ($this->activeTab) {
'pos' => $this->posFields[$key] = !($this->posFields[$key] ?? true),
'invoice' => $this->invoiceFields[$key] = !($this->invoiceFields[$key] ?? true),
'payment' => $this->paymentFields[$key] = !($this->paymentFields[$key] ?? true),
};
$this->saved = false;
}
public function toggleSection(int $index): void
{
if (isset($this->sections[$index])) {
$this->sections[$index]['enabled'] = !$this->sections[$index]['enabled'];
if ($this->activeTab === 'pos' && isset($this->posSections[$index])) {
$this->posSections[$index]['enabled'] = !$this->posSections[$index]['enabled'];
$this->saved = false;
}
}
public function moveSectionUp(int $index): void
{
if ($index > 0) {
$temp = $this->sections[$index - 1];
$this->sections[$index - 1] = $this->sections[$index];
$this->sections[$index] = $temp;
if ($this->activeTab === 'pos' && $index > 0) {
$temp = $this->posSections[$index - 1];
$this->posSections[$index - 1] = $this->posSections[$index];
$this->posSections[$index] = $temp;
$this->saved = false;
}
}
public function moveSectionDown(int $index): void
{
if ($index < count($this->sections) - 1) {
$temp = $this->sections[$index + 1];
$this->sections[$index + 1] = $this->sections[$index];
$this->sections[$index] = $temp;
if ($this->activeTab === 'pos' && $index < count($this->posSections) - 1) {
$temp = $this->posSections[$index + 1];
$this->posSections[$index + 1] = $this->posSections[$index];
$this->posSections[$index] = $temp;
$this->saved = false;
}
}
......@@ -88,43 +130,67 @@ public function save(): void
$academy = app('current_academy');
if (!$academy) return;
$template = ReceiptTemplate::where('academy_id', $academy->id)
match ($this->activeTab) {
'pos' => $this->saveTemplate('pos', $this->posFields, $this->posSettings, $this->posSections),
'invoice' => $this->saveTemplate('invoice', $this->invoiceFields, $this->invoiceSettings),
'payment' => $this->saveTemplate('payment', $this->paymentFields, $this->paymentSettings),
};
$this->saved = true;
session()->flash('success', __('تم حفظ الإعدادات'));
}
private function saveTemplate(string $type, array $fields, array $settings, array $sections = []): void
{
$academy = app('current_academy');
$data = [
'visible_fields' => $fields,
'settings' => $settings,
];
if ($sections) {
$data['sections'] = $sections;
}
$tpl = ReceiptTemplate::where('academy_id', $academy->id)
->where('branch_id', $this->selectedBranchId)
->where('type', 'pos')
->where('type', $type)
->where('is_default', true)
->first();
if (!$template) {
$template = ReceiptTemplate::create([
if (!$tpl) {
$names = ['pos' => ['POS Receipt', 'إيصال الكاشير'], 'invoice' => ['Invoice', 'الفاتورة'], 'payment' => ['Payment Receipt', 'إيصال الدفع']];
ReceiptTemplate::create(array_merge($data, [
'academy_id' => $academy->id,
'branch_id' => $this->selectedBranchId,
'name' => 'POS Receipt',
'name_ar' => 'إيصال نقطة البيع',
'type' => 'pos',
'branch_id' => $this->selectedBranchId,
'name' => $names[$type][0],
'name_ar' => $names[$type][1],
'type' => $type,
'is_default' => true,
'is_active' => true,
'is_active' => true,
'created_by' => auth()->id(),
'sections' => $this->sections,
'visible_fields' => $this->visibleFields,
'settings' => $this->settings,
]);
]));
} else {
$template->update([
'sections' => $this->sections,
'visible_fields' => $this->visibleFields,
'settings' => $this->settings,
]);
$tpl->update($data);
}
$this->saved = true;
session()->flash('success', __('تم حفظ إعدادات الإيصال'));
}
public function resetToDefaults(): void
{
$this->visibleFields = ReceiptTemplate::defaultPosFields();
$this->sections = ReceiptTemplate::defaultPosSections();
$this->settings = ReceiptTemplate::defaultSettings();
match ($this->activeTab) {
'pos' => [
$this->posFields = ReceiptTemplate::defaultPosFields(),
$this->posSections = ReceiptTemplate::defaultPosSections(),
$this->posSettings = ReceiptTemplate::defaultSettings(),
],
'invoice' => [
$this->invoiceFields = ReceiptTemplate::defaultInvoiceFields(),
$this->invoiceSettings = ReceiptTemplate::defaultInvoiceSettings(),
],
'payment' => [
$this->paymentFields = ReceiptTemplate::defaultPaymentFields(),
$this->paymentSettings = ReceiptTemplate::defaultPaymentSettings(),
],
};
$this->saved = false;
}
......@@ -132,44 +198,78 @@ public function render()
{
$branches = Branch::where('is_active', true)->orderBy('name_ar')->get();
$fieldLabels = [
'header_logo' => 'شعار المنشأة',
$posFieldLabels = [
'header_logo' => 'شعار المنشأة',
'header_academy_name' => 'اسم الأكاديمية',
'header_branch_name' => 'اسم الفرع',
'header_branch_address' => 'عنوان الفرع',
'header_branch_phone' => 'هاتف الفرع',
'header_tax_number' => 'الرقم الضريبي',
'receipt_number' => 'رقم الإيصال',
'receipt_date' => 'التاريخ',
'receipt_time' => 'الوقت',
'cashier_name' => 'اسم الكاشير',
'participant_name' => 'اسم العميل',
'participant_code' => 'كود المشترك',
'items_table' => 'جدول الأصناف',
'item_description' => 'وصف الصنف',
'item_quantity' => 'الكمية',
'item_unit_price' => 'سعر الوحدة',
'item_discount' => 'خصم الصنف',
'item_total' => 'إجمالي الصنف',
'subtotal' => 'المجموع الفرعي',
'discount_total' => 'إجمالي الخصم',
'tax_amount' => 'الضريبة',
'service_fee' => 'مصاريف خدمة',
'grand_total' => 'الإجمالي النهائي',
'payment_method' => 'طريقة الدفع',
'payment_split_details' => 'تفاصيل الدفع المقسم',
'coupon_code' => 'كود الكوبون',
'invoice_number' => 'رقم الفاتورة',
'footer_text' => 'نص التذييل',
'footer_return_policy' => 'سياسة الإرجاع',
'footer_thank_you' => 'رسالة الشكر',
'qr_code' => 'كود QR',
'barcode' => 'باركود',
];
$invoiceFieldLabels = [
'header_logo' => 'شعار المنشأة',
'header_academy_name'=> 'اسم الأكاديمية',
'header_branch' => 'معلومات الفرع',
'header_tax_number' => 'الرقم الضريبي',
'client_details' => 'بيانات العميل',
'items_table' => 'جدول البنود',
'subtotal' => 'المجموع الفرعي',
'discount' => 'الخصم',
'tax_amount' => 'الضريبة',
'service_fee' => 'رسوم الخدمة',
'paid_amount' => 'المبلغ المدفوع / المتبقي',
'payments_history' => 'جدول المدفوعات',
'signature' => 'التوقيع',
'terms_conditions' => 'الشروط والأحكام',
'footer_text' => 'نص التذييل',
];
$paymentFieldLabels = [
'header_logo' => 'شعار المنشأة',
'header_academy_name' => 'اسم الأكاديمية',
'header_branch_name' => 'اسم الفرع',
'header_branch_address' => 'عنوان الفرع',
'header_branch_phone' => 'هاتف الفرع',
'header_tax_number' => 'الرقم الضريبي',
'receipt_number' => 'رقم الإيصال',
'receipt_date' => 'التاريخ',
'receipt_time' => 'الوقت',
'cashier_name' => 'اسم الكاشير',
'participant_name' => 'اسم العميل',
'participant_code' => 'كود المشترك',
'items_table' => 'جدول الأصناف',
'item_description' => 'وصف الصنف',
'item_quantity' => 'الكمية',
'item_unit_price' => 'سعر الوحدة',
'item_discount' => 'خصم الصنف',
'item_total' => 'إجمالي الصنف',
'subtotal' => 'المجموع الفرعي',
'discount_total' => 'إجمالي الخصم',
'tax_amount' => 'الضريبة',
'service_fee' => 'مصاريف خدمة',
'grand_total' => 'الإجمالي النهائي',
'payment_method' => 'طريقة الدفع',
'payment_split_details' => 'تفاصيل الدفع المقسم',
'coupon_code' => 'كود الكوبون',
'invoice_number' => 'رقم الفاتورة',
'footer_text' => 'نص التذييل',
'footer_return_policy' => 'سياسة الإرجاع',
'footer_thank_you' => 'رسالة الشكر',
'qr_code' => 'كود QR',
'barcode' => 'باركود',
'reference_number' => 'رقم المرجع',
'date' => 'التاريخ',
'payment_method' => 'طريقة الدفع',
'invoice_number' => 'رقم الفاتورة',
'payer_name' => 'اسم الدافع',
'received_by' => 'استلم بواسطة',
'status_badge' => 'حالة الدفع',
'notes' => 'الملاحظات',
'footer_text' => 'نص التذييل',
];
return view('livewire.settings.receipt-settings', [
'branches' => $branches,
'fieldLabels' => $fieldLabels,
'branches' => $branches,
'posFieldLabels' => $posFieldLabels,
'invoiceFieldLabels' => $invoiceFieldLabels,
'paymentFieldLabels' => $paymentFieldLabels,
]);
}
}
......@@ -3,14 +3,14 @@
namespace Database\Seeders;
use App\Domain\Shared\Models\SystemSetting;
use App\Domain\Identity\Models\Organization;
use App\Domain\Shared\Models\Academy;
use Illuminate\Database\Seeder;
class EnrollmentSettingsSeeder extends Seeder
{
public function run(): void
{
$academies = Organization::all();
$academies = Academy::all();
$defaults = [
[
......
<div class="space-y-6">
{{-- Page Header --}}
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-4 sm:mb-6">
<h1 class="text-xl sm:text-2xl font-bold text-gray-900">{{ __('إعدادات الإيصال') }}</h1>
<div>
<h1 class="text-xl sm:text-2xl font-bold text-gray-900">{{ __('إعدادات الإيصالات والفواتير') }}</h1>
<p class="text-sm text-gray-500 mt-1">{{ __('تحكم في شكل ومحتوى جميع المستندات المالية من مكان واحد') }}</p>
</div>
<div class="w-full sm:w-auto flex flex-col-reverse sm:flex-row items-stretch sm:items-center gap-2 sm:gap-3">
<button wire:click="resetToDefaults" class="w-full sm:w-auto py-2.5 text-center px-4 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50">
<button wire:click="resetToDefaults"
class="w-full sm:w-auto py-2.5 text-center px-4 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50">
{{ __('استعادة الافتراضي') }}
</button>
<button wire:click="save" wire:loading.attr="disabled" wire:target="save"
class="w-full sm:w-auto py-2.5 text-center px-4 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50">
class="w-full sm:w-auto py-2.5 text-center px-6 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50">
<span wire:loading.remove wire:target="save">{{ __('حفظ الإعدادات') }}</span>
<span wire:loading wire:target="save">{{ __('جارٍ الحفظ...') }}</span>
</button>
......@@ -16,7 +20,13 @@ class="w-full sm:w-auto py-2.5 text-center px-4 text-sm font-medium text-white b
@if($saved)
<div class="p-3 text-sm text-green-700 bg-green-50 border border-green-200 rounded-lg">
{{ __('تم حفظ إعدادات الإيصال بنجاح') }}
{{ __('تم حفظ الإعدادات بنجاح') }}
</div>
@endif
@if(session('success'))
<div class="p-3 text-sm text-green-700 bg-green-50 border border-green-200 rounded-lg">
{{ session('success') }}
</div>
@endif
......@@ -35,93 +45,210 @@ class="w-full sm:w-auto py-2.5 text-center px-4 text-sm font-medium text-white b
</select>
</div>
@if($selectedBranchId)
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6">
{{-- Sections Order --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
<h2 class="text-base sm:text-lg font-semibold text-gray-900 mb-4">{{ __('ترتيب الأقسام') }}</h2>
<div class="space-y-2">
@foreach($sections as $index => $section)
<div class="flex items-center gap-3 p-3 rounded-lg border {{ $section['enabled'] ? 'border-blue-200 bg-blue-50' : 'border-gray-200 bg-gray-50' }}">
<div class="flex flex-col gap-0.5">
<button wire:click="moveSectionUp({{ $index }})" @if($index === 0) disabled @endif
class="text-gray-400 hover:text-gray-700 disabled:opacity-30 disabled:cursor-not-allowed">
<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 15l7-7 7 7"/></svg>
</button>
<button wire:click="moveSectionDown({{ $index }})" @if($index === count($sections) - 1) disabled @endif
class="text-gray-400 hover:text-gray-700 disabled:opacity-30 disabled:cursor-not-allowed">
<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 9l-7 7-7-7"/></svg>
</button>
</div>
<label class="flex items-center flex-1 cursor-pointer">
<input type="checkbox" wire:click="toggleSection({{ $index }})" {{ $section['enabled'] ? 'checked' : '' }}
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 ms-2">
<span class="text-sm font-medium {{ $section['enabled'] ? 'text-gray-900' : 'text-gray-400' }}">
{{ $section['label'] }}
</span>
</label>
</div>
@endforeach
</div>
{{-- Document Type Tabs --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="border-b border-gray-200">
<nav class="flex" aria-label="Tabs">
<button wire:click="$set('activeTab', 'pos')"
class="flex-1 py-4 px-4 text-center text-sm font-medium border-b-2 transition-colors
{{ $activeTab === 'pos' ? 'border-blue-500 text-blue-600 bg-blue-50' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' }}">
<svg class="w-5 h-5 mx-auto mb-1" 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 11h.01M12 11h.01M15 11h.01M4 19h16a2 2 0 002-2V7a2 2 0 00-2-2H4a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
{{ __('إيصال الكاشير') }}
</button>
<button wire:click="$set('activeTab', 'invoice')"
class="flex-1 py-4 px-4 text-center text-sm font-medium border-b-2 transition-colors
{{ $activeTab === 'invoice' ? 'border-blue-500 text-blue-600 bg-blue-50' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' }}">
<svg class="w-5 h-5 mx-auto mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
{{ __('الفاتورة') }}
</button>
<button wire:click="$set('activeTab', 'payment')"
class="flex-1 py-4 px-4 text-center text-sm font-medium border-b-2 transition-colors
{{ $activeTab === 'payment' ? 'border-blue-500 text-blue-600 bg-blue-50' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' }}">
<svg class="w-5 h-5 mx-auto mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z"/>
</svg>
{{ __('إيصال الدفع') }}
</button>
</nav>
</div>
{{-- Field Toggles --}}
<div class="lg:col-span-2 bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
<h2 class="text-base sm:text-lg font-semibold text-gray-900 mb-4">{{ __('الحقول المعروضة') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
@foreach($fieldLabels as $key => $label)
<label class="flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition-colors
{{ ($visibleFields[$key] ?? true) ? 'border-green-200 bg-green-50 hover:bg-green-100' : 'border-gray-200 bg-gray-50 hover:bg-gray-100' }}">
<input type="checkbox" wire:click="toggleField('{{ $key }}')" {{ ($visibleFields[$key] ?? true) ? 'checked' : '' }}
class="rounded border-gray-300 text-green-600 focus:ring-green-500">
<span class="text-sm {{ ($visibleFields[$key] ?? true) ? 'text-gray-900' : 'text-gray-400' }}">{{ $label }}</span>
</label>
@endforeach
<div class="p-4 sm:p-6">
@if(!$selectedBranchId)
<div class="text-center py-10 text-gray-400">
<svg class="w-12 h-12 mx-auto mb-3 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
</svg>
<p class="text-sm">{{ __('اختر الفرع أولاً لعرض الإعدادات') }}</p>
</div>
</div>
</div>
@else
{{-- Receipt Settings --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 sm:p-6">
<h2 class="text-base sm:text-lg font-semibold text-gray-900 mb-4">{{ __('إعدادات المظهر') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 lg:gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('عرض الورق') }}</label>
<select wire:model="settings.paper_width" class="w-full rounded-lg border-gray-300 focus:ring-blue-500">
<option value="58mm">58mm (حراري صغير)</option>
<option value="80mm">80mm (حراري عادي)</option>
<option value="110mm">110mm (عريض)</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('حجم الخط') }}</label>
<select wire:model="settings.font_size" class="w-full rounded-lg border-gray-300 focus:ring-blue-500">
<option value="small">{{ __('صغير') }}</option>
<option value="normal">{{ __('عادي') }}</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ارتفاع الشعار') }}</label>
<select wire:model="settings.logo_height" class="w-full rounded-lg border-gray-300 focus:ring-blue-500">
<option value="30px">30px</option>
<option value="40px">40px</option>
<option value="50px">50px</option>
<option value="60px">60px</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('رمز العملة') }}</label>
<input type="text" wire:model="settings.currency_symbol" class="w-full rounded-lg border-gray-300 focus:ring-blue-500" placeholder="ج.م">
{{-- =================== POS TAB =================== --}}
@if($activeTab === 'pos')
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div>
<h2 class="text-base font-semibold text-gray-900 mb-4">{{ __('ترتيب الأقسام') }}</h2>
<div class="space-y-2">
@foreach($posSections as $index => $section)
<div class="flex items-center gap-3 p-3 rounded-lg border {{ $section['enabled'] ? 'border-blue-200 bg-blue-50' : 'border-gray-200 bg-gray-50' }}">
<div class="flex flex-col gap-0.5">
<button wire:click="moveSectionUp({{ $index }})" @if($index === 0) disabled @endif
class="text-gray-400 hover:text-gray-700 disabled:opacity-30 disabled:cursor-not-allowed">
<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 15l7-7 7 7"/></svg>
</button>
<button wire:click="moveSectionDown({{ $index }})" @if($index === count($posSections) - 1) disabled @endif
class="text-gray-400 hover:text-gray-700 disabled:opacity-30 disabled:cursor-not-allowed">
<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 9l-7 7-7-7"/></svg>
</button>
</div>
<label class="flex items-center flex-1 gap-2 cursor-pointer">
<input type="checkbox" wire:click="toggleSection({{ $index }})" {{ $section['enabled'] ? 'checked' : '' }}
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="text-sm font-medium {{ $section['enabled'] ? 'text-gray-900' : 'text-gray-400' }}">{{ $section['label'] }}</span>
</label>
</div>
@endforeach
</div>
</div>
<div class="lg:col-span-2 space-y-6">
<div>
<h2 class="text-base font-semibold text-gray-900 mb-4">{{ __('الحقول المعروضة') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
@foreach($posFieldLabels as $key => $label)
<label class="flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition-colors
{{ ($posFields[$key] ?? true) ? 'border-green-200 bg-green-50 hover:bg-green-100' : 'border-gray-200 bg-gray-50 hover:bg-gray-100' }}">
<input type="checkbox" wire:click="toggleField('{{ $key }}')" {{ ($posFields[$key] ?? true) ? 'checked' : '' }}
class="rounded border-gray-300 text-green-600 focus:ring-green-500">
<span class="text-sm {{ ($posFields[$key] ?? true) ? 'text-gray-900' : 'text-gray-400' }}">{{ $label }}</span>
</label>
@endforeach
</div>
</div>
<div>
<h2 class="text-base font-semibold text-gray-900 mb-4">{{ __('إعدادات المظهر') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('عرض الورق') }}</label>
<select wire:model="posSettings.paper_width" class="w-full rounded-lg border-gray-300 focus:ring-blue-500">
<option value="58mm">58mm (حراري صغير)</option>
<option value="80mm">80mm (حراري عادي)</option>
<option value="110mm">110mm (عريض)</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('حجم الخط') }}</label>
<select wire:model="posSettings.font_size" class="w-full rounded-lg border-gray-300 focus:ring-blue-500">
<option value="small">{{ __('صغير') }}</option>
<option value="normal">{{ __('عادي') }}</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('ارتفاع الشعار') }}</label>
<select wire:model="posSettings.logo_height" class="w-full rounded-lg border-gray-300 focus:ring-blue-500">
<option value="30px">30px</option>
<option value="40px">40px</option>
<option value="50px">50px</option>
<option value="60px">60px</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('رمز العملة') }}</label>
<input type="text" wire:model="posSettings.currency_symbol" class="w-full rounded-lg border-gray-300 focus:ring-blue-500" placeholder="ج.م">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نص التذييل') }}</label>
<input type="text" wire:model="posSettings.footer_text" class="w-full rounded-lg border-gray-300 focus:ring-blue-500" placeholder="شكراً لزيارتكم">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سياسة الإرجاع') }}</label>
<input type="text" wire:model="posSettings.return_policy" class="w-full rounded-lg border-gray-300 focus:ring-blue-500">
</div>
</div>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نص التذييل') }}</label>
<input type="text" wire:model="settings.footer_text" class="w-full rounded-lg border-gray-300 focus:ring-blue-500" placeholder="شكراً لزيارتكم">
@endif
{{-- =================== INVOICE TAB =================== --}}
@if($activeTab === 'invoice')
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2">
<h2 class="text-base font-semibold text-gray-900 mb-4">{{ __('الحقول المعروضة في الفاتورة') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
@foreach($invoiceFieldLabels as $key => $label)
<label class="flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition-colors
{{ ($invoiceFields[$key] ?? true) ? 'border-green-200 bg-green-50 hover:bg-green-100' : 'border-gray-200 bg-gray-50 hover:bg-gray-100' }}">
<input type="checkbox" wire:click="toggleField('{{ $key }}')" {{ ($invoiceFields[$key] ?? true) ? 'checked' : '' }}
class="rounded border-gray-300 text-green-600 focus:ring-green-500">
<span class="text-sm {{ ($invoiceFields[$key] ?? true) ? 'text-gray-900' : 'text-gray-400' }}">{{ $label }}</span>
</label>
@endforeach
</div>
</div>
<div>
<h2 class="text-base font-semibold text-gray-900 mb-4">{{ __('إعدادات الفاتورة') }}</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نص التذييل') }}</label>
<textarea wire:model="invoiceSettings.footer_text" rows="3"
class="w-full rounded-lg border-gray-300 focus:ring-blue-500 text-sm"
placeholder="{{ __('نص يظهر أسفل كل فاتورة') }}"></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('رمز العملة') }}</label>
<input type="text" wire:model="invoiceSettings.currency_symbol" class="w-full rounded-lg border-gray-300 focus:ring-blue-500" placeholder="ج.م">
</div>
<div class="p-3 bg-blue-50 border border-blue-200 rounded-lg text-xs text-blue-700">
{{ __('الشعار والألوان والخط تُحكم من صفحة الهوية البصرية') }}
<a href="{{ route('settings.branding') }}" wire:navigate class="underline font-medium">{{ __('اضغط هنا') }}</a>
</div>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('سياسة الإرجاع') }}</label>
<input type="text" wire:model="settings.return_policy" class="w-full rounded-lg border-gray-300 focus:ring-blue-500" placeholder="لا يمكن استرجاع المبلغ بعد 7 أيام">
@endif
{{-- =================== PAYMENT TAB =================== --}}
@if($activeTab === 'payment')
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2">
<h2 class="text-base font-semibold text-gray-900 mb-4">{{ __('الحقول المعروضة في إيصال الدفع') }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
@foreach($paymentFieldLabels as $key => $label)
<label class="flex items-center gap-3 p-3 rounded-lg border cursor-pointer transition-colors
{{ ($paymentFields[$key] ?? true) ? 'border-green-200 bg-green-50 hover:bg-green-100' : 'border-gray-200 bg-gray-50 hover:bg-gray-100' }}">
<input type="checkbox" wire:click="toggleField('{{ $key }}')" {{ ($paymentFields[$key] ?? true) ? 'checked' : '' }}
class="rounded border-gray-300 text-green-600 focus:ring-green-500">
<span class="text-sm {{ ($paymentFields[$key] ?? true) ? 'text-gray-900' : 'text-gray-400' }}">{{ $label }}</span>
</label>
@endforeach
</div>
</div>
<div>
<h2 class="text-base font-semibold text-gray-900 mb-4">{{ __('إعدادات إيصال الدفع') }}</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('نص التذييل') }}</label>
<textarea wire:model="paymentSettings.footer_text" rows="3"
class="w-full rounded-lg border-gray-300 focus:ring-blue-500 text-sm"
placeholder="{{ __('نص يظهر أسفل كل إيصال') }}"></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('رمز العملة') }}</label>
<input type="text" wire:model="paymentSettings.currency_symbol" class="w-full rounded-lg border-gray-300 focus:ring-blue-500" placeholder="ج.م">
</div>
</div>
</div>
</div>
@endif
@endif {{-- end selectedBranchId --}}
</div>
</div>
@endif
</div>
......@@ -8,9 +8,20 @@
$branding = app(\App\Domain\Shared\Services\SettingsService::class);
$brandLogo = $branding->get('branding.logo');
$brandPrimary = $branding->get('branding.primary_color', '#1e40af');
$receiptFooter = $branding->get('branding.receipt_footer_text', __('تم إنشاء هذه الفاتورة إلكترونياً ولا تحتاج إلى توقيع'));
$academyName = app()->bound('current_academy') ? app('current_academy')->name_ar : 'الكابتن';
$branch = \App\Domain\Identity\Models\Branch::where('academy_id', app('current_academy')?->id)->first();
// Load invoice template for field visibility
$invoiceTpl = $branch ? \App\Domain\POS\Models\ReceiptTemplate::resolveFor($branch->id, 'invoice') : null;
$f = $invoiceTpl?->visible_fields ?? \App\Domain\POS\Models\ReceiptTemplate::defaultInvoiceFields();
$tplSettings = $invoiceTpl?->settings ?? \App\Domain\POS\Models\ReceiptTemplate::defaultInvoiceSettings();
$receiptFooter = !empty($tplSettings['footer_text'])
? $tplSettings['footer_text']
: $branding->get('branding.receipt_footer_text', 'تم إنشاء هذه الفاتورة إلكترونياً ولا تحتاج إلى توقيع');
$showSignature = $branding->get('branding.show_signature_in_invoice', false);
$signature = $branding->get('branding.signature');
$termsText = $branding->get('branding.terms_and_conditions', '');
$taxNumber = $branding->get('financial.tax_registration_number', '');
@endphp
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&display=swap" rel="stylesheet">
<style>
......@@ -151,15 +162,20 @@
<div class="invoice-wrapper">
<div class="header">
<div class="brand">
@if($brandLogo)
@if(($f['header_logo'] ?? true) && $brandLogo)
<img src="{{ Storage::disk('public')->url($brandLogo) }}" alt="{{ $academyName }}">
@endif
<div>
<h1>{{ __('فاتورة') }}</h1>
@if($f['header_academy_name'] ?? true)
<p style="color: #64748b; margin-top: 2px; font-size: 13px;">{{ $academyName }}</p>
@if($branch)
@endif
@if(($f['header_branch'] ?? true) && $branch)
<p style="color: #94a3b8; font-size: 11px;">{{ $branch->name_ar }}</p>
@endif
@if(($f['header_tax_number'] ?? false) && $taxNumber)
<p style="color: #94a3b8; font-size: 11px;">{{ __('الرقم الضريبي') }}: {{ $taxNumber }}</p>
@endif
</div>
</div>
<div class="meta">
......@@ -175,7 +191,9 @@
</div>
</div>
@if(($f['client_details'] ?? true) || ($f['header_branch'] ?? true))
<div class="parties">
@if($f['client_details'] ?? true)
<div class="party">
<h3>{{ __('العميل') }}</h3>
@if($invoice->billable)
......@@ -185,6 +203,8 @@
@endif
@endif
</div>
@endif
@if($f['header_branch'] ?? true)
<div class="party">
<h3>{{ __('من') }}</h3>
<p><strong>{{ $academyName }}</strong></p>
......@@ -197,8 +217,11 @@
@endif
@endif
</div>
@endif
</div>
@endif
@if($f['items_table'] ?? true)
<div class="items-table">
<table>
<thead>
......@@ -225,26 +248,29 @@
</tbody>
</table>
</div>
@endif
<div class="totals-section">
<div class="totals">
@if($f['subtotal'] ?? true)
<div class="row">
<span>{{ __('المجموع الفرعي') }}</span>
<span dir="ltr">{{ format_money($invoice->subtotal_amount ?? $invoice->total_amount) }}</span>
</div>
@if($invoice->discount_amount > 0)
@endif
@if(($f['discount'] ?? true) && $invoice->discount_amount > 0)
<div class="row">
<span>{{ __('الخصم') }}</span>
<span dir="ltr" style="color: #dc2626;">-{{ format_money($invoice->discount_amount) }}</span>
</div>
@endif
@if(($invoice->service_fee_amount ?? 0) > 0)
@if(($f['service_fee'] ?? true) && ($invoice->service_fee_amount ?? 0) > 0)
<div class="row">
<span>{{ __('رسوم الخدمة') }}</span>
<span dir="ltr">{{ format_money($invoice->service_fee_amount) }}</span>
</div>
@endif
@if($invoice->tax_amount > 0)
@if(($f['tax_amount'] ?? true) && $invoice->tax_amount > 0)
<div class="row">
<span>{{ __('الضريبة') }}</span>
<span dir="ltr">{{ format_money($invoice->tax_amount) }}</span>
......@@ -254,7 +280,7 @@
<span>{{ __('الإجمالي') }}</span>
<span dir="ltr">{{ format_money($invoice->total_amount) }}</span>
</div>
@if($invoice->paid_amount > 0)
@if(($f['paid_amount'] ?? true) && $invoice->paid_amount > 0)
<div class="row paid">
<span>{{ __('المدفوع') }}</span>
<span dir="ltr">{{ format_money($invoice->paid_amount) }}</span>
......@@ -267,7 +293,7 @@
</div>
</div>
@if($invoice->payments && $invoice->payments->count())
@if(($f['payments_history'] ?? true) && $invoice->payments && $invoice->payments->count())
<div class="payments-section">
<h3>{{ __('المدفوعات') }}</h3>
<table>
......@@ -293,9 +319,25 @@
</div>
@endif
@if(($f['signature'] ?? false) && $showSignature && $signature)
<div style="padding: 12px 20px; text-align: end;">
<img src="{{ Storage::disk('public')->url($signature) }}" alt="{{ __('التوقيع') }}" style="height: 50px; object-fit: contain;">
<p style="font-size: 11px; color: #94a3b8; margin-top: 4px;">{{ __('التوقيع المعتمد') }}</p>
</div>
@endif
@if(($f['terms_conditions'] ?? false) && $termsText)
<div style="padding: 12px 20px; border-top: 1px solid #e2e8f0; font-size: 11px; color: #64748b;">
<p style="font-weight: 600; margin-bottom: 4px;">{{ __('الشروط والأحكام') }}</p>
<p style="white-space: pre-wrap;">{{ $termsText }}</p>
</div>
@endif
@if($f['footer_text'] ?? true)
<div class="footer">
{{ $receiptFooter }}
</div>
@endif
<div class="actions">
<button onclick="window.print()" class="btn btn-print">
......
......@@ -9,6 +9,13 @@
$brandLogo = $branding->get('branding.logo');
$brandPrimary = $branding->get('branding.primary_color', '#1e40af');
$academyName = app()->bound('current_academy') ? app('current_academy')->name_ar : 'الكابتن';
$payBranch = \App\Domain\Identity\Models\Branch::where('academy_id', app('current_academy')?->id)->first();
$payTpl = $payBranch ? \App\Domain\POS\Models\ReceiptTemplate::resolveFor($payBranch->id, 'payment') : null;
$pf = $payTpl?->visible_fields ?? \App\Domain\POS\Models\ReceiptTemplate::defaultPaymentFields();
$payTplSettings = $payTpl?->settings ?? \App\Domain\POS\Models\ReceiptTemplate::defaultPaymentSettings();
$payFooter = !empty($payTplSettings['footer_text'])
? $payTplSettings['footer_text']
: 'تم إنشاء هذا المستند إلكترونياً ولا يحتاج إلى توقيع';
@endphp
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&display=swap" rel="stylesheet">
<style>
......@@ -114,11 +121,13 @@
<body>
<div class="receipt-wrapper">
<div class="header">
@if($brandLogo)
@if(($pf['header_logo'] ?? true) && $brandLogo)
<img src="{{ Storage::disk('public')->url($brandLogo) }}" alt="{{ $academyName }}">
@endif
<h1>{{ __('إيصال دفع') }}</h1>
@if($pf['header_academy_name'] ?? true)
<div class="academy">{{ $academyName }}</div>
@endif
</div>
<div class="amount-section">
......@@ -126,50 +135,49 @@
<div class="amount-value">{{ format_money($payment->amount) }}</div>
</div>
@php
$methodLabels = ['cash'=>'نقدي','card'=>'بطاقة','bank_transfer'=>'تحويل بنكي','wallet'=>'محفظة','online'=>'إلكتروني','cheque'=>'شيك','other'=>'أخرى'];
$methodValue = $payment->method?->value ?? $payment->method ?? '';
$payerName = $payment->invoice?->contact_name
?? $payment->invoice?->billable?->person?->name_ar
?? $payment->invoice?->billable?->person?->name
?? null;
$statusValue = $payment->status?->value ?? $payment->status ?? 'pending';
$statusLabels = ['pending'=>'معلق','confirmed'=>'مؤكد','failed'=>'فاشل','cancelled'=>'ملغى','refunded'=>'مسترد'];
@endphp
<div class="info-section">
@if($pf['reference_number'] ?? true)
<div class="info-row">
<span class="label">{{ __('رقم المرجع') }}</span>
<span class="value" dir="ltr">{{ $payment->reference ?? 'PAY-' . str_pad($payment->id, 6, '0', STR_PAD_LEFT) }}</span>
</div>
@endif
@if($pf['date'] ?? true)
<div class="info-row">
<span class="label">{{ __('التاريخ') }}</span>
<span class="value" dir="ltr">{{ $payment->payment_date?->format('Y/m/d') ?? $payment->created_at?->format('Y/m/d') }}</span>
</div>
@php
$methodLabels = [
'cash' => 'نقدي',
'card' => 'بطاقة',
'bank_transfer' => 'تحويل بنكي',
'wallet' => 'محفظة',
'online' => 'إلكتروني',
'cheque' => 'شيك',
'other' => 'أخرى',
];
$methodValue = $payment->method?->value ?? $payment->method ?? '';
@endphp
@endif
@if($pf['payment_method'] ?? true)
<div class="info-row">
<span class="label">{{ __('طريقة الدفع') }}</span>
<span class="value">{{ $methodLabels[$methodValue] ?? $methodValue }}</span>
</div>
@if($payment->invoice)
@endif
@if(($pf['invoice_number'] ?? true) && $payment->invoice)
<div class="info-row">
<span class="label">{{ __('رقم الفاتورة') }}</span>
<span class="value" dir="ltr">{{ $payment->invoice->number }}</span>
</div>
@endif
@php
$payerName = $payment->invoice?->contact_name
?? $payment->invoice?->billable?->person?->name_ar
?? $payment->invoice?->billable?->person?->name
?? null;
@endphp
@if($payerName)
@if(($pf['payer_name'] ?? true) && $payerName)
<div class="info-row">
<span class="label">{{ __('الدافع') }}</span>
<span class="value">{{ $payerName }}</span>
</div>
@endif
@if($payment->creator)
@if(($pf['received_by'] ?? true) && $payment->creator)
<div class="info-row">
<span class="label">{{ __('استلم بواسطة') }}</span>
<span class="value">{{ $payment->creator->name ?? '' }}</span>
......@@ -177,30 +185,24 @@
@endif
</div>
@php
$statusValue = $payment->status?->value ?? $payment->status ?? 'pending';
$statusLabels = [
'pending' => 'معلق',
'confirmed' => 'مؤكد',
'failed' => 'فاشل',
'cancelled' => 'ملغى',
'refunded' => 'مسترد',
];
@endphp
@if($pf['status_badge'] ?? true)
<div style="text-align: center; padding: 12px 16px; border-bottom: 1px dashed #e2e8f0;">
<span class="status-badge status-{{ $statusValue }}">{{ $statusLabels[$statusValue] ?? $statusValue }}</span>
</div>
@endif
@if($payment->notes)
@if(($pf['notes'] ?? true) && $payment->notes)
<div class="info-section">
<div style="font-size: 12px; color: #64748b;">{{ __('ملاحظات') }}:</div>
<div style="font-size: 13px; margin-top: 4px;">{{ $payment->notes }}</div>
</div>
@endif
@if($pf['footer_text'] ?? true)
<div class="footer">
{{ __('تم إنشاء هذا المستند إلكترونياً ولا يحتاج إلى توقيع') }}
{{ $payFooter }}
</div>
@endif
<div class="actions">
<button onclick="window.print()" class="btn btn-print">
......
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