refactor: POS Invoice merging and cancellation (#24351)
* feat: pos invoice merging with background jobs
* fix: invoice threshold for queueing job
* refactor: cancellation flow of point of sale
* feat: tests for cancellation of pos closing
Co-authored-by: Marica <maricadsouza221197@gmail.com>
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 c26e14f..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"))
@@ -64,17 +63,22 @@
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
- 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()
-
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 ae968d9..07c8e44 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -16,6 +16,7 @@
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();
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 71ddcf5..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
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 1539d5f..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
@@ -7,8 +7,11 @@
from frappe import _
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 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()
@@ -163,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']],
@@ -184,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:
@@ -194,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/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 6304438..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)
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/patches.txt b/erpnext/patches.txt
index 7e438a4..348f98f 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -741,6 +741,7 @@
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
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''')