diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 52e9ff8b764..ef0d3a3cb1f 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -165,9 +165,9 @@ def toggle_disabling(doc):
frappe.clear_cache(doctype=doctype)
def get_doctypes_with_dimensions():
- doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
+ doclist = ["GL Entry", "Sales Invoice", "POS Invoice", "Purchase Invoice", "Payment Entry", "Asset",
"Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
- "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
+ "Sales Invoice Item", "POS Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
"Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
index 2b91c74ce6d..c26e14ff6f8 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
@@ -57,7 +57,11 @@ class POSClosingEntry(Document):
if not invalid_rows:
return
- error_list = [_("Row #{}: {}").format(row.get('idx'), row.get('msg')) for row in invalid_rows]
+ error_list = []
+ for row in invalid_rows:
+ for msg in row.get('msg'):
+ error_list.append(_("Row #{}: {}").format(row.get('idx'), msg))
+
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
def on_submit(self):
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
index 86062d1e7cc..ae968d9ac42 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -2,6 +2,7 @@
// For license information, please see license.txt
{% include 'erpnext/selling/sales_common.js' %};
+frappe.provide("erpnext.accounts");
erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend({
setup(doc) {
@@ -9,12 +10,18 @@ erpnext.selling.POSInvoiceController = erpnext.selling.SellingController.extend(
this._super(doc);
},
+ company: function() {
+ erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
+ },
+
onload(doc) {
this._super();
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
this.frm.script_manager.trigger("is_pos");
this.frm.refresh_fields();
}
+
+ erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
},
refresh(doc) {
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index ac98dccdb5e..71ddcf58036 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -78,7 +78,7 @@ class POSInvoice(SalesInvoice):
mode_of_payment=pay.mode_of_payment, status="Paid"),
fieldname="grand_total")
- if pay.amount != paid_amt:
+ if paid_amt and pay.amount != paid_amt:
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
def validate_stock_availablility(self):
@@ -297,7 +297,9 @@ class POSInvoice(SalesInvoice):
self.set(fieldname, profile.get(fieldname))
if self.customer:
- customer_price_list, customer_group = frappe.db.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
+ customer_price_list, customer_group, customer_currency = frappe.db.get_value(
+ "Customer", self.customer, ['default_price_list', 'customer_group', 'default_currency']
+ )
customer_group_price_list = frappe.db.get_value("Customer Group", customer_group, 'default_price_list')
selling_price_list = customer_price_list or customer_group_price_list or profile.get('selling_price_list')
else:
@@ -305,6 +307,8 @@ class POSInvoice(SalesInvoice):
if selling_price_list:
self.set('selling_price_list', selling_price_list)
+ if customer_currency != profile.get('currency'):
+ self.set('currency', customer_currency)
# set pos values in items
for item in self.get("items"):
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index add27e9dffb..1539d5ff4a2 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -5,10 +5,10 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
-from frappe.model.document import Document
-from frappe.model.mapper import map_doc
from frappe.model import default_fields
+from frappe.model.document import Document
+from frappe.model.mapper import map_doc, map_child_doc
+from frappe.utils import flt, getdate, nowdate
from six import iteritems
@@ -83,7 +83,7 @@ class POSInvoiceMergeLog(Document):
credit_note.is_consolidated = 1
# TODO: return could be against multiple sales invoice which could also have been consolidated?
- credit_note.return_against = self.consolidated_invoice
+ # credit_note.return_against = self.consolidated_invoice
credit_note.save()
credit_note.submit()
self.consolidated_credit_note = credit_note.name
@@ -111,7 +111,9 @@ class POSInvoiceMergeLog(Document):
i.qty = i.qty + item.qty
if not found:
item.rate = item.net_rate
- items.append(item)
+ item.price_list_rate = 0
+ si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
+ items.append(si_item)
for tax in doc.get('taxes'):
found = False
@@ -147,6 +149,8 @@ class POSInvoiceMergeLog(Document):
invoice.set('taxes', taxes)
invoice.additional_discount_percentage = 0
invoice.discount_amount = 0.0
+ invoice.taxes_and_charges = None
+ invoice.ignore_pricing_rule = 1
return invoice
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json
index d856ae34762..4b69f6e2efe 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.json
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json
@@ -12,8 +12,6 @@
"company",
"country",
"column_break_9",
- "update_stock",
- "ignore_pricing_rule",
"warehouse",
"campaign",
"company_address",
@@ -25,8 +23,14 @@
"hide_images",
"hide_unavailable_items",
"auto_add_item_to_cart",
- "item_groups",
"column_break_16",
+ "update_stock",
+ "ignore_pricing_rule",
+ "allow_rate_change",
+ "allow_discount_change",
+ "section_break_23",
+ "item_groups",
+ "column_break_25",
"customer_groups",
"section_break_16",
"print_format",
@@ -309,6 +313,7 @@
"default": "1",
"fieldname": "update_stock",
"fieldtype": "Check",
+ "hidden": 1,
"label": "Update Stock",
"read_only": 1
},
@@ -329,13 +334,34 @@
"fieldname": "auto_add_item_to_cart",
"fieldtype": "Check",
"label": "Automatically Add Filtered Item To Cart"
+ },
+ {
+ "default": "0",
+ "fieldname": "allow_rate_change",
+ "fieldtype": "Check",
+ "label": "Allow User to Edit Rate"
+ },
+ {
+ "default": "0",
+ "fieldname": "allow_discount_change",
+ "fieldtype": "Check",
+ "label": "Allow User to Edit Discount"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "section_break_23",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_25",
+ "fieldtype": "Column Break"
}
],
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2020-12-10 13:59:28.877572",
+ "modified": "2021-01-06 14:42:41.713864",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 7116a6a62b0..630443808c3 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -435,7 +435,9 @@ class SalesInvoice(SellingController):
if not for_validate and not self.customer:
self.customer = pos.customer
- self.ignore_pricing_rule = pos.ignore_pricing_rule
+ if not for_validate:
+ self.ignore_pricing_rule = pos.ignore_pricing_rule
+
if pos.get('account_for_change_amount'):
self.account_for_change_amount = pos.get('account_for_change_amount')
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 9d9d1b363a9..2f4eb817014 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -302,6 +302,7 @@ class AccountsController(TransactionBase):
args["doctype"] = self.doctype
args["name"] = self.name
args["child_docname"] = item.name
+ args["ignore_pricing_rule"] = self.ignore_pricing_rule if hasattr(self, 'ignore_pricing_rule') else 0
if not args.get("transaction_date"):
args["transaction_date"] = args.get("posting_date")
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index a048d6e2dfb..0e1829a7676 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -262,6 +262,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
if doc.get("is_return"):
if doc.doctype == 'Sales Invoice' or doc.doctype == 'POS Invoice':
+ doc.consolidated_invoice = ""
doc.set('payments', [])
for data in source.payments:
paid_amount = 0.00
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index e085048f99b..a774a95382d 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -469,13 +469,19 @@ class SellingController(StockController):
non_stock_items = [d.item_code, d.description]
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
+ duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code))
+ duplicate_items_msg += "
"
+ duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
+ frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
+ get_link_to_form("Selling Settings", "Selling Settings")
+ )
if stock_items in check_list:
- frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
+ frappe.throw(duplicate_items_msg)
else:
check_list.append(stock_items)
else:
if non_stock_items in chk_dupl_itm:
- frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
+ frappe.throw(duplicate_items_msg)
else:
chk_dupl_itm.append(non_stock_items)
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 76309f8799a..fd744a779d0 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -107,7 +107,7 @@ class calculate_taxes_and_totals(object):
elif item.discount_amount and item.pricing_rules:
item.rate = item.price_list_rate - item.discount_amount
- if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item']:
+ if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item']:
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
if flt(item.rate_with_margin) > 0:
item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
index 4e86d365e36..49f6d95a6e5 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
@@ -44,6 +44,7 @@ class TestMpesaSettings(unittest.TestCase):
create_mpesa_settings(payment_gateway_name="Payment")
mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account")
frappe.db.set_value("Account", mpesa_account, "account_currency", "KES")
+ frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES")
pos_invoice = create_pos_invoice(do_not_submit=1)
pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 500})
@@ -69,6 +70,8 @@ class TestMpesaSettings(unittest.TestCase):
self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
self.assertEquals(integration_request.status, "Completed")
+ frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
+
def create_mpesa_settings(payment_gateway_name="Express"):
if frappe.db.exists("Mpesa Settings", payment_gateway_name):
return frappe.get_doc("Mpesa Settings", payment_gateway_name)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index f3660b3c51c..7e438a4aa18 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -677,7 +677,7 @@ erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
erpnext.patches.v12_0.fix_quotation_expired_status
erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
erpnext.patches.v12_0.rename_pos_closing_doctype
-erpnext.patches.v13_0.replace_pos_payment_mode_table
+erpnext.patches.v13_0.replace_pos_payment_mode_table #2020-12-29
erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22
erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
execute:frappe.reload_doc("HR", "doctype", "Employee Advance")
@@ -743,6 +743,7 @@ erpnext.patches.v13_0.updates_for_multi_currency_payroll
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
erpnext.patches.v13_0.add_po_to_global_search
erpnext.patches.v13_0.update_returned_qty_in_pr_dn
+erpnext.patches.v13_0.create_uae_pos_invoice_fields
erpnext.patches.v13_0.update_project_template_tasks
erpnext.patches.v13_0.set_company_in_leave_ledger_entry
erpnext.patches.v13_0.convert_qi_parameter_to_link_field
diff --git a/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py
new file mode 100644
index 00000000000..48d5cb4cc8f
--- /dev/null
+++ b/erpnext/patches/v13_0/create_uae_pos_invoice_fields.py
@@ -0,0 +1,14 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+from erpnext.regional.united_arab_emirates.setup import make_custom_fields
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': ['in', ['Saudi Arabia', 'United Arab Emirates']]})
+ if not company:
+ return
+
+ make_custom_fields()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py
index 1ca211bf1be..7cb264830ab 100644
--- a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py
+++ b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py
@@ -6,12 +6,10 @@ from __future__ import unicode_literals
import frappe
def execute():
- frappe.reload_doc("accounts", "doctype", "POS Payment Method")
+ frappe.reload_doc("accounts", "doctype", "pos_payment_method")
pos_profiles = frappe.get_all("POS Profile")
for pos_profile in pos_profiles:
- if not pos_profile.get("payments"): return
-
payments = frappe.db.sql("""
select idx, parentfield, parenttype, parent, mode_of_payment, `default` from `tabSales Invoice Payment` where parent=%s
""", pos_profile.name, as_dict=1)
diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py
index 013ae5cf73c..776a82c7306 100644
--- a/erpnext/regional/united_arab_emirates/setup.py
+++ b/erpnext/regional/united_arab_emirates/setup.py
@@ -110,9 +110,11 @@ def make_custom_fields():
'Purchase Order': purchase_invoice_fields + invoice_fields,
'Purchase Receipt': purchase_invoice_fields + invoice_fields,
'Sales Invoice': sales_invoice_fields + invoice_fields,
+ 'POS Invoice': sales_invoice_fields + invoice_fields,
'Sales Order': sales_invoice_fields + invoice_fields,
'Delivery Note': sales_invoice_fields + invoice_fields,
'Sales Invoice Item': invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt],
+ 'POS Invoice Item': invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt],
'Purchase Invoice Item': invoice_item_fields,
'Sales Order Item': invoice_item_fields,
'Delivery Note Item': invoice_item_fields,
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index d4cde433590..45b4e30bf01 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -69,6 +69,10 @@ erpnext.PointOfSale.Controller = class {
dialog.fields_dict.balance_details.grid.refresh();
});
}
+ const pos_profile_query = {
+ query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
+ filters: { company: frappe.defaults.get_default('company') }
+ }
const dialog = new frappe.ui.Dialog({
title: __('Create POS Opening Entry'),
static: true,
@@ -80,6 +84,7 @@ erpnext.PointOfSale.Controller = class {
{
fieldtype: 'Link', label: __('POS Profile'),
options: 'POS Profile', fieldname: 'pos_profile', reqd: 1,
+ get_query: () => pos_profile_query,
onchange: () => fetch_pos_payment_methods()
},
{
@@ -124,9 +129,8 @@ erpnext.PointOfSale.Controller = class {
});
frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => {
+ Object.assign(this.settings, profile);
this.settings.customer_groups = profile.customer_groups.map(group => group.customer_group);
- this.settings.hide_images = profile.hide_images;
- this.settings.auto_add_item_to_cart = profile.auto_add_item_to_cart;
this.make_app();
});
}
@@ -255,11 +259,9 @@ erpnext.PointOfSale.Controller = class {
get_frm: () => this.frm,
cart_item_clicked: (item_code, batch_no, uom) => {
- const item_row = this.frm.doc.items.find(
- i => i.item_code === item_code
- && i.uom === uom
- && (!batch_no || (batch_no && i.batch_no === batch_no))
- );
+ const search_field = batch_no ? 'batch_no' : 'item_code';
+ const search_value = batch_no || item_code;
+ const item_row = this.frm.doc.items.find(i => i[search_field] === search_value && i.uom === uom);
this.item_details.toggle_item_details_section(item_row);
},
@@ -281,6 +283,7 @@ erpnext.PointOfSale.Controller = class {
init_item_details() {
this.item_details = new erpnext.PointOfSale.ItemDetails({
wrapper: this.$components_wrapper,
+ settings: this.settings,
events: {
get_frm: () => this.frm,
@@ -415,6 +418,11 @@ erpnext.PointOfSale.Controller = class {
() => this.item_selector.toggle_component(true)
]);
},
+ delete_order: (name) => {
+ frappe.model.delete_doc(this.frm.doc.doctype, name, () => {
+ this.recent_order_list.refresh_list();
+ });
+ },
new_order: () => {
frappe.run_serially([
() => frappe.dom.freeze(),
@@ -696,14 +704,14 @@ erpnext.PointOfSale.Controller = class {
frappe.dom.freeze();
const { doctype, name, current_item } = this.item_details;
- frappe.model.set_value(doctype, name, 'qty', 0);
-
- this.frm.script_manager.trigger('qty', doctype, name).then(() => {
- frappe.model.clear_doc(doctype, name);
- this.update_cart_html(current_item, true);
- this.item_details.toggle_item_details_section(undefined);
- frappe.dom.unfreeze();
- })
+ frappe.model.set_value(doctype, name, 'qty', 0)
+ .then(() => {
+ frappe.model.clear_doc(doctype, name);
+ this.update_cart_html(current_item, true);
+ this.item_details.toggle_item_details_section(undefined);
+ frappe.dom.unfreeze();
+ })
+ .catch(e => console.log(e));
}
}
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index 3938300a2ad..cc47245aef9 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -5,6 +5,8 @@ erpnext.PointOfSale.ItemCart = class {
this.customer_info = undefined;
this.hide_images = settings.hide_images;
this.allowed_customer_groups = settings.customer_groups;
+ this.allow_rate_change = settings.allow_rate_change;
+ this.allow_discount_change = settings.allow_discount_change;
this.init_component();
}
@@ -201,7 +203,7 @@ erpnext.PointOfSale.ItemCart = class {
me.events.checkout();
me.toggle_checkout_btn(false);
- me.$add_discount_elem.removeClass("d-none");
+ me.allow_discount_change && me.$add_discount_elem.removeClass("d-none");
});
this.$totals_section.on('click', '.edit-cart-btn', () => {
@@ -479,11 +481,15 @@ erpnext.PointOfSale.ItemCart = class {
update_totals_section(frm) {
if (!frm) frm = this.events.get_frm();
- this.render_net_total(frm.doc.base_net_total);
- this.render_grand_total(frm.doc.base_grand_total);
+ this.render_net_total(frm.doc.net_total);
+ this.render_grand_total(frm.doc.grand_total);
- const taxes = frm.doc.taxes.map(t => { return { description: t.description, rate: t.rate }})
- this.render_taxes(frm.doc.base_total_taxes_and_charges, taxes);
+ const taxes = frm.doc.taxes.map(t => {
+ return {
+ description: t.description, rate: t.rate
+ }
+ });
+ this.render_taxes(frm.doc.total_taxes_and_charges, taxes);
}
render_net_total(value) {
@@ -545,7 +551,7 @@ erpnext.PointOfSale.ItemCart = class {
get_cart_item({ item_code, batch_no, uom }) {
const batch_attr = `[data-batch-no="${escape(batch_no)}"]`;
const item_code_attr = `[data-item-code="${escape(item_code)}"]`;
- const uom_attr = `[data-uom=${escape(uom)}]`;
+ const uom_attr = `[data-uom="${escape(uom)}"]`;
const item_selector = batch_no ?
`.cart-item-wrapper${batch_attr}${uom_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}`;
@@ -667,7 +673,7 @@ erpnext.PointOfSale.ItemCart = class {
update_selector_value_in_cart_item(selector, value, item) {
const $item_to_update = this.get_cart_item(item);
- $item_to_update.attr(`data-${selector}`, value);
+ $item_to_update.attr(`data-${selector}`, escape(value));
}
toggle_checkout_btn(show_checkout) {
@@ -702,14 +708,26 @@ erpnext.PointOfSale.ItemCart = class {
on_numpad_event($btn) {
const current_action = $btn.attr('data-button-value');
const action_is_field_edit = ['qty', 'discount_percentage', 'rate'].includes(current_action);
-
- this.highlight_numpad_btn($btn, current_action);
+ const action_is_allowed = action_is_field_edit ? (
+ (current_action == 'rate' && this.allow_rate_change) ||
+ (current_action == 'discount_percentage' && this.allow_discount_change) ||
+ (current_action == 'qty')) : true;
const action_is_pressed_twice = this.prev_action === current_action;
const first_click_event = !this.prev_action;
const field_to_edit_changed = this.prev_action && this.prev_action != current_action;
if (action_is_field_edit) {
+ if (!action_is_allowed) {
+ const label = current_action == 'rate' ? 'Rate'.bold() : 'Discount'.bold();
+ const message = __('Editing {0} is not allowed as per POS Profile settings', [label]);
+ frappe.show_alert({
+ indicator: 'red',
+ message: message
+ });
+ frappe.utils.play_sound("error");
+ return;
+ }
if (first_click_event || field_to_edit_changed) {
this.prev_action = current_action;
@@ -753,6 +771,7 @@ erpnext.PointOfSale.ItemCart = class {
this.numpad_value = current_action;
}
+ this.highlight_numpad_btn($btn, current_action);
this.events.numpad_event(this.numpad_value, this.prev_action);
}
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
index a4de9f165dc..259631d14d2 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -1,7 +1,9 @@
erpnext.PointOfSale.ItemDetails = class {
- constructor({ wrapper, events }) {
+ constructor({ wrapper, events, settings }) {
this.wrapper = wrapper;
this.events = events;
+ this.allow_rate_change = settings.allow_rate_change;
+ this.allow_discount_change = settings.allow_discount_change;
this.current_item = {};
this.init_component();
@@ -207,17 +209,27 @@ erpnext.PointOfSale.ItemDetails = class {
bind_custom_control_change_event() {
const me = this;
if (this.rate_control) {
- this.rate_control.df.onchange = function() {
- if (this.value || flt(this.value) === 0) {
- me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => {
- const item_row = frappe.get_doc(me.doctype, me.name);
- const doc = me.events.get_frm().doc;
-
- me.$item_price.html(format_currency(item_row.rate, doc.currency));
- me.render_discount_dom(item_row);
- });
- }
+ if (this.allow_rate_change) {
+ this.rate_control.df.onchange = function() {
+ if (this.value || flt(this.value) === 0) {
+ me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => {
+ const item_row = frappe.get_doc(me.doctype, me.name);
+ const doc = me.events.get_frm().doc;
+
+ me.$item_price.html(format_currency(item_row.rate, doc.currency));
+ me.render_discount_dom(item_row);
+ });
+ }
+ };
+ } else {
+ this.rate_control.df.read_only = 1;
}
+ this.rate_control.refresh();
+ }
+
+ if (this.discount_percentage_control && !this.allow_discount_change) {
+ this.discount_percentage_control.df.read_only = 1;
+ this.discount_percentage_control.refresh();
}
if (this.warehouse_control) {
@@ -294,8 +306,16 @@ erpnext.PointOfSale.ItemDetails = class {
}
frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => {
+ const { item_code, batch_no, uom } = this.current_item;
+ const item_code_is_same = item_code === item_row.item_code;
+ const batch_is_same = batch_no == item_row.batch_no;
+ const uom_is_same = uom === item_row.uom;
+ // check if current_item is same as item_row
+ const item_is_same = item_code_is_same && batch_is_same && uom_is_same ? true : false;
+
const field_control = me[`${fieldname}_control`];
- if (field_control) {
+
+ if (item_is_same && field_control && field_control.get_value() !== value) {
field_control.set_value(value);
cur_pos.update_cart_html(item_row);
}
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
index 6fd4c26bea1..598f50f1921 100644
--- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
@@ -265,6 +265,14 @@ erpnext.PointOfSale.PastOrderSummary = class {
this.$summary_wrapper.addClass('d-none');
});
+ this.$summary_container.on('click', '.delete-btn', () => {
+ this.events.delete_order(this.doc.name);
+ this.show_summary_placeholder();
+ // this.toggle_component(false);
+ // this.$component.find('.no-summary-placeholder').removeClass('d-none');
+ // this.$summary_wrapper.addClass('d-none');
+ });
+
this.$summary_container.on('click', '.new-btn', () => {
this.events.new_order();
this.toggle_component(false);
@@ -401,7 +409,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
return [{ condition: true, visible_btns: ['Print Receipt', 'Email Receipt', 'New Order'] }];
return [
- { condition: this.doc.docstatus === 0, visible_btns: ['Edit Order'] },
+ { condition: this.doc.docstatus === 0, visible_btns: ['Edit Order', 'Delete Order'] },
{ condition: !this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt', 'Return']},
{ condition: this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt']}
];
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index be845d9d9d5..cda10698919 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -672,13 +672,14 @@ class Item(WebsiteGenerator):
if not records: return
document = _("Stock Reconciliation") if len(records) == 1 else _("Stock Reconciliations")
- msg = _("The items {0} and {1} are present in the following {2} :
"
- .format(frappe.bold(old_name), frappe.bold(new_name), document))
+ msg = _("The items {0} and {1} are present in the following {2} : ").format(
+ frappe.bold(old_name), frappe.bold(new_name), document)
+ msg += '
'
msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "
"
- msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}"
- .format(frappe.bold(old_name)))
+ msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}").format(
+ frappe.bold(old_name))
frappe.throw(_(msg), title=_("Merge not allowed"))
@@ -971,7 +972,7 @@ class Item(WebsiteGenerator):
frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field))))
def check_if_linked_document_exists(self, field):
- linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "Purchase Receipt Item",
+ linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "POS Invoice Item", "Purchase Receipt Item",
"Purchase Invoice Item", "Stock Entry Detail", "Stock Reconciliation Item"]
# For "Is Stock Item", following doctypes is important
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index dfe8fea67b9..873cfec85ec 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -19,7 +19,7 @@ from erpnext.stock.doctype.item_manufacturer.item_manufacturer import get_item_m
from six import string_types, iteritems
-sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']
+sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice', 'POS Invoice']
purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
@frappe.whitelist()