From a7614c91cd02e8b889f6e4dfa8d92168409f812d Mon Sep 17 00:00:00 2001 From: pbao9 Date: Sun, 15 Feb 2026 00:55:17 +0700 Subject: [PATCH] feat: Implement invoice creation functionality and integrate DataTables for invoice listing with dynamic column rendering. --- .../Controllers/admin/InvoiceController.php | 79 +++++++ .../Invoice/InvoiceCachedRepository.php | 6 +- .../Invoice/InvoiceRepository.php | 10 +- .../Invoice/InvoiceRepositoryInterface.php | 2 +- resources/sass/_fonts.scss | 126 +--------- resources/sass/_variables.scss | 2 +- .../components/data-table.blade.php | 37 ++- .../administrator/pages/invoice/add.blade.php | 142 +++++++++++ .../pages/invoice/datatable/action.blade.php | 27 +++ .../invoice/datatable/customer.blade.php | 1 + .../pages/invoice/datatable/date.blade.php | 1 + .../invoice/datatable/invoice_num.blade.php | 3 + .../pages/invoice/datatable/payment.blade.php | 1 + .../pages/invoice/datatable/status.blade.php | 1 + .../pages/invoice/datatable/total.blade.php | 1 + .../pages/invoice/index.blade.php | 221 ++++-------------- .../views/components/data-table.blade.php | 65 ++++-- routes/admin.php | 3 + 18 files changed, 404 insertions(+), 324 deletions(-) create mode 100644 resources/views/administrator/pages/invoice/add.blade.php create mode 100644 resources/views/administrator/pages/invoice/datatable/action.blade.php create mode 100644 resources/views/administrator/pages/invoice/datatable/customer.blade.php create mode 100644 resources/views/administrator/pages/invoice/datatable/date.blade.php create mode 100644 resources/views/administrator/pages/invoice/datatable/invoice_num.blade.php create mode 100644 resources/views/administrator/pages/invoice/datatable/payment.blade.php create mode 100644 resources/views/administrator/pages/invoice/datatable/status.blade.php create mode 100644 resources/views/administrator/pages/invoice/datatable/total.blade.php diff --git a/app/Http/Controllers/admin/InvoiceController.php b/app/Http/Controllers/admin/InvoiceController.php index 958e609..b4b132b 100644 --- a/app/Http/Controllers/admin/InvoiceController.php +++ b/app/Http/Controllers/admin/InvoiceController.php @@ -4,10 +4,13 @@ namespace App\Http\Controllers\admin; use App\Http\Controllers\Controller; use App\Http\Requests\InvoiceRequest; +use App\Models\Customer; +use App\Models\Payment; use App\Repositories\Invoice\InvoiceRepositoryInterface; use App\Repositories\Order\OrderRepositoryInterface; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; +use Yajra\DataTables\Facades\DataTables; class InvoiceController extends Controller { @@ -31,6 +34,46 @@ class InvoiceController extends Controller return view('administrator.pages.invoice.index', compact('title', 'invoices')); } + public function create() + { + $title = 'Add new Invoice'; + $customer = Customer::select('id', 'fullname')->where('status', 1)->get(); + $payment = Payment::select('id', 'name')->where('status', 1)->get(); + + return view('administrator.pages.invoice.add', compact('title', 'customer', 'payment')); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'customer_id' => 'required|numeric', + 'payment_id' => 'required|numeric', + 'sub_total' => 'required|numeric', + 'total' => 'required|numeric', + ]); + + $invoiceData = [ + 'date' => time(), + 'due_date' => time() + (7 * 24 * 60 * 60), + 'sub_total' => $validated['sub_total'], + 'tax' => $validated['sub_total'] * 0.1, + 'total' => $validated['total'], + 'payment_id' => $validated['payment_id'], + 'customer_id' => $validated['customer_id'], + 'status' => 0, + ]; + + DB::beginTransaction(); + try { + $invoice = $this->invoiceRepository->create($invoiceData); + DB::commit(); + return redirect()->route('administrator.pages.invoice.edit', ['id' => $invoice->id])->with('success', 'Invoice created successfully'); + } catch (\Exception $error) { + DB::rollBack(); + return back()->withErrors($error->getMessage()); + } + } + public function edit(int $id = 0) { $invoice = $this->invoiceRepository->findWithRelationships($id, ['payment', 'invoiceItem', 'customer']); @@ -96,4 +139,40 @@ class InvoiceController extends Controller return redirect()->route('administrator.pages.invoice.index')->with('success', 'Invoice deleted successfully'); } + + public function datatable(Request $request) + { + $query = $this->invoiceRepository->filter( + $request->all(), + ['payment', 'customer'], + [], + ['*'], + true + ); + + return DataTables::eloquent($query) + ->addColumn('invoice_num', function ($d) { + return view('administrator.pages.invoice.datatable.invoice_num', ['row' => $d])->render(); + }) + ->addColumn('date', function ($d) { + return view('administrator.pages.invoice.datatable.date', ['row' => $d])->render(); + }) + ->addColumn('customer', function ($d) { + return view('administrator.pages.invoice.datatable.customer', ['row' => $d])->render(); + }) + ->addColumn('payment', function ($d) { + return view('administrator.pages.invoice.datatable.payment', ['row' => $d])->render(); + }) + ->addColumn('total', function ($d) { + return view('administrator.pages.invoice.datatable.total', ['row' => $d])->render(); + }) + ->addColumn('status', function ($d) { + return view('administrator.pages.invoice.datatable.status', ['row' => $d])->render(); + }) + ->addColumn('action', function ($d) { + return view('administrator.pages.invoice.datatable.action', ['row' => $d])->render(); + }) + ->rawColumns(['invoice_num', 'date', 'customer', 'payment', 'total', 'status', 'action']) + ->make(true); + } } diff --git a/app/Repositories/Invoice/InvoiceCachedRepository.php b/app/Repositories/Invoice/InvoiceCachedRepository.php index a7b9b03..ea525a6 100644 --- a/app/Repositories/Invoice/InvoiceCachedRepository.php +++ b/app/Repositories/Invoice/InvoiceCachedRepository.php @@ -16,8 +16,12 @@ class InvoiceCachedRepository extends BaseCachedRepository implements InvoiceRep { return $this->invoiceRepository->getModel(); } - public function filter(array $data = [], array $relationships = [], array $withCount = [], array $select = ['*']) + public function filter(array $data = [], array $relationships = [], array $withCount = [], array $select = ['*'], bool $returnQuery = false) { + if ($returnQuery) { + return $this->invoiceRepository->filter($data, $relationships, $withCount, $select, $returnQuery); + } + $cacheKey = $this->getCacheKey(md5(json_encode([ 'filterItem' => [ 'data' => $data, diff --git a/app/Repositories/Invoice/InvoiceRepository.php b/app/Repositories/Invoice/InvoiceRepository.php index 425af94..73a9915 100644 --- a/app/Repositories/Invoice/InvoiceRepository.php +++ b/app/Repositories/Invoice/InvoiceRepository.php @@ -12,10 +12,13 @@ class InvoiceRepository extends BaseRepository implements InvoiceRepositoryInter { return Invoice::class; } - public function filter(array $data = [], array $relationships = [], array $withCount = [], array $select = ['*']) + public function filter(array $data = [], array $relationships = [], array $withCount = [], array $select = ['*'], bool $returnQuery = false) { $model = $this->model->with($relationships)->select($select); + if (isset($data['client']) && ! empty($data['client'])) { + $model = $model->where("customer_id", $data['client']); + } if (isset($data['invoice_type'])) { $model = $model->where("invoice_type", $data['invoice_type']); } @@ -86,6 +89,11 @@ class InvoiceRepository extends BaseRepository implements InvoiceRepositoryInter if (count($withCount) > 0) { $model = $model->withCount($withCount); } + + if ($returnQuery) { + return $model; + } + if (isset($data['limit']) && $data['limit']) { return $model->latest('id')->take($data['limit'])->get(); } diff --git a/app/Repositories/Invoice/InvoiceRepositoryInterface.php b/app/Repositories/Invoice/InvoiceRepositoryInterface.php index 3c2e673..bf55feb 100644 --- a/app/Repositories/Invoice/InvoiceRepositoryInterface.php +++ b/app/Repositories/Invoice/InvoiceRepositoryInterface.php @@ -5,7 +5,7 @@ namespace App\Repositories\Invoice; use App\Repositories\BaseRepositoryInterface; interface InvoiceRepositoryInterface extends BaseRepositoryInterface{ - public function filter(array $data = [],array $relationships = [],array $withCount = [],array $select = ['*']); + public function filter(array $data = [],array $relationships = [],array $withCount = [],array $select = ['*'], bool $returnQuery = false); public function get_billing_year(int $year = 0); public function get_billing_month(int $month = 0); public function get_billing_day(int|string $day = ''); diff --git a/resources/sass/_fonts.scss b/resources/sass/_fonts.scss index a17b90b..9c9a366 100644 --- a/resources/sass/_fonts.scss +++ b/resources/sass/_fonts.scss @@ -1,125 +1 @@ -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-Thin.ttf') format('truetype'); - font-weight: 100; - font-style: normal; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-ThinItalic.ttf') format('truetype'); - font-weight: 100; - font-style: italic; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-ExtraLight.ttf') format('truetype'); - font-weight: 200; - font-style: normal; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-ExtraLightItalic.ttf') format('truetype'); - font-weight: 200; - font-style: italic; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-Light.ttf') format('truetype'); - font-weight: 300; - font-style: normal; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-LightItalic.ttf') format('truetype'); - font-weight: 300; - font-style: italic; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-Regular.ttf') format('truetype'); - font-weight: 400; - font-style: normal; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-Italic.ttf') format('truetype'); - font-weight: 400; - font-style: italic; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-Medium.ttf') format('truetype'); - font-weight: 500; - font-style: normal; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-MediumItalic.ttf') format('truetype'); - font-weight: 500; - font-style: italic; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-SemiBold.ttf') format('truetype'); - font-weight: 600; - font-style: normal; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-SemiBoldItalic.ttf') format('truetype'); - font-weight: 600; - font-style: italic; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-Bold.ttf') format('truetype'); - font-weight: 700; - font-style: normal; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-BoldItalic.ttf') format('truetype'); - font-weight: 700; - font-style: italic; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-ExtraBold.ttf') format('truetype'); - font-weight: 800; - font-style: normal; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-ExtraBoldItalic.ttf') format('truetype'); - font-weight: 800; - font-style: italic; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-Black.ttf') format('truetype'); - font-weight: 900; - font-style: normal; -} - -@font-face { - font-family: 'Be Vietnam Pro'; - src: url('/fonts/bevietnampro/BeVietnamPro-BlackItalic.ttf') format('truetype'); - font-weight: 900; - font-style: italic; -} +@import url('https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,100..900;1,100..900&display=swap'); diff --git a/resources/sass/_variables.scss b/resources/sass/_variables.scss index abfc971..6a1a181 100644 --- a/resources/sass/_variables.scss +++ b/resources/sass/_variables.scss @@ -233,7 +233,7 @@ $component-hover-bg: rgba($secondary, .2); // custom variable // Font, line-height, and color for body text, headings, and more. // Font family -$font-family-sans-serif: "Be Vietnam Pro", Helvetica, sans-serif !default; +$font-family-sans-serif: "Inter Tight", Helvetica, sans-serif !default; $font-size-base: 0.875rem !default; // 14px $font-size-lg: 1rem !default; // 16px diff --git a/resources/views/administrator/components/data-table.blade.php b/resources/views/administrator/components/data-table.blade.php index c02fa65..ecbdaa1 100644 --- a/resources/views/administrator/components/data-table.blade.php +++ b/resources/views/administrator/components/data-table.blade.php @@ -1,14 +1,21 @@ @php - $dtColumns = collect($columns)->map(function ($c) { + $dtColumns = collect($columns)->map(function ($c, $index) { return [ 'data' => $c['key'], 'name' => $c['key'], 'orderable' => $c['orderable'] ?? true, 'searchable' => $c['searchable'] ?? true, + 'visible' => $c['visible'] ?? true, ]; })->values(); @endphp +
+ +
+
@@ -21,15 +28,41 @@
+
+
+
Column Visibility
+ +
+
+ @foreach($columns as $index => $col) + @if((!isset($col['visibility']) || $col['visibility'] !== false) && $col['key'] !== 'checkbox') +
+ + +
+ @endif + @endforeach +
+
+ @push('custom-scripts') @endpush diff --git a/resources/views/administrator/pages/invoice/add.blade.php b/resources/views/administrator/pages/invoice/add.blade.php new file mode 100644 index 0000000..8842b3f --- /dev/null +++ b/resources/views/administrator/pages/invoice/add.blade.php @@ -0,0 +1,142 @@ +@extends('administrator.layout.master') + +@section('breadcrumb') + @php +$breadcrumbItems = [ + ['label' => 'Invoice'], + ['label' => 'Invoice Management', 'url' => route('administrator.pages.invoice.index')], + ['label' => 'Add New Invoice', 'active' => true], +]; + @endphp + +@endsection + +@section('content') + @if(auth()->guard('admins')->user()->can("add invoice")) +
+ @csrf +
+
+
+
+
Invoice Information
+
+
+
+ + +
+ +
+ + +
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+
Invoice Summary
+
+
+ + + + + + + + + + + + + + + +
Sub Total0đ
VAT (10%)0đ
Total0đ
+
+
+
+
+ + + Cancel + Create Invoice + +
+ @else +
+
Access Denied - You are not assigned to any Invoice + management department
+
+ @endif +@endsection + +@push('custom-scripts') + +@endpush diff --git a/resources/views/administrator/pages/invoice/datatable/action.blade.php b/resources/views/administrator/pages/invoice/datatable/action.blade.php new file mode 100644 index 0000000..943c096 --- /dev/null +++ b/resources/views/administrator/pages/invoice/datatable/action.blade.php @@ -0,0 +1,27 @@ + diff --git a/resources/views/administrator/pages/invoice/datatable/customer.blade.php b/resources/views/administrator/pages/invoice/datatable/customer.blade.php new file mode 100644 index 0000000..b07f2d3 --- /dev/null +++ b/resources/views/administrator/pages/invoice/datatable/customer.blade.php @@ -0,0 +1 @@ +{{ $row->customer->firstname . ' ' . $row->customer->lastname }} diff --git a/resources/views/administrator/pages/invoice/datatable/date.blade.php b/resources/views/administrator/pages/invoice/datatable/date.blade.php new file mode 100644 index 0000000..7645631 --- /dev/null +++ b/resources/views/administrator/pages/invoice/datatable/date.blade.php @@ -0,0 +1 @@ +@time($row->date) diff --git a/resources/views/administrator/pages/invoice/datatable/invoice_num.blade.php b/resources/views/administrator/pages/invoice/datatable/invoice_num.blade.php new file mode 100644 index 0000000..b0c7325 --- /dev/null +++ b/resources/views/administrator/pages/invoice/datatable/invoice_num.blade.php @@ -0,0 +1,3 @@ + + {{ $row->id }} + diff --git a/resources/views/administrator/pages/invoice/datatable/payment.blade.php b/resources/views/administrator/pages/invoice/datatable/payment.blade.php new file mode 100644 index 0000000..03e4d33 --- /dev/null +++ b/resources/views/administrator/pages/invoice/datatable/payment.blade.php @@ -0,0 +1 @@ +{{ $row->payment->name ?? 'VN Pay' }} diff --git a/resources/views/administrator/pages/invoice/datatable/status.blade.php b/resources/views/administrator/pages/invoice/datatable/status.blade.php new file mode 100644 index 0000000..ef35087 --- /dev/null +++ b/resources/views/administrator/pages/invoice/datatable/status.blade.php @@ -0,0 +1 @@ +{!! $row->getStatus() !!} diff --git a/resources/views/administrator/pages/invoice/datatable/total.blade.php b/resources/views/administrator/pages/invoice/datatable/total.blade.php new file mode 100644 index 0000000..516bf3c --- /dev/null +++ b/resources/views/administrator/pages/invoice/datatable/total.blade.php @@ -0,0 +1 @@ +{{ number_format($row->total, 0, ',', '.') }}đ diff --git a/resources/views/administrator/pages/invoice/index.blade.php b/resources/views/administrator/pages/invoice/index.blade.php index 027c982..6fc71f7 100644 --- a/resources/views/administrator/pages/invoice/index.blade.php +++ b/resources/views/administrator/pages/invoice/index.blade.php @@ -1,184 +1,42 @@ -@extends("administrator.layout.master") -@section("content") +@extends('administrator.layout.master') +@push('plugin-styles') + @foreach (config('assets.datatable.css') as $datatable) + + @endforeach +@endpush + +@section('breadcrumb') + @php + $breadcrumbItems = [ + ['label' => 'Invoice'], + ['label' => 'Invoice Management'], + ['label' => 'Invoice List', 'active' => true], + ]; + @endphp + +@endsection + +@section('content') @if(auth()->guard('admins')->user()->can("manager invoice")) -
-
-
-
-
- {{-- Customer --}} - -
-
-
- - -
-
-
- {{-- Payment --}} - -
-
-
- - -
-
-
-
- -
-
- - - -
- -
-
-
-
-
- -
-
- - - -
- -
-
-
-
-
- -
-
- - - -
- -
-
-
- - -
- - -
-
-
- {{ $invoices->count() }} Records Found +
+
+ + Add New Invoice +
-
- - - - - - - - - - - - - - - - @if($invoices->count()) - @foreach($invoices->all() as $item) - - - - - - - - - - - - @endforeach - @endif - -
- - Invoice #Client NameDatePayment MethodTotalStatus
- - - $item->id]) }}" - title="View Order"> {{ $item->id }} - - @isset($item->customer->firstname) - {{ $item->customer->firstname . ' ' . $item->customer->lastname }} - @endisset - - @time($item->date) - - {{ $item->payment->name ?? 'VN Pay' }} - - {{ number_format($item->total,0,',','.') }}đ - - {!! $item->getStatus() !!} - - - - - - @if(auth()->guard('admins')->user()->can("delete invoice")) -
- @csrf - @method('delete') - -
- @endif -
-
- @else @@ -188,3 +46,8 @@
@endif @endsection +@push('plugin-scripts') + @foreach (config('assets.datatable.js') as $datatable) + + @endforeach +@endpush diff --git a/resources/views/components/data-table.blade.php b/resources/views/components/data-table.blade.php index 67da1cb..20a8f8b 100644 --- a/resources/views/components/data-table.blade.php +++ b/resources/views/components/data-table.blade.php @@ -2,32 +2,34 @@ @php $dtColumns = collect($columns) - ->map(function ($c) { + ->map(function ($c, $index) { return [ 'data' => $c['key'], 'name' => $c['key'], 'orderable' => $c['orderable'] ?? true, 'searchable' => $c['searchable'] ?? true, 'className' => $c['className'] ?? '', + 'visible' => $c['visible'] ?? true, ]; }) ->values(); @endphp + @if ($bulkDeleteUrl) -
-
-
- - 0 items selected -
-
- - Delete Selected - +
+
+
+ + 0 items selected
+
+ + Delete Selected +
+