From 4dbb46fcebdf59fc12ffa27da6c983bb002e2db1 Mon Sep 17 00:00:00 2001 From: Yousef Restom Date: Fri, 7 Jul 2023 15:27:37 +0300 Subject: [PATCH 01/12] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 91cd0e95..021a3548 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -11,7 +11,7 @@ assignees: '' **IMPORTANT** Before reporting a bug: -A properly detailed bug report can save a LOT of time and help fixing issues as soon as possible. +A properly detailed bug report can save a LOT of time and help fix issues as soon as possible. - -> ### Versions @@ -31,7 +31,7 @@ A properly detailed bug report can save a LOT of time and help fixing issues as 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' -4. See error +4. See the error ### What is Expected? @@ -44,4 +44,4 @@ Add any other context about the problem here. ### Screenshots -If applicable, add screenshots to help explain your problem. +If applicable, could you add screenshots to help explain your problem? From c650f6f58453c0ac5f0e8b5150a778764d8d1813 Mon Sep 17 00:00:00 2001 From: Yousef Date: Sat, 8 Jul 2023 18:28:31 +0200 Subject: [PATCH 02/12] refactor: auto set batch --- posawesome/posawesome/api/posapp.py | 30 ++++++- .../js/posapp/components/pos/Invoice.vue | 89 ++++++++++++++++--- 2 files changed, 102 insertions(+), 17 deletions(-) diff --git a/posawesome/posawesome/api/posapp.py b/posawesome/posawesome/api/posapp.py index 4aca5faf..68306efd 100644 --- a/posawesome/posawesome/api/posapp.py +++ b/posawesome/posawesome/api/posapp.py @@ -266,6 +266,7 @@ def _get_items(pos_profile, price_list, item_group, search_value): "batch_qty": batch.qty, "expiry_date": batch_doc.expiry_date, "batch_price": batch_doc.posa_batch_price, + "manufacturing_date": batch_doc.manufacturing_date, } ) serial_no_data = [] @@ -884,6 +885,7 @@ def _get_items_details(pos_profile, items_data): "batch_qty": batch.qty, "expiry_date": batch_doc.expiry_date, "batch_price": batch_doc.posa_batch_price, + "manufacturing_date": batch_doc.manufacturing_date, } ) @@ -913,12 +915,31 @@ def _get_items_details(pos_profile, items_data): @frappe.whitelist() def get_item_detail(item, doc=None, warehouse=None, price_list=None): item = json.loads(item) + today = nowdate() item_code = item.get("item_code") - if warehouse and item.get("has_batch_no") and not item.get("batch_no"): - item["batch_no"] = get_batch_no( - item_code, warehouse, item.get("qty"), False, item.get("d") - ) + batch_no_data = [] + if warehouse and item.get("has_batch_no"): + batch_list = get_batch_qty(warehouse=warehouse, item_code=item_code) + if batch_list: + for batch in batch_list: + if batch.qty > 0 and batch.batch_no: + batch_doc = frappe.get_cached_doc("Batch", batch.batch_no) + if ( + str(batch_doc.expiry_date) > str(today) + or batch_doc.expiry_date in ["", None] + ) and batch_doc.disabled == 0: + batch_no_data.append( + { + "batch_no": batch.batch_no, + "batch_qty": batch.qty, + "expiry_date": batch_doc.expiry_date, + "batch_price": batch_doc.posa_batch_price, + "manufacturing_date": batch_doc.manufacturing_date, + } + ) + item["selling_price_list"] = price_list + max_discount = frappe.get_value("Item", item_code, "max_discount") res = get_item_details( item, @@ -928,6 +949,7 @@ def get_item_detail(item, doc=None, warehouse=None, price_list=None): if item.get("is_stock_item") and warehouse: res["actual_qty"] = get_stock_availability(item_code, warehouse) res["max_discount"] = max_discount + res["batch_no_data"] = batch_no_data return res diff --git a/posawesome/public/js/posapp/components/pos/Invoice.vue b/posawesome/public/js/posapp/components/pos/Invoice.vue index 0805b1bf..ab647841 100644 --- a/posawesome/public/js/posapp/components/pos/Invoice.vue +++ b/posawesome/public/js/posapp/components/pos/Invoice.vue @@ -947,7 +947,7 @@ export default { item.uom = item.stock_uom; } let index = -1; - if (!this.new_item) { + if (!this.new_line) { index = this.items.findIndex( (el) => el.item_code === item.item_code && @@ -1501,13 +1501,15 @@ export default { callback: function (r) { if (r.message) { const data = r.message; + if (data.batch_no_data) { + item.batch_no_data = data.batch_no_data; + } if ( item.has_batch_no && vm.pos_profile.posa_auto_set_batch && !item.batch_no && data.batch_no ) { - item.batch_no = data.batch_no; vm.set_batch_qty(item, item.batch_no, false); } if (data.has_pricing_rule) { @@ -1706,19 +1708,80 @@ export default { }, set_batch_qty(item, value, update = true) { - const batch_no = item.batch_no_data.find( - (element) => element.batch_no == value + console.info('set_batch_qty', item, value, update); + const existing_items = this.items.filter( + (element) => + element.item_code == item.item_code && + element.posa_row_id != item.posa_row_id ); - item.actual_batch_qty = batch_no.batch_qty; - item.batch_no_expiry_date = batch_no.expiry_date; - if (batch_no.batch_price) { - item.batch_price = batch_no.batch_price; - item.price_list_rate = batch_no.batch_price; - item.rate = batch_no.batch_price; - } else if (update) { + // console.info('existing_items', existing_items); + const used_batches = {}; + item.batch_no_data.forEach((batch) => { + used_batches[batch.batch_no] = { + ...batch, + used_qty: 0, + remaining_qty: batch.batch_qty, + }; + existing_items.forEach((element) => { + if (element.batch_no && element.batch_no == batch.batch_no) { + used_batches[batch.batch_no].used_qty += element.qty; + used_batches[batch.batch_no].remaining_qty -= element.qty; + used_batches[batch.batch_no].batch_qty -= element.qty; + } + }); + }); + + // set item batch_no based on: + // 1. if batch has expiry_date we should use the batch with the nearest expiry_date + // 2. if batch has no expiry_date we should use the batch with the earliest manufacturing_date + // 3. we should not use batch with remaining_qty = 0 + // 4. we should the highest remaining_qty + const batch_no_data = Object.values(used_batches) + .filter((batch) => batch.remaining_qty > 0) + .sort((a, b) => { + if (a.expiry_date && b.expiry_date) { + return a.expiry_date - b.expiry_date; + } else if (a.expiry_date) { + return -1; + } else if (b.expiry_date) { + return 1; + } else if (a.manufacturing_date && b.manufacturing_date) { + return a.manufacturing_date - b.manufacturing_date; + } else if (a.manufacturing_date) { + return -1; + } else if (b.manufacturing_date) { + return 1; + } else { + return b.remaining_qty - a.remaining_qty; + } + }); + if (batch_no_data.length > 0) { + let batch_to_use = null; + if (value) { + batch_to_use = batch_no_data.find((batch) => batch.batch_no == value); + } + if (!batch_to_use) { + batch_to_use = batch_no_data[0]; + } + item.batch_no = batch_to_use.batch_no; + item.actual_batch_qty = batch_to_use.batch_qty; + item.batch_no_expiry_date = batch_to_use.expiry_date; + if (batch_to_use.batch_price) { + item.batch_price = batch_to_use.batch_price; + item.price_list_rate = batch_to_use.batch_price; + item.rate = batch_to_use.batch_price; + } else if (update) { + item.batch_price = null; + this.update_item_detail(item); + } + } else { + item.batch_no = null; + item.actual_batch_qty = null; + item.batch_no_expiry_date = null; item.batch_price = null; - this.update_item_detail(item); } + // update item batch_no_data from batch_no_data + item.batch_no_data = batch_no_data; }, shortOpenPayment(e) { @@ -2642,7 +2705,7 @@ export default { evntBus.$emit('set_customer_info_to_edit', this.customer_info); }, expanded(data_value) { - this.update_items_details(data_value); + // this.update_items_details(data_value); if (data_value.length > 0) { this.update_item_detail(data_value[0]); } From 3ba78bfd4fedd3afa29dd89c8fb9247fcc2996d2 Mon Sep 17 00:00:00 2001 From: Yousef Date: Sat, 8 Jul 2023 18:29:14 +0200 Subject: [PATCH 03/12] chore: clean up the code --- posawesome/public/js/posapp/components/pos/Invoice.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/posawesome/public/js/posapp/components/pos/Invoice.vue b/posawesome/public/js/posapp/components/pos/Invoice.vue index ab647841..b2c2188c 100644 --- a/posawesome/public/js/posapp/components/pos/Invoice.vue +++ b/posawesome/public/js/posapp/components/pos/Invoice.vue @@ -1708,13 +1708,11 @@ export default { }, set_batch_qty(item, value, update = true) { - console.info('set_batch_qty', item, value, update); const existing_items = this.items.filter( (element) => element.item_code == item.item_code && element.posa_row_id != item.posa_row_id ); - // console.info('existing_items', existing_items); const used_batches = {}; item.batch_no_data.forEach((batch) => { used_batches[batch.batch_no] = { From 482bd873d6dea4b6b7f1b5eb0e7e6385c8c08c48 Mon Sep 17 00:00:00 2001 From: Yousef Date: Fri, 14 Jul 2023 01:37:23 +0200 Subject: [PATCH 04/12] feat: apply max discount allowed on item discount --- .../js/posapp/components/pos/Invoice.vue | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/posawesome/public/js/posapp/components/pos/Invoice.vue b/posawesome/public/js/posapp/components/pos/Invoice.vue index b2c2188c..3acc6883 100644 --- a/posawesome/public/js/posapp/components/pos/Invoice.vue +++ b/posawesome/public/js/posapp/components/pos/Invoice.vue @@ -1282,6 +1282,26 @@ export default { validate() { let value = true; this.items.forEach((item) => { + if (this.pos_profile.posa_max_discount_allowed) { + if (item.discount_amount && this.flt(item.discount_amount) > 0) { + // calc discount percentage + const discount_percentage = + (this.flt(item.discount_amount) * 100) / + this.flt(item.price_list_rate); + if ( + discount_percentage > this.pos_profile.posa_max_discount_allowed + ) { + evntBus.$emit('show_mesage', { + text: __( + `Discount percentage for item '{0}' cannot be greater than {1}%`, + [item.item_name, this.pos_profile.posa_max_discount_allowed] + ), + color: 'error', + }); + value = false; + } + } + } if (this.stock_settings.allow_negative_stock != 1) { if ( this.invoiceType == 'Invoice' && From 37e422a0fd6c59e44f0c6fa4faee12ca206c986e Mon Sep 17 00:00:00 2001 From: Yousef Date: Fri, 14 Jul 2023 02:15:35 +0200 Subject: [PATCH 05/12] fix: v-model cannot be used on a prop in OpeningDialog #349 --- posawesome/posawesome/api/posapp.py | 2 +- .../posapp/components/pos/OpeningDialog.vue | 76 ++++++++++--------- 2 files changed, 40 insertions(+), 38 deletions(-) diff --git a/posawesome/posawesome/api/posapp.py b/posawesome/posawesome/api/posapp.py index 68306efd..23fbb0f1 100644 --- a/posawesome/posawesome/api/posapp.py +++ b/posawesome/posawesome/api/posapp.py @@ -35,7 +35,7 @@ @frappe.whitelist() def get_opening_dialog_data(): data = {} - data["companys"] = frappe.get_list("Company", limit_page_length=0, order_by="name") + data["companies"] = frappe.get_list("Company", limit_page_length=0, order_by="name") data["pos_profiles_data"] = frappe.get_list( "POS Profile", filters={"disabled": 0}, diff --git a/posawesome/public/js/posapp/components/pos/OpeningDialog.vue b/posawesome/public/js/posapp/components/pos/OpeningDialog.vue index 00cd5aec..d075981f 100644 --- a/posawesome/public/js/posapp/components/pos/OpeningDialog.vue +++ b/posawesome/public/js/posapp/components/pos/OpeningDialog.vue @@ -1,6 +1,6 @@ From ebdca8280bab03fef0f41e0903867a29ec416a72 Mon Sep 17 00:00:00 2001 From: Yousef Date: Thu, 20 Jul 2023 20:28:06 +0200 Subject: [PATCH 11/12] feat: allow submit return invoice without payment --- .../js/posapp/components/pos/Payments.vue | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/posawesome/public/js/posapp/components/pos/Payments.vue b/posawesome/public/js/posapp/components/pos/Payments.vue index 248d1374..f3c2c399 100644 --- a/posawesome/public/js/posapp/components/pos/Payments.vue +++ b/posawesome/public/js/posapp/components/pos/Payments.vue @@ -856,17 +856,24 @@ export default { this.back_to_invoice(); }, submit_invoice(print) { + let totalPayedAmount = 0; this.invoice_doc.payments.forEach((payment) => { payment.amount = flt(payment.amount); + totalPayedAmount += payment.amount; }); + if (this.invoice_doc.is_return && totalPayedAmount == 0) { + this.invoice_doc.is_pos = 0; + } if (this.customer_credit_dict.length) { this.customer_credit_dict.forEach((row) => { row.credit_to_redeem = flt(row.credit_to_redeem); }); } let data = {}; - data["total_change"] = -this.diff_payment; - data["paid_change"] = this.paid_change; + data["total_change"] = !this.invoice_doc.is_return + ? -this.diff_payment + : 0; + data["paid_change"] = !this.invoice_doc.is_return ? this.paid_change : 0; data["credit_change"] = -this.credit_change; data["redeemed_customer_credit"] = this.redeemed_customer_credit; data["customer_credit_dict"] = this.customer_credit_dict; @@ -1333,12 +1340,19 @@ export default { ); this.is_credit_sale = 0; this.is_write_off_change = 0; - if (default_payment) { + if (default_payment && !invoice_doc.is_return) { default_payment.amount = this.flt( invoice_doc.rounded_total || invoice_doc.grand_total, this.currency_precision ); } + if (invoice_doc.is_return) { + this.is_return = true; + invoice_doc.payments.forEach((payment) => { + payment.amount = 0; + payment.base_amount = 0; + }); + } this.loyalty_amount = 0; this.get_addresses(); this.get_sales_person_names(); From 001bc4573826085b561ae4726ed1ec093e5b801b Mon Sep 17 00:00:00 2001 From: Yousef Date: Sat, 22 Jul 2023 15:55:44 +0200 Subject: [PATCH 12/12] Version: 6.1.0 --- posawesome/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posawesome/__init__.py b/posawesome/__init__.py index 7a5f57c6..21e6bdff 100644 --- a/posawesome/__init__.py +++ b/posawesome/__init__.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import frappe -__version__ = "6.0.4" +__version__ = "6.1.0" def console(*data):