From c3b74affe33758013cc87f11d1447242f2c56317 Mon Sep 17 00:00:00 2001 From: P-Froggy <60393001+P-Froggy@users.noreply.github.com> Date: Thu, 18 Jun 2020 01:48:37 +0200 Subject: [PATCH 01/10] fix: Set Value of wrong Bank Account Field in Payment Entry Company bank account was wrongly inserted into the field "Party Bank Account" in payment entry, instead of "Bank Account". Also changes the label of "Default Bank Account" to "Default Company Bank Account", like suggested in PR #20632 --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 8 ++++---- erpnext/buying/doctype/supplier/supplier.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index a378a51cdf7..a47da78f1f3 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -25,7 +25,7 @@ frappe.ui.form.on('Payment Entry', { }); frm.set_query("party_type", function() { return{ - "filters": { + filters: { "name": ["in", Object.keys(frappe.boot.party_account_types)], } } @@ -33,7 +33,7 @@ frappe.ui.form.on('Payment Entry', { frm.set_query("party_bank_account", function() { return { filters: { - "is_company_account":0, + is_company_account: 0, party_type: frm.doc.party_type, party: frm.doc.party } @@ -42,7 +42,7 @@ frappe.ui.form.on('Payment Entry', { frm.set_query("bank_account", function() { return { filters: { - "is_company_account":1 + is_company_account: 1 } } }); @@ -341,7 +341,7 @@ frappe.ui.form.on('Payment Entry', { () => { frm.set_party_account_based_on_party = false; if (r.message.bank_account) { - frm.set_value("party_bank_account", r.message.bank_account); + frm.set_value("bank_account", r.message.bank_account); } } ]); diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 4606395ebe5..40362b1d404 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -97,7 +97,7 @@ { "fieldname": "default_bank_account", "fieldtype": "Link", - "label": "Default Bank Account", + "label": "Default Company Bank Account", "options": "Bank Account" }, { @@ -384,7 +384,7 @@ "idx": 370, "image_field": "image", "links": [], - "modified": "2020-03-17 09:48:30.578242", + "modified": "2020-06-17 23:18:20", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", From 4e40b9bdbe364ea19efb665ddc80daf159f853f7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 24 Jun 2020 15:41:19 +0530 Subject: [PATCH 02/10] feat: log everything --- .../doctype/membership/membership.py | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 7a0caed621e..a2c63afa868 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -81,17 +81,24 @@ def verify_signature(data): @frappe.whitelist(allow_guest=True) def trigger_razorpay_subscription(*args, **kwargs): data = frappe.request.get_data(as_text=True) - verify_signature(data) + try: + verify_signature(data) + except Exception as e: + frappe.log_error(e, "Webhook Verification Error") if isinstance(data, six.string_types): data = json.loads(data) data = frappe._dict(data) - subscription = data.payload.get("subscription", {}).get('entity', {}) - subscription = frappe._dict(subscription) + try: + subscription = data.payload.get("subscription", {}).get('entity', {}) + subscription = frappe._dict(subscription) - payment = data.payload.get("payment", {}).get('entity', {}) - payment = frappe._dict(payment) + payment = data.payload.get("payment", {}).get('entity', {}) + payment = frappe._dict(payment) + except Exception as e: + frappe.log_error(e, "Webhook Data Parsing Error") + return False try: data_json = json.dumps(data, indent=4, sort_keys=True) @@ -103,30 +110,32 @@ def trigger_razorpay_subscription(*args, **kwargs): if not member: return False + try: + if data.event == "subscription.activated": + member.customer_id = payment.customer_id + elif data.event == "subscription.charged": + membership = frappe.new_doc("Membership") + membership.update({ + "member": member.name, + "membership_status": "Current", + "membership_type": member.membership_type, + "currency": "INR", + "paid": 1, + "payment_id": payment.id, + "webhook_payload": data_json, + "from_date": datetime.fromtimestamp(subscription.current_start), + "to_date": datetime.fromtimestamp(subscription.current_end), + "amount": payment.amount / 100 # Convert to rupees from paise + }) + membership.insert(ignore_permissions=True) - if data.event == "subscription.activated": - member.customer_id = payment.customer_id - elif data.event == "subscription.charged": - membership = frappe.new_doc("Membership") - membership.update({ - "member": member.name, - "membership_status": "Current", - "membership_type": member.membership_type, - "currency": "INR", - "paid": 1, - "payment_id": payment.id, - "webhook_payload": data_json, - "from_date": datetime.fromtimestamp(subscription.current_start), - "to_date": datetime.fromtimestamp(subscription.current_end), - "amount": payment.amount / 100 # Convert to rupees from paise - }) - membership.insert(ignore_permissions=True) - - # Update these values anyway - member.subscription_start = datetime.fromtimestamp(subscription.start_at) - member.subscription_end = datetime.fromtimestamp(subscription.end_at) - member.subscription_activated = 1 - member.save(ignore_permissions=True) + # Update these values anyway + member.subscription_start = datetime.fromtimestamp(subscription.start_at) + member.subscription_end = datetime.fromtimestamp(subscription.end_at) + member.subscription_activated = 1 + member.save(ignore_permissions=True) + except Exception as e: + frappe.log_error(e, "Error creating membership entry") return True From e43d362d53df7972b705c30268d4c590eacec059 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 14 Jul 2020 19:45:29 +0530 Subject: [PATCH 03/10] feat: verbose logging for verification --- erpnext/non_profit/doctype/membership/membership.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index a2c63afa868..eb393ec361c 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -84,6 +84,8 @@ def trigger_razorpay_subscription(*args, **kwargs): try: verify_signature(data) except Exception as e: + signature = frappe.request.headers.get('X-Razorpay-Signature') + log = "{0} \n\n {1} \n\n {2} \n\n {3}".format(e, frappe.get_traceback(), signature, data) frappe.log_error(e, "Webhook Verification Error") if isinstance(data, six.string_types): From 9e3776e001d0bc0506356ebf352068bb5a9cd162 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 27 Jul 2020 14:26:37 +0530 Subject: [PATCH 04/10] feat: improve webhook logging --- erpnext/non_profit/doctype/member/member.py | 2 +- .../doctype/membership/membership.py | 22 +++++++++---------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index d1294ccc08b..7818c99fbee 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -121,7 +121,7 @@ def create_member_subscription_order(user_details): 'subscription_id': 'sub_EZycCvXFvqnC6p' } """ - # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} + user_details = frappe._dict(user_details) member = get_or_create_member(user_details) if not member: diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index eb393ec361c..729e111e577 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -92,15 +92,11 @@ def trigger_razorpay_subscription(*args, **kwargs): data = json.loads(data) data = frappe._dict(data) - try: - subscription = data.payload.get("subscription", {}).get('entity', {}) - subscription = frappe._dict(subscription) + subscription = data.payload.get("subscription", {}).get('entity', {}) + subscription = frappe._dict(subscription) - payment = data.payload.get("payment", {}).get('entity', {}) - payment = frappe._dict(payment) - except Exception as e: - frappe.log_error(e, "Webhook Data Parsing Error") - return False + payment = data.payload.get("payment", {}).get('entity', {}) + payment = frappe._dict(payment) try: data_json = json.dumps(data, indent=4, sort_keys=True) @@ -108,10 +104,10 @@ def trigger_razorpay_subscription(*args, **kwargs): except Exception as e: error_log = frappe.log_error(frappe.get_traceback() + '\n' + data_json , _("Membership Webhook Failed")) notify_failure(error_log) - return False + return { status: 'Failed' } if not member: - return False + return { status: 'Failed' } try: if data.event == "subscription.activated": member.customer_id = payment.customer_id @@ -137,9 +133,11 @@ def trigger_razorpay_subscription(*args, **kwargs): member.subscription_activated = 1 member.save(ignore_permissions=True) except Exception as e: - frappe.log_error(e, "Error creating membership entry") + log = frappe.log_error(e, "Error creating membership entry") + notify_failure(log) + return { status: 'Failed' } - return True + return { status: 'Success' } def notify_failure(log): From 97a316c09ac48b70c09bae494b4a70e364aa0d79 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 27 Jul 2020 15:24:50 +0530 Subject: [PATCH 05/10] feat: add create customer button to member --- erpnext/non_profit/doctype/member/member.js | 8 ++++++++ erpnext/non_profit/doctype/member/member.py | 19 +++++++++++++++++-- .../doctype/membership/membership.json | 4 +++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/erpnext/non_profit/doctype/member/member.js b/erpnext/non_profit/doctype/member/member.js index 3e9d0baba5f..199dcfc04f5 100644 --- a/erpnext/non_profit/doctype/member/member.js +++ b/erpnext/non_profit/doctype/member/member.js @@ -29,6 +29,14 @@ frappe.ui.form.on('Member', { frappe.set_route('query-report', 'Accounts Receivable', {member:frm.doc.name}); }); + if (!frm.doc.customer) { + frm.add_custom_button(__('Create Customer'), () => { + frm.call('make_customer_and_link').then(() => { + frm.reload_doc(); + }); + }); + } + // indicator erpnext.utils.set_party_dashboard_indicators(frm); diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 7818c99fbee..c52082ca23d 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -53,6 +53,19 @@ class Member(Document): return subscription + def make_customer_and_link(self): + if self.customer: + frappe.msgprint(_("A customer is already linked to this Member")) + cust = create_customer(frappe._dict({ + 'fullname': self.member_name, + 'email': self.email_id or self.user, + 'phone': None + })) + + self.customer = cust + self.save() + + def get_or_create_member(user_details): member_list = frappe.get_all("Member", filters={'email': user_details.email, 'membership_type': user_details.plan_id}) if member_list and member_list[0]: @@ -83,8 +96,10 @@ def create_customer(user_details): try: contact = frappe.new_doc("Contact") contact.first_name = user_details.fullname - contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1) - contact.add_email(user_details.email, is_primary=1) + if user_details.mobile: + contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1) + if user_details.email: + contact.add_email(user_details.email, is_primary=1) contact.insert(ignore_permissions=True) contact.append("links", { diff --git a/erpnext/non_profit/doctype/membership/membership.json b/erpnext/non_profit/doctype/membership/membership.json index 9f10d0cfc70..238f4c31fd3 100644 --- a/erpnext/non_profit/doctype/membership/membership.json +++ b/erpnext/non_profit/doctype/membership/membership.json @@ -120,13 +120,15 @@ { "fieldname": "webhook_payload", "fieldtype": "Code", + "hidden": 1, "label": "Webhook Payload", "options": "JSON", "read_only": 1 } ], + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-04-06 14:29:33.856060", + "modified": "2020-07-27 14:28:11.532696", "modified_by": "Administrator", "module": "Non Profit", "name": "Membership", From 9119b4c5386b489fbdd343288089e55b21b77771 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 28 Jul 2020 08:49:44 +0530 Subject: [PATCH 06/10] fix: Unable to submit backdated stock transactions for different items (#22648) * fix: Unable to submit backdated stock transactions for different items * fix: Test cases * fix: Test Cases * fix: Test Cases * fix: Test for stock account JV * fix: Journal Entry Test --- .../doctype/coupon_code/test_coupon_code.py | 27 +++++++----- .../journal_entry/test_journal_entry.py | 42 +++++++++++++++--- .../doctype/pos_invoice/test_pos_invoice.py | 44 ++++++++++++------- erpnext/accounts/general_ledger.py | 5 ++- erpnext/stock/doctype/item/item.py | 4 +- .../doctype/stock_entry/test_stock_entry.py | 26 ++++++++--- 6 files changed, 102 insertions(+), 46 deletions(-) diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py index 990b896fde3..3a0d4162ae7 100644 --- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py +++ b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py @@ -26,22 +26,22 @@ def test_create_test_data(): "item_group": "_Test Item Group", "item_name": "_Test Tesla Car", "apply_warehouse_wise_reorder_level": 0, - "warehouse":"_Test Warehouse - _TC", + "warehouse":"Stores - TCP1", "gst_hsn_code": "999800", "valuation_rate": 5000, "standard_rate":5000, "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "_Test Warehouse - _TC", + "company": "_Test Company with perpetual inventory", + "default_warehouse": "Stores - TCP1", "default_price_list":"_Test Price List", - "expense_account": "_Test Account Cost for Goods Sold - _TC", - "buying_cost_center": "_Test Cost Center - _TC", - "selling_cost_center": "_Test Cost Center - _TC", - "income_account": "Sales - _TC" + "expense_account": "Cost of Goods Sold - TCP1", + "buying_cost_center": "Main - TCP1", + "selling_cost_center": "Main - TCP1", + "income_account": "Sales - TCP1" }], "show_in_website": 1, "route":"-test-tesla-car", - "website_warehouse": "_Test Warehouse - _TC" + "website_warehouse": "Stores - TCP1" }) item.insert() # create test item price @@ -63,12 +63,12 @@ def test_create_test_data(): "items": [{ "item_code": "_Test Tesla Car" }], - "warehouse":"_Test Warehouse - _TC", + "warehouse":"Stores - TCP1", "coupon_code_based":1, "selling": 1, "rate_or_discount": "Discount Percentage", "discount_percentage": 30, - "company": "_Test Company", + "company": "_Test Company with perpetual inventory", "currency":"INR", "for_price_list":"_Test Price List" }) @@ -112,7 +112,10 @@ class TestCouponCode(unittest.TestCase): self.assertEqual(coupon_code.get("used"),0) def test_2_sales_order_with_coupon_code(self): - so = make_sales_order(customer="_Test Customer",selling_price_list="_Test Price List",item_code="_Test Tesla Car", rate=5000,qty=1, do_not_submit=True) + so = make_sales_order(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', + customer="_Test Customer", selling_price_list="_Test Price List", item_code="_Test Tesla Car", rate=5000,qty=1, + do_not_submit=True) + so = frappe.get_doc('Sales Order', so.name) # check item price before coupon code is applied self.assertEqual(so.items[0].rate, 5000) @@ -120,7 +123,7 @@ class TestCouponCode(unittest.TestCase): so.sales_partner='_Test Coupon Partner' so.save() # check item price after coupon code is applied - self.assertEqual(so.items[0].rate, 3500) + self.assertEqual(so.items[0].rate, 3500) so.submit() def test_3_check_coupon_code_used_after_so(self): diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 23ad1eef14c..479d4b64bb8 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -6,6 +6,7 @@ import unittest, frappe from frappe.utils import flt, nowdate from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.exceptions import InvalidAccountCurrency +from erpnext.accounts.general_ledger import StockAccountInvalidTransaction class TestJournalEntry(unittest.TestCase): def test_journal_entry_with_against_jv(self): @@ -81,19 +82,46 @@ class TestJournalEntry(unittest.TestCase): from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory set_perpetual_inventory() - jv = frappe.copy_doc(test_records[0]) + jv = frappe.copy_doc({ + "cheque_date": nowdate(), + "cheque_no": "33", + "company": "_Test Company with perpetual inventory", + "doctype": "Journal Entry", + "accounts": [ + { + "account": "Debtors - TCP1", + "party_type": "Customer", + "party": "_Test Customer", + "credit_in_account_currency": 400.0, + "debit_in_account_currency": 0.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts", + "cost_center": "Main - TCP1" + }, + { + "account": "_Test Bank - TCP1", + "credit_in_account_currency": 0.0, + "debit_in_account_currency": 400.0, + "doctype": "Journal Entry Account", + "parentfield": "accounts", + "cost_center": "Main - TCP1" + } + ], + "naming_series": "_T-Journal Entry-", + "posting_date": nowdate(), + "user_remark": "test", + "voucher_type": "Bank Entry" + }) + jv.get("accounts")[0].update({ - "account": get_inventory_account('_Test Company'), - "company": "_Test Company", + "account": get_inventory_account('_Test Company with perpetual inventory'), + "company": "_Test Company with perpetual inventory", "party_type": None, "party": None }) - jv.insert() - - from erpnext.accounts.general_ledger import StockAccountInvalidTransaction self.assertRaises(StockAccountInvalidTransaction, jv.submit) - + jv.cancel() set_perpetual_inventory(0) def test_multi_currency(self): diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index f29572542c8..9c62a87677e 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -23,13 +23,13 @@ class TestPOSInvoice(unittest.TestCase): import time time.sleep(1) self.assertRaises(frappe.TimestampMismatchError, w2.save) - + def test_change_naming_series(self): inv = create_pos_invoice(do_not_submit=1) inv.naming_series = 'TEST-' self.assertRaises(frappe.CannotChangeConstantError, inv.save) - + def test_discount_and_inclusive_tax(self): inv = create_pos_invoice(qty=100, rate=50, do_not_save=1) inv.append("taxes", { @@ -66,7 +66,7 @@ class TestPOSInvoice(unittest.TestCase): self.assertEqual(inv.net_total, 4298.25) self.assertEqual(inv.grand_total, 4900.00) - + def test_tax_calculation_with_multiple_items(self): inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=True) item_row = inv.get("items")[0] @@ -148,7 +148,7 @@ class TestPOSInvoice(unittest.TestCase): self.assertEqual(inv.grand_total, 5675.57) self.assertEqual(inv.rounding_adjustment, 0.43) self.assertEqual(inv.rounded_total, 5676.0) - + def test_tax_calculation_with_multiple_items_and_discount(self): inv = create_pos_invoice(qty=1, rate=75, do_not_save=True) item_row = inv.get("items")[0] @@ -194,7 +194,7 @@ class TestPOSInvoice(unittest.TestCase): self.assertEqual(pos_return.get('payments')[0].amount, -500) self.assertEqual(pos_return.get('payments')[1].amount, -500) - + def test_pos_change_amount(self): pos = create_pos_invoice(company= "_Test Company", debit_to="Debtors - _TC", income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105, @@ -208,33 +208,43 @@ class TestPOSInvoice(unittest.TestCase): self.assertEqual(pos.grand_total, 105.0) self.assertEqual(pos.change_amount, 5.0) - + def test_without_payment(self): inv = create_pos_invoice(do_not_save=1) # Check that the invoice cannot be submitted without payments inv.payments = [] self.assertRaises(frappe.ValidationError, inv.insert) - + def test_serialized_item_transaction(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos - se = make_serialized_item(target_warehouse="_Test Warehouse - _TC") + se = make_serialized_item(company='_Test Company with perpetual inventory', + target_warehouse="Stores - TCP1", cost_center='Main - TCP1', expense_account='Cost of Goods Sold - TCP1') + serial_nos = get_serial_nos(se.get("items")[0].serial_no) - pos = create_pos_invoice(item=se.get("items")[0].item_code, rate=1000, do_not_save=1) + pos = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1', + account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1', + expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1', + item=se.get("items")[0].item_code, rate=1000, do_not_save=1) + pos.get("items")[0].serial_no = serial_nos[0] - pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000}) + pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000}) pos.insert() pos.submit() - pos2 = create_pos_invoice(item=se.get("items")[0].item_code, rate=1000, do_not_save=1) + pos2 = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1', + account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1', + expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1', + item=se.get("items")[0].item_code, rate=1000, do_not_save=1) + pos2.get("items")[0].serial_no = serial_nos[0] - pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000}) - + pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000}) + self.assertRaises(frappe.ValidationError, pos2.insert) - + def test_loyalty_points(self): from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points @@ -255,14 +265,14 @@ class TestPOSInvoice(unittest.TestCase): inv.cancel() after_cancel_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program) self.assertEqual(after_cancel_lp_details.loyalty_points, before_lp_details.loyalty_points) - + def test_loyalty_points_redeemption(self): from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points # add 10 loyalty points create_pos_invoice(customer="Test Loyalty Customer", rate=10000) before_lp_details = get_loyalty_program_details_with_points("Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty") - + inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1) inv.redeem_loyalty_points = 1 inv.loyalty_points = before_lp_details.loyalty_points @@ -299,7 +309,7 @@ def create_pos_invoice(**args): pos_inv.return_against = args.return_against pos_inv.currency=args.currency or "INR" pos_inv.conversion_rate = args.conversion_rate or 1 - pos_inv.account_for_change_amount = "Cash - _TC" + pos_inv.account_for_change_amount = args.account_for_change_amount or "Cash - _TC" pos_inv.append("items", { "item_code": args.item or args.item_code or "_Test Item", diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index a245d63f52b..cf3deb828f4 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -158,8 +158,10 @@ def validate_account_for_perpetual_inventory(gl_map): if account not in aii_accounts: continue + # Always use current date to get stock and account balance as there can future entries for + # other items account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account, - gl_map[0].posting_date, gl_map[0].company) + getdate(), gl_map[0].company) if gl_map[0].voucher_type=="Journal Entry": # In case of Journal Entry, there are no corresponding SL entries, @@ -169,7 +171,6 @@ def validate_account_for_perpetual_inventory(gl_map): frappe.throw(_("Account: {0} can only be updated via Stock Transactions") .format(account), StockAccountInvalidTransaction) - # This has been comment for a temporary, will add this code again on release of immutable ledger elif account_bal != stock_bal: precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), currency=frappe.get_cached_value('Company', gl_map[0].company, "default_currency")) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index d5f479ff828..d7b43bf399b 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -13,7 +13,7 @@ from erpnext.controllers.item_variant import (ItemVariantExistsError, from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for) from frappe import _, msgprint from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate, - now_datetime, random_string, strip, get_link_to_form) + now_datetime, random_string, strip, get_link_to_form, nowtime) from frappe.utils.html_utils import clean_html from frappe.website.doctype.website_slideshow.website_slideshow import \ get_slideshow @@ -194,7 +194,7 @@ class Item(WebsiteGenerator): if default_warehouse: stock_entry = make_stock_entry(item_code=self.name, target=default_warehouse, qty=self.opening_stock, - rate=self.valuation_rate, company=default.company) + rate=self.valuation_rate, company=default.company, posting_date=getdate(), posting_time=nowtime()) stock_entry.add_comment("Comment", _("Opening Stock")) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 0fbc63101e6..8e25804511e 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -413,7 +413,7 @@ class TestStockEntry(unittest.TestCase): def test_serial_item_error(self): se, serial_nos = self.test_serial_by_series() if not frappe.db.exists('Serial No', 'ABCD'): - make_serialized_item("_Test Serialized Item", "ABCD\nEFGH") + make_serialized_item(item_code="_Test Serialized Item", serial_no="ABCD\nEFGH") se = frappe.copy_doc(test_records[0]) se.purpose = "Material Transfer" @@ -823,15 +823,29 @@ class TestStockEntry(unittest.TestCase): ]) ) -def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None): +def make_serialized_item(**args): + args = frappe._dict(args) se = frappe.copy_doc(test_records[0]) - se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series" - se.get("items")[0].serial_no = serial_no + + if args.company: + se.company = args.company + + se.get("items")[0].item_code = args.item_code or "_Test Serialized Item With Series" + + if args.serial_no: + se.get("items")[0].serial_no = args.serial_no + + if args.cost_center: + se.get("items")[0].cost_center = args.cost_center + + if args.expense_account: + se.get("items")[0].expense_account = args.expense_account + se.get("items")[0].qty = 2 se.get("items")[0].transfer_qty = 2 - if target_warehouse: - se.get("items")[0].t_warehouse = target_warehouse + if args.target_warehouse: + se.get("items")[0].t_warehouse = args.target_warehouse se.set_stock_entry_type() se.insert() From 48be7d37b864165f94b7d0e2e2481c24f0553cd7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 28 Jul 2020 09:00:09 +0530 Subject: [PATCH 07/10] fix: add order_by explicitly for lead (#22820) --- erpnext/hr/doctype/holiday_list/holiday_list_calendar.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/hr/doctype/holiday_list/holiday_list_calendar.js b/erpnext/hr/doctype/holiday_list/holiday_list_calendar.js index 3cc8dd5036f..4e188add3e0 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list_calendar.js +++ b/erpnext/hr/doctype/holiday_list/holiday_list_calendar.js @@ -9,6 +9,7 @@ frappe.views.calendar["Holiday List"] = { "title": "description", "allDay": "allDay" }, + order_by: `from_date`, get_events_method: "erpnext.hr.doctype.holiday_list.holiday_list.get_events", filters: [ { From 701474cbaa80be58c51c2eb4fc75cb4040bc9c75 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 28 Jul 2020 09:11:37 +0530 Subject: [PATCH 08/10] fix: POS patch fix (#22818) --- erpnext/patches/v12_0/rename_pos_closing_doctype.py | 4 ++-- erpnext/patches/v13_0/replace_pos_payment_mode_table.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v12_0/rename_pos_closing_doctype.py b/erpnext/patches/v12_0/rename_pos_closing_doctype.py index 8ca92ef65c6..0577f81234c 100644 --- a/erpnext/patches/v12_0/rename_pos_closing_doctype.py +++ b/erpnext/patches/v12_0/rename_pos_closing_doctype.py @@ -12,11 +12,11 @@ def execute(): frappe.rename_doc('DocType', 'POS Closing Voucher Taxes', 'POS Closing Entry Taxes', force=True) if not frappe.db.exists('DocType', 'POS Closing Voucher Details'): - frappe.rename_doc('DocType', 'POS Closing Voucher Details', 'POS Closing Entry Details', force=True) + frappe.rename_doc('DocType', 'POS Closing Voucher Details', 'POS Closing Entry Detail', force=True) frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry') frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry Taxes') - frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry Details') + frappe.reload_doc('Accounts', 'doctype', 'POS Closing Entry Detail') if frappe.db.exists("DocType", "POS Closing Voucher"): frappe.delete_doc("DocType", "POS Closing Voucher") 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 4a621b6a514..1ca211bf1be 100644 --- a/erpnext/patches/v13_0/replace_pos_payment_mode_table.py +++ b/erpnext/patches/v13_0/replace_pos_payment_mode_table.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.reload_doc("Selling", "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: From 81ae6c32400991a5dcbcc9602b99a01b153b893b Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 28 Jul 2020 12:27:41 +0530 Subject: [PATCH 09/10] fix: reload HR Settings to fix failing setup (#22802) Co-authored-by: Marica --- erpnext/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a24f5f76c8d..3bd416952f8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -15,7 +15,7 @@ erpnext.patches.v4_0.move_warehouse_user_to_restrictions erpnext.patches.v4_0.global_defaults_to_system_settings erpnext.patches.v4_0.update_incharge_name_to_sales_person_in_maintenance_schedule execute:frappe.reload_doc("accounts", "doctype", "POS Payment Method") #2020-05-28 -execute:frappe.reload_doc("HR", "doctype", "HR Settings") #2020-01-16 +execute:frappe.reload_doc("HR", "doctype", "HR Settings") #2020-01-16 #2020-07-24 execute:frappe.reload_doc('stock', 'doctype', 'warehouse') # 2017-04-24 execute:frappe.reload_doc('accounts', 'doctype', 'sales_invoice') # 2016-08-31 execute:frappe.reload_doc('selling', 'doctype', 'sales_order') # 2014-01-29 From 5903002039c2b71f45ca5cdbc80bbc86ac0ede8e Mon Sep 17 00:00:00 2001 From: anoop Date: Tue, 28 Jul 2020 21:15:54 +0530 Subject: [PATCH 10/10] fix: exclude Cancelled appointments while Get Items in Sales Invoice --- erpnext/healthcare/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index 9abaa0784a7..dbd3b83f09b 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -40,7 +40,7 @@ def get_appointments_to_invoice(patient, company): patient_appointments = frappe.get_list( 'Patient Appointment', fields = '*', - filters = {'patient': patient.name, 'company': company, 'invoiced': 0}, + filters = {'patient': patient.name, 'company': company, 'invoiced': 0, 'status': ['not in', 'Cancelled']}, order_by = 'appointment_date' )