diff --git a/app/Jobs/Document/CreateDocumentItem.php b/app/Jobs/Document/CreateDocumentItem.php index 529245aaa..abc57ca81 100644 --- a/app/Jobs/Document/CreateDocumentItem.php +++ b/app/Jobs/Document/CreateDocumentItem.php @@ -33,110 +33,49 @@ class CreateDocumentItem extends Job implements HasOwner, HasSource, ShouldCreat $item_amount = (double) $this->request['price'] * (double) $this->request['quantity']; - $discount = 0; $item_discounted_amount = $item_amount; // Apply line discount to amount if (!empty($this->request['discount'])) { - $discount = $this->request['discount']; - if ($this->request['discount_type'] === 'percentage') { - $item_discounted_amount = $item_amount -= ($item_amount * ($this->request['discount'] / 100)); + $item_discounted_amount -= ($item_amount * ($this->request['discount'] / 100)); } else { - $item_discounted_amount = $item_amount -= $this->request['discount']; + $item_discounted_amount -= $this->request['discount']; + } + } + + // Apply total discount to amount + if (!empty($this->request['global_discount'])) { + if ($this->request['global_discount_type'] === 'percentage') { + $item_discounted_amount -= $item_discounted_amount * ($this->request['global_discount'] / 100); + } else { + $item_discounted_amount -= $this->request['global_discount']; } } $tax_amount = 0; - $item_taxes = []; $item_tax_total = 0; + $actual_price_item = $item_amount = $item_discounted_amount; + + $item_taxes = []; + $doc_parms = [ + 'company_id' => $this->document->company_id, + 'type' => $this->document->type, + 'document_id' => $this->document->id, + ]; if (!empty($this->request['tax_ids'])) { - $inclusives = $compounds = []; - + // New variables by tax type & tax sorting foreach ((array) $this->request['tax_ids'] as $tax_id) { $tax = Tax::find($tax_id); - - switch ($tax->type) { - case 'inclusive': - $inclusives[] = $tax; - - break; - case 'compound': - $compounds[] = $tax; - - break; - case 'fixed': - $tax_amount = $tax->rate * (double) $this->request['quantity']; - - // @todo tax calculate check here - //$tax_amount = round(abs($tax_amount), $precision); - - $item_taxes[] = [ - 'company_id' => $this->document->company_id, - 'type' => $this->document->type, - 'document_id' => $this->document->id, - 'tax_id' => $tax_id, - 'name' => $tax->name, - 'amount' => $tax_amount, - ]; - - $item_tax_total += $tax_amount; - - break; - case 'withholding': - $tax_amount = 0 - $item_discounted_amount * ($tax->rate / 100); - - // @todo tax calculate check here - //$tax_amount = round(abs($tax_amount), $precision); - - $item_taxes[] = [ - 'company_id' => $this->document->company_id, - 'type' => $this->document->type, - 'document_id' => $this->document->id, - 'tax_id' => $tax_id, - 'name' => $tax->name, - 'amount' => $tax_amount, - ]; - - $item_tax_total += $tax_amount; - - break; - default: - $tax_amount = $item_discounted_amount * ($tax->rate / 100); - - // @todo tax calculate check here - //$tax_amount = round(abs($tax_amount), $precision); - - $item_taxes[] = [ - 'company_id' => $this->document->company_id, - 'type' => $this->document->type, - 'document_id' => $this->document->id, - 'tax_id' => $tax_id, - 'name' => $tax->name, - 'amount' => $tax_amount, - ]; - - $item_tax_total += $tax_amount; - - break; - } + ${$tax->type . 's'}[] = $tax; } - if ($inclusives) { - $item_amount = $item_discounted_amount + $item_tax_total; - - $item_base_rate = $item_amount / (1 + collect($inclusives)->sum('rate') / 100); - + if (isset($inclusives)) { foreach ($inclusives as $inclusive) { - $tax_amount = $item_base_rate * ($inclusive->rate / 100); + $tax_amount = $item_discounted_amount - ($item_discounted_amount / (1 + $inclusive->rate / 100)); - $tax_amount = round(abs($tax_amount), $precision); - - $item_taxes[] = [ - 'company_id' => $this->document->company_id, - 'type' => $this->document->type, - 'document_id' => $this->document->id, + $item_taxes[] = $doc_parms + [ 'tax_id' => $inclusive->id, 'name' => $inclusive->name, 'amount' => $tax_amount, @@ -145,23 +84,59 @@ class CreateDocumentItem extends Job implements HasOwner, HasSource, ShouldCreat $item_tax_total += $tax_amount; } - if (!empty($this->request['discount_type']) && $this->request['discount_type'] === 'fixed') { - $item_amount = ($item_amount - $item_tax_total) - $discount; - } else { - $item_amount = ($item_amount - $item_tax_total) / (1 - $discount / 100); + $actual_price_item = $item_discounted_amount - $item_tax_total; + } + + if (isset($fixeds)) { + foreach ($fixeds as $tax) { + $tax_amount = $tax->rate * (double) $this->request['quantity']; + + $item_taxes[] = $doc_parms + [ + 'tax_id' => $tax->id, + 'name' => $tax->name, + 'amount' => $tax_amount, + ]; + + $item_tax_total += $tax_amount; + $item_amount += $tax_amount; } } - if ($compounds) { + if (isset($normals)) { + foreach ($normals as $tax) { + $tax_amount = $actual_price_item * ($tax->rate / 100); + + $item_taxes[] = $doc_parms + [ + 'tax_id' => $tax->id, + 'name' => $tax->name, + 'amount' => $tax_amount, + ]; + + $item_tax_total += $tax_amount; + $item_amount += $tax_amount; + } + } + + if (isset($withholdings)) { + foreach ($withholdings as $tax) { + $tax_amount = -($actual_price_item * ($tax->rate / 100)); + + $item_taxes[] = $doc_parms + [ + 'tax_id' => $tax->id, + 'name' => $tax->name, + 'amount' => $tax_amount, + ]; + + $item_tax_total += $tax_amount; + $item_amount += $tax_amount; + } + } + + if (isset($compounds)) { foreach ($compounds as $compound) { - $tax_amount = (($item_discounted_amount + $item_tax_total) / 100) * $compound->rate; + $tax_amount = ($item_amount / 100) * $compound->rate; - $tax_amount = round(abs($tax_amount), $precision); - - $item_taxes[] = [ - 'company_id' => $this->document->company_id, - 'type' => $this->document->type, - 'document_id' => $this->document->id, + $item_taxes[] = $doc_parms + [ 'tax_id' => $compound->id, 'name' => $compound->name, 'amount' => $tax_amount, @@ -183,7 +158,7 @@ class CreateDocumentItem extends Job implements HasOwner, HasSource, ShouldCreat $this->request['tax'] = round($item_tax_total, $precision); $this->request['discount_type'] = !empty($this->request['discount_type']) ? $this->request['discount_type'] : 'percentage'; $this->request['discount_rate'] = !empty($this->request['discount']) ? $this->request['discount'] : 0; - $this->request['total'] = round($item_amount, $precision); + $this->request['total'] = round($actual_price_item, $precision); $this->request['created_from'] = $this->request['created_from']; $this->request['created_by'] = $this->request['created_by']; @@ -195,8 +170,8 @@ class CreateDocumentItem extends Job implements HasOwner, HasSource, ShouldCreat if (!empty($item_taxes)) { $document_item->item_taxes = $item_taxes; - $document_item->inclusives = $inclusives; - $document_item->compounds = $compounds; + $document_item->inclusives = $inclusives ?? null; + $document_item->compounds = $compounds ?? null; foreach ($item_taxes as $item_tax) { $item_tax['document_item_id'] = $document_item->id; diff --git a/app/Jobs/Document/CreateDocumentItemsAndTotals.php b/app/Jobs/Document/CreateDocumentItemsAndTotals.php index 01c355d93..5da28bbd7 100644 --- a/app/Jobs/Document/CreateDocumentItemsAndTotals.php +++ b/app/Jobs/Document/CreateDocumentItemsAndTotals.php @@ -30,7 +30,7 @@ class CreateDocumentItemsAndTotals extends Job implements HasOwner, HasSource, S { $precision = config('money.' . $this->document->currency_code . '.precision'); - list($sub_total, $discount_amount_total, $taxes) = $this->createItems(); + list($sub_total, $actual_total, $discount_amount_total, $taxes) = $this->createItems(); $sort_order = 1; @@ -47,7 +47,7 @@ class CreateDocumentItemsAndTotals extends Job implements HasOwner, HasSource, S 'created_by' => $this->request['created_by'], ]); - $this->request['amount'] += $sub_total; + $this->request['amount'] += $actual_total; $sort_order++; @@ -70,7 +70,7 @@ class CreateDocumentItemsAndTotals extends Job implements HasOwner, HasSource, S if (!empty($this->request['discount'])) { if ($this->request['discount_type'] === 'percentage') { - $discount_total = $sub_total * ($this->request['discount'] / 100); + $discount_total = ($sub_total - $discount_amount_total) * ($this->request['discount'] / 100); } else { $discount_total = $this->request['discount']; } @@ -87,8 +87,6 @@ class CreateDocumentItemsAndTotals extends Job implements HasOwner, HasSource, S 'created_by' => $this->request['created_by'], ]); - $this->request['amount'] -= $discount_total; - $sort_order++; } @@ -160,19 +158,29 @@ class CreateDocumentItemsAndTotals extends Job implements HasOwner, HasSource, S protected function createItems(): array { - $sub_total = $discount_amount = $discount_amount_total = 0; + $sub_total = $actual_total = $discount_amount = $discount_amount_total = 0; $taxes = []; if (empty($this->request['items'])) { - return [$sub_total, $discount_amount_total, $taxes]; + return [$sub_total, $actual_total, $discount_amount_total, $taxes]; } - foreach ((array) $this->request['items'] as $item) { + if (!empty($this->request['discount']) && $this->request['discount_type'] !== 'percentage') { + $for_fixed_discount = $this->fixedDiscountCalculate(); + } + + foreach ((array) $this->request['items'] as $key => $item) { $item['global_discount'] = 0; if (!empty($this->request['discount'])) { - $item['global_discount'] = $this->request['discount']; + if (isset($for_fixed_discount)) { + $item['global_discount'] = ($for_fixed_discount[$key] / ($for_fixed_discount['total'] / 100)) * ($this->request['discount'] / 100); + $item['global_discount_type'] = ''; + } else { + $item['global_discount'] = $this->request['discount']; + $item['global_discount_type'] = $this->request['discount_type']; + } } $item['created_from'] = $this->request['created_from']; @@ -214,7 +222,8 @@ class CreateDocumentItemsAndTotals extends Job implements HasOwner, HasSource, S } // Calculate totals - $sub_total += $document_item->total; + $sub_total += $item_amount; + $actual_total += $document_item->total; $discount_amount_total += $discount_amount; @@ -235,6 +244,30 @@ class CreateDocumentItemsAndTotals extends Job implements HasOwner, HasSource, S } } - return [$sub_total, $discount_amount_total, $taxes]; + return [$sub_total, $actual_total, $discount_amount_total, $taxes]; + } + + public function fixedDiscountCalculate() + { + $total = 0; + + foreach ((array) $this->request['items'] as $item) { + $sub = (double) $item['price'] * (double) $item['quantity']; + + if (! empty($this->request['discount'])) { + if ($item['discount_type'] === 'percentage') { + $sub -= ($sub * ($item['discount'] / 100)); + } else { + $sub -= $item['discount']; + } + } + + $total += $sub; + $item_total[] = $sub; + } + + $item_total['total'] = $total; + + return $item_total; } } diff --git a/resources/assets/js/views/common/documents.js b/resources/assets/js/views/common/documents.js index c3587307a..3ede25ce0 100644 --- a/resources/assets/js/views/common/documents.js +++ b/resources/assets/js/views/common/documents.js @@ -105,141 +105,45 @@ const app = new Vue({ onCalculateTotal() { let global_discount = parseFloat(this.form.discount); - let discount_total = 0; + let total_discount = 0; let line_item_discount_total = 0; - let taxes = this.dynamic_taxes; let sub_total = 0; let totals_taxes = []; let grand_total = 0; - let inclusive_tax_total = 0; + let items_amount = this.calculateTotalBeforeDiscountAndTax(); // items calculate this.items.forEach(function(item, index) { - let item_discount = 0; + item.total = item.grand_total = item.price * item.quantity; - item.total = item.price * item.quantity; - item.grand_total = item.price * item.quantity; + let item_discounted_total = items_amount[index]; - // item discount calculate. - let line_discount_amount = 0; + let line_discount_amount = item.total - item_discounted_total; - if (item.discount) { - if (item.discount_type === 'percentage') { - if (item.discount > 100) { - item.discount = 100; + // Apply line & total discount to item + if (global_discount) { + if (this.form.discount_type === 'percentage') { + if (global_discount > 100) { + global_discount = 100; } - line_discount_amount = item.total * (item.discount / 100); + total_discount += (item_discounted_total / 100) * global_discount; + item_discounted_total -= (item_discounted_total / 100) * global_discount; } else { - if (parseInt(item.discount) > item.price) { - item.discount = item.price; - } - line_discount_amount = parseFloat(item.discount); - } - - item.discount_amount = line_discount_amount - - item_discounted_total = item.total -= line_discount_amount; - item_discount = item.discount; - } - - let item_discounted_total = item.total; - - // item tax calculate. - if (item.tax_ids) { - let inclusives = []; - let compounds = []; - - item.tax_ids.forEach(function(item_tax, item_tax_index) { - for (var index_taxes = 0; index_taxes < taxes.length; index_taxes++) { - let tax = taxes[index_taxes]; - - if (item_tax.id != tax.id) { - continue; - } - - switch (tax.type) { - case 'inclusive': - inclusives.push({ - tax_index: item_tax_index, - tax_id: tax.id, - tax_name: tax.title, - tax_rate: tax.rate - }); - break; - case 'compound': - compounds.push({ - tax_index: item_tax_index, - tax_id: tax.id, - tax_name: tax.title, - tax_rate: tax.rate - }); - break; - case 'fixed': - item_tax.price = tax.rate * item.quantity; - - totals_taxes = this.calculateTotalsTax(totals_taxes, tax.id, tax.title, item_tax.price); - - item.grand_total += item_tax.price; - break; - case 'withholding': - item_tax.price = 0 - item.total * (tax.rate / 100); - - totals_taxes = this.calculateTotalsTax(totals_taxes, tax.id, tax.title, item_tax.price); - - item.grand_total += item_tax.price; - break; - default: - item_tax.price = item.total * (tax.rate / 100); - - totals_taxes = this.calculateTotalsTax(totals_taxes, tax.id, tax.title, item_tax.price); - - item.grand_total += item_tax.price; - break; - } - } - }, this); - - if (inclusives.length) { - let inclusive_total = 0; - - inclusives.forEach(function(inclusive) { - inclusive_total += inclusive.tax_rate; - - // tax price - item.tax_ids[inclusive.tax_index].price = item.grand_total - (item.grand_total / (1 + inclusive.tax_rate / 100)); - - totals_taxes = this.calculateTotalsTax(totals_taxes, inclusive.tax_id, inclusive.tax_name, item.tax_ids[inclusive.tax_index].price); - }, this); - - let item_base_rate = parseFloat(item.grand_total / (1 + inclusive_total / 100)); - //item.grand_total = item.grand_total + item_base_rate; - - item.total = item_base_rate; - - inclusive_tax_total += parseFloat(item.grand_total - item.total); - } - - if (compounds.length) { - let price = 0; - - compounds.forEach(function(compound) { - price = (item.grand_total / 100) * compound.tax_rate; - - item.tax_ids[compound.tax_index].price = price; - - totals_taxes = this.calculateTotalsTax(totals_taxes, compound.tax_id, compound.tax_name, price); - }, this); - - item.grand_total += price; + total_discount += (items_amount[index] / (items_amount['total'] / 100)) * (global_discount / 100); + item_discounted_total -= (items_amount[index] / (items_amount['total'] / 100)) * (global_discount / 100); } } // set item total - if (item.discount) { + if (item.discount || global_discount) { item.grand_total = item_discounted_total; } + this.calculateItemTax(item, totals_taxes, total_discount + line_discount_amount); + + item.total = item.price * item.quantity; + // calculate sub, tax, discount all items. line_item_discount_total += line_discount_amount; sub_total += item.total; @@ -250,23 +154,12 @@ const app = new Vue({ this.form.items[index].quantity = item.quantity; this.form.items[index].price = item.price; this.form.items[index].discount = item.discount; + this.form.items[index].discount_type = item.discount_type; this.form.items[index].total = item.total; }, this); - // Apply discount to total - if (global_discount) { - if (this.form.discount_type === 'percentage') { - discount_total = parseFloat(sub_total + inclusive_tax_total) * (global_discount / 100); - } else { - discount_total = global_discount; - } - - this.totals.discount = discount_total; - - grand_total -= discount_total; - } - this.totals.item_discount = line_item_discount_total; + this.totals.discount = total_discount; this.totals.sub = sub_total; this.totals.taxes = totals_taxes; this.totals.total = grand_total; @@ -288,7 +181,176 @@ const app = new Vue({ this.currencyConversion(); }, - calculateTotalsTax(totals_taxes, id, name, price) { + calculateItemTax(item, totals_taxes, total_discount_amount) { + let taxes = this.dynamic_taxes; + + if (item.tax_ids) { + let inclusive_tax_total = 0; + let price_for_tax = 0; + let total_tax_amount = 0; + let inclusives = []; + let compounds = []; + let fixed = []; + let withholding = []; + let normal = []; + + item.tax_ids.forEach(function(item_tax, item_tax_index) { + for (var index_taxes = 0; index_taxes < taxes.length; index_taxes++) { + let tax = taxes[index_taxes]; + + if (item_tax.id != tax.id) { + continue; + } + + switch (tax.type) { + case 'inclusive': + inclusives.push({ + tax_index: item_tax_index, + tax_id: tax.id, + tax_name: tax.title, + tax_rate: tax.rate + }); + break; + case 'compound': + compounds.push({ + tax_index: item_tax_index, + tax_id: tax.id, + tax_name: tax.title, + tax_rate: tax.rate + }); + break; + case 'fixed': + fixed.push({ + tax_index: item_tax_index, + tax_id: tax.id, + tax_name: tax.title, + tax_rate: tax.rate + }); + break; + case 'withholding': + withholding.push({ + tax_index: item_tax_index, + tax_id: tax.id, + tax_name: tax.title, + tax_rate: tax.rate + }); + break; + default: + normal.push({ + tax_index: item_tax_index, + tax_id: tax.id, + tax_name: tax.title, + tax_rate: tax.rate + }); + break; + } + } + }, this); + + if (inclusives.length) { + inclusives.forEach(function(inclusive) { + item.tax_ids[inclusive.tax_index].price = item.grand_total - (item.grand_total / (1 + inclusive.tax_rate / 100)); + + inclusive_tax_total += item.tax_ids[inclusive.tax_index].price; + + totals_taxes = this.calculateTotalsTax(totals_taxes, inclusive.tax_id, inclusive.tax_name, inclusive.tax_type, item.tax_ids[inclusive.tax_index].price); + }, this); + + item.total = parseFloat(item.grand_total - inclusive_tax_total); + } + + if (fixed.length) { + fixed.forEach(function(fixed) { + item.tax_ids[fixed.tax_index].price = fixed.tax_rate * item.quantity; + + total_tax_amount += item.tax_ids[fixed.tax_index].price; + + totals_taxes = this.calculateTotalsTax(totals_taxes, fixed.tax_id, fixed.tax_name, fixed.tax_type, item.tax_ids[fixed.tax_index].price); + }, this); + } + + if (inclusives.length) { + price_for_tax = item.total; + } else { + price_for_tax = item.grand_total; + } + + if (normal.length) { + normal.forEach(function(normal) { + item.tax_ids[normal.tax_index].price = price_for_tax * (normal.tax_rate / 100); + + total_tax_amount += item.tax_ids[normal.tax_index].price; + + totals_taxes = this.calculateTotalsTax(totals_taxes, normal.tax_id, normal.tax_name, normal.tax_type, item.tax_ids[normal.tax_index].price); + }, this); + } + + if (withholding.length) { + withholding.forEach(function(withholding) { + item.tax_ids[withholding.tax_index].price = -(price_for_tax * (withholding.tax_rate / 100)); + + total_tax_amount += item.tax_ids[withholding.tax_index].price; + + totals_taxes = this.calculateTotalsTax(totals_taxes, withholding.tax_id, withholding.tax_name, withholding.tax_type, item.tax_ids[withholding.tax_index].price); + }, this); + } + + item.grand_total += total_tax_amount; + + if (compounds.length) { + compounds.forEach(function(compound) { + item.tax_ids[compound.tax_index].price = (item.grand_total / 100) * compound.tax_rate; + + totals_taxes = this.calculateTotalsTax(totals_taxes, compound.tax_id, compound.tax_name, compound.tax_type, item.tax_ids[compound.tax_index].price); + + item.grand_total += item.tax_ids[compound.tax_index].price; + }, this); + } + + if (inclusives.length) { + item.total += total_discount_amount; + } + } + }, + + calculateTotalBeforeDiscountAndTax() { + let amount_before_discount_and_tax = []; + let total = 0; + + this.items.forEach(function(item, index) { + let item_total = 0; + + item_total = item.price * item.quantity; + + // item discount calculate. + if (item.discount) { + if (item.discount_type === 'percentage') { + if (item.discount > 100) { + item.discount = 100; + } + + item.discount_amount = item_total * (item.discount / 100); + } else { + if (parseInt(item.discount) > item_total) { + item.discount_amount = item_total; + } else { + item.discount_amount = parseFloat(item.discount); + } + } + } else { + item.discount_amount = 0; + } + + total += item_total - item.discount_amount; + amount_before_discount_and_tax[index] = item_total - item.discount_amount; + }); + + amount_before_discount_and_tax['total'] = total; + + return amount_before_discount_and_tax; + }, + + calculateTotalsTax(totals_taxes, id, name, type, price) { let total_tax_index = totals_taxes.findIndex(total_tax => { if (total_tax.id === id) { return true; @@ -455,6 +517,8 @@ const app = new Vue({ onDeleteDiscount(item_index) { this.items[item_index].add_discount = false; + this.items[item_index].discount = 0; + this.onCalculateTotal(); }, onAddTax(item_index) {