Merge pull request #3258 from CihanSenturk/add-document-item-quantity-mathematical-operations

Added document item quantity mathematical operations
This commit is contained in:
Cihan Şentürk 2025-01-23 11:28:44 +03:00 committed by GitHub
commit 608fe4fd92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 145 additions and 18 deletions

View File

@ -93,6 +93,8 @@ class Document extends FormRequest
foreach ($items as $key => $item) {
$size = 10;
$items[$key]['quantity'] = calculation_to_quantity($item['quantity']);
if (Str::contains($item['quantity'], ['.', ','])) {
$size = 12;
}
@ -101,6 +103,8 @@ class Document extends FormRequest
$this->items_quantity_size[$key] = $size;
}
$this->request->set('items', $items);
}
return $rules;

View File

@ -31,7 +31,7 @@ class CreateDocumentItem extends Job implements HasOwner, HasSource, ShouldCreat
$item_id = ! empty($this->request['item_id']) ? $this->request['item_id'] : 0;
$precision = currency($this->document->currency_code)->getPrecision();
$item_amount = (double) $this->request['price'] * (double) $this->request['quantity'];
$item_amount = (double) $this->request['price'] * (double) calculation_to_quantity($this->request['quantity']);
$item_discounted_amount = $item_amount;
@ -159,7 +159,7 @@ class CreateDocumentItem extends Job implements HasOwner, HasSource, ShouldCreat
$this->request['item_id'] = $item_id;
$this->request['name'] = Str::limit($this->request['name'], 180, '');
$this->request['description'] = ! empty($this->request['description']) ? $this->request['description'] : '';
$this->request['quantity'] = (double) $this->request['quantity'];
$this->request['quantity'] = (double) calculation_to_quantity($this->request['quantity']);
$this->request['price'] = round($this->request['price'], $precision);
$this->request['tax'] = round($item_tax_total, $precision);
$this->request['discount_type'] = ! empty($this->request['discount_type']) ? $this->request['discount_type'] : 'percent';

View File

@ -429,3 +429,22 @@ if (! function_exists('request_is_portal')) {
return $r->is($company_id . '/portal') || $r->is($company_id . '/portal/*');
}
}
if (! function_exists('calculation_to_quantity')) {
function calculation_to_quantity($quantity)
{
if (! preg_match('/^[0-9+\-x*\/().\s]+$/', $quantity)) {
throw new \InvalidArgumentException('Invalid mathematical expression.');
}
$quantity = Str::replace('x', '*', $quantity);
try {
$result = eval('return ' . $quantity . ';');
} catch (\Throwable $e) {
throw new \InvalidArgumentException('Error evaluating the expression: ' . $e->getMessage());
}
return $result;
}
}

89
package-lock.json generated
View File

@ -33,6 +33,7 @@
"jsonwebtoken": "^9.0.2",
"laravel-mix-tailwind": "^0.1.2",
"lodash": "^4.17.21",
"mathjs": "^14.0.1",
"moment": ">=2.29.4",
"nprogress": "^0.2.0",
"popper.js": "^1.16.1",
@ -2213,9 +2214,9 @@
"license": "MIT"
},
"node_modules/@babel/runtime": {
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
"integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
@ -7672,6 +7673,19 @@
"integrity": "sha512-Ue/Zd9aOucHzHXwaCe4yeHR7jypp7TKrIBZ5yls35nPNiVXlW14npmNVKM1ZaLlQTKZ6/4ewA//gYKHHIwCpOw==",
"license": "MIT"
},
"node_modules/complex.js": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.2.tgz",
"integrity": "sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==",
"license": "MIT",
"engines": {
"node": "*"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
@ -8628,6 +8642,12 @@
"node": ">=0.10.0"
}
},
"node_modules/decimal.js": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
"license": "MIT"
},
"node_modules/deep-equal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
@ -9510,6 +9530,12 @@
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/escape-latex": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
"integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==",
"license": "MIT"
},
"node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
@ -12416,6 +12442,12 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/javascript-natural-sort": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
"license": "MIT"
},
"node_modules/javascript-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz",
@ -13807,6 +13839,42 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mathjs": {
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.0.1.tgz",
"integrity": "sha512-yyJgLwC6UXuve724np8tHRMYaTtb5UqiOGQkjwbSXgH8y1C/LcJ0pvdNDZLI2LT7r+iExh2Y5HwfAY+oZFtGIQ==",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.25.7",
"complex.js": "^2.2.5",
"decimal.js": "^10.4.3",
"escape-latex": "^1.2.0",
"fraction.js": "^5.2.1",
"javascript-natural-sort": "^0.7.1",
"seedrandom": "^3.0.5",
"tiny-emitter": "^2.1.0",
"typed-function": "^4.2.1"
},
"bin": {
"mathjs": "bin/cli.js"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/mathjs/node_modules/fraction.js": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.2.1.tgz",
"integrity": "sha512-Ah6t/7YCYjrPUFUFsOsRLMXAdnYM+aQwmojD2Ayb/Ezr82SwES0vuyQ8qZ3QO8n9j7W14VJuVZZet8U3bhSdQQ==",
"license": "MIT",
"engines": {
"node": ">= 12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
@ -18385,6 +18453,12 @@
"license": "MIT",
"peer": true
},
"node_modules/seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
"license": "MIT"
},
"node_modules/select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
@ -20443,6 +20517,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/typed-function": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.1.tgz",
"integrity": "sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==",
"license": "MIT",
"engines": {
"node": ">= 18"
}
},
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",

View File

@ -23,19 +23,25 @@
"@sentry/vue": "^7.47.0",
"@tailwindcss/forms": "^0.5.3",
"@themesberg/flowbite": "^1.2.0",
"ajv": "^6.12.6",
"axios": "^1.3.5",
"date-fns": "^2.29.3",
"dropzone": "^5.9.3",
"element-ui": "^2.15.13",
"es6-promise": "^4.2.8",
"express": "^4.19.2",
"flatpickr": "^4.6.13",
"fuse.js": "^6.6.2",
"glightbox": "^3.2.0",
"json-schema": ">=0.4.0",
"jsonwebtoken": "^9.0.2",
"laravel-mix-tailwind": "^0.1.2",
"lodash": "^4.17.21",
"mathjs": "^14.0.1",
"moment": ">=2.29.4",
"nprogress": "^0.2.0",
"popper.js": "^1.16.1",
"qs": "^6.11.1",
"swiper": "^9.2.0",
"tailwind": "^4.0.0",
"tailwindcss": "^3.3.6",
@ -48,12 +54,7 @@
"vue2-editor": "^2.10.3",
"vue2-transitions": "^0.3.0",
"vuedraggable": "^2.24.3",
"moment": ">=2.29.4",
"qs": "^6.11.1",
"jsonwebtoken": "^9.0.2",
"ws": "^8.14.2",
"ajv": "^6.12.6",
"express": "^4.19.2"
"ws": "^8.14.2"
},
"devDependencies": {
"@babel/core": "^7.23.5",

View File

@ -15,6 +15,13 @@ function getQueryVariable(variable) {
return(false);
}
const { evaluate } = require('mathjs');
// use the evaluate function to evaluate the expression
function calculationToQuantity(quantity) {
return evaluate(quantity);
}
//This function wraps setTimeout function in a promise in order to display dom manipulations on root components asynchronously & fast
const setPromiseTimeout = time =>
new Promise(resolve =>
@ -23,4 +30,4 @@ const setPromiseTimeout = time =>
, time)
);
export {getQueryVariable, setPromiseTimeout}
export {getQueryVariable, calculationToQuantity, setPromiseTimeout}

View File

@ -8,7 +8,7 @@ import Vue from 'vue';
import DashboardPlugin from './../../plugins/dashboard-plugin';
import { addDays, format } from 'date-fns';
import { setPromiseTimeout, getQueryVariable } from './../../plugins/functions';
import { setPromiseTimeout, calculationToQuantity, getQueryVariable } from './../../plugins/functions';
import Global from './../../mixins/global';
@ -318,7 +318,7 @@ const app = new Vue({
// items calculate
this.items.forEach(function(item, index) {
item.total = item.grand_total = item.price * item.quantity;
item.total = item.grand_total = item.price * calculationToQuantity(item.quantity);
let item_discounted_total = items_amount[index];
@ -346,7 +346,7 @@ const app = new Vue({
this.calculateItemTax(item, totals_taxes, total_discount + line_discount_amount);
item.total = item.price * item.quantity;
item.total = item.price * calculationToQuantity(item.quantity);
// calculate sub, tax, discount all items.
line_item_discount_total += line_discount_amount;
@ -474,7 +474,7 @@ const app = new Vue({
if (fixed.length) {
fixed.forEach(function(fixed) {
item.tax_ids[fixed.tax_index].name = fixed.tax_name;
item.tax_ids[fixed.tax_index].price = fixed.tax_rate * item.quantity;
item.tax_ids[fixed.tax_index].price = fixed.tax_rate * calculationToQuantity(item.quantity);
total_tax_amount += item.tax_ids[fixed.tax_index].price;
@ -536,7 +536,7 @@ const app = new Vue({
this.items.forEach(function(item, index) {
let item_total = 0;
item_total = item.price * item.quantity;
item_total = item.price * calculationToQuantity(item.quantity);
// item discount calculate.
if (item.discount) {
@ -1055,6 +1055,19 @@ const app = new Vue({
this.form.discount.replace(',', '.');
},
'items': {
handler(newItems, oldItems) {
const regex = /^[0-9+\-x*\/().\s]+$/;
newItems.forEach((item, index) => {
if (item.quantity && ! regex.test(item.quantity)) {
item.quantity = item.quantity.replace(/[^0-9+\-x*\/().\s]/g, "");
}
});
},
deep: true,
},
'form.loading': function (newVal, oldVal) {
if (! newVal) {
this.send_to = false;

View File

@ -98,7 +98,7 @@
@stack('quantity_input_start')
<input
type="number"
type="text"
min="0"
:ref="'items-' + index + '-quantity'"
class="w-full text-sm px-3 py-2.5 mt-0 text-right rounded-lg border border-light-gray text-black placeholder-light-gray bg-white disabled:bg-gray-200 focus:outline-none focus:ring-transparent focus:border-purple input-number-disabled"