From bb4f640ee0a6960ec7b840bbd94ee7a811c53dd5 Mon Sep 17 00:00:00 2001 From: joeyjanson Date: Mon, 19 Dec 2022 21:07:49 +0100 Subject: [PATCH 1/9] migration for the credit column --- ...22_08_09_195557_add_column_to_invoices.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 database/migrations/2022_08_09_195557_add_column_to_invoices.php diff --git a/database/migrations/2022_08_09_195557_add_column_to_invoices.php b/database/migrations/2022_08_09_195557_add_column_to_invoices.php new file mode 100644 index 000000000..357ede75d --- /dev/null +++ b/database/migrations/2022_08_09_195557_add_column_to_invoices.php @@ -0,0 +1,32 @@ +boolean('credit')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('invoices', function (Blueprint $table) { + $table->dropForeign(['credit']); + }); + } +} From a745166f5f97e4931c1bfa601b98e31b0e12c20b Mon Sep 17 00:00:00 2001 From: joeyjanson Date: Mon, 19 Dec 2022 21:08:37 +0100 Subject: [PATCH 2/9] style: default disable from input --- .../admin/components/modal-components/SendInvoiceModal.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/scripts/admin/components/modal-components/SendInvoiceModal.vue b/resources/scripts/admin/components/modal-components/SendInvoiceModal.vue index 63e7e6af4..b3e40c4c6 100644 --- a/resources/scripts/admin/components/modal-components/SendInvoiceModal.vue +++ b/resources/scripts/admin/components/modal-components/SendInvoiceModal.vue @@ -26,6 +26,7 @@ v-model="invoiceMailForm.from" type="text" :invalid="v$.from.$error" + :disabled="true" @input="v$.from.$touch()" /> From 4d5fb709577cfa0dcec39b6a82166c200bc27466 Mon Sep 17 00:00:00 2001 From: joeyjanson Date: Mon, 19 Dec 2022 21:09:11 +0100 Subject: [PATCH 3/9] feat: add credit functionality for Vue and JS --- .../dropdowns/InvoiceIndexDropdown.vue | 7 ++++--- .../estimate-invoice-common/CreateTotal.vue | 2 +- resources/scripts/admin/stores/invoice.js | 12 +++++++---- resources/scripts/admin/stub/invoice.js | 1 + .../scripts/admin/views/invoices/Index.vue | 4 ++-- .../scripts/admin/views/invoices/View.vue | 4 ++-- .../create/InvoiceCreateBasicFields.vue | 21 +++++++++++++++++++ resources/scripts/locales/en.json | 4 +++- 8 files changed, 42 insertions(+), 13 deletions(-) diff --git a/resources/scripts/admin/components/dropdowns/InvoiceIndexDropdown.vue b/resources/scripts/admin/components/dropdowns/InvoiceIndexDropdown.vue index 35d8d9052..7c26e7fc0 100755 --- a/resources/scripts/admin/components/dropdowns/InvoiceIndexDropdown.vue +++ b/resources/scripts/admin/components/dropdowns/InvoiceIndexDropdown.vue @@ -12,7 +12,7 @@ v-if="userStore.hasAbilities(abilities.EDIT_INVOICE)" :to="`/admin/invoices/${row.id}/edit`" > - + - + - + diff --git a/resources/scripts/admin/stores/invoice.js b/resources/scripts/admin/stores/invoice.js index 97bcebff9..e05835664 100644 --- a/resources/scripts/admin/stores/invoice.js +++ b/resources/scripts/admin/stores/invoice.js @@ -45,9 +45,11 @@ export const useInvoiceStore = (useWindow = false) => { }, getSubTotal() { - return this.newInvoice.items.reduce(function (a, b) { + let isCredit = !!this.newInvoice.credit; + let calc = this.newInvoice.items.reduce(function (a, b) { return a + b['total'] - }, 0) + }, 0); + return isCredit ? (0 - calc) : calc; }, getTotalSimpleTax() { @@ -69,14 +71,15 @@ export const useInvoiceStore = (useWindow = false) => { }, getTotalTax() { + let isCredit = !!this.newInvoice.credit; if ( this.newInvoice.tax_per_item === 'NO' || this.newInvoice.tax_per_item === null ) { - return this.getTotalSimpleTax + this.getTotalCompoundTax + return isCredit ? 0 - (this.getTotalSimpleTax + this.getTotalCompoundTax) : (this.getTotalSimpleTax + this.getTotalCompoundTax) } return _.sumBy(this.newInvoice.items, function (tax) { - return tax.tax + return isCredit ? 0 - tax.tax : tax.tax }) }, @@ -119,6 +122,7 @@ export const useInvoiceStore = (useWindow = false) => { .then((response) => { this.invoices = response.data.data this.invoiceTotalCount = response.data.meta.invoice_total_count + resolve(response) }) .catch((err) => { diff --git a/resources/scripts/admin/stub/invoice.js b/resources/scripts/admin/stub/invoice.js index 665c877bc..767c6c66e 100644 --- a/resources/scripts/admin/stub/invoice.js +++ b/resources/scripts/admin/stub/invoice.js @@ -35,5 +35,6 @@ export default function () { fields: [], selectedNote: null, selectedCurrency: '', + credit: Boolean(0), } } diff --git a/resources/scripts/admin/views/invoices/Index.vue b/resources/scripts/admin/views/invoices/Index.vue index c1b218749..7a1798584 100644 --- a/resources/scripts/admin/views/invoices/Index.vue +++ b/resources/scripts/admin/views/invoices/Index.vue @@ -224,8 +224,8 @@ diff --git a/resources/scripts/admin/views/invoices/View.vue b/resources/scripts/admin/views/invoices/View.vue index 6e2829343..bb167bcfd 100644 --- a/resources/scripts/admin/views/invoices/View.vue +++ b/resources/scripts/admin/views/invoices/View.vue @@ -451,10 +451,10 @@ onSearched = debounce(onSearched, 500) {{ invoice.invoice_number }} - {{ invoice.status }} + {{ invoice.credit != 0 ? 'CREDIT' : invoice.status }} diff --git a/resources/scripts/admin/views/invoices/create/InvoiceCreateBasicFields.vue b/resources/scripts/admin/views/invoices/create/InvoiceCreateBasicFields.vue index 9181b5734..42cdf18aa 100644 --- a/resources/scripts/admin/views/invoices/create/InvoiceCreateBasicFields.vue +++ b/resources/scripts/admin/views/invoices/create/InvoiceCreateBasicFields.vue @@ -48,6 +48,27 @@ /> +
+
+ +
+ +
+

+ {{ $t('invoices.is_credit') }} +

+

+ {{ $t('invoices.is_credit_desc') }} +

+
+
+ Date: Mon, 19 Dec 2022 21:09:35 +0100 Subject: [PATCH 4/9] feat: smaller size output with enable_font_subsetting true --- config/dompdf.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/dompdf.php b/config/dompdf.php index 2203d1439..d6a85e0d0 100644 --- a/config/dompdf.php +++ b/config/dompdf.php @@ -76,7 +76,7 @@ /** * Whether to enable font subsetting or not. */ - "enable_font_subsetting" => false, + "enable_font_subsetting" => true, /** * The PDF rendering backend to use From 09cf3f37531af9ec01928c861ac1f07d5f8d762d Mon Sep 17 00:00:00 2001 From: joeyjanson Date: Mon, 19 Dec 2022 21:10:01 +0100 Subject: [PATCH 5/9] feat: credit functionality for the Invoice model --- app/Models/Invoice.php | 96 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 39cd31659..7137ed4fa 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -29,6 +29,7 @@ class Invoice extends Model implements HasMedia public const STATUS_SENT = 'SENT'; public const STATUS_VIEWED = 'VIEWED'; public const STATUS_COMPLETED = 'COMPLETED'; + public const STATUS_CREDIT = 'CREDIT'; public const STATUS_UNPAID = 'UNPAID'; public const STATUS_PARTIALLY_PAID = 'PARTIALLY_PAID'; @@ -187,6 +188,46 @@ public function getFormattedInvoiceDateAttribute($value) return Carbon::parse($this->invoice_date)->format($dateFormat); } + public function getTotalAttribute($value) + { + return $this->credit ? 0 - $value : $value; + } + + public function getSubTotalAttribute($value) + { + return $this->credit ? 0 - $value : $value; + } + + public function getTaxAttribute($value) + { + return $this->credit ? 0 - $value : $value; + } + + public function getBaseTotalAttribute($value) + { + return $this->credit ? 0 - $value : $value; + } + + public function getBaseSubTotalAttribute($value) + { + return $this->credit ? 0 - $value : $value; + } + + public function getBaseTaxAttribute($value) + { + return $this->credit ? 0 - $value : $value; + } + + public function getBaseDueAmountAttribute($value) + { + return $this->credit ? 0 - $value : $value; + } + + public function getDueAmountAttribute($value) + { + return $this->credit ? 0 - $value : $value; + } + public function scopeWhereStatus($query, $status) { return $query->where('invoices.status', $status); @@ -224,7 +265,8 @@ public function scopeWhereSearch($query, $search) $query->whereHas('customer', function ($query) use ($term) { $query->where('name', 'LIKE', '%'.$term.'%') ->orWhere('contact_name', 'LIKE', '%'.$term.'%') - ->orWhere('company_name', 'LIKE', '%'.$term.'%'); + ->orWhere('company_name', 'LIKE', '%'.$term.'%') + ->orWhere('invoice_number', 'LIKE', '%'.$term.'%'); }); } } @@ -249,6 +291,8 @@ public function scopeApplyFilters($query, array $filters) $filters->get('status') == self::STATUS_PAID ) { $query->wherePaidStatus($filters->get('status')); + } elseif ($filters->get('status') == self::STATUS_CREDIT) { + $query->whereCredit(true); } elseif ($filters->get('status') == 'DUE') { $query->whereDueStatus($filters->get('status')); } else { @@ -314,6 +358,11 @@ public function scopePaginateData($query, $limit) return $query->paginate($limit); } + public function scopeWhereCredit($query, bool $isCredit) + { + $query->where('invoices.credit', $isCredit); + } + public static function createInvoice($request) { $data = $request->getInvoicePayload(); @@ -322,8 +371,11 @@ public static function createInvoice($request) $data['status'] = Invoice::STATUS_SENT; } - $invoice = Invoice::create($data); + if ($data['credit']) { + $data = self::getAbsoluteValues($data); + } + $invoice = Invoice::create($data); $serial = (new SerialNumberFormatter()) ->setModel($invoice) ->setCompany($invoice->company_id) @@ -382,7 +434,9 @@ public function updateInvoice($request) } if ($request->total < $total_paid_amount) { - return 'total_invoice_amount_must_be_more_than_paid_amount'; + if (! $request->credit) { + return 'total_invoice_amount_must_be_more_than_paid_amount'; + } } if ($oldTotal != $request->total) { @@ -397,6 +451,10 @@ public function updateInvoice($request) $this->changeInvoiceStatus($data['due_amount']); + if ($request->credit) { + $data = self::getAbsoluteValues($data); + } + $this->update($data); $company_currency = CompanySetting::getSetting('currency', $request->header('company')); @@ -462,6 +520,12 @@ public function preview($data) public function send($data) { + if ($this->status == Invoice::STATUS_DRAFT) { + $this->invoice_date = Carbon::now(); + $this->due_date = Carbon::now()->addDays(14); + $this->save(); + } + $data = $this->sendInvoiceData($data); \Mail::to($data['to'])->send(new SendInvoiceMail($data)); @@ -722,4 +786,30 @@ public static function deleteInvoices($ids) return true; } + + private static function getAbsoluteValues($data) + { + foreach ($data as $field => &$value) { + if (in_array($field, [ + 'tax', + 'sub_total', + 'total', + 'due_amount', + 'base_total', + 'base_sub_total', + 'base_tax', + 'base_due_amount' + ])) { + $value = abs($value); + } + } + + $data['sent'] = 1; + $data['status'] = Invoice::STATUS_SENT; + $data['paid_status'] = Invoice::STATUS_PAID; + $data['due_amount'] = 0; + $data['base_due_amount'] = 0; + + return $data; + } } From 5802f94767f678542d7d52d528b060673fb02ae9 Mon Sep 17 00:00:00 2001 From: joeyjanson Date: Mon, 19 Dec 2022 21:10:11 +0100 Subject: [PATCH 6/9] feat: credit for invoice resource --- app/Http/Resources/InvoiceResource.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Resources/InvoiceResource.php b/app/Http/Resources/InvoiceResource.php index c70d2a8ca..17269953e 100644 --- a/app/Http/Resources/InvoiceResource.php +++ b/app/Http/Resources/InvoiceResource.php @@ -56,6 +56,7 @@ public function toArray($request) 'sales_tax_type' => $this->sales_tax_type, 'sales_tax_address_type' => $this->sales_tax_address_type, 'overdue' => $this->overdue, + 'credit' => $this->credit, 'items' => $this->when($this->items()->exists(), function () { return InvoiceItemResource::collection($this->items); }), From 26e929202a4f19c67ada6928134c3b89ff5dc2fd Mon Sep 17 00:00:00 2001 From: joeyjanson Date: Mon, 19 Dec 2022 21:10:15 +0100 Subject: [PATCH 7/9] feat: credit for invoice resource --- app/Http/Resources/Customer/InvoiceResource.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Resources/Customer/InvoiceResource.php b/app/Http/Resources/Customer/InvoiceResource.php index d190c0baa..643f66567 100644 --- a/app/Http/Resources/Customer/InvoiceResource.php +++ b/app/Http/Resources/Customer/InvoiceResource.php @@ -52,6 +52,7 @@ public function toArray($request) 'formatted_due_date' => $this->formattedDueDate, 'payment_module_enabled' => $this->payment_module_enabled, 'overdue' => $this->overdue, + 'credit' => $this->credit, 'items' => $this->when($this->items()->exists(), function () { return InvoiceItemResource::collection($this->items); }), From a60e1eb73001987e08786f6560ab62c47d564cb8 Mon Sep 17 00:00:00 2001 From: joeyjanson Date: Mon, 19 Dec 2022 21:10:38 +0100 Subject: [PATCH 8/9] feat: correct calculation queries for dashboard exclude credit for totals --- .../Controllers/V1/Admin/Dashboard/DashboardController.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Http/Controllers/V1/Admin/Dashboard/DashboardController.php b/app/Http/Controllers/V1/Admin/Dashboard/DashboardController.php index 4e1d96d22..9d69ea97d 100644 --- a/app/Http/Controllers/V1/Admin/Dashboard/DashboardController.php +++ b/app/Http/Controllers/V1/Admin/Dashboard/DashboardController.php @@ -66,6 +66,7 @@ public function __invoke(Request $request) [$start->format('Y-m-d'), $end->format('Y-m-d')] ) ->whereCompany() + ->whereCredit(false) ->sum('base_total') ); array_push( @@ -105,6 +106,7 @@ public function __invoke(Request $request) [$startDate->format('Y-m-d'), $start->format('Y-m-d')] ) ->whereCompany() + ->whereCredit(false) ->sum('base_total'); $total_receipts = Payment::whereBetween( @@ -136,6 +138,7 @@ public function __invoke(Request $request) ->count(); $total_estimate_count = Estimate::whereCompany()->count(); $total_amount_due = Invoice::whereCompany() + ->whereCredit(false) ->sum('base_due_amount'); $recent_due_invoices = Invoice::with('customer') From 7d997fa1e1262fa1de07305cda2e99fad90c1858 Mon Sep 17 00:00:00 2001 From: joeyjanson Date: Mon, 19 Dec 2022 21:10:44 +0100 Subject: [PATCH 9/9] feat: correct calculation queries for dashboard exclude credit for totals --- .../Controllers/V1/Admin/Customer/CustomerStatsController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Controllers/V1/Admin/Customer/CustomerStatsController.php b/app/Http/Controllers/V1/Admin/Customer/CustomerStatsController.php index 1d2c0415a..b75e45c5c 100644 --- a/app/Http/Controllers/V1/Admin/Customer/CustomerStatsController.php +++ b/app/Http/Controllers/V1/Admin/Customer/CustomerStatsController.php @@ -103,6 +103,7 @@ public function __invoke(Request $request, Customer $customer) ) ->whereCompany() ->whereCustomer($customer->id) + ->whereCredit(false) ->sum('total'); $totalReceipts = Payment::whereBetween( 'payment_date',