Merge pull request #24365 from rohitwaghchaure/fixed-extra-trasnferred_qty_issue-develop
fix: extra transferred qty has not consumed against work order
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 52e9ff8..ef0d3a3 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -165,9 +165,9 @@
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.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
index 57baac7..73367fd 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
@@ -3,6 +3,7 @@
frappe.ui.form.on('POS Closing Entry', {
onload: function(frm) {
+ frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
frm.set_query("pos_profile", function(doc) {
return {
filters: { 'user': doc.user }
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
index 32bca3b..18d430f 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
@@ -11,6 +11,7 @@
"column_break_3",
"posting_date",
"pos_opening_entry",
+ "status",
"section_break_5",
"company",
"column_break_7",
@@ -184,11 +185,27 @@
"label": "POS Opening Entry",
"options": "POS Opening Entry",
"reqd": 1
+ },
+ {
+ "allow_on_submit": 1,
+ "default": "Draft",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "label": "Status",
+ "options": "Draft\nSubmitted\nQueued\nCancelled",
+ "print_hide": 1,
+ "read_only": 1
}
],
"is_submittable": 1,
- "links": [],
- "modified": "2020-05-29 15:03:22.226113",
+ "links": [
+ {
+ "link_doctype": "POS Invoice Merge Log",
+ "link_fieldname": "pos_closing_entry"
+ }
+ ],
+ "modified": "2021-01-12 12:21:05.388650",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Closing Entry",
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 2b91c74..edf3d5a 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
@@ -6,13 +6,12 @@
import frappe
import json
from frappe import _
-from frappe.model.document import Document
-from frappe.utils import getdate, get_datetime, flt
-from collections import defaultdict
+from frappe.utils import get_datetime, flt
+from erpnext.controllers.status_updater import StatusUpdater
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
-from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
+from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices, unconsolidate_pos_invoices
-class POSClosingEntry(Document):
+class POSClosingEntry(StatusUpdater):
def validate(self):
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
@@ -57,20 +56,29 @@
if not invalid_rows:
return
- error_list = [_("Row #{}: {}").format(row.get('idx'), row.get('msg')) for row in invalid_rows]
- frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
+ error_list = []
+ for row in invalid_rows:
+ for msg in row.get('msg'):
+ error_list.append(_("Row #{}: {}").format(row.get('idx'), msg))
- def on_submit(self):
- merge_pos_invoices(self.pos_transactions)
- opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry)
- opening_entry.pos_closing_entry = self.name
- opening_entry.set_status()
- opening_entry.save()
+ frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
def get_payment_reconciliation_details(self):
currency = frappe.get_cached_value('Company', self.company, "default_currency")
return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
{"data": self, "currency": currency})
+
+ def on_submit(self):
+ consolidate_pos_invoices(closing_entry=self)
+
+ def on_cancel(self):
+ unconsolidate_pos_invoices(closing_entry=self)
+
+ def update_opening_entry(self, for_cancel=False):
+ opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry)
+ opening_entry.pos_closing_entry = self.name if not for_cancel else None
+ opening_entry.set_status()
+ opening_entry.save()
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js
new file mode 100644
index 0000000..20fd610
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry_list.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+// License: GNU General Public License v3. See license.txt
+
+// render
+frappe.listview_settings['POS Closing Entry'] = {
+ get_indicator: function(doc) {
+ var status_color = {
+ "Draft": "red",
+ "Submitted": "blue",
+ "Queued": "orange",
+ "Cancelled": "red"
+
+ };
+ return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
+ }
+};
diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
index 8de54d5..40db09e 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
+++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
@@ -13,7 +13,6 @@
class TestPOSClosingEntry(unittest.TestCase):
def test_pos_closing_entry(self):
test_user, pos_profile = init_user_and_profile()
-
opening_entry = create_opening_entry(pos_profile, test_user.name)
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
@@ -45,6 +44,49 @@
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
+ def test_cancelling_of_pos_closing_entry(self):
+ test_user, pos_profile = init_user_and_profile()
+ opening_entry = create_opening_entry(pos_profile, test_user.name)
+
+ pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
+ pos_inv1.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500
+ })
+ pos_inv1.submit()
+
+ pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
+ pos_inv2.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
+ })
+ pos_inv2.submit()
+
+ pcv_doc = make_closing_entry_from_opening(opening_entry)
+ payment = pcv_doc.payment_reconciliation[0]
+
+ self.assertEqual(payment.mode_of_payment, 'Cash')
+
+ for d in pcv_doc.payment_reconciliation:
+ if d.mode_of_payment == 'Cash':
+ d.closing_amount = 6700
+
+ pcv_doc.submit()
+
+ pos_inv1.load_from_db()
+ self.assertRaises(frappe.ValidationError, pos_inv1.cancel)
+
+ si_doc = frappe.get_doc("Sales Invoice", pos_inv1.consolidated_invoice)
+ self.assertRaises(frappe.ValidationError, si_doc.cancel)
+
+ pcv_doc.load_from_db()
+ pcv_doc.cancel()
+ si_doc.load_from_db()
+ pos_inv1.load_from_db()
+ self.assertEqual(si_doc.docstatus, 2)
+ self.assertEqual(pos_inv1.status, 'Paid')
+
+ frappe.set_user("Administrator")
+ frappe.db.sql("delete from `tabPOS Profile`")
+
def init_user_and_profile(**args):
user = 'test@example.com'
test_user = frappe.get_doc('User', user)
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
index 86062d1..07c8e44 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,19 @@
this._super(doc);
},
+ company: function() {
+ erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
+ },
+
onload(doc) {
this._super();
+ this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log'];
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 ac98dcc..8d8babb 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -6,10 +6,9 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from erpnext.controllers.selling_controller import SellingController
-from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
from erpnext.accounts.utils import get_account_currency
from erpnext.accounts.party import get_party_account, get_due_date
+from frappe.utils import cint, flt, getdate, nowdate, get_link_to_form
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos
@@ -58,6 +57,22 @@
self.apply_loyalty_points()
self.check_phone_payments()
self.set_status(update=True)
+
+ def before_cancel(self):
+ if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1:
+ pos_closing_entry = frappe.get_all(
+ "POS Invoice Reference",
+ ignore_permissions=True,
+ filters={ 'pos_invoice': self.name },
+ pluck="parent",
+ limit=1
+ )
+ frappe.throw(
+ _('You need to cancel POS Closing Entry {} to be able to cancel this document.').format(
+ get_link_to_form("POS Closing Entry", pos_closing_entry[0])
+ ),
+ title=_('Not Allowed')
+ )
def on_cancel(self):
# run on cancel method of selling controller
@@ -78,7 +93,7 @@
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 +312,9 @@
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 +322,8 @@
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/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index c179360..57a23af 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -290,7 +290,7 @@
def test_merging_into_sales_invoice_with_discount(self):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
- from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
+ from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
@@ -306,7 +306,7 @@
})
pos_inv2.submit()
- merge_pos_invoices()
+ consolidate_pos_invoices()
pos_inv.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
@@ -315,7 +315,7 @@
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
- from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
+ from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
frappe.db.sql("delete from `tabPOS Invoice`")
test_user, pos_profile = init_user_and_profile()
@@ -348,7 +348,7 @@
})
pos_inv2.submit()
- merge_pos_invoices()
+ consolidate_pos_invoices()
pos_inv.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
@@ -357,7 +357,7 @@
def test_merging_with_validate_selling_price(self):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
- from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
+ from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
@@ -393,7 +393,7 @@
})
pos_inv2.submit()
- merge_pos_invoices()
+ consolidate_pos_invoices()
pos_inv2.load_from_db()
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json
index 8f97639..da2984f 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json
@@ -7,6 +7,8 @@
"field_order": [
"posting_date",
"customer",
+ "column_break_3",
+ "pos_closing_entry",
"section_break_3",
"pos_invoices",
"references_section",
@@ -76,11 +78,22 @@
"label": "Consolidated Credit Note",
"options": "Sales Invoice",
"read_only": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "pos_closing_entry",
+ "fieldtype": "Link",
+ "label": "POS Closing Entry",
+ "options": "POS Closing Entry"
}
],
+ "index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2020-05-29 15:08:41.317100",
+ "modified": "2020-12-01 11:53:57.267579",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Merge Log",
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 add27e9..58409cd 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,13 @@
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.utils import flt, getdate, nowdate
+from frappe.utils.background_jobs import enqueue
+from frappe.model.mapper import map_doc, map_child_doc
+from frappe.utils.scheduler import is_scheduler_inactive
+from frappe.core.page.background_jobs.background_jobs import get_info
from six import iteritems
@@ -61,7 +64,13 @@
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
- self.update_pos_invoices(sales_invoice, credit_note)
+ self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
+
+ def on_cancel(self):
+ pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
+
+ self.update_pos_invoices(pos_invoice_docs)
+ self.cancel_linked_invoices()
def process_merging_into_sales_invoice(self, data):
sales_invoice = self.get_new_sales_invoice()
@@ -83,7 +92,7 @@
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 +120,9 @@
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 +158,8 @@
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
@@ -159,17 +172,21 @@
return sales_invoice
- def update_pos_invoices(self, sales_invoice, credit_note):
- for d in self.pos_invoices:
- doc = frappe.get_doc('POS Invoice', d.pos_invoice)
- if not doc.is_return:
- doc.update({'consolidated_invoice': sales_invoice})
- else:
- doc.update({'consolidated_invoice': credit_note})
+ def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''):
+ for doc in invoice_docs:
+ doc.load_from_db()
+ doc.update({ 'consolidated_invoice': None if self.docstatus==2 else (credit_note if doc.is_return else sales_invoice) })
doc.set_status(update=True)
doc.save()
-def get_all_invoices():
+ def cancel_linked_invoices(self):
+ for si_name in [self.consolidated_invoice, self.consolidated_credit_note]:
+ if not si_name: continue
+ si = frappe.get_doc('Sales Invoice', si_name)
+ si.flags.ignore_validate = True
+ si.cancel()
+
+def get_all_unconsolidated_invoices():
filters = {
'consolidated_invoice': [ 'in', [ '', None ]],
'status': ['not in', ['Consolidated']],
@@ -180,7 +197,7 @@
return pos_invoices
-def get_invoices_customer_map(pos_invoices):
+def get_invoice_customer_map(pos_invoices):
# pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] }
pos_invoice_customer_map = {}
for invoice in pos_invoices:
@@ -190,20 +207,82 @@
return pos_invoice_customer_map
-def merge_pos_invoices(pos_invoices=[]):
- if not pos_invoices:
- pos_invoices = get_all_invoices()
-
- pos_invoice_map = get_invoices_customer_map(pos_invoices)
- create_merge_logs(pos_invoice_map)
+def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):
+ invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices()
+ invoice_by_customer = get_invoice_customer_map(invoices)
-def create_merge_logs(pos_invoice_customer_map):
- for customer, invoices in iteritems(pos_invoice_customer_map):
+ if len(invoices) >= 5 and closing_entry:
+ enqueue_job(create_merge_logs, invoice_by_customer, closing_entry)
+ closing_entry.set_status(update=True, status='Queued')
+ else:
+ create_merge_logs(invoice_by_customer, closing_entry)
+
+def unconsolidate_pos_invoices(closing_entry):
+ merge_logs = frappe.get_all(
+ 'POS Invoice Merge Log',
+ filters={ 'pos_closing_entry': closing_entry.name },
+ pluck='name'
+ )
+
+ if len(merge_logs) >= 5:
+ enqueue_job(cancel_merge_logs, merge_logs, closing_entry)
+ closing_entry.set_status(update=True, status='Queued')
+ else:
+ cancel_merge_logs(merge_logs, closing_entry)
+
+def create_merge_logs(invoice_by_customer, closing_entry={}):
+ for customer, invoices in iteritems(invoice_by_customer):
merge_log = frappe.new_doc('POS Invoice Merge Log')
merge_log.posting_date = getdate(nowdate())
merge_log.customer = customer
+ merge_log.pos_closing_entry = closing_entry.get('name', None)
merge_log.set('pos_invoices', invoices)
merge_log.save(ignore_permissions=True)
merge_log.submit()
+
+ if closing_entry:
+ closing_entry.set_status(update=True, status='Submitted')
+ closing_entry.update_opening_entry()
+def cancel_merge_logs(merge_logs, closing_entry={}):
+ for log in merge_logs:
+ merge_log = frappe.get_doc('POS Invoice Merge Log', log)
+ merge_log.flags.ignore_permissions = True
+ merge_log.cancel()
+
+ if closing_entry:
+ closing_entry.set_status(update=True, status='Cancelled')
+ closing_entry.update_opening_entry(for_cancel=True)
+
+def enqueue_job(job, invoice_by_customer, closing_entry):
+ check_scheduler_status()
+
+ job_name = closing_entry.get("name")
+ if not job_already_enqueued(job_name):
+ enqueue(
+ job,
+ queue="long",
+ timeout=10000,
+ event="processing_merge_logs",
+ job_name=job_name,
+ closing_entry=closing_entry,
+ invoice_by_customer=invoice_by_customer,
+ now=frappe.conf.developer_mode or frappe.flags.in_test
+ )
+
+ if job == create_merge_logs:
+ msg = _('POS Invoices will be consolidated in a background process')
+ else:
+ msg = _('POS Invoices will be unconsolidated in a background process')
+
+ frappe.msgprint(msg, alert=1)
+
+def check_scheduler_status():
+ if is_scheduler_inactive() and not frappe.flags.in_test:
+ frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
+
+def job_already_enqueued(job_name):
+ enqueued_jobs = [d.get("job_name") for d in get_info()]
+ if job_name in enqueued_jobs:
+ return True
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
index 0f34272..db046c9 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
@@ -7,7 +7,7 @@
import unittest
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
-from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
+from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
class TestPOSInvoiceMergeLog(unittest.TestCase):
@@ -34,7 +34,7 @@
})
pos_inv3.submit()
- merge_pos_invoices()
+ consolidate_pos_invoices()
pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
@@ -79,7 +79,7 @@
pos_inv_cn.paid_amount = -300
pos_inv_cn.submit()
- merge_pos_invoices()
+ consolidate_pos_invoices()
pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py
index acac1c4..cb5b3a5 100644
--- a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py
+++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.py
@@ -6,7 +6,6 @@
import frappe
from frappe import _
from frappe.utils import cint, get_link_to_form
-from frappe.model.document import Document
from erpnext.controllers.status_updater import StatusUpdater
class POSOpeningEntry(StatusUpdater):
diff --git a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry_list.js b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry_list.js
index 6c26ded..1ad3c91 100644
--- a/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry_list.js
+++ b/erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry_list.js
@@ -5,7 +5,7 @@
frappe.listview_settings['POS Opening Entry'] = {
get_indicator: function(doc) {
var status_color = {
- "Draft": "grey",
+ "Draft": "red",
"Open": "orange",
"Closed": "green",
"Cancelled": "red"
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json
index d856ae3..4b69f6e 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.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index f2a62cd..5bef9e2 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -20,6 +20,7 @@
var me = this;
this._super();
+ this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice'];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
// show debit_to in print format
this.frm.set_df_property("debit_to", "print_hide", 0);
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 447cee4..018bc7e 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -1987,8 +1987,15 @@
"icon": "fa fa-file-text",
"idx": 181,
"is_submittable": 1,
- "links": [],
- "modified": "2020-12-25 22:57:32.555067",
+ "links": [
+ {
+ "custom": 1,
+ "group": "Reference",
+ "link_doctype": "POS Invoice",
+ "link_fieldname": "consolidated_invoice"
+ }
+ ],
+ "modified": "2021-01-12 12:16:15.192520",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 7116a6a..ac4f4df 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -233,7 +233,25 @@
if len(self.payments) == 0 and self.is_pos:
frappe.throw(_("At least one mode of payment is required for POS invoice."))
+ def check_if_consolidated_invoice(self):
+ # since POS Invoice extends Sales Invoice, we explicitly check if doctype is Sales Invoice
+ if self.doctype == "Sales Invoice" and self.is_consolidated:
+ invoice_or_credit_note = "consolidated_credit_note" if self.is_return else "consolidated_invoice"
+ pos_closing_entry = frappe.get_all(
+ "POS Invoice Merge Log",
+ filters={ invoice_or_credit_note: self.name },
+ pluck="pos_closing_entry"
+ )
+ if pos_closing_entry:
+ msg = _("To cancel a {} you need to cancel the POS Closing Entry {}. ").format(
+ frappe.bold("Consolidated Sales Invoice"),
+ get_link_to_form("POS Closing Entry", pos_closing_entry[0])
+ )
+ frappe.throw(msg, title=_("Not Allowed"))
+
def before_cancel(self):
+ self.check_if_consolidated_invoice()
+
super(SalesInvoice, self).before_cancel()
self.update_time_sheet(None)
@@ -435,7 +453,9 @@
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 9d9d1b3..2f4eb81 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -302,6 +302,7 @@
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 a048d6e..0e1829a 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -262,6 +262,7 @@
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 e085048..a774a95 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -469,13 +469,19 @@
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 += "<br><br>"
+ 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/status_updater.py b/erpnext/controllers/status_updater.py
index 8c05134..0987d09 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -93,6 +93,12 @@
["Open", "eval:self.docstatus == 1 and not self.pos_closing_entry"],
["Closed", "eval:self.docstatus == 1 and self.pos_closing_entry"],
["Cancelled", "eval:self.docstatus == 2"],
+ ],
+ "POS Closing Entry": [
+ ["Draft", None],
+ ["Submitted", "eval:self.docstatus == 1"],
+ ["Queued", "eval:self.status == 'Queued'"],
+ ["Cancelled", "eval:self.docstatus == 2"],
]
}
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 76309f8..fd744a7 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -107,7 +107,7 @@
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 4e86d36..49f6d95 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 @@
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 @@
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/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
index 2bfe6d3..a3e69bb 100644
--- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
+++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
@@ -81,7 +81,7 @@
+ loan['penalty']
if loan_wise_security_value.get(loan.loan):
- loan['loan_to_value'] = (loan['principal_outstanding'] * 100) / loan_wise_security_value.get(loan.loan)
+ loan['loan_to_value'] = flt((loan['principal_outstanding'] * 100) / loan_wise_security_value.get(loan.loan))
return loan_details
@@ -148,7 +148,7 @@
WHERE u.parent = up.name
AND up.status = 'Approved'
{conditions}
- GROUP BY up.loan
+ GROUP BY up.loan, u.loan_security
""".format(conditions=conditions), filters, as_dict=1)
for unpledge in unpledges:
@@ -160,7 +160,7 @@
WHERE p.parent = lp.name
AND lp.status = 'Pledged'
{conditions}
- GROUP BY lp.loan
+ GROUP BY lp.loan, p.loan_security
""".format(conditions=conditions), filters, as_dict=1)
for security in pledges:
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index f3660b3..348f98f 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -677,7 +677,7 @@
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")
@@ -741,8 +741,10 @@
erpnext.patches.v13_0.update_custom_fields_for_shopify
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.update_pos_closing_entry_in_merge_log
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 0000000..48d5cb4
--- /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 1ca211b..7cb2648 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 @@
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/patches/v13_0/update_pos_closing_entry_in_merge_log.py b/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py
new file mode 100644
index 0000000..42bca7c
--- /dev/null
+++ b/erpnext/patches/v13_0/update_pos_closing_entry_in_merge_log.py
@@ -0,0 +1,25 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc("accounts", "doctype", "POS Invoice Merge Log")
+ frappe.reload_doc("accounts", "doctype", "POS Closing Entry")
+ if frappe.db.count('POS Invoice Merge Log'):
+ frappe.db.sql('''
+ UPDATE
+ `tabPOS Invoice Merge Log` log, `tabPOS Invoice Reference` log_ref
+ SET
+ log.pos_closing_entry = (
+ SELECT clo_ref.parent FROM `tabPOS Invoice Reference` clo_ref
+ WHERE clo_ref.pos_invoice = log_ref.pos_invoice
+ AND clo_ref.parenttype = 'POS Closing Entry'
+ )
+ WHERE
+ log_ref.parent = log.name
+ ''')
+
+ frappe.db.sql('''UPDATE `tabPOS Closing Entry` SET status = 'Submitted' where docstatus = 1''')
+ frappe.db.sql('''UPDATE `tabPOS Closing Entry` SET status = 'Cancelled' where docstatus = 2''')
diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py
index 013ae5c..776a82c 100644
--- a/erpnext/regional/united_arab_emirates/setup.py
+++ b/erpnext/regional/united_arab_emirates/setup.py
@@ -110,9 +110,11 @@
'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 d4cde43..45b4e30 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 @@
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 @@
{
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 @@
});
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 @@
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 @@
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 @@
() => 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 @@
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 3938300..cc47245 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 a4de9f1..259631d 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 @@
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 @@
}
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 6fd4c26..598f50f 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 @@
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 @@
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/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index c2549fe..c2d743b 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -180,6 +180,13 @@
lead_doc.update(lead)
lead_doc.set('lead_owner', '')
+ if not frappe.db.exists('Lead Source', 'Product Inquiry'):
+ frappe.get_doc({
+ 'doctype': 'Lead Source',
+ 'source_name' : 'Product Inquiry'
+ }).insert(ignore_permissions=True)
+ lead_doc.set('source', 'Product Inquiry')
+
try:
lead_doc.save(ignore_permissions=True)
except frappe.exceptions.DuplicateEntryError:
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index be845d9..cda1069 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -672,13 +672,14 @@
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} : <br>"
- .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 += '<br>'
msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "<br><br>"
- 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 @@
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 dfe8fea..873cfec 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -19,7 +19,7 @@
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()
diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html
index 3033d15..876eaea 100644
--- a/erpnext/templates/pages/cart.html
+++ b/erpnext/templates/pages/cart.html
@@ -47,6 +47,9 @@
{% if doc.items %}
<div class="place-order-container">
+ <a class="btn btn-primary-light mr-2" href="/all-products">
+ {{ _("Continue Shopping") }}
+ </a>
{% if cart_settings.enable_checkout %}
<button class="btn btn-primary btn-place-order" type="button">
{{ _("Place Order") }}