Merge branch 'develop' into tcs_calculation
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 270d25a..d94626d 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -262,7 +262,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/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/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index a77bd15..e562d6c 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -550,6 +550,48 @@
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
+ def test_extra_material_transfer(self):
+ frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
+ frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on",
+ "Material Transferred for Manufacture")
+
+ wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
+
+ ste_cancel_list = []
+ ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
+ target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
+ ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
+
+ ste_cancel_list.extend([ste1, ste2])
+
+ itemwise_qty = {}
+ s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
+ for row in s.items:
+ row.qty = row.qty + 2
+ itemwise_qty.setdefault(row.item_code, row.qty)
+
+ s.submit()
+ ste_cancel_list.append(s)
+
+ ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
+ for ste_row in ste3.items:
+ if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse:
+ self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
+
+ ste3.submit()
+ ste_cancel_list.append(ste3)
+
+ ste2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
+ for ste_row in ste2.items:
+ if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse:
+ self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
+
+ for ste_doc in ste_cancel_list:
+ ste_doc.cancel()
+
+ frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
+
def get_scrap_item_details(bom_no):
scrap_items = {}
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
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''')
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 2366fcb..c8e7e14 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -161,7 +161,7 @@
item.description = d.item_name.replace('"', '\\"')
item.qty = abs(item.qty)
- item.discount_amount = abs(item.discount_amount * item.qty)
+ item.discount_amount = 0
item.unit_rate = abs(item.base_net_amount / item.qty)
item.gross_amount = abs(item.base_net_amount)
item.taxable_value = abs(item.base_net_amount)
@@ -221,11 +221,12 @@
if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
invoice_value_details.base_total = abs(invoice.base_total)
+ invoice_value_details.invoice_discount_amt = invoice.base_discount_amount
else:
invoice_value_details.base_total = abs(invoice.base_net_total)
+ # since tax already considers discount amount
+ invoice_value_details.invoice_discount_amt = 0
- # since tax already considers discount amount
- invoice_value_details.invoice_discount_amt = 0 # invoice.base_discount_amount
invoice_value_details.round_off = invoice.base_rounding_adjustment
invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index d623d5c..d77b70f 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1200,7 +1200,10 @@
else:
qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
else:
- qty = req_qty_each * flt(self.fg_completed_qty)
+ if self.flags.backflush_based_on == "Material Transferred for Manufacture":
+ qty = (item.qty/trans_qty) * flt(self.fg_completed_qty)
+ else:
+ qty = req_qty_each * flt(self.fg_completed_qty)
elif backflushed_materials.get(item.item_code):
for d in backflushed_materials.get(item.item_code):
@@ -1208,7 +1211,8 @@
if (qty > req_qty):
qty = (qty/trans_qty) * flt(self.fg_completed_qty)
- if consumed_qty:
+ if consumed_qty and frappe.db.get_single_value("Manufacturing Settings",
+ "material_consumption"):
qty -= consumed_qty
if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):