From 95491e17180fe0e4c0234e54e56af178dbc4ad29 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 18 May 2022 12:44:07 +0530 Subject: [PATCH 01/48] fix(india): eway bill cancel api is disabled (#31060) --- erpnext/regional/india/e_invoice/einvoice.js | 67 ++++++-------------- erpnext/regional/india/e_invoice/utils.py | 14 ++-- 2 files changed, 28 insertions(+), 53 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index ea56d07d6dad..4748b265dc2b 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -149,58 +149,27 @@ erpnext.setup_einvoice_actions = (doctype) => { } if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { - const fields = [ - { - "label": "Reason", - "fieldname": "reason", - "fieldtype": "Select", - "reqd": 1, - "default": "1-Duplicate", - "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] - }, - { - "label": "Remark", - "fieldname": "remark", - "fieldtype": "Data", - "reqd": 1 - } - ]; const action = () => { - const d = new frappe.ui.Dialog({ - title: __('Cancel E-Way Bill'), - fields: fields, - primary_action: function() { - const data = d.get_values(); - frappe.call({ - method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', - args: { - doctype, - docname: name, - eway_bill: ewaybill, - reason: data.reason.split('-')[0], - remark: data.remark - }, - freeze: true, - callback: () => { - frappe.show_alert({ - message: __('E-Way Bill Cancelled successfully'), - indicator: 'green' - }, 7); - frm.reload_doc(); - d.hide(); - }, - error: () => { - frappe.show_alert({ - message: __('E-Way Bill was not Cancelled'), - indicator: 'red' - }, 7); - d.hide(); - } - }); + let message = __('Cancellation of e-way bill is currently not supported.') + ' '; + message += '

'; + message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.'); + + const dialog = frappe.msgprint({ + title: __('Update E-Way Bill Cancelled Status?'), + message: message, + indicator: 'orange', + primary_action: { + action: function() { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', + args: { doctype, docname: name }, + freeze: true, + callback: () => frm.reload_doc() && dialog.hide() + }); + } }, - primary_action_label: __('Submit') + primary_action_label: __('Yes') }); - d.show(); }; add_custom_button(__("Cancel E-Way Bill"), action); } diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 75da981c2bcb..75647b0dc29a 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -796,7 +796,8 @@ def __init__(self, doctype=None, docname=None): self.irn_details_url = self.base_url + "/enriched/ei/api/invoice/irn" self.generate_irn_url = self.base_url + "/enriched/ei/api/invoice" self.gstin_details_url = self.base_url + "/enriched/ei/api/master/gstin" - self.cancel_ewaybill_url = self.base_url + "/enriched/ei/api/ewayapi" + # cancel_ewaybill_url will only work if user have bought ewb api from adaequare. + self.cancel_ewaybill_url = self.base_url + "/enriched/ewb/ewayapi?action=CANEWB" self.generate_ewaybill_url = self.base_url + "/enriched/ei/api/ewaybill" self.get_qrcode_url = self.base_url + "/enriched/ei/others/qr/image" @@ -1184,6 +1185,7 @@ def cancel_eway_bill(self, eway_bill, reason, remark): headers = self.get_headers() data = json.dumps({"ewbNo": eway_bill, "cancelRsnCode": reason, "cancelRmrk": remark}, indent=4) headers["username"] = headers["user_name"] + del headers["user_name"] try: res = self.make_request("post", self.cancel_ewaybill_url, headers, data) if res.get("success"): @@ -1357,9 +1359,13 @@ def generate_eway_bill(doctype, docname, **kwargs): @frappe.whitelist() -def cancel_eway_bill(doctype, docname, eway_bill, reason, remark): - gsp_connector = GSPConnector(doctype, docname) - gsp_connector.cancel_eway_bill(eway_bill, reason, remark) +def cancel_eway_bill(doctype, docname): + # NOTE: cancel_eway_bill api is disabled by Adequare. + # gsp_connector = GSPConnector(doctype, docname) + # gsp_connector.cancel_eway_bill(eway_bill, reason, remark) + + frappe.db.set_value(doctype, docname, "ewaybill", "") + frappe.db.set_value(doctype, docname, "eway_bill_cancelled", 1) @frappe.whitelist() From 1461d66ddaacb084b6e7c40630ac7970efab27aa Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 18 May 2022 12:44:24 +0530 Subject: [PATCH 02/48] fix(india): error while parsing e-invoice (#31061) --- erpnext/regional/india/e_invoice/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 75647b0dc29a..e20a915bb227 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -648,6 +648,8 @@ def make_einvoice(invoice): try: einvoice = safe_json_load(einvoice) einvoice = santize_einvoice_fields(einvoice) + except json.JSONDecodeError: + raise except Exception: show_link_to_error_log(invoice, einvoice) @@ -764,7 +766,9 @@ def safe_json_load(json_string): frappe.throw( _( "Error in input data. Please check for any special characters near following input:
{}" - ).format(snippet) + ).format(snippet), + title=_("Invalid JSON"), + exc=e, ) From 0efbabe7cf780358ee946c48e99239aafe647eb5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 19 May 2022 14:27:19 +0530 Subject: [PATCH 03/48] fix: payments duplicate on pos closing entry (backport #30976) (#31005) --- .../doctype/pos_closing_entry/pos_closing_entry.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 264d4a68b009..572410fc6651 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -64,13 +64,15 @@ frappe.ui.form.on('POS Closing Entry', { pos_opening_entry(frm) { if (frm.doc.pos_opening_entry && frm.doc.period_start_date && frm.doc.period_end_date && frm.doc.user) { reset_values(frm); - frm.trigger("set_opening_amounts"); - frm.trigger("get_pos_invoices"); + frappe.run_serially([ + () => frm.trigger("set_opening_amounts"), + () => frm.trigger("get_pos_invoices") + ]); } }, set_opening_amounts(frm) { - frappe.db.get_doc("POS Opening Entry", frm.doc.pos_opening_entry) + return frappe.db.get_doc("POS Opening Entry", frm.doc.pos_opening_entry) .then(({ balance_details }) => { balance_details.forEach(detail => { frm.add_child("payment_reconciliation", { @@ -83,7 +85,7 @@ frappe.ui.form.on('POS Closing Entry', { }, get_pos_invoices(frm) { - frappe.call({ + return frappe.call({ method: 'erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices', args: { start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date), From f519dc613cd4d05e193cda03fbc84c34a1216327 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 19 May 2022 16:02:20 +0530 Subject: [PATCH 04/48] fix: always update item_name for stock entry (backport #31068) (#31071) fix: always update item_name for stock entry (#31068) If item_name is already set and for some reason becomes outdated then it's not updated in backend. Fix: always set item_name and stock_uom when fetching item details (cherry picked from commit 6d6616dbcdfec4171022bac9c0538c04a1fa5cd5) Co-authored-by: Ankush Menat --- .../stock/doctype/stock_entry/stock_entry.py | 24 +++++++++---------- .../doctype/stock_entry/test_stock_entry.py | 18 ++++++++++++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index de9af084abec..70161d763390 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -299,19 +299,17 @@ def validate_item(self): for_update=True, ) - for f in ( - "uom", - "stock_uom", - "description", - "item_name", - "expense_account", - "cost_center", - "conversion_factor", - ): - if f == "stock_uom" or not item.get(f): - item.set(f, item_details.get(f)) - if f == "conversion_factor" and item.uom == item_details.get("stock_uom"): - item.set(f, item_details.get(f)) + reset_fields = ("stock_uom", "item_name") + for field in reset_fields: + item.set(field, item_details.get(field)) + + update_fields = ("uom", "description", "expense_account", "cost_center", "conversion_factor") + + for field in update_fields: + if not item.get(field): + item.set(field, item_details.get(field)) + if field == "conversion_factor" and item.uom == item_details.get("stock_uom"): + item.set(field, item_details.get(field)) if not item.transfer_qty and item.qty: item.transfer_qty = flt( diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index f824787226b8..8703aefdda7f 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -2,8 +2,6 @@ # License: GNU General Public License v3. See license.txt -import unittest - import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings @@ -13,6 +11,7 @@ from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( create_item, + make_item, make_item_variant, set_item_variant_settings, ) @@ -1400,6 +1399,21 @@ def test_mapped_stock_entry(self): self.assertEqual(mapped_se.items[0].basic_rate, 100) self.assertEqual(mapped_se.items[0].basic_amount, 200) + def test_stock_entry_item_details(self): + item = make_item() + + se = make_stock_entry( + item_code=item.name, qty=1, to_warehouse="_Test Warehouse - _TC", do_not_submit=True + ) + + self.assertEqual(se.items[0].item_name, item.item_name) + se.items[0].item_name = "wat" + se.items[0].stock_uom = "Kg" + se.save() + + self.assertEqual(se.items[0].item_name, item.item_name) + self.assertEqual(se.items[0].stock_uom, item.stock_uom) + def make_serialized_item(**args): args = frappe._dict(args) From 90b1147365ed3f2175eda90994d88932c95d10ba Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 13 May 2022 14:08:22 +0530 Subject: [PATCH 05/48] fix: multiple entries for same payment term (cherry picked from commit e82609315054d220e72effa2d1a6d649af205aa9) --- .../payment_terms_status_for_sales_order.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py index cb22fb6a80f6..91f4a5e50a5c 100644 --- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py +++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py @@ -187,8 +187,9 @@ def get_so_with_invoices(filters): .on(soi.parent == so.name) .join(ps) .on(ps.parent == so.name) + .select(so.name) + .distinct() .select( - so.name, so.customer, so.transaction_date.as_("submitted"), ifelse(datediff(ps.due_date, functions.CurDate()) < 0, "Overdue", "Unpaid").as_("status"), From c17c260a65e56a9391328bfbf16206112b4b15a2 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 21 May 2022 14:28:27 +0530 Subject: [PATCH 06/48] fix: corrective job card creation (backport #31083) (#31084) * test: simplify job card tests (cherry picked from commit e625394488626e35f5f18af843554fe240e1f0d2) * fix: creation of corrective job card fails This used to fail because sub_operations is a child table that's not initalized by default till v13, in develop branch we init tables with empty list. (cherry picked from commit 66cf9aa3441d709c910204be1bedeea1437d7620) Co-authored-by: Ankush Menat --- .../doctype/job_card/job_card.py | 1 + .../doctype/job_card/test_job_card.py | 158 +++++++++++------- 2 files changed, 98 insertions(+), 61 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 983b3b17e689..fcdda33b7fb3 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -866,6 +866,7 @@ def set_missing_values(source, target): target.set("time_logs", []) target.set("employee", []) target.set("items", []) + target.set("sub_operations", []) target.set_sub_operations() target.get_required_items() target.validate_time_logs() diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 4647ddf05f7c..943bc9737724 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -2,14 +2,20 @@ # See license.txt import frappe -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import random_string +from frappe.utils.data import add_to_date, now -from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError +from erpnext.manufacturing.doctype.job_card.job_card import ( + OperationMismatchError, + OverlapError, + make_corrective_job_card, +) from erpnext.manufacturing.doctype.job_card.job_card import ( make_stock_entry as make_stock_entry_from_jc, ) from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record +from erpnext.manufacturing.doctype.work_order.work_order import WorkOrder from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -17,34 +23,36 @@ class TestJobCard(FrappeTestCase): def setUp(self): make_bom_for_jc_tests() - - transfer_material_against, source_warehouse = None, None - - tests_that_skip_setup = ("test_job_card_material_transfer_correctness",) - tests_that_transfer_against_jc = ( - "test_job_card_multiple_materials_transfer", - "test_job_card_excess_material_transfer", - "test_job_card_partial_material_transfer", - ) - - if self._testMethodName in tests_that_skip_setup: - return - - if self._testMethodName in tests_that_transfer_against_jc: - transfer_material_against = "Job Card" - source_warehouse = "Stores - _TC" - - self.work_order = make_wo_order_test_record( - item="_Test FG Item 2", - qty=2, - transfer_material_against=transfer_material_against, - source_warehouse=source_warehouse, - ) + self.transfer_material_against = "Work Order" + self.source_warehouse = None + self._work_order = None + + @property + def work_order(self) -> WorkOrder: + """Work Order lazily created for tests.""" + if not self._work_order: + self._work_order = make_wo_order_test_record( + item="_Test FG Item 2", + qty=2, + transfer_material_against=self.transfer_material_against, + source_warehouse=self.source_warehouse, + ) + return self._work_order + + def generate_required_stock(self, work_order: WorkOrder) -> None: + """Create twice the stock for all required items in work order.""" + for item in work_order.required_items: + make_stock_entry( + item_code=item.item_code, + target=item.source_warehouse or self.source_warehouse, + qty=item.required_qty * 2, + basic_rate=100, + ) def tearDown(self): frappe.db.rollback() - def test_job_card(self): + def test_job_card_operations(self): job_cards = frappe.get_all( "Job Card", filters={"work_order": self.work_order.name}, fields=["operation_id", "name"] @@ -58,9 +66,6 @@ def test_job_card(self): doc.operation_id = "Test Data" self.assertRaises(OperationMismatchError, doc.save) - for d in job_cards: - frappe.delete_doc("Job Card", d.name) - def test_job_card_with_different_work_station(self): job_cards = frappe.get_all( "Job Card", @@ -96,19 +101,11 @@ def test_job_card_with_different_work_station(self): ) self.assertEqual(completed_qty, job_card.for_quantity) - doc.cancel() - - for d in job_cards: - frappe.delete_doc("Job Card", d.name) - def test_job_card_overlap(self): wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2) - jc1_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name}) - jc2_name = frappe.db.get_value("Job Card", {"work_order": wo2.name}) - - jc1 = frappe.get_doc("Job Card", jc1_name) - jc2 = frappe.get_doc("Job Card", jc2_name) + jc1 = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name}) + jc2 = frappe.get_last_doc("Job Card", {"work_order": wo2.name}) employee = "_T-Employee-00001" # from test records @@ -137,10 +134,10 @@ def test_job_card_overlap(self): def test_job_card_multiple_materials_transfer(self): "Test transferring RMs separately against Job Card with multiple RMs." - make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=10, basic_rate=100) - make_stock_entry( - item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=6, basic_rate=100 - ) + self.transfer_material_against = "Job Card" + self.source_warehouse = "Stores - _TC" + + self.generate_required_stock(self.work_order) job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name}) job_card = frappe.get_doc("Job Card", job_card_name) @@ -167,22 +164,21 @@ def test_job_card_multiple_materials_transfer(self): def test_job_card_excess_material_transfer(self): "Test transferring more than required RM against Job Card." - make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100) - make_stock_entry( - item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100 - ) + self.transfer_material_against = "Job Card" + self.source_warehouse = "Stores - _TC" - job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name}) - job_card = frappe.get_doc("Job Card", job_card_name) + self.generate_required_stock(self.work_order) + + job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name}) self.assertEqual(job_card.status, "Open") # fully transfer both RMs - transfer_entry_1 = make_stock_entry_from_jc(job_card_name) + transfer_entry_1 = make_stock_entry_from_jc(job_card.name) transfer_entry_1.insert() transfer_entry_1.submit() # transfer extra qty of both RM due to previously damaged RM - transfer_entry_2 = make_stock_entry_from_jc(job_card_name) + transfer_entry_2 = make_stock_entry_from_jc(job_card.name) # deliberately change 'For Quantity' transfer_entry_2.fg_completed_qty = 1 transfer_entry_2.items[0].qty = 5 @@ -195,7 +191,7 @@ def test_job_card_excess_material_transfer(self): # Check if 'For Quantity' is negative # as 'transferred_qty' > Qty to Manufacture - transfer_entry_3 = make_stock_entry_from_jc(job_card_name) + transfer_entry_3 = make_stock_entry_from_jc(job_card.name) self.assertEqual(transfer_entry_3.fg_completed_qty, 0) job_card.append( @@ -210,17 +206,15 @@ def test_job_card_excess_material_transfer(self): def test_job_card_partial_material_transfer(self): "Test partial material transfer against Job Card" + self.transfer_material_against = "Job Card" + self.source_warehouse = "Stores - _TC" - make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100) - make_stock_entry( - item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100 - ) + self.generate_required_stock(self.work_order) - job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name}) - job_card = frappe.get_doc("Job Card", job_card_name) + job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name}) # partially transfer - transfer_entry = make_stock_entry_from_jc(job_card_name) + transfer_entry = make_stock_entry_from_jc(job_card.name) transfer_entry.fg_completed_qty = 1 transfer_entry.get_items() transfer_entry.insert() @@ -232,7 +226,7 @@ def test_job_card_partial_material_transfer(self): self.assertEqual(transfer_entry.items[1].qty, 3) # transfer remaining - transfer_entry_2 = make_stock_entry_from_jc(job_card_name) + transfer_entry_2 = make_stock_entry_from_jc(job_card.name) self.assertEqual(transfer_entry_2.fg_completed_qty, 1) self.assertEqual(transfer_entry_2.items[0].qty, 5) @@ -277,7 +271,49 @@ def test_job_card_material_transfer_correctness(self): self.assertEqual(transfer_entry.items[0].item_code, "_Test Item") self.assertEqual(transfer_entry.items[0].qty, 2) - # rollback via tearDown method + @change_settings( + "Manufacturing Settings", {"add_corrective_operation_cost_in_finished_good_valuation": 1} + ) + def test_corrective_costing(self): + job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name}) + + job_card.append( + "time_logs", + {"from_time": now(), "to_time": add_to_date(now(), hours=1), "completed_qty": 2}, + ) + job_card.submit() + + self.work_order.reload() + original_cost = self.work_order.total_operating_cost + + # Create a corrective operation against it + corrective_action = frappe.get_doc( + doctype="Operation", is_corrective_operation=1, name=frappe.generate_hash() + ).insert() + + corrective_job_card = make_corrective_job_card( + job_card.name, operation=corrective_action.name, for_operation=job_card.operation + ) + corrective_job_card.hour_rate = 100 + corrective_job_card.insert() + corrective_job_card.append( + "time_logs", + { + "from_time": add_to_date(now(), hours=2), + "to_time": add_to_date(now(), hours=2, minutes=30), + "completed_qty": 2, + }, + ) + corrective_job_card.submit() + + self.work_order.reload() + cost_after_correction = self.work_order.total_operating_cost + self.assertGreater(cost_after_correction, original_cost) + + corrective_job_card.cancel() + self.work_order.reload() + cost_after_cancel = self.work_order.total_operating_cost + self.assertEqual(cost_after_cancel, original_cost) def create_bom_with_multiple_operations(): From ba76b6419e9d5909a599aeb1ea9243fc37f5a769 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 23 May 2022 10:32:41 +0530 Subject: [PATCH 07/48] fix: Leave Encashment calculations (backport #31062) (#31091) Co-authored-by: Rucha Mahabal --- .../leave_encashment/leave_encashment.py | 34 +++++--- .../leave_encashment/test_leave_encashment.py | 87 +++++++++++++------ 2 files changed, 85 insertions(+), 36 deletions(-) diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index 0f655e3e0fc4..7c0f0db19756 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -7,7 +7,7 @@ from frappe.model.document import Document from frappe.utils import getdate, nowdate -from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves +from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry from erpnext.hr.utils import set_employee_name, validate_active_employee from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import ( @@ -107,7 +107,10 @@ def get_leave_details_for_encashment(self): self.leave_balance = ( allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count - - get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) + # adding this because the function returns a -ve number + + get_leaves_for_period( + self.employee, self.leave_type, allocation.from_date, self.encashment_date + ) ) encashable_days = self.leave_balance - frappe.db.get_value( @@ -126,14 +129,25 @@ def get_leave_details_for_encashment(self): return True def get_leave_allocation(self): - leave_allocation = frappe.db.sql( - """select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}' - between from_date and to_date and docstatus=1 and leave_type='{1}' - and employee= '{2}'""".format( - self.encashment_date or getdate(nowdate()), self.leave_type, self.employee - ), - as_dict=1, - ) # nosec + date = self.encashment_date or getdate() + + LeaveAllocation = frappe.qb.DocType("Leave Allocation") + leave_allocation = ( + frappe.qb.from_(LeaveAllocation) + .select( + LeaveAllocation.name, + LeaveAllocation.from_date, + LeaveAllocation.to_date, + LeaveAllocation.total_leaves_allocated, + LeaveAllocation.carry_forwarded_leaves_count, + ) + .where( + ((LeaveAllocation.from_date <= date) & (date <= LeaveAllocation.to_date)) + & (LeaveAllocation.docstatus == 1) + & (LeaveAllocation.leave_type == self.leave_type) + & (LeaveAllocation.employee == self.employee) + ) + ).run(as_dict=True) return leave_allocation[0] if leave_allocation else None diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index 83eb969feb01..d06b6a3764df 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -4,26 +4,42 @@ import unittest import frappe -from frappe.utils import add_months, today +from frappe.tests.utils import FrappeTestCase +from frappe.utils import add_days, get_year_ending, get_year_start, getdate from erpnext.hr.doctype.employee.test_employee import make_employee +from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import ( create_assignment_for_multiple_employees, ) +from erpnext.payroll.doctype.salary_slip.test_salary_slip import ( + make_holiday_list, + make_leave_application, +) from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure -test_dependencies = ["Leave Type"] +test_records = frappe.get_test_records("Leave Type") -class TestLeaveEncashment(unittest.TestCase): +class TestLeaveEncashment(FrappeTestCase): def setUp(self): - frappe.db.sql("""delete from `tabLeave Period`""") - frappe.db.sql("""delete from `tabLeave Policy Assignment`""") - frappe.db.sql("""delete from `tabLeave Allocation`""") - frappe.db.sql("""delete from `tabLeave Ledger Entry`""") - frappe.db.sql("""delete from `tabAdditional Salary`""") + frappe.db.delete("Leave Period") + frappe.db.delete("Leave Policy Assignment") + frappe.db.delete("Leave Allocation") + frappe.db.delete("Leave Ledger Entry") + frappe.db.delete("Additional Salary") + frappe.db.delete("Leave Encashment") + + if not frappe.db.exists("Leave Type", "_Test Leave Type Encashment"): + frappe.get_doc(test_records[2]).insert() + + date = getdate() + year_start = getdate(get_year_start(date)) + year_end = getdate(get_year_ending(date)) + + make_holiday_list("_Test Leave Encashment", year_start, year_end) # create the leave policy leave_policy = create_leave_policy( @@ -32,9 +48,9 @@ def setUp(self): leave_policy.submit() # create employee, salary structure and assignment - self.employee = make_employee("test_employee_encashment@example.com") + self.employee = make_employee("test_employee_encashment@example.com", company="_Test Company") - self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3)) + self.leave_period = create_leave_period(year_start, year_end, "_Test Company") data = { "assignment_based_on": "Leave Period", @@ -53,27 +69,15 @@ def setUp(self): other_details={"leave_encashment_amount_per_day": 50}, ) - def tearDown(self): - for dt in [ - "Leave Period", - "Leave Allocation", - "Leave Ledger Entry", - "Additional Salary", - "Leave Encashment", - "Salary Structure", - "Leave Policy", - ]: - frappe.db.sql("delete from `tab%s`" % dt) - + @set_holiday_list("_Test Leave Encashment", "_Test Company") def test_leave_balance_value_and_amount(self): - frappe.db.sql("""delete from `tabLeave Encashment`""") leave_encashment = frappe.get_doc( dict( doctype="Leave Encashment", employee=self.employee, leave_type="_Test Leave Type Encashment", leave_period=self.leave_period.name, - payroll_date=today(), + encashment_date=self.leave_period.to_date, currency="INR", ) ).insert() @@ -88,15 +92,46 @@ def test_leave_balance_value_and_amount(self): add_sal = frappe.get_all("Additional Salary", filters={"ref_docname": leave_encashment.name})[0] self.assertTrue(add_sal) + @set_holiday_list("_Test Leave Encashment", "_Test Company") + def test_leave_balance_value_with_leaves_and_amount(self): + date = self.leave_period.from_date + leave_application = make_leave_application( + self.employee, date, add_days(date, 3), "_Test Leave Type Encashment" + ) + leave_application.reload() + + leave_encashment = frappe.get_doc( + dict( + doctype="Leave Encashment", + employee=self.employee, + leave_type="_Test Leave Type Encashment", + leave_period=self.leave_period.name, + encashment_date=self.leave_period.to_date, + currency="INR", + ) + ).insert() + + self.assertEqual(leave_encashment.leave_balance, 10 - leave_application.total_leave_days) + # encashable days threshold is 5, total leaves are 6, so encashable days = 6-5 = 1 + # with charge of 50 per day + self.assertEqual(leave_encashment.encashable_days, leave_encashment.leave_balance - 5) + self.assertEqual(leave_encashment.encashment_amount, 50) + + leave_encashment.submit() + + # assert links + add_sal = frappe.get_all("Additional Salary", filters={"ref_docname": leave_encashment.name})[0] + self.assertTrue(add_sal) + + @set_holiday_list("_Test Leave Encashment", "_Test Company") def test_creation_of_leave_ledger_entry_on_submit(self): - frappe.db.sql("""delete from `tabLeave Encashment`""") leave_encashment = frappe.get_doc( dict( doctype="Leave Encashment", employee=self.employee, leave_type="_Test Leave Type Encashment", leave_period=self.leave_period.name, - payroll_date=today(), + encashment_date=self.leave_period.to_date, currency="INR", ) ).insert() From 0ab9fc0040baf02ed8ed6e7a8a5848f2542f38aa Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 23 May 2022 10:08:20 +0530 Subject: [PATCH 08/48] fix: Use directly and style it as button instead of using button Since few email servers (like outlook) strips out link in the button making them unclickable. (cherry picked from commit a29b92febc4397cebb251d4d3f34210e4fb85c21) --- .../emails/request_for_quotation.html | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/erpnext/templates/emails/request_for_quotation.html b/erpnext/templates/emails/request_for_quotation.html index 3283987fab05..5b073e604ff9 100644 --- a/erpnext/templates/emails/request_for_quotation.html +++ b/erpnext/templates/emails/request_for_quotation.html @@ -1,24 +1,29 @@

{{_("Request for Quotation")}}

{{ supplier_salutation if supplier_salutation else ''}} {{ supplier_name }},

{{ message }}

-

{{_("The Request for Quotation can be accessed by clicking on the following button")}}:

-

- -


- -

{{_("Regards")}},
-{{ user_fullname }}


- +
+ + {{ _("Submit your Quotation") }} + +
+
{% if update_password_link %} - +

{{_("Please click on the following button to set your new password")}}:

+ + {{_("Set Password") }} + +
+
+{% endif %}

- + {{_("Regards")}},
+ {{ user_fullname }}

- -{% endif %} From ddee0893e62d3392d288455c963efc93d0b126b3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 23 May 2022 12:02:30 +0530 Subject: [PATCH 09/48] fix translation German "Designation" (backport #31082) (#31093) fix translation German "Designation" (#31082) changed "Bezeichnung" to "Position" as the is more precice in the field of employment which erpnext refers to here (cherry picked from commit 348a674df968dd9fe754fc8e85eef9cb51c227f4) Co-authored-by: Wolfram Schmidt --- erpnext/translations/de.csv | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 329fd3b1b9cb..545d0dde0447 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -1695,7 +1695,7 @@ No Permission,Keine Berechtigung, No Remarks,Keine Anmerkungen, No Result to submit,Kein Ergebnis zur Einreichung, No Salary Structure assigned for Employee {0} on given date {1},Keine Gehaltsstruktur für Mitarbeiter {0} am angegebenen Datum {1} zugewiesen, -No Staffing Plans found for this Designation,Für diese Bezeichnung wurden keine Stellenpläne gefunden, +No Staffing Plans found for this Designation,Für diese Position wurden keine Stellenpläne gefunden, No Student Groups created.,Keine Studentengruppen erstellt., No Students in,Keine Studenten in, No Tax Withholding data found for the current Fiscal Year.,Keine Steuerverweigerungsdaten für das aktuelle Geschäftsjahr gefunden., @@ -2021,7 +2021,7 @@ Please select BOM in BOM field for Item {0},Bitte aus dem Stücklistenfeld eine Please select Category first,Bitte zuerst Kategorie auswählen, Please select Charge Type first,Bitte zuerst Chargentyp auswählen, Please select Company,Bitte Unternehmen auswählen, -Please select Company and Designation,Bitte wählen Sie Unternehmen und Stelle, +Please select Company and Designation,Bitte wählen Sie Unternehmen und Position, Please select Company and Posting Date to getting entries,"Bitte wählen Sie Unternehmen und Buchungsdatum, um Einträge zu erhalten", Please select Company first,Bitte zuerst Unternehmen auswählen, Please select Completion Date for Completed Asset Maintenance Log,Bitte wählen Sie Fertigstellungsdatum für das abgeschlossene Wartungsprotokoll für den Vermögenswert, @@ -2765,7 +2765,7 @@ Split,Teilt, Split Batch,Split Batch, Split Issue,Split-Problem, Sports,Sport, -Staffing Plan {0} already exist for designation {1},Personalplan {0} existiert bereits für Bezeichnung {1}, +Staffing Plan {0} already exist for designation {1},Personalplan {0} existiert bereits für Position {1}, Standard,Standard, Standard Buying,Standard-Kauf, Standard Selling,Standard-Vertrieb, @@ -3703,7 +3703,7 @@ Delivered Quantity,Gelieferte Menge, Delivery Notes,Lieferscheine, Depreciated Amount,Abschreibungsbetrag, Description,Beschreibung, -Designation,Bezeichnung, +Designation,Position, Difference Value,Differenzwert, Dimension Filter,Dimensionsfilter, Disabled,Deaktiviert, @@ -3913,7 +3913,7 @@ Please enter Difference Account or set default Stock Adjustment Accoun Please enter GSTIN and state for the Company Address {0},Bitte geben Sie GSTIN ein und geben Sie die Firmenadresse {0} an., Please enter Item Code to get item taxes,"Bitte geben Sie den Artikelcode ein, um die Artikelsteuern zu erhalten", Please enter Warehouse and Date,Bitte geben Sie Lager und Datum ein, -Please enter the designation,Bitte geben Sie die Bezeichnung ein, +Please enter the designation,Bitte geben Sie die Position ein, Please login as a Marketplace User to edit this item.,"Bitte melden Sie sich als Marketplace-Benutzer an, um diesen Artikel zu bearbeiten.", Please login as a Marketplace User to report this item.,"Bitte melden Sie sich als Marketplace-Benutzer an, um diesen Artikel zu melden.", Please select Template Type to download template,"Bitte wählen Sie Vorlagentyp , um die Vorlage herunterzuladen", @@ -6235,7 +6235,7 @@ Checking this will create Lab Test(s) specified in the Sales Invoice on submissi Create Sample Collection document for Lab Test,Erstellen Sie ein Probensammeldokument für den Labortest, Checking this will create a Sample Collection document every time you create a Lab Test,"Wenn Sie dies aktivieren, wird jedes Mal, wenn Sie einen Labortest erstellen, ein Probensammeldokument erstellt", Employee name and designation in print,Name und Bezeichnung des Mitarbeiters im Druck, -Check this if you want the Name and Designation of the Employee associated with the User who submits the document to be printed in the Lab Test Report.,"Aktivieren Sie diese Option, wenn Sie möchten, dass der Name und die Bezeichnung des Mitarbeiters, der dem Benutzer zugeordnet ist, der das Dokument einreicht, im Labortestbericht gedruckt werden.", +Check this if you want the Name and Designation of the Employee associated with the User who submits the document to be printed in the Lab Test Report.,"Aktivieren Sie diese Option, wenn Sie möchten, dass der Name und die Position des Mitarbeiters, der dem Benutzer zugeordnet ist, der das Dokument einreicht, im Labortestbericht gedruckt werden.", Do not print or email Lab Tests without Approval,Drucken oder senden Sie Labortests nicht ohne Genehmigung per E-Mail, Checking this will restrict printing and emailing of Lab Test documents unless they have the status as Approved.,"Wenn Sie dies aktivieren, wird das Drucken und E-Mailen von Labortestdokumenten eingeschränkt, sofern diese nicht den Status "Genehmigt" haben.", Custom Signature in Print,Kundenspezifische Unterschrift im Druck, @@ -6491,7 +6491,7 @@ Department Approver,Abteilungsgenehmiger, Approver,Genehmiger, Required Skills,Benötigte Fähigkeiten, Skills,Kompetenzen, -Designation Skill,Bezeichnung Fähigkeit, +Designation Skill,Positions Fähigkeit, Skill,Fertigkeit, Driver,Fahrer/-in, HR-DRI-.YYYY.-,HR-DRI-.YYYY.-, @@ -6790,7 +6790,7 @@ Select Employees,Mitarbeiter auswählen, Employment Type (optional),Anstellungsart (optional), Branch (optional),Zweigstelle (optional), Department (optional),Abteilung (optional), -Designation (optional),Bezeichnung (optional), +Designation (optional),Position (optional), Employee Grade (optional),Dienstgrad (optional), Employee (optional),Mitarbeiter (optional), Allocate Leaves,Blätter zuweisen, @@ -7761,7 +7761,7 @@ Authorized Value,Autorisierter Wert, Applicable To (Role),Anwenden auf (Rolle), Applicable To (Employee),Anwenden auf (Mitarbeiter), Applicable To (User),Anwenden auf (Benutzer), -Applicable To (Designation),Anwenden auf (Bezeichnung), +Applicable To (Designation),Anwenden auf (Position), Approving Role (above authorized value),Genehmigende Rolle (über dem autorisierten Wert), Approving User (above authorized value),Genehmigender Benutzer (über dem autorisierten Wert), Brand Defaults,Markenstandards, @@ -8937,7 +8937,7 @@ Requesting Practitioner,Praktizierender anfordern, Requesting Department,Abteilung anfordern, Employee (Lab Technician),Mitarbeiter (Labortechniker), Lab Technician Name,Name des Labortechnikers, -Lab Technician Designation,Bezeichnung des Labortechnikers, +Lab Technician Designation,Position des Labortechnikers, Compound Test Result,Zusammengesetztes Testergebnis, Organism Test Result,Organismustestergebnis, Sensitivity Test Result,Empfindlichkeitstestergebnis, From f6b2f36ca80c85490a12a5c61a9042028fff59ae Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 23 May 2022 15:13:13 +0530 Subject: [PATCH 10/48] test: search test failing because of stale data (backport #31098) (#31099) test: search test failing because of stale data (#31098) (cherry picked from commit a36174afdf09e70e636aca2125a6cb092fb24735) Co-authored-by: Ankush Menat --- erpnext/tests/test_search.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/tests/test_search.py b/erpnext/tests/test_search.py index ffe9a5ae5410..3685828667ca 100644 --- a/erpnext/tests/test_search.py +++ b/erpnext/tests/test_search.py @@ -8,6 +8,7 @@ class TestSearch(unittest.TestCase): # Search for the word "cond", part of the word "conduire" (Lead) in french. def test_contact_search_in_foreign_language(self): try: + frappe.local.lang_full_dict = None # reset cached translations frappe.local.lang = "fr" output = filter_dynamic_link_doctypes( "DocType", "cond", "name", 0, 20, {"fieldtype": "HTML", "fieldname": "contact_html"} From 3984f04a49649b168c4a6cef29c444e25d1415a3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 23 May 2022 15:20:47 +0530 Subject: [PATCH 11/48] fix: Job Card excess transfer behaviour (backport #31054) (#31096) * fix: Job Card excess transfer behaviour - Block excess transfer of items if not allowed in settings - Behaviour made consistent with js behaviour (button disappears if not pending and not allowed in settings) - Test for same case (cherry picked from commit e07ce6efe0afde1bdbade6cbed9f53ac0dd236f0) # Conflicts: # erpnext/manufacturing/doctype/job_card/test_job_card.py * chore: Run `_validate_over_transfer` only if excess transfer is blocked in settings (cherry picked from commit 9f6e10663b77489ba1f98ede96e30c23682c111a) * chore: conflicts * chore: missing conflict resolution changes Co-authored-by: marination Co-authored-by: Ankush Menat --- .../doctype/job_card/job_card.py | 51 +++++++++++++++---- .../doctype/job_card/test_job_card.py | 26 ++++++++++ .../stock/doctype/stock_entry/stock_entry.py | 2 +- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index fcdda33b7fb3..cc2f8c60e586 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -42,6 +42,10 @@ class JobCardCancelError(frappe.ValidationError): pass +class JobCardOverTransferError(frappe.ValidationError): + pass + + class JobCard(Document): def onload(self): excess_transfer = frappe.db.get_single_value( @@ -522,23 +526,50 @@ def get_current_operation_data(self): }, ) - def set_transferred_qty_in_job_card(self, ste_doc): + def set_transferred_qty_in_job_card_item(self, ste_doc): + from frappe.query_builder.functions import Sum + + def _validate_over_transfer(row, transferred_qty): + "Block over transfer of items if not allowed in settings." + required_qty = frappe.db.get_value("Job Card Item", row.job_card_item, "required_qty") + is_excess = flt(transferred_qty) > flt(required_qty) + if is_excess: + frappe.throw( + _( + "Row #{0}: Cannot transfer more than Required Qty {1} for Item {2} against Job Card {3}" + ).format( + row.idx, frappe.bold(required_qty), frappe.bold(row.item_code), ste_doc.job_card + ), + title=_("Excess Transfer"), + exc=JobCardOverTransferError, + ) + for row in ste_doc.items: if not row.job_card_item: continue - qty = frappe.db.sql( - """ SELECT SUM(qty) from `tabStock Entry Detail` sed, `tabStock Entry` se - WHERE sed.job_card_item = %s and se.docstatus = 1 and sed.parent = se.name and - se.purpose = 'Material Transfer for Manufacture' - """, - (row.job_card_item), - )[0][0] + sed = frappe.qb.DocType("Stock Entry Detail") + se = frappe.qb.DocType("Stock Entry") + transferred_qty = ( + frappe.qb.from_(sed) + .join(se) + .on(sed.parent == se.name) + .select(Sum(sed.qty)) + .where( + (sed.job_card_item == row.job_card_item) + & (se.docstatus == 1) + & (se.purpose == "Material Transfer for Manufacture") + ) + ).run()[0][0] + + allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer") + if not allow_excess: + _validate_over_transfer(row, transferred_qty) - frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(qty)) + frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty)) def set_transferred_qty(self, update_status=False): - "Set total FG Qty for which RM was transferred." + "Set total FG Qty in Job Card for which RM was transferred." if not self.items: self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0 diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 943bc9737724..b5371af2ccbd 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -7,6 +7,7 @@ from frappe.utils.data import add_to_date, now from erpnext.manufacturing.doctype.job_card.job_card import ( + JobCardOverTransferError, OperationMismatchError, OverlapError, make_corrective_job_card, @@ -162,6 +163,7 @@ def test_job_card_multiple_materials_transfer(self): # transfer was made for 2 fg qty in first transfer Stock Entry self.assertEqual(transfer_entry_2.fg_completed_qty, 0) + @change_settings("Manufacturing Settings", {"job_card_excess_transfer": 1}) def test_job_card_excess_material_transfer(self): "Test transferring more than required RM against Job Card." self.transfer_material_against = "Job Card" @@ -204,6 +206,30 @@ def test_job_card_excess_material_transfer(self): # JC is Completed with excess transfer self.assertEqual(job_card.status, "Completed") + @change_settings("Manufacturing Settings", {"job_card_excess_transfer": 0}) + def test_job_card_excess_material_transfer_block(self): + + self.transfer_material_against = "Job Card" + self.source_warehouse = "Stores - _TC" + + self.generate_required_stock(self.work_order) + + job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name}) + + # fully transfer both RMs + transfer_entry_1 = make_stock_entry_from_jc(job_card_name) + transfer_entry_1.insert() + transfer_entry_1.submit() + + # transfer extra qty of both RM due to previously damaged RM + transfer_entry_2 = make_stock_entry_from_jc(job_card_name) + # deliberately change 'For Quantity' + transfer_entry_2.fg_completed_qty = 1 + transfer_entry_2.items[0].qty = 5 + transfer_entry_2.items[1].qty = 3 + transfer_entry_2.insert() + self.assertRaises(JobCardOverTransferError, transfer_entry_2.submit) + def test_job_card_partial_material_transfer(self): "Test partial material transfer against Job Card" self.transfer_material_against = "Job Card" diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 70161d763390..52011afefd18 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1137,7 +1137,7 @@ def _validate_work_order(pro_doc): if self.job_card: job_doc = frappe.get_doc("Job Card", self.job_card) job_doc.set_transferred_qty(update_status=True) - job_doc.set_transferred_qty_in_job_card(self) + job_doc.set_transferred_qty_in_job_card_item(self) if self.work_order: pro_doc = frappe.get_doc("Work Order", self.work_order) From c2a08f1285638c8f0cfa267fee5e306407b4dc73 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 24 May 2022 10:41:50 +0530 Subject: [PATCH 12/48] chore: error logging for auto material requests (backport #31103) (#31105) chore: error logging for auto material requests (#31103) (cherry picked from commit ecb39d81e021f786f5a4ded3c344cf4f5c71bc26) Co-authored-by: Ankush Menat --- erpnext/stock/reorder_item.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py index a96ffefd474b..f2594f65fab9 100644 --- a/erpnext/stock/reorder_item.py +++ b/erpnext/stock/reorder_item.py @@ -253,11 +253,14 @@ def notify_errors(exceptions_list): ) for exception in exceptions_list: - exception = json.loads(exception) - error_message = """
{0}

""".format( - _(exception.get("message")) - ) - content += error_message + try: + exception = json.loads(exception) + error_message = """
{0}

""".format( + _(exception.get("message")) + ) + content += error_message + except Exception: + pass content += _("Regards,") + "
" + _("Administrator") From 42e7a86a3b86672dc1cd102b7452ce1b85ce2957 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 18 Jan 2022 14:36:22 +0530 Subject: [PATCH 13/48] feat(Employee Advance): add 'Returned' and 'Partly Claimed and Returned' status --- .../employee_advance/employee_advance.json | 42 +++++++++++++++-- .../employee_advance/employee_advance.py | 47 ++++++++++++++----- .../hr/doctype/expense_claim/expense_claim.js | 2 +- .../hr/doctype/expense_claim/expense_claim.py | 34 +++++++------- 4 files changed, 92 insertions(+), 33 deletions(-) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json index 3a561216ccae..1b838ad98638 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.json +++ b/erpnext/hr/doctype/employee_advance/employee_advance.json @@ -2,7 +2,7 @@ "actions": [], "allow_import": 1, "autoname": "naming_series:", - "creation": "2017-10-09 14:26:29.612365", + "creation": "2022-01-17 18:36:51.450395", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", @@ -121,7 +121,7 @@ "fieldtype": "Select", "label": "Status", "no_copy": 1, - "options": "Draft\nPaid\nUnpaid\nClaimed\nCancelled", + "options": "Draft\nPaid\nUnpaid\nClaimed\nReturned\nPartly Claimed and Returned\nCancelled", "read_only": 1 }, { @@ -200,7 +200,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-09-11 18:38:38.617478", + "modified": "2022-01-17 19:33:52.345823", "modified_by": "Administrator", "module": "HR", "name": "Employee Advance", @@ -236,5 +236,41 @@ "search_fields": "employee,employee_name", "sort_field": "modified", "sort_order": "DESC", + "states": [ + { + "color": "Red", + "custom": 1, + "title": "Draft" + }, + { + "color": "Green", + "custom": 1, + "title": "Paid" + }, + { + "color": "Orange", + "custom": 1, + "title": "Unpaid" + }, + { + "color": "Blue", + "custom": 1, + "title": "Claimed" + }, + { + "color": "Gray", + "title": "Returned" + }, + { + "color": "Yellow", + "title": "Partly Claimed and Returned" + }, + { + "color": "Red", + "custom": 1, + "title": "Cancelled" + } + ], + "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index 3d4023d3195a..7605d209ad14 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -30,18 +30,41 @@ def validate(self): def on_cancel(self): self.ignore_linked_doctypes = "GL Entry" - def set_status(self): + def set_status(self, update=False): + precision = self.precision("paid_amount") + total_amount = flt(flt(self.claimed_amount) + flt(self.return_amount), precision) + status = None + if self.docstatus == 0: - self.status = "Draft" - if self.docstatus == 1: - if self.claimed_amount and flt(self.claimed_amount) == flt(self.paid_amount): - self.status = "Claimed" - elif self.paid_amount and self.advance_amount == flt(self.paid_amount): - self.status = "Paid" + status = "Draft" + elif self.docstatus == 1: + if flt(self.claimed_amount) > 0 and flt(self.claimed_amount, precision) == flt( + self.paid_amount, precision + ): + status = "Claimed" + elif flt(self.return_amount) > 0 and flt(self.return_amount, precision) == flt( + self.paid_amount, precision + ): + status = "Returned" + elif ( + flt(self.claimed_amount) > 0 + and (flt(self.return_amount) > 0) + and total_amount == flt(self.paid_amount, precision) + ): + status = "Partly Claimed and Returned" + elif flt(self.paid_amount) > 0 and flt(self.advance_amount, precision) == flt( + self.paid_amount, precision + ): + status = "Paid" else: - self.status = "Unpaid" + status = "Unpaid" elif self.docstatus == 2: - self.status = "Cancelled" + status = "Cancelled" + + if update: + self.db_set("status", status) + else: + self.status = status def set_total_advance_paid(self): gle = frappe.qb.DocType("GL Entry") @@ -89,8 +112,7 @@ def set_total_advance_paid(self): self.db_set("paid_amount", paid_amount) self.db_set("return_amount", return_amount) - self.set_status() - frappe.db.set_value("Employee Advance", self.name, "status", self.status) + self.set_status(update=True) def update_claimed_amount(self): claimed_amount = ( @@ -112,8 +134,7 @@ def update_claimed_amount(self): frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount)) self.reload() - self.set_status() - frappe.db.set_value("Employee Advance", self.name, "status", self.status) + self.set_status(update=True) @frappe.whitelist() diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 047945787d7b..af80b63845e8 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -171,7 +171,7 @@ frappe.ui.form.on("Expense Claim", { ['docstatus', '=', 1], ['employee', '=', frm.doc.employee], ['paid_amount', '>', 0], - ['status', '!=', 'Claimed'] + ['status', 'not in', ['Claimed', 'Returned', 'Partly Claimed and Returned']] ] }; }); diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 311a1eb81c80..89d86c1bc7ce 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -414,25 +414,27 @@ def get_expense_claim_account(expense_claim_type, company): @frappe.whitelist() def get_advances(employee, advance_id=None): + advance = frappe.qb.DocType("Employee Advance") + + query = frappe.qb.from_(advance).select( + advance.name, + advance.posting_date, + advance.paid_amount, + advance.claimed_amount, + advance.advance_account, + ) + if not advance_id: - condition = "docstatus=1 and employee={0} and paid_amount > 0 and paid_amount > claimed_amount + return_amount".format( - frappe.db.escape(employee) + query = query.where( + (advance.docstatus == 1) + & (advance.employee == employee) + & (advance.paid_amount > 0) + & (advance.status.notin(["Claimed", "Returned", "Partly Claimed and Returned"])) ) else: - condition = "name={0}".format(frappe.db.escape(advance_id)) - - return frappe.db.sql( - """ - select - name, posting_date, paid_amount, claimed_amount, advance_account - from - `tabEmployee Advance` - where {0} - """.format( - condition - ), - as_dict=1, - ) + query = query.where(advance.name == advance_id) + + return query.run(as_dict=True) @frappe.whitelist() From cac9e245b62a4f35c78720c2a250ce5c687be35d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 18 Jan 2022 14:36:38 +0530 Subject: [PATCH 14/48] patch: Employee Advance return statuses --- erpnext/patches.txt | 2 ++ .../v13_0/update_employee_advance_status.py | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 erpnext/patches/v13_0/update_employee_advance_status.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1ff33c7f7bfb..85780501def6 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -366,3 +366,5 @@ erpnext.patches.v13_0.education_deprecation_warning erpnext.patches.v13_0.requeue_recoverable_reposts erpnext.patches.v13_0.create_accounting_dimensions_in_orders erpnext.patches.v13_0.set_per_billed_in_return_delivery_note +erpnext.patches.v13_0.update_employee_advance_status + diff --git a/erpnext/patches/v13_0/update_employee_advance_status.py b/erpnext/patches/v13_0/update_employee_advance_status.py new file mode 100644 index 000000000000..fc9e05e836d2 --- /dev/null +++ b/erpnext/patches/v13_0/update_employee_advance_status.py @@ -0,0 +1,29 @@ +import frappe + + +def execute(): + frappe.reload_doc("hr", "doctype", "employee_advance") + + advance = frappe.qb.DocType("Employee Advance") + ( + frappe.qb.update(advance) + .set(advance.status, "Returned") + .where( + (advance.docstatus == 1) + & ((advance.return_amount) & (advance.paid_amount == advance.return_amount)) + & (advance.status == "Paid") + ) + ).run() + + ( + frappe.qb.update(advance) + .set(advance.status, "Partly Claimed and Returned") + .where( + (advance.docstatus == 1) + & ( + (advance.claimed_amount & advance.return_amount) + & (advance.paid_amount == (advance.return_amount + advance.claimed_amount)) + ) + & (advance.status == "Paid") + ) + ).run() From d59c3d21423639c13055309ec30bfc2b04c44545 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 18 Jan 2022 14:38:16 +0530 Subject: [PATCH 15/48] fix: employee advance status update on return via additional salary --- erpnext/payroll/doctype/additional_salary/additional_salary.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index f57d9d37cf1e..18bd3b7733c0 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -124,6 +124,8 @@ def update_return_amount_in_employee_advance(self): return_amount += self.amount frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", return_amount) + advance = frappe.get_doc("Employee Advance", self.ref_docname) + advance.set_status(update=True) def update_employee_referral(self, cancel=False): if self.ref_doctype == "Employee Referral": From 806752111eadbe7b3679d13620c64dc38488ae50 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 18 Jan 2022 18:35:25 +0530 Subject: [PATCH 16/48] test: employee advance status --- .../employee_advance/employee_advance.py | 2 + .../employee_advance/test_employee_advance.py | 77 +++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index 7605d209ad14..2378482364df 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -286,6 +286,7 @@ def make_return_entry( "party_type": "Employee", "party": employee, "is_advance": "Yes", + "cost_center": erpnext.get_default_cost_center(company), }, ) @@ -303,6 +304,7 @@ def make_return_entry( "account_currency": bank_cash_account.account_currency, "account_type": bank_cash_account.account_type, "exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1, + "cost_center": erpnext.get_default_cost_center(company), }, ) diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py index 9b006ffcffe5..a8ff6a794d6c 100644 --- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py @@ -12,8 +12,13 @@ EmployeeAdvanceOverPayment, create_return_through_additional_salary, make_bank_entry, + make_return_entry, ) from erpnext.hr.doctype.expense_claim.expense_claim import get_advances +from erpnext.hr.doctype.expense_claim.test_expense_claim import ( + get_payable_account, + make_expense_claim, +) from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure @@ -53,9 +58,79 @@ def test_paid_amount_on_pe_cancellation(self): self.assertEqual(advance.paid_amount, 0) self.assertEqual(advance.status, "Unpaid") + def test_claimed_and_returned_status(self): + # Claimed Status check, full amount claimed + payable_account = get_payable_account("_Test Company") + claim = make_expense_claim( + payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True + ) + + advance = make_employee_advance(claim.employee) + pe = make_payment_entry(advance) + pe.submit() + + claim = get_advances_for_claim(claim, advance.name) + claim.save() + claim.submit() + + advance.reload() + self.assertEqual(advance.claimed_amount, 1000) + self.assertEqual(advance.status, "Claimed") + + # cancel claim; status should be Paid + claim.cancel() + advance.reload() + self.assertEqual(advance.claimed_amount, 0) + self.assertEqual(advance.status, "Paid") + + # Partly Claimed and Returned status check + # 500 Claimed, 500 Returned + claim = make_expense_claim( + payable_account, 500, 500, "_Test Company", "Travel Expenses - _TC", do_not_submit=True + ) + + advance = make_employee_advance(claim.employee) + pe = make_payment_entry(advance) + pe.submit() + + claim = get_advances_for_claim(claim, advance.name, amount=500) + claim.save() + claim.submit() + + advance.reload() + self.assertEqual(advance.claimed_amount, 500) + self.assertEqual(advance.status, "Paid") + + entry = make_return_entry( + employee=advance.employee, + company=advance.company, + employee_advance_name=advance.name, + return_amount=flt(advance.paid_amount - advance.claimed_amount), + advance_account=advance.advance_account, + mode_of_payment=advance.mode_of_payment, + currency=advance.currency, + exchange_rate=advance.exchange_rate, + ) + + entry = frappe.get_doc(entry) + entry.insert() + entry.submit() + + advance.reload() + self.assertEqual(advance.return_amount, 500) + self.assertEqual(advance.status, "Partly Claimed and Returned") + + # Cancel return entry; status should change to Paid + entry.cancel() + advance.reload() + self.assertEqual(advance.return_amount, 0) + self.assertEqual(advance.status, "Paid") + def test_repay_unclaimed_amount_from_salary(self): employee_name = make_employee("_T@employe.advance") advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1}) + pe = make_payment_entry(advance) + pe.submit() args = {"type": "Deduction"} create_salary_component("Advance Salary - Deduction", **args) @@ -85,11 +160,13 @@ def test_repay_unclaimed_amount_from_salary(self): advance.reload() self.assertEqual(advance.return_amount, 1000) + self.assertEqual(advance.status, "Returned") # update advance return amount on additional salary cancellation additional_salary.cancel() advance.reload() self.assertEqual(advance.return_amount, 700) + self.assertEqual(advance.status, "Paid") def tearDown(self): frappe.db.rollback() From b265ca467c6ff2d406786fb43b1c96281644b007 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 1 Mar 2022 21:40:39 +0530 Subject: [PATCH 17/48] test: test advance filters in expense claim and cancelled status --- .../employee_advance/employee_advance.py | 1 + .../employee_advance/test_employee_advance.py | 40 +++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index 2378482364df..c1876b117576 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -29,6 +29,7 @@ def validate(self): def on_cancel(self): self.ignore_linked_doctypes = "GL Entry" + self.set_status(update=True) def set_status(self, update=False): precision = self.precision("paid_amount") diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py index a8ff6a794d6c..44d68c948335 100644 --- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py @@ -24,6 +24,9 @@ class TestEmployeeAdvance(unittest.TestCase): + def setUp(self): + frappe.db.delete("Employee Advance") + def test_paid_amount_and_status(self): employee_name = make_employee("_T@employe.advance") advance = make_employee_advance(employee_name) @@ -58,8 +61,12 @@ def test_paid_amount_on_pe_cancellation(self): self.assertEqual(advance.paid_amount, 0) self.assertEqual(advance.status, "Unpaid") - def test_claimed_and_returned_status(self): - # Claimed Status check, full amount claimed + advance.cancel() + advance.reload() + self.assertEqual(advance.status, "Cancelled") + + def test_claimed_status(self): + # CLAIMED Status check, full amount claimed payable_account = get_payable_account("_Test Company") claim = make_expense_claim( payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True @@ -77,13 +84,28 @@ def test_claimed_and_returned_status(self): self.assertEqual(advance.claimed_amount, 1000) self.assertEqual(advance.status, "Claimed") + # advance should not be shown in claims + advances = get_advances(claim.employee) + advances = [entry.name for entry in advances] + self.assertTrue(advance.name not in advances) + # cancel claim; status should be Paid claim.cancel() advance.reload() self.assertEqual(advance.claimed_amount, 0) self.assertEqual(advance.status, "Paid") - # Partly Claimed and Returned status check + def test_partly_claimed_and_returned_status(self): + payable_account = get_payable_account("_Test Company") + claim = make_expense_claim( + payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True + ) + + advance = make_employee_advance(claim.employee) + pe = make_payment_entry(advance) + pe.submit() + + # PARTLY CLAIMED AND RETURNED status check # 500 Claimed, 500 Returned claim = make_expense_claim( payable_account, 500, 500, "_Test Company", "Travel Expenses - _TC", do_not_submit=True @@ -120,12 +142,22 @@ def test_claimed_and_returned_status(self): self.assertEqual(advance.return_amount, 500) self.assertEqual(advance.status, "Partly Claimed and Returned") - # Cancel return entry; status should change to Paid + # advance should not be shown in claims + advances = get_advances(claim.employee) + advances = [entry.name for entry in advances] + self.assertTrue(advance.name not in advances) + + # Cancel return entry; status should change to PAID entry.cancel() advance.reload() self.assertEqual(advance.return_amount, 0) self.assertEqual(advance.status, "Paid") + # advance should be shown in claims + advances = get_advances(claim.employee) + advances = [entry.name for entry in advances] + self.assertTrue(advance.name in advances) + def test_repay_unclaimed_amount_from_salary(self): employee_name = make_employee("_T@employe.advance") advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1}) From 78e9e66d63955b95e6b2f1c10b92c55973207234 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 23 May 2022 19:49:05 +0530 Subject: [PATCH 18/48] fix: replace document states with list settings --- .../employee_advance/employee_advance.json | 37 +------------------ .../employee_advance/employee_advance_list.js | 15 ++++++++ 2 files changed, 16 insertions(+), 36 deletions(-) create mode 100644 erpnext/hr/doctype/employee_advance/employee_advance_list.js diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json index 1b838ad98638..8b2eea113379 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.json +++ b/erpnext/hr/doctype/employee_advance/employee_advance.json @@ -200,7 +200,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2022-01-17 19:33:52.345823", + "modified": "2022-05-23 19:33:52.345823", "modified_by": "Administrator", "module": "HR", "name": "Employee Advance", @@ -236,41 +236,6 @@ "search_fields": "employee,employee_name", "sort_field": "modified", "sort_order": "DESC", - "states": [ - { - "color": "Red", - "custom": 1, - "title": "Draft" - }, - { - "color": "Green", - "custom": 1, - "title": "Paid" - }, - { - "color": "Orange", - "custom": 1, - "title": "Unpaid" - }, - { - "color": "Blue", - "custom": 1, - "title": "Claimed" - }, - { - "color": "Gray", - "title": "Returned" - }, - { - "color": "Yellow", - "title": "Partly Claimed and Returned" - }, - { - "color": "Red", - "custom": 1, - "title": "Cancelled" - } - ], "title_field": "employee_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_advance/employee_advance_list.js b/erpnext/hr/doctype/employee_advance/employee_advance_list.js new file mode 100644 index 000000000000..433669a71f3c --- /dev/null +++ b/erpnext/hr/doctype/employee_advance/employee_advance_list.js @@ -0,0 +1,15 @@ +frappe.listview_settings["Employee Advance"] = { + get_indicator: function(doc) { + let status_color = { + "Draft": "red", + "Submitted": "blue", + "Cancelled": "red", + "Paid": "green", + "Unpaid": "orange", + "Claimed": "blue", + "Returned": "gray", + "Partly Claimed and Returned": "yellow" + }; + return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; + } +}; \ No newline at end of file From e9968cc6fc3589467794fa4bbe0c41fbba1e7af2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 24 May 2022 14:32:56 +0530 Subject: [PATCH 19/48] chore: disable feed for material request --- erpnext/stock/doctype/material_request/material_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 55c9ac47e4c8..3474ca0db683 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -24,7 +24,7 @@ class MaterialRequest(BuyingController): def get_feed(self): - return _("{0}: {1}").format(self.status, self.material_request_type) + return def check_if_already_pulled(self): pass From 34928d29f1ff8c823217b9d5b7b751a7ab6c5b0e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 24 May 2022 18:14:06 +0530 Subject: [PATCH 20/48] fix: remove bad default for Membership From Date --- erpnext/non_profit/doctype/membership/membership.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index f29005a6d4b9..835e2db85198 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -61,10 +61,6 @@ def validate_membership_period(self): frappe.throw(_("You can only renew if your membership expires within 30 days")) self.from_date = add_days(last_membership.to_date, 1) - elif frappe.session.user == "Administrator": - self.from_date = self.from_date - else: - self.from_date = nowdate() if frappe.db.get_single_value("Non Profit Settings", "billing_cycle") == "Yearly": self.to_date = add_years(self.from_date, 1) From 168a9d417b57e6441da7e9446100e0de62aeaea5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 24 May 2022 18:16:56 +0530 Subject: [PATCH 21/48] fix: don't capture payments with invoice_id as donations - if donations and subscriptions are set up in the same dashboard, membership payments also trigger payment webhook - in order to differentiate there is already a check for RP's auto generated description but if subscriptions are configured using subscription links, RP doesn't send descriptions - use invoice_id to ignore such payments instead --- erpnext/non_profit/doctype/donation/donation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index 8e5ac5b61bf4..ed4b3d05b3ff 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -100,7 +100,9 @@ def capture_razorpay_donations(*args, **kwargs): return # to avoid capturing subscription payments as donations - if payment.description and "subscription" in str(payment.description).lower(): + if payment.invoice_id or ( + payment.description and "subscription" in str(payment.description).lower() + ): return donor = get_donor(payment.email) From 2ea331852ac61eacc86019b208d0af785499d4c2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 20 May 2022 15:33:03 +0530 Subject: [PATCH 22/48] fix(India): Async issue in company address trigger (cherry picked from commit c41f9f046fb987d942298db99b5b1cf64f8b7684) --- erpnext/regional/india/taxes.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index 5f6dcdeb9227..c0e6b91a1c06 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -1,6 +1,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => { frappe.ui.form.on(doctype, { company_address: function(frm) { + console.log("#########"); frm.trigger('get_tax_template'); }, shipping_address: function(frm) { @@ -22,6 +23,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => { 'shipping_address': frm.doc.shipping_address || '', 'shipping_address_name': frm.doc.shipping_address_name || '', 'customer_address': frm.doc.customer_address || '', + 'company_address': frm.doc.company_address, 'supplier_address': frm.doc.supplier_address, 'customer': frm.doc.customer, 'supplier': frm.doc.supplier, @@ -39,6 +41,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => { }, debounce: 2000, callback: function(r) { + console.log(r.message); if(r.message) { frm.set_value('taxes_and_charges', r.message.taxes_and_charges); frm.set_value('taxes', r.message.taxes); From 559fc509e7e61a874a95292c383c73bd16f17489 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 20 May 2022 15:34:03 +0530 Subject: [PATCH 23/48] chore: Linting issues (cherry picked from commit 8fd0b3b9f50dfc7794b68fabb136d28c2913e196) --- erpnext/regional/india/taxes.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index c0e6b91a1c06..88973e36b6ab 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -1,7 +1,6 @@ erpnext.setup_auto_gst_taxation = (doctype) => { frappe.ui.form.on(doctype, { company_address: function(frm) { - console.log("#########"); frm.trigger('get_tax_template'); }, shipping_address: function(frm) { @@ -41,7 +40,6 @@ erpnext.setup_auto_gst_taxation = (doctype) => { }, debounce: 2000, callback: function(r) { - console.log(r.message); if(r.message) { frm.set_value('taxes_and_charges', r.message.taxes_and_charges); frm.set_value('taxes', r.message.taxes); From ea6d754f734e8db76fa035acf30e97f3b24e7870 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 20 May 2022 08:31:37 +0530 Subject: [PATCH 24/48] fix: Loan repayment entries for payroll payable account (cherry picked from commit 3128f9603ed74d08855c367a4b75bdc76f56399b) --- .../doctype/loan_repayment/loan_repayment.py | 2 -- erpnext/payroll/doctype/payroll_entry/payroll_entry.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 9a43c2aec63e..d3840bfb2e25 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -449,8 +449,6 @@ def make_gl_entries(self, cancel=0, adv_adj=0): "remarks": remarks, "cost_center": self.cost_center, "posting_date": getdate(self.posting_date), - "party_type": self.applicant_type if self.repay_from_salary else "", - "party": self.applicant if self.repay_from_salary else "", } ) ) diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py index 60d38f4ca492..5f2af74dca68 100644 --- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py @@ -15,6 +15,7 @@ comma_and, date_diff, flt, + get_link_to_form, getdate, ) @@ -44,6 +45,7 @@ def on_submit(self): def before_submit(self): self.validate_employee_details() + self.validate_payroll_payable_account() if self.validate_attendance: if self.validate_employee_attendance(): frappe.throw(_("Cannot Submit, Employees left to mark attendance")) @@ -65,6 +67,14 @@ def validate_employee_details(self): if len(emp_with_sal_slip): frappe.throw(_("Salary Slip already exists for {0}").format(comma_and(emp_with_sal_slip))) + def validate_payroll_payable_account(self): + if frappe.db.get_value("Account", self.payroll_payable_account, "account_type"): + frappe.throw( + _( + "Account type cannot be set for payroll payable account {0}, please remove and try again" + ).format(frappe.bold(get_link_to_form("Account", self.payroll_payable_account))) + ) + def on_cancel(self): frappe.delete_doc( "Salary Slip", From ce3a21eb039453d4786dfd0871a278b588039d80 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 25 May 2022 11:51:07 +0530 Subject: [PATCH 25/48] fix: Handle missing HSN Codes --- erpnext/regional/report/gstr_1/gstr_1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 373e6e502ba0..602a71c3b8ec 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -449,7 +449,7 @@ def get_items_based_on_tax_rate(self): hsn_code = self.item_hsn_map.get(item_code) tax_rate = 0 taxable_value = items.get(item_code) - for rates in hsn_wise_tax_rate.get(hsn_code): + for rates in hsn_wise_tax_rate.get(hsn_code, []): if taxable_value > rates.get("minimum_taxable_value"): tax_rate = rates.get("tax_rate") From 216c32f4bc13376335ae31bd09fe6b5c01855ba2 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 25 May 2022 12:12:35 +0530 Subject: [PATCH 26/48] fix: timesheet fetching in sales invoice --- .../doctype/sales_invoice/sales_invoice.js | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index c6a110dcab6e..dfa22641a5e2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -475,7 +475,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte let row = frappe.get_doc(d.doctype, d.name) set_timesheet_detail_rate(row.doctype, row.name, me.frm.doc.currency, row.timesheet_detail) }); - frm.trigger("calculate_timesheet_totals"); + this.frm.trigger("calculate_timesheet_totals"); } } }); @@ -885,27 +885,44 @@ frappe.ui.form.on('Sales Invoice', { set_timesheet_data: function(frm, timesheets) { frm.clear_table("timesheets") - timesheets.forEach(timesheet => { + timesheets.forEach(async (timesheet) => { if (frm.doc.currency != timesheet.currency) { - frappe.call({ - method: "erpnext.setup.utils.get_exchange_rate", - args: { - from_currency: timesheet.currency, - to_currency: frm.doc.currency - }, - callback: function(r) { - if (r.message) { - exchange_rate = r.message; - frm.events.append_time_log(frm, timesheet, exchange_rate); - } - } - }); + const exchange_rate = await frm.events.get_exchange_rate( + frm, timesheet.currency, frm.doc.currency + ) + frm.events.append_time_log(frm, timesheet, exchange_rate) } else { frm.events.append_time_log(frm, timesheet, 1.0); } }); }, + async get_exchange_rate(frm, from_currency, to_currency) { + if ( + frm.exchange_rates + && frm.exchange_rates[from_currency] + && frm.exchange_rates[from_currency][to_currency] + ) { + return frm.exchange_rates[from_currency][to_currency]; + } + + return frappe.call({ + method: "erpnext.setup.utils.get_exchange_rate", + args: { + from_currency, + to_currency + }, + callback: function(r) { + if (r.message) { + // cache exchange rates + frm.exchange_rates = frm.exchange_rates || {}; + frm.exchange_rates[from_currency] = frm.exchange_rates[from_currency] || {}; + frm.exchange_rates[from_currency][to_currency] = r.message; + } + } + }); + }, + append_time_log: function(frm, time_log, exchange_rate) { const row = frm.add_child("timesheets"); row.activity_type = time_log.activity_type; @@ -916,7 +933,7 @@ frappe.ui.form.on('Sales Invoice', { row.billing_hours = time_log.billing_hours; row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate); row.timesheet_detail = time_log.name; - row.project_name = time_log.project_name; + row.project_name = time_log.project_name; frm.refresh_field("timesheets"); frm.trigger("calculate_timesheet_totals"); From 98eb7da06acdd18c2c667363fbf017a013d338c9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 25 May 2022 12:24:09 +0530 Subject: [PATCH 27/48] fix(pos): paid amount calculation for multicurrency invoice (#31122) --- .../public/js/controllers/taxes_and_totals.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 2b1b0e3576b4..fe23ff381265 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -767,11 +767,23 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) { $.each(this.frm.doc['payments'] || [], function(index, data) { if(data.default && payment_status && total_amount_to_pay > 0) { - let base_amount = flt(total_amount_to_pay, precision("base_amount", data)); + let base_amount, amount; + + if (me.frm.doc.party_account_currency == me.frm.doc.currency) { + // if customer/supplier currency is same as company currency + // total_amount_to_pay is already in customer/supplier currency + // so base_amount has to be calculated using total_amount_to_pay + base_amount = flt(total_amount_to_pay * me.frm.doc.conversion_rate, precision("base_amount", data)); + amount = flt(total_amount_to_pay, precision("amount", data)); + } else { + base_amount = flt(total_amount_to_pay, precision("base_amount", data)); + amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data)); + } + frappe.model.set_value(data.doctype, data.name, "base_amount", base_amount); - let amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data)); frappe.model.set_value(data.doctype, data.name, "amount", amount); payment_status = false; + } else if(me.frm.doc.paid_amount) { frappe.model.set_value(data.doctype, data.name, "amount", 0.0); } From 228f10bf30b0c9e304927b5a877cc0730a66ab7c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 12 Aug 2021 15:39:07 +0530 Subject: [PATCH 28/48] fix: Account currency validation for first transaction (cherry picked from commit 80c85dd17cf0c0fdcf07d2e0c2151b2852a0c611) # Conflicts: # erpnext/controllers/accounts_controller.py --- erpnext/controllers/accounts_controller.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 7d41c84acfca..d821915a4cf5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -44,6 +44,12 @@ set_print_templates_for_taxes, ) from erpnext.controllers.sales_and_purchase_return import validate_return +<<<<<<< HEAD +======= +from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled, get_party_gle_currency +from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_transaction, + apply_pricing_rule_for_free_items, get_applied_pricing_rules) +>>>>>>> 80c85dd17c (fix: Account currency validation for first transaction) from erpnext.exceptions import InvalidCurrency from erpnext.setup.utils import get_exchange_rate from erpnext.stock.doctype.packed_item.packed_item import make_packing_list @@ -169,6 +175,7 @@ def validate(self): self.validate_party() self.validate_currency() + self.validate_party_account_currency() if self.doctype in ["Purchase Invoice", "Sales Invoice"]: pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid" @@ -1445,6 +1452,19 @@ def validate_currency(self): # at quotation / sales order level and we shouldn't stop someone # from creating a sales invoice if sales order is already created + def validate_party_account_currency(self): + if self.doctype not in ('Sales Invoice', 'Purchase Invoice'): + return + + party_type, party = self.get_party() + party_gle_currency = get_party_gle_currency(party_type, party, self.company) + party_account = self.get('debit_to') if self.doctype == 'Sales Invoice' else self.get('credit_to') + party_account_currency = get_account_currency(party_account) + + if not party_gle_currency and (party_account_currency != self.currency): + frappe.throw(_("Party Account {0} currency and document currency should be same").format(frappe.bold(party_account))) + + def delink_advance_entries(self, linked_doc_name): total_allocated_amount = 0 for adv in self.advances: From 7f853b1f0febad2a33548558f2f0556801c0196f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 15 Aug 2021 21:18:13 +0530 Subject: [PATCH 29/48] fix: Add party account validation for journal entry (cherry picked from commit f00620a3ca8d507dc947b82abdb5a90c6759bde5) --- .../accounts/doctype/journal_entry/journal_entry.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 8660c18bf950..ba0b1416d7e6 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -51,6 +51,7 @@ def validate(self): self.validate_party() self.validate_entries_for_advance() self.validate_multi_currency() + self.validate_party_account_currency() self.set_amounts_in_company_currency() self.validate_debit_credit_amount() @@ -655,6 +656,18 @@ def validate_multi_currency(self): self.set_exchange_rate() + def validate_party_account_currency(self): + for d in self.get("accounts"): + if self.party_type not in ('Customer', 'Supplier'): + continue + + party_gle_currency = get_party_gle_currency(self.party_type, self.party, self.company) + party_account_currency = get_account_currency(d.account) + + if not party_gle_currency and (party_account_currency != self.currency): + frappe.throw(_("Row {0}: Party Account {1} currency and document currency should be same").format( + frappe.bold(d.idx), frappe.bold(d.account))) + def set_amounts_in_company_currency(self): for d in self.get("accounts"): d.debit_in_account_currency = flt( From 09a42a122f0a446d73942d9b8a69149721379e42 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 15 Aug 2021 21:19:18 +0530 Subject: [PATCH 30/48] fix: Healthcare module accounting test cases (cherry picked from commit bcaf4752952f4aa7819c057ce61c8bd2ef69df78) # Conflicts: # erpnext/healthcare/doctype/lab_test/test_lab_test.py --- erpnext/healthcare/doctype/lab_test/test_lab_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/healthcare/doctype/lab_test/test_lab_test.py b/erpnext/healthcare/doctype/lab_test/test_lab_test.py index 06c02d1ea079..754ee94c3965 100644 --- a/erpnext/healthcare/doctype/lab_test/test_lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/test_lab_test.py @@ -163,8 +163,14 @@ def create_sales_invoice(): sales_invoice.patient = patient sales_invoice.customer = frappe.db.get_value("Patient", patient, "customer") sales_invoice.due_date = getdate() +<<<<<<< HEAD sales_invoice.company = "_Test Company" sales_invoice.debit_to = get_receivable_account("_Test Company") +======= + sales_invoice.company = '_Test Company' + sales_invoice.currency = 'INR' + sales_invoice.debit_to = get_receivable_account('_Test Company') +>>>>>>> bcaf475295 (fix: Healthcare module accounting test cases) tests = [insulin_resistance_template, blood_test_template] for entry in tests: From 9d43a90eb08ddbcb80a7b488b9696762058e69cc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 16 Aug 2021 10:40:26 +0530 Subject: [PATCH 31/48] Revert "fix: Add party account validation for journal entry" This reverts commit f00620a3ca8d507dc947b82abdb5a90c6759bde5. (cherry picked from commit 0a618817dc76e7da00e8ae16521bf554b5fd9704) --- .../accounts/doctype/journal_entry/journal_entry.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index ba0b1416d7e6..8660c18bf950 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -51,7 +51,6 @@ def validate(self): self.validate_party() self.validate_entries_for_advance() self.validate_multi_currency() - self.validate_party_account_currency() self.set_amounts_in_company_currency() self.validate_debit_credit_amount() @@ -656,18 +655,6 @@ def validate_multi_currency(self): self.set_exchange_rate() - def validate_party_account_currency(self): - for d in self.get("accounts"): - if self.party_type not in ('Customer', 'Supplier'): - continue - - party_gle_currency = get_party_gle_currency(self.party_type, self.party, self.company) - party_account_currency = get_account_currency(d.account) - - if not party_gle_currency and (party_account_currency != self.currency): - frappe.throw(_("Row {0}: Party Account {1} currency and document currency should be same").format( - frappe.bold(d.idx), frappe.bold(d.account))) - def set_amounts_in_company_currency(self): for d in self.get("accounts"): d.debit_in_account_currency = flt( From 8f969fbd666a9db6c934afbb50958e41b4039159 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 21 Aug 2021 23:05:48 +0530 Subject: [PATCH 32/48] test: Update test cases for currency change validation (cherry picked from commit 60915e874d9f466618b313be65023a62591d0f97) # Conflicts: # erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py # erpnext/healthcare/doctype/patient_appointment/patient_appointment.py # erpnext/healthcare/doctype/therapy_plan/therapy_plan.py # erpnext/non_profit/doctype/membership/membership.py --- .../test_period_closing_voucher.py | 8 +++++++ erpnext/controllers/accounts_controller.py | 3 +++ .../patient_appointment.py | 12 ++++++++++ .../doctype/therapy_plan/therapy_plan.py | 11 ++++++++++ .../doctype/membership/membership.py | 22 +++++++++++++++++++ .../doctype/membership/test_membership.py | 2 +- 6 files changed, 57 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 8e0e62d5f8c2..a61f32c3d1ef 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -78,6 +78,10 @@ def test_cost_center_wise_posting(self): expense_account="Cost of Goods Sold - TPC", rate=400, debit_to="Debtors - TPC", +<<<<<<< HEAD +======= + currency="USD" +>>>>>>> 60915e874d (test: Update test cases for currency change validation) ) create_sales_invoice( company=company, @@ -86,6 +90,10 @@ def test_cost_center_wise_posting(self): expense_account="Cost of Goods Sold - TPC", rate=200, debit_to="Debtors - TPC", +<<<<<<< HEAD +======= + currency="USD" +>>>>>>> 60915e874d (test: Update test cases for currency change validation) ) pcv = self.make_period_closing_voucher(submit=False) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d821915a4cf5..60ff067a91f7 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1456,6 +1456,9 @@ def validate_party_account_currency(self): if self.doctype not in ('Sales Invoice', 'Purchase Invoice'): return + if self.is_opening == 'Yes': + return + party_type, party = self.get_party() party_gle_currency = get_party_gle_currency(party_type, party, self.company) party_account = self.get('debit_to') if self.doctype == 'Sales Invoice' else self.get('credit_to') diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index b6e30060437e..131767797dbf 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -22,6 +22,7 @@ manage_fee_validity, ) from erpnext.hr.doctype.employee.employee import is_holiday +<<<<<<< HEAD class MaximumCapacityError(frappe.ValidationError): @@ -31,6 +32,11 @@ class MaximumCapacityError(frappe.ValidationError): class OverlapError(frappe.ValidationError): pass +======= +from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account +from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_practitioner_charge, manage_fee_validity +from erpnext import get_company_currency +>>>>>>> 60915e874d (test: Update test cases for currency change validation) class PatientAppointment(Document): def validate(self): @@ -251,7 +257,13 @@ def invoice_appointment(appointment_doc): def create_sales_invoice(appointment_doc): sales_invoice = frappe.new_doc("Sales Invoice") sales_invoice.patient = appointment_doc.patient +<<<<<<< HEAD sales_invoice.customer = frappe.get_value("Patient", appointment_doc.patient, "customer") +======= + sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer') + sales_invoice.currency = frappe.get_value('Customer', sales_invoice.customer, 'default_currency') \ + or get_company_currency(appointment_doc.currency) +>>>>>>> 60915e874d (test: Update test cases for currency change validation) sales_invoice.appointment = appointment_doc.name sales_invoice.due_date = getdate() sales_invoice.company = appointment_doc.company diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py index 44f0a9785c44..b82894c366b9 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py @@ -4,8 +4,13 @@ import frappe from frappe.model.document import Document +<<<<<<< HEAD from frappe.utils import flt +======= +from frappe.utils import flt, today +from erpnext import get_company_currency +>>>>>>> 60915e874d (test: Update test cases for currency change validation) class TherapyPlan(Document): def validate(self): @@ -71,7 +76,13 @@ def make_sales_invoice(reference_name, patient, company, therapy_plan_template): si = frappe.new_doc("Sales Invoice") si.company = company si.patient = patient +<<<<<<< HEAD si.customer = frappe.db.get_value("Patient", patient, "customer") +======= + si.customer = frappe.db.get_value('Patient', patient, 'customer') + si.currency = frappe.get_value('Customer', si.customer, 'default_currency') \ + or get_company_currency(si.company) +>>>>>>> 60915e874d (test: Update test cases for currency change validation) item = frappe.db.get_value("Therapy Plan Template", therapy_plan_template, "linked_item") price_list, price_list_currency = frappe.db.get_values( diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 835e2db85198..c420a2879b7f 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -13,8 +13,12 @@ from frappe.utils import add_days, add_months, add_years, get_link_to_form, getdate, nowdate import erpnext +<<<<<<< HEAD from erpnext.non_profit.doctype.member.member import create_member +======= +from erpnext import get_company_currency +>>>>>>> 60915e874d (test: Update test cases for currency change validation) class Membership(Document): def validate(self): @@ -198,6 +202,7 @@ def generate_and_send_invoice(self): def make_invoice(membership, member, plan, settings): +<<<<<<< HEAD invoice = frappe.get_doc( { "doctype": "Sales Invoice", @@ -209,6 +214,23 @@ def make_invoice(membership, member, plan, settings): "items": [{"item_code": plan.linked_item, "rate": membership.amount, "qty": 1}], } ) +======= + invoice = frappe.get_doc({ + "doctype": "Sales Invoice", + "customer": member.customer, + "debit_to": settings.membership_debit_account, + "currency": membership.currency or get_company_currency(settings.company), + "company": settings.company, + "is_pos": 0, + "items": [ + { + "item_code": plan.linked_item, + "rate": membership.amount, + "qty": 1 + } + ] + }) +>>>>>>> 60915e874d (test: Update test cases for currency change validation) invoice.set_missing_values() invoice.insert() invoice.submit() diff --git a/erpnext/non_profit/doctype/membership/test_membership.py b/erpnext/non_profit/doctype/membership/test_membership.py index aef34a69606e..d73c2bed5f4a 100644 --- a/erpnext/non_profit/doctype/membership/test_membership.py +++ b/erpnext/non_profit/doctype/membership/test_membership.py @@ -94,7 +94,7 @@ def make_membership(member, payload={}): "member": member, "membership_status": "Current", "membership_type": "_rzpy_test_milythm", - "currency": "INR", + "currency": "USD", "paid": 1, "from_date": nowdate(), "amount": 100, From e28f6b7d312e9a06346700a9f9627a21c06d64d9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 22 Aug 2021 18:05:24 +0530 Subject: [PATCH 33/48] test: fix property name (cherry picked from commit c10a22529c8b612ca31d2221700e45184426753d) # Conflicts: # erpnext/healthcare/doctype/patient_appointment/patient_appointment.py --- .../doctype/patient_appointment/patient_appointment.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 131767797dbf..713e5b3501e5 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -262,8 +262,12 @@ def create_sales_invoice(appointment_doc): ======= sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer') sales_invoice.currency = frappe.get_value('Customer', sales_invoice.customer, 'default_currency') \ +<<<<<<< HEAD or get_company_currency(appointment_doc.currency) >>>>>>> 60915e874d (test: Update test cases for currency change validation) +======= + or get_company_currency(appointment_doc.company) +>>>>>>> c10a22529c (test: fix property name) sales_invoice.appointment = appointment_doc.name sales_invoice.due_date = getdate() sales_invoice.company = appointment_doc.company From 4727482737e96aeefa04aa4b22ab3afd78b20ca2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 22 Aug 2021 23:48:23 +0530 Subject: [PATCH 34/48] test: Set default currency for patient (cherry picked from commit 30876a105ca9ebb7f51741068b50385ca5a915c5) # Conflicts: # erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py --- .../test_patient_appointment.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 048547a93223..274f1afdaae7 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -235,6 +235,7 @@ def test_invalid_healthcare_service_unit_validation(self): ) ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name) mark_invoiced_inpatient_occupancy(ip_record1) +<<<<<<< HEAD discharge_patient(ip_record1, now_datetime()) def test_payment_should_be_mandatory_for_new_patient_appointment(self): @@ -415,6 +416,44 @@ def create_practitioner(id=0, medical_department=None): return practitioner.name +======= + discharge_patient(ip_record1) + + +def create_healthcare_docs(): + patient = create_patient() + practitioner = frappe.db.exists('Healthcare Practitioner', '_Test Healthcare Practitioner') + medical_department = frappe.db.exists('Medical Department', '_Test Medical Department') + + if not medical_department: + medical_department = frappe.new_doc('Medical Department') + medical_department.department = '_Test Medical Department' + medical_department.save(ignore_permissions=True) + medical_department = medical_department.name + + if not practitioner: + practitioner = frappe.new_doc('Healthcare Practitioner') + practitioner.first_name = '_Test Healthcare Practitioner' + practitioner.gender = 'Female' + practitioner.department = medical_department + practitioner.op_consulting_charge = 500 + practitioner.inpatient_visit_charge = 500 + practitioner.save(ignore_permissions=True) + practitioner = practitioner.name + + return patient, medical_department, practitioner + +def create_patient(): + patient = frappe.db.exists('Patient', '_Test Patient') + if not patient: + patient = frappe.new_doc('Patient') + patient.first_name = '_Test Patient' + patient.sex = 'Female' + patient.default_currency = 'INR' + patient.save(ignore_permissions=True) + patient = patient.name + return patient +>>>>>>> 30876a105c (test: Set default currency for patient) def create_encounter(appointment): if appointment: From d10c2e50bee2256b43039d7d09b53dc01e72160d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 26 Aug 2021 17:13:36 +0530 Subject: [PATCH 35/48] fix: Party account validation in JV (cherry picked from commit 417d6abcf48668d053b413dae577f7c775c08416) # Conflicts: # erpnext/accounts/doctype/journal_entry/journal_entry.py --- .../doctype/journal_entry/journal_entry.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 8660c18bf950..a7c52dd24d04 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -26,6 +26,13 @@ ) from erpnext.controllers.accounts_controller import AccountsController from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount +<<<<<<< HEAD +======= +from erpnext.accounts.doctype.invoice_discounting.invoice_discounting \ + import get_party_account_based_on_invoice_discounting +from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts +from erpnext.accounts.party import get_party_gle_currency +>>>>>>> 417d6abcf4 (fix: Party account validation in JV) class StockAccountInvalidTransaction(frappe.ValidationError): @@ -49,6 +56,7 @@ def validate(self): self.clearance_date = None self.validate_party() + self.validate_party_account_currency() self.validate_entries_for_advance() self.validate_multi_currency() self.set_amounts_in_company_currency() @@ -327,11 +335,25 @@ def validate_party(self): account_type = frappe.db.get_value("Account", d.account, "account_type") if account_type in ["Receivable", "Payable"]: if not (d.party_type and d.party): +<<<<<<< HEAD frappe.throw( _("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format( d.idx, d.account ) ) +======= + frappe.throw(_("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(d.idx, d.account)) + + def validate_party_account_currency(self): + for d in self.get("accounts"): + if d.party_type in ('Customer', 'Supplier'): + party_gle_currency = get_party_gle_currency(d.party_type, d.party, self.company) + party_account_currency = get_account_currency(d.account) + party_currency = frappe.db.get_value(d.party_type, d.party, 'default_currency') + + if not party_gle_currency and (party_account_currency != party_currency): + frappe.throw(_("Party Account {0} currency and default party currency should be same").format(frappe.bold(d.account))) +>>>>>>> 417d6abcf4 (fix: Party account validation in JV) def check_credit_limit(self): customers = list( From 4ca6cdca7621e40093abe43d016124798ede25ea Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 15 May 2022 21:10:52 +0530 Subject: [PATCH 36/48] fix: Remove validation from Journal Entry (cherry picked from commit 5b8726405d54c2a0601d379dbc78482e19858cda) # Conflicts: # erpnext/accounts/doctype/journal_entry/journal_entry.py --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index a7c52dd24d04..7bd978561baf 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -56,7 +56,6 @@ def validate(self): self.clearance_date = None self.validate_party() - self.validate_party_account_currency() self.validate_entries_for_advance() self.validate_multi_currency() self.set_amounts_in_company_currency() @@ -341,6 +340,7 @@ def validate_party(self): d.idx, d.account ) ) +<<<<<<< HEAD ======= frappe.throw(_("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(d.idx, d.account)) @@ -354,6 +354,8 @@ def validate_party_account_currency(self): if not party_gle_currency and (party_account_currency != party_currency): frappe.throw(_("Party Account {0} currency and default party currency should be same").format(frappe.bold(d.account))) >>>>>>> 417d6abcf4 (fix: Party account validation in JV) +======= +>>>>>>> 5b8726405d (fix: Remove validation from Journal Entry) def check_credit_limit(self): customers = list( From 0628785c646a221b66277f8fb22c1e9dddfefbfc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 16 May 2022 10:52:58 +0530 Subject: [PATCH 37/48] test: Update test cases (cherry picked from commit 65232edfd5634cb270a20cab076d2ad3cc644e28) # Conflicts: # erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py # erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py --- .../test_period_closing_voucher.py | 15 +++++++- .../doctype/pricing_rule/test_pricing_rule.py | 35 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index a61f32c3d1ef..8a8e186a826a 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -79,9 +79,14 @@ def test_cost_center_wise_posting(self): rate=400, debit_to="Debtors - TPC", <<<<<<< HEAD +<<<<<<< HEAD ======= currency="USD" >>>>>>> 60915e874d (test: Update test cases for currency change validation) +======= + currency="USD", + customer="_Test Customer USD", +>>>>>>> 65232edfd5 (test: Update test cases) ) create_sales_invoice( company=company, @@ -91,9 +96,14 @@ def test_cost_center_wise_posting(self): rate=200, debit_to="Debtors - TPC", <<<<<<< HEAD +<<<<<<< HEAD ======= currency="USD" >>>>>>> 60915e874d (test: Update test cases for currency change validation) +======= + currency="USD", + customer="_Test Customer USD", +>>>>>>> 65232edfd5 (test: Update test cases) ) pcv = self.make_period_closing_voucher(submit=False) @@ -127,14 +137,17 @@ def test_period_closing_with_finance_book_entries(self): surplus_account = create_account() cost_center = create_cost_center("Test Cost Center 1") - create_sales_invoice( + si = create_sales_invoice( company=company, income_account="Sales - TPC", expense_account="Cost of Goods Sold - TPC", cost_center=cost_center, rate=400, debit_to="Debtors - TPC", + currency="USD", + customer="_Test Customer USD", ) + jv = make_journal_entry( account1="Cash - TPC", account2="Sales - TPC", diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 4b81a7d6a239..709f0a52eecb 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -768,6 +768,41 @@ def test_remove_pricing_rule(self): frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete() item.delete() +<<<<<<< HEAD +======= + def test_multiple_pricing_rules_with_min_qty(self): + make_pricing_rule( + discount_percentage=20, + selling=1, + priority=1, + min_qty=4, + apply_multiple_pricing_rules=1, + title="_Test Pricing Rule with Min Qty - 1", + ) + make_pricing_rule( + discount_percentage=10, + selling=1, + priority=2, + min_qty=4, + apply_multiple_pricing_rules=1, + title="_Test Pricing Rule with Min Qty - 2", + ) + + si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1) + item = si.items[0] + item.stock_qty = 1 + si.save() + self.assertFalse(item.discount_percentage) + item.qty = 5 + item.stock_qty = 5 + si.save() + self.assertEqual(item.discount_percentage, 30) + si.delete() + + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 1") + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 2") + +>>>>>>> 65232edfd5 (test: Update test cases) test_dependencies = ["Campaign"] From 761669c7ca73448ba5306816f9b412a0b6cbd425 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 25 May 2022 15:42:47 +0530 Subject: [PATCH 38/48] chore: Update test case (cherry picked from commit bc3473770947ed5796a8e7d6fa718b2a55f326eb) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py --- erpnext/accounts/party.py | 15 ++++++ .../purchase_receipt/test_purchase_receipt.py | 52 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index b76ce29b5050..177624ca0322 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -898,3 +898,18 @@ def get_default_contact(doctype, name): return None else: return None + + +def add_party_account(party_type, party, company, account): + doc = frappe.get_doc(party_type, party) + account_exists = False + for d in doc.get("accounts"): + if d.account == account: + account_exists = True + + if not account_exists: + accounts = {"company": company, "account": account} + + doc.append("accounts", accounts) + + doc.save() diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 65c30de09786..441bfc1b63ca 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1295,6 +1295,58 @@ def test_po_to_pi_and_po_to_pr_worflow_partial(self): self.assertEqual(pr.status, "To Bill") self.assertAlmostEqual(pr.per_billed, 50.0, places=2) +<<<<<<< HEAD +======= + def test_purchase_receipt_with_exchange_rate_difference(self): + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import ( + make_purchase_receipt as create_purchase_receipt, + ) + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import ( + make_purchase_invoice as create_purchase_invoice, + ) + from erpnext.accounts.party import add_party_account + + add_party_account( + "Supplier", + "_Test Supplier USD", + "_Test Company with perpetual inventory", + "_Test Payable USD - TCP1", + ) + + pi = create_purchase_invoice( + company="_Test Company with perpetual inventory", + cost_center="Main - TCP1", + warehouse="Stores - TCP1", + expense_account="_Test Account Cost for Goods Sold - TCP1", + currency="USD", + conversion_rate=70, + supplier="_Test Supplier USD", + ) + + pr = create_purchase_receipt(pi.name) + pr.conversion_rate = 80 + pr.items[0].purchase_invoice = pi.name + pr.items[0].purchase_invoice_item = pi.items[0].name + + pr.save() + pr.submit() + + # Get exchnage gain and loss account + exchange_gain_loss_account = frappe.db.get_value( + "Company", pr.company, "exchange_gain_loss_account" + ) + + # fetching the latest GL Entry with exchange gain and loss account account + amount = frappe.db.get_value( + "GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pr.name}, "credit" + ) + discrepancy_caused_by_exchange_rate_diff = abs( + pi.items[0].base_net_amount - pr.items[0].base_net_amount + ) + + self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) + +>>>>>>> bc34737709 (chore: Update test case) def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, From f724f6d1bbd727789b6136b5adb390323927f5f2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 25 May 2022 19:04:20 +0530 Subject: [PATCH 39/48] chore: Resolve conflicts --- .../doctype/journal_entry/journal_entry.py | 30 +-- .../test_period_closing_voucher.py | 15 +- .../doctype/pricing_rule/test_pricing_rule.py | 35 --- erpnext/controllers/accounts_controller.py | 22 +- .../patient_appointment.py | 22 +- .../test_patient_appointment.py | 209 ++---------------- .../doctype/therapy_plan/therapy_plan.py | 14 +- .../doctype/membership/membership.py | 25 +-- .../purchase_receipt/test_purchase_receipt.py | 52 ----- 9 files changed, 48 insertions(+), 376 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 7bd978561baf..cc6e6137a138 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -17,7 +17,7 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, ) -from erpnext.accounts.party import get_party_account +from erpnext.accounts.party import get_party_account, get_party_gle_currency from erpnext.accounts.utils import ( get_account_currency, get_balance_on, @@ -26,13 +26,6 @@ ) from erpnext.controllers.accounts_controller import AccountsController from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount -<<<<<<< HEAD -======= -from erpnext.accounts.doctype.invoice_discounting.invoice_discounting \ - import get_party_account_based_on_invoice_discounting -from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts -from erpnext.accounts.party import get_party_gle_currency ->>>>>>> 417d6abcf4 (fix: Party account validation in JV) class StockAccountInvalidTransaction(frappe.ValidationError): @@ -334,28 +327,25 @@ def validate_party(self): account_type = frappe.db.get_value("Account", d.account, "account_type") if account_type in ["Receivable", "Payable"]: if not (d.party_type and d.party): -<<<<<<< HEAD frappe.throw( _("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format( d.idx, d.account ) ) -<<<<<<< HEAD -======= - frappe.throw(_("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(d.idx, d.account)) - + def validate_party_account_currency(self): for d in self.get("accounts"): - if d.party_type in ('Customer', 'Supplier'): + if d.party_type in ("Customer", "Supplier"): party_gle_currency = get_party_gle_currency(d.party_type, d.party, self.company) party_account_currency = get_account_currency(d.account) - party_currency = frappe.db.get_value(d.party_type, d.party, 'default_currency') - + party_currency = frappe.db.get_value(d.party_type, d.party, "default_currency") + if not party_gle_currency and (party_account_currency != party_currency): - frappe.throw(_("Party Account {0} currency and default party currency should be same").format(frappe.bold(d.account))) ->>>>>>> 417d6abcf4 (fix: Party account validation in JV) -======= ->>>>>>> 5b8726405d (fix: Remove validation from Journal Entry) + frappe.throw( + _("Party Account {0} currency and default party currency should be same").format( + frappe.bold(d.account) + ) + ) def check_credit_limit(self): customers = list( diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 8a8e186a826a..a944a3738328 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -78,16 +78,10 @@ def test_cost_center_wise_posting(self): expense_account="Cost of Goods Sold - TPC", rate=400, debit_to="Debtors - TPC", -<<<<<<< HEAD -<<<<<<< HEAD -======= - currency="USD" ->>>>>>> 60915e874d (test: Update test cases for currency change validation) -======= currency="USD", customer="_Test Customer USD", ->>>>>>> 65232edfd5 (test: Update test cases) ) + create_sales_invoice( company=company, cost_center=cost_center2, @@ -95,15 +89,8 @@ def test_cost_center_wise_posting(self): expense_account="Cost of Goods Sold - TPC", rate=200, debit_to="Debtors - TPC", -<<<<<<< HEAD -<<<<<<< HEAD -======= - currency="USD" ->>>>>>> 60915e874d (test: Update test cases for currency change validation) -======= currency="USD", customer="_Test Customer USD", ->>>>>>> 65232edfd5 (test: Update test cases) ) pcv = self.make_period_closing_voucher(submit=False) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 709f0a52eecb..4b81a7d6a239 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -768,41 +768,6 @@ def test_remove_pricing_rule(self): frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete() item.delete() -<<<<<<< HEAD -======= - def test_multiple_pricing_rules_with_min_qty(self): - make_pricing_rule( - discount_percentage=20, - selling=1, - priority=1, - min_qty=4, - apply_multiple_pricing_rules=1, - title="_Test Pricing Rule with Min Qty - 1", - ) - make_pricing_rule( - discount_percentage=10, - selling=1, - priority=2, - min_qty=4, - apply_multiple_pricing_rules=1, - title="_Test Pricing Rule with Min Qty - 2", - ) - - si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1) - item = si.items[0] - item.stock_qty = 1 - si.save() - self.assertFalse(item.discount_percentage) - item.qty = 5 - item.stock_qty = 5 - si.save() - self.assertEqual(item.discount_percentage, 30) - si.delete() - - frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 1") - frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 2") - ->>>>>>> 65232edfd5 (test: Update test cases) test_dependencies = ["Campaign"] diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 60ff067a91f7..01586b3de1c7 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -35,6 +35,7 @@ from erpnext.accounts.party import ( get_party_account, get_party_account_currency, + get_party_gle_currency, validate_party_frozen_disabled, ) from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year @@ -44,12 +45,6 @@ set_print_templates_for_taxes, ) from erpnext.controllers.sales_and_purchase_return import validate_return -<<<<<<< HEAD -======= -from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled, get_party_gle_currency -from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_transaction, - apply_pricing_rule_for_free_items, get_applied_pricing_rules) ->>>>>>> 80c85dd17c (fix: Account currency validation for first transaction) from erpnext.exceptions import InvalidCurrency from erpnext.setup.utils import get_exchange_rate from erpnext.stock.doctype.packed_item.packed_item import make_packing_list @@ -1453,20 +1448,25 @@ def validate_currency(self): # from creating a sales invoice if sales order is already created def validate_party_account_currency(self): - if self.doctype not in ('Sales Invoice', 'Purchase Invoice'): + if self.doctype not in ("Sales Invoice", "Purchase Invoice"): return - if self.is_opening == 'Yes': + if self.is_opening == "Yes": return party_type, party = self.get_party() party_gle_currency = get_party_gle_currency(party_type, party, self.company) - party_account = self.get('debit_to') if self.doctype == 'Sales Invoice' else self.get('credit_to') + party_account = ( + self.get("debit_to") if self.doctype == "Sales Invoice" else self.get("credit_to") + ) party_account_currency = get_account_currency(party_account) if not party_gle_currency and (party_account_currency != self.currency): - frappe.throw(_("Party Account {0} currency and document currency should be same").format(frappe.bold(party_account))) - + frappe.throw( + _("Party Account {0} currency and document currency should be same").format( + frappe.bold(party_account) + ) + ) def delink_advance_entries(self, linked_doc_name): total_allocated_amount = 0 diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 713e5b3501e5..db61e0d9c55e 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -12,6 +12,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.utils import flt, get_link_to_form, get_time, getdate +from erpnext import get_company_currency from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import ( get_income_account, get_receivable_account, @@ -22,7 +23,6 @@ manage_fee_validity, ) from erpnext.hr.doctype.employee.employee import is_holiday -<<<<<<< HEAD class MaximumCapacityError(frappe.ValidationError): @@ -32,11 +32,6 @@ class MaximumCapacityError(frappe.ValidationError): class OverlapError(frappe.ValidationError): pass -======= -from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account -from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_practitioner_charge, manage_fee_validity -from erpnext import get_company_currency ->>>>>>> 60915e874d (test: Update test cases for currency change validation) class PatientAppointment(Document): def validate(self): @@ -257,17 +252,12 @@ def invoice_appointment(appointment_doc): def create_sales_invoice(appointment_doc): sales_invoice = frappe.new_doc("Sales Invoice") sales_invoice.patient = appointment_doc.patient -<<<<<<< HEAD + sales_invoice.customer = frappe.get_value("Patient", appointment_doc.patient, "customer") -======= - sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer') - sales_invoice.currency = frappe.get_value('Customer', sales_invoice.customer, 'default_currency') \ -<<<<<<< HEAD - or get_company_currency(appointment_doc.currency) ->>>>>>> 60915e874d (test: Update test cases for currency change validation) -======= - or get_company_currency(appointment_doc.company) ->>>>>>> c10a22529c (test: fix property name) + sales_invoice.currency = frappe.get_value( + "Customer", sales_invoice.customer, "default_currency" + ) or get_company_currency(appointment_doc.currency) + sales_invoice.appointment = appointment_doc.name sales_invoice.due_date = getdate() sales_invoice.company = appointment_doc.company diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 274f1afdaae7..21c481dac42b 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -235,206 +235,24 @@ def test_invalid_healthcare_service_unit_validation(self): ) ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name) mark_invoiced_inpatient_occupancy(ip_record1) -<<<<<<< HEAD - discharge_patient(ip_record1, now_datetime()) - - def test_payment_should_be_mandatory_for_new_patient_appointment(self): - frappe.db.set_value("Healthcare Settings", None, "enable_free_follow_ups", 1) - frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 1) - frappe.db.set_value("Healthcare Settings", None, "max_visits", 3) - frappe.db.set_value("Healthcare Settings", None, "valid_days", 30) - - patient = create_patient() - assert check_is_new_patient(patient) - payment_required = check_payment_fields_reqd(patient) - assert payment_required is True - - def test_sales_invoice_should_be_generated_for_new_patient_appointment(self): - patient, practitioner = create_healthcare_docs() - frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 1) - invoice_count = frappe.db.count("Sales Invoice") - - assert check_is_new_patient(patient) - create_appointment(patient, practitioner, nowdate()) - new_invoice_count = frappe.db.count("Sales Invoice") - - assert new_invoice_count == invoice_count + 1 - - def test_overlap_appointment(self): - from erpnext.healthcare.doctype.patient_appointment.patient_appointment import OverlapError - - patient, practitioner = create_healthcare_docs(id=1) - patient_1, practitioner_1 = create_healthcare_docs(id=2) - service_unit = create_service_unit(id=0) - service_unit_1 = create_service_unit(id=1) - appointment = create_appointment( - patient, practitioner, nowdate(), service_unit=service_unit - ) # valid - - # patient and practitioner cannot have overlapping appointments - appointment = create_appointment( - patient, practitioner, nowdate(), service_unit=service_unit, save=0 - ) - self.assertRaises(OverlapError, appointment.save) - appointment = create_appointment( - patient, practitioner, nowdate(), service_unit=service_unit_1, save=0 - ) # diff service unit - self.assertRaises(OverlapError, appointment.save) - appointment = create_appointment( - patient, practitioner, nowdate(), save=0 - ) # with no service unit link - self.assertRaises(OverlapError, appointment.save) - - # patient cannot have overlapping appointments with other practitioners - appointment = create_appointment( - patient, practitioner_1, nowdate(), service_unit=service_unit, save=0 - ) - self.assertRaises(OverlapError, appointment.save) - appointment = create_appointment( - patient, practitioner_1, nowdate(), service_unit=service_unit_1, save=0 - ) - self.assertRaises(OverlapError, appointment.save) - appointment = create_appointment(patient, practitioner_1, nowdate(), save=0) - self.assertRaises(OverlapError, appointment.save) - - # practitioner cannot have overlapping appointments with other patients - appointment = create_appointment( - patient_1, practitioner, nowdate(), service_unit=service_unit, save=0 - ) - self.assertRaises(OverlapError, appointment.save) - appointment = create_appointment( - patient_1, practitioner, nowdate(), service_unit=service_unit_1, save=0 - ) - self.assertRaises(OverlapError, appointment.save) - appointment = create_appointment(patient_1, practitioner, nowdate(), save=0) - self.assertRaises(OverlapError, appointment.save) - - def test_service_unit_capacity(self): - from erpnext.healthcare.doctype.patient_appointment.patient_appointment import ( - MaximumCapacityError, - OverlapError, - ) - - practitioner = create_practitioner() - capacity = 3 - overlap_service_unit_type = create_service_unit_type( - id=10, allow_appointments=1, overlap_appointments=1 - ) - overlap_service_unit = create_service_unit( - id=100, service_unit_type=overlap_service_unit_type, service_unit_capacity=capacity - ) - - for i in range(0, capacity): - patient = create_patient(id=i) - create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit) # valid - appointment = create_appointment( - patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0 - ) # overlap - self.assertRaises(OverlapError, appointment.save) - - patient = create_patient(id=capacity) - appointment = create_appointment( - patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0 - ) - self.assertRaises(MaximumCapacityError, appointment.save) - - def test_patient_appointment_should_consider_permissions_while_fetching_appointments(self): - patient, practitioner = create_healthcare_docs() - create_appointment(patient, practitioner, nowdate()) - - patient, new_practitioner = create_healthcare_docs(id=2) - create_appointment(patient, new_practitioner, nowdate()) - - roles = [{"doctype": "Has Role", "role": "Physician"}] - user = create_user(roles=roles) - new_practitioner = frappe.get_doc("Healthcare Practitioner", new_practitioner) - new_practitioner.user_id = user.email - new_practitioner.save() - - frappe.set_user(user.name) - appointments = frappe.get_list("Patient Appointment") - assert len(appointments) == 1 - - frappe.set_user("Administrator") - appointments = frappe.get_list("Patient Appointment") - assert len(appointments) == 2 - - -def create_healthcare_docs(id=0): - patient = create_patient(id) - practitioner = create_practitioner(id) - - return patient, practitioner - - -def create_patient( - id=0, patient_name=None, email=None, mobile=None, customer=None, create_user=False -): - if frappe.db.exists("Patient", {"firstname": f"_Test Patient {str(id)}"}): - patient = frappe.db.get_value("Patient", {"first_name": f"_Test Patient {str(id)}"}, ["name"]) - return patient - - patient = frappe.new_doc("Patient") - patient.first_name = patient_name if patient_name else f"_Test Patient {str(id)}" - patient.sex = "Female" - patient.mobile = mobile - patient.email = email - patient.customer = customer - patient.invite_user = create_user - patient.save(ignore_permissions=True) - - return patient.name - - -def create_medical_department(id=0): - if frappe.db.exists("Medical Department", f"_Test Medical Department {str(id)}"): - return f"_Test Medical Department {str(id)}" - - medical_department = frappe.new_doc("Medical Department") - medical_department.department = f"_Test Medical Department {str(id)}" - medical_department.save(ignore_permissions=True) - - return medical_department.name - - -def create_practitioner(id=0, medical_department=None): - if frappe.db.exists( - "Healthcare Practitioner", {"firstname": f"_Test Healthcare Practitioner {str(id)}"} - ): - practitioner = frappe.db.get_value( - "Healthcare Practitioner", {"firstname": f"_Test Healthcare Practitioner {str(id)}"}, ["name"] - ) - return practitioner - - practitioner = frappe.new_doc("Healthcare Practitioner") - practitioner.first_name = f"_Test Healthcare Practitioner {str(id)}" - practitioner.gender = "Female" - practitioner.department = medical_department or create_medical_department(id) - practitioner.op_consulting_charge = 500 - practitioner.inpatient_visit_charge = 500 - practitioner.save(ignore_permissions=True) - - return practitioner.name - -======= discharge_patient(ip_record1) def create_healthcare_docs(): patient = create_patient() - practitioner = frappe.db.exists('Healthcare Practitioner', '_Test Healthcare Practitioner') - medical_department = frappe.db.exists('Medical Department', '_Test Medical Department') + practitioner = frappe.db.exists("Healthcare Practitioner", "_Test Healthcare Practitioner") + medical_department = frappe.db.exists("Medical Department", "_Test Medical Department") if not medical_department: - medical_department = frappe.new_doc('Medical Department') - medical_department.department = '_Test Medical Department' + medical_department = frappe.new_doc("Medical Department") + medical_department.department = "_Test Medical Department" medical_department.save(ignore_permissions=True) medical_department = medical_department.name if not practitioner: - practitioner = frappe.new_doc('Healthcare Practitioner') - practitioner.first_name = '_Test Healthcare Practitioner' - practitioner.gender = 'Female' + practitioner = frappe.new_doc("Healthcare Practitioner") + practitioner.first_name = "_Test Healthcare Practitioner" + practitioner.gender = "Female" practitioner.department = medical_department practitioner.op_consulting_charge = 500 practitioner.inpatient_visit_charge = 500 @@ -443,17 +261,18 @@ def create_healthcare_docs(): return patient, medical_department, practitioner + def create_patient(): - patient = frappe.db.exists('Patient', '_Test Patient') + patient = frappe.db.exists("Patient", "_Test Patient") if not patient: - patient = frappe.new_doc('Patient') - patient.first_name = '_Test Patient' - patient.sex = 'Female' - patient.default_currency = 'INR' + patient = frappe.new_doc("Patient") + patient.first_name = "_Test Patient" + patient.sex = "Female" + patient.default_currency = "INR" patient.save(ignore_permissions=True) patient = patient.name return patient ->>>>>>> 30876a105c (test: Set default currency for patient) + def create_encounter(appointment): if appointment: diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py index b82894c366b9..6cb2a24e6af1 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py @@ -4,13 +4,10 @@ import frappe from frappe.model.document import Document -<<<<<<< HEAD from frappe.utils import flt -======= -from frappe.utils import flt, today from erpnext import get_company_currency ->>>>>>> 60915e874d (test: Update test cases for currency change validation) + class TherapyPlan(Document): def validate(self): @@ -76,13 +73,10 @@ def make_sales_invoice(reference_name, patient, company, therapy_plan_template): si = frappe.new_doc("Sales Invoice") si.company = company si.patient = patient -<<<<<<< HEAD si.customer = frappe.db.get_value("Patient", patient, "customer") -======= - si.customer = frappe.db.get_value('Patient', patient, 'customer') - si.currency = frappe.get_value('Customer', si.customer, 'default_currency') \ - or get_company_currency(si.company) ->>>>>>> 60915e874d (test: Update test cases for currency change validation) + si.currency = frappe.get_value( + "Customer", si.customer, "default_currency" + ) or get_company_currency(si.company) item = frappe.db.get_value("Therapy Plan Template", therapy_plan_template, "linked_item") price_list, price_list_currency = frappe.db.get_values( diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index c420a2879b7f..7f7abd065949 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -13,12 +13,9 @@ from frappe.utils import add_days, add_months, add_years, get_link_to_form, getdate, nowdate import erpnext -<<<<<<< HEAD +from erpnext import get_company_currency from erpnext.non_profit.doctype.member.member import create_member -======= -from erpnext import get_company_currency ->>>>>>> 60915e874d (test: Update test cases for currency change validation) class Membership(Document): def validate(self): @@ -202,35 +199,17 @@ def generate_and_send_invoice(self): def make_invoice(membership, member, plan, settings): -<<<<<<< HEAD invoice = frappe.get_doc( { "doctype": "Sales Invoice", "customer": member.customer, "debit_to": settings.membership_debit_account, - "currency": membership.currency, + "currency": membership.currency or get_company_currency(settings.company), "company": settings.company, "is_pos": 0, "items": [{"item_code": plan.linked_item, "rate": membership.amount, "qty": 1}], } ) -======= - invoice = frappe.get_doc({ - "doctype": "Sales Invoice", - "customer": member.customer, - "debit_to": settings.membership_debit_account, - "currency": membership.currency or get_company_currency(settings.company), - "company": settings.company, - "is_pos": 0, - "items": [ - { - "item_code": plan.linked_item, - "rate": membership.amount, - "qty": 1 - } - ] - }) ->>>>>>> 60915e874d (test: Update test cases for currency change validation) invoice.set_missing_values() invoice.insert() invoice.submit() diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 441bfc1b63ca..65c30de09786 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1295,58 +1295,6 @@ def test_po_to_pi_and_po_to_pr_worflow_partial(self): self.assertEqual(pr.status, "To Bill") self.assertAlmostEqual(pr.per_billed, 50.0, places=2) -<<<<<<< HEAD -======= - def test_purchase_receipt_with_exchange_rate_difference(self): - from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import ( - make_purchase_receipt as create_purchase_receipt, - ) - from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import ( - make_purchase_invoice as create_purchase_invoice, - ) - from erpnext.accounts.party import add_party_account - - add_party_account( - "Supplier", - "_Test Supplier USD", - "_Test Company with perpetual inventory", - "_Test Payable USD - TCP1", - ) - - pi = create_purchase_invoice( - company="_Test Company with perpetual inventory", - cost_center="Main - TCP1", - warehouse="Stores - TCP1", - expense_account="_Test Account Cost for Goods Sold - TCP1", - currency="USD", - conversion_rate=70, - supplier="_Test Supplier USD", - ) - - pr = create_purchase_receipt(pi.name) - pr.conversion_rate = 80 - pr.items[0].purchase_invoice = pi.name - pr.items[0].purchase_invoice_item = pi.items[0].name - - pr.save() - pr.submit() - - # Get exchnage gain and loss account - exchange_gain_loss_account = frappe.db.get_value( - "Company", pr.company, "exchange_gain_loss_account" - ) - - # fetching the latest GL Entry with exchange gain and loss account account - amount = frappe.db.get_value( - "GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pr.name}, "credit" - ) - discrepancy_caused_by_exchange_rate_diff = abs( - pi.items[0].base_net_amount - pr.items[0].base_net_amount - ) - - self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) - ->>>>>>> bc34737709 (chore: Update test case) def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import ( create_payment_terms_template, From 3fab8a22133e963a06892a415feb1f8168e795a1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 25 May 2022 19:20:09 +0530 Subject: [PATCH 40/48] chore: Remove unintended changes --- .../doctype/journal_entry/journal_entry.py | 16 +- .../doctype/lab_test/test_lab_test.py | 6 - .../patient_appointment.py | 6 - .../test_patient_appointment.py | 216 +++++++++++++++--- .../doctype/therapy_plan/therapy_plan.py | 5 - 5 files changed, 180 insertions(+), 69 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index cc6e6137a138..8660c18bf950 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -17,7 +17,7 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( get_party_tax_withholding_details, ) -from erpnext.accounts.party import get_party_account, get_party_gle_currency +from erpnext.accounts.party import get_party_account from erpnext.accounts.utils import ( get_account_currency, get_balance_on, @@ -333,20 +333,6 @@ def validate_party(self): ) ) - def validate_party_account_currency(self): - for d in self.get("accounts"): - if d.party_type in ("Customer", "Supplier"): - party_gle_currency = get_party_gle_currency(d.party_type, d.party, self.company) - party_account_currency = get_account_currency(d.account) - party_currency = frappe.db.get_value(d.party_type, d.party, "default_currency") - - if not party_gle_currency and (party_account_currency != party_currency): - frappe.throw( - _("Party Account {0} currency and default party currency should be same").format( - frappe.bold(d.account) - ) - ) - def check_credit_limit(self): customers = list( set( diff --git a/erpnext/healthcare/doctype/lab_test/test_lab_test.py b/erpnext/healthcare/doctype/lab_test/test_lab_test.py index 754ee94c3965..06c02d1ea079 100644 --- a/erpnext/healthcare/doctype/lab_test/test_lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/test_lab_test.py @@ -163,14 +163,8 @@ def create_sales_invoice(): sales_invoice.patient = patient sales_invoice.customer = frappe.db.get_value("Patient", patient, "customer") sales_invoice.due_date = getdate() -<<<<<<< HEAD sales_invoice.company = "_Test Company" sales_invoice.debit_to = get_receivable_account("_Test Company") -======= - sales_invoice.company = '_Test Company' - sales_invoice.currency = 'INR' - sales_invoice.debit_to = get_receivable_account('_Test Company') ->>>>>>> bcaf475295 (fix: Healthcare module accounting test cases) tests = [insulin_resistance_template, blood_test_template] for entry in tests: diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index db61e0d9c55e..b6e30060437e 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -12,7 +12,6 @@ from frappe.model.mapper import get_mapped_doc from frappe.utils import flt, get_link_to_form, get_time, getdate -from erpnext import get_company_currency from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import ( get_income_account, get_receivable_account, @@ -252,12 +251,7 @@ def invoice_appointment(appointment_doc): def create_sales_invoice(appointment_doc): sales_invoice = frappe.new_doc("Sales Invoice") sales_invoice.patient = appointment_doc.patient - sales_invoice.customer = frappe.get_value("Patient", appointment_doc.patient, "customer") - sales_invoice.currency = frappe.get_value( - "Customer", sales_invoice.customer, "default_currency" - ) or get_company_currency(appointment_doc.currency) - sales_invoice.appointment = appointment_doc.name sales_invoice.due_date = getdate() sales_invoice.company = appointment_doc.company diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 21c481dac42b..048547a93223 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -235,43 +235,185 @@ def test_invalid_healthcare_service_unit_validation(self): ) ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name) mark_invoiced_inpatient_occupancy(ip_record1) - discharge_patient(ip_record1) - - -def create_healthcare_docs(): - patient = create_patient() - practitioner = frappe.db.exists("Healthcare Practitioner", "_Test Healthcare Practitioner") - medical_department = frappe.db.exists("Medical Department", "_Test Medical Department") - - if not medical_department: - medical_department = frappe.new_doc("Medical Department") - medical_department.department = "_Test Medical Department" - medical_department.save(ignore_permissions=True) - medical_department = medical_department.name - - if not practitioner: - practitioner = frappe.new_doc("Healthcare Practitioner") - practitioner.first_name = "_Test Healthcare Practitioner" - practitioner.gender = "Female" - practitioner.department = medical_department - practitioner.op_consulting_charge = 500 - practitioner.inpatient_visit_charge = 500 - practitioner.save(ignore_permissions=True) - practitioner = practitioner.name - - return patient, medical_department, practitioner - - -def create_patient(): - patient = frappe.db.exists("Patient", "_Test Patient") - if not patient: - patient = frappe.new_doc("Patient") - patient.first_name = "_Test Patient" - patient.sex = "Female" - patient.default_currency = "INR" - patient.save(ignore_permissions=True) - patient = patient.name - return patient + discharge_patient(ip_record1, now_datetime()) + + def test_payment_should_be_mandatory_for_new_patient_appointment(self): + frappe.db.set_value("Healthcare Settings", None, "enable_free_follow_ups", 1) + frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 1) + frappe.db.set_value("Healthcare Settings", None, "max_visits", 3) + frappe.db.set_value("Healthcare Settings", None, "valid_days", 30) + + patient = create_patient() + assert check_is_new_patient(patient) + payment_required = check_payment_fields_reqd(patient) + assert payment_required is True + + def test_sales_invoice_should_be_generated_for_new_patient_appointment(self): + patient, practitioner = create_healthcare_docs() + frappe.db.set_value("Healthcare Settings", None, "automate_appointment_invoicing", 1) + invoice_count = frappe.db.count("Sales Invoice") + + assert check_is_new_patient(patient) + create_appointment(patient, practitioner, nowdate()) + new_invoice_count = frappe.db.count("Sales Invoice") + + assert new_invoice_count == invoice_count + 1 + + def test_overlap_appointment(self): + from erpnext.healthcare.doctype.patient_appointment.patient_appointment import OverlapError + + patient, practitioner = create_healthcare_docs(id=1) + patient_1, practitioner_1 = create_healthcare_docs(id=2) + service_unit = create_service_unit(id=0) + service_unit_1 = create_service_unit(id=1) + appointment = create_appointment( + patient, practitioner, nowdate(), service_unit=service_unit + ) # valid + + # patient and practitioner cannot have overlapping appointments + appointment = create_appointment( + patient, practitioner, nowdate(), service_unit=service_unit, save=0 + ) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment( + patient, practitioner, nowdate(), service_unit=service_unit_1, save=0 + ) # diff service unit + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment( + patient, practitioner, nowdate(), save=0 + ) # with no service unit link + self.assertRaises(OverlapError, appointment.save) + + # patient cannot have overlapping appointments with other practitioners + appointment = create_appointment( + patient, practitioner_1, nowdate(), service_unit=service_unit, save=0 + ) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment( + patient, practitioner_1, nowdate(), service_unit=service_unit_1, save=0 + ) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient, practitioner_1, nowdate(), save=0) + self.assertRaises(OverlapError, appointment.save) + + # practitioner cannot have overlapping appointments with other patients + appointment = create_appointment( + patient_1, practitioner, nowdate(), service_unit=service_unit, save=0 + ) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment( + patient_1, practitioner, nowdate(), service_unit=service_unit_1, save=0 + ) + self.assertRaises(OverlapError, appointment.save) + appointment = create_appointment(patient_1, practitioner, nowdate(), save=0) + self.assertRaises(OverlapError, appointment.save) + + def test_service_unit_capacity(self): + from erpnext.healthcare.doctype.patient_appointment.patient_appointment import ( + MaximumCapacityError, + OverlapError, + ) + + practitioner = create_practitioner() + capacity = 3 + overlap_service_unit_type = create_service_unit_type( + id=10, allow_appointments=1, overlap_appointments=1 + ) + overlap_service_unit = create_service_unit( + id=100, service_unit_type=overlap_service_unit_type, service_unit_capacity=capacity + ) + + for i in range(0, capacity): + patient = create_patient(id=i) + create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit) # valid + appointment = create_appointment( + patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0 + ) # overlap + self.assertRaises(OverlapError, appointment.save) + + patient = create_patient(id=capacity) + appointment = create_appointment( + patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0 + ) + self.assertRaises(MaximumCapacityError, appointment.save) + + def test_patient_appointment_should_consider_permissions_while_fetching_appointments(self): + patient, practitioner = create_healthcare_docs() + create_appointment(patient, practitioner, nowdate()) + + patient, new_practitioner = create_healthcare_docs(id=2) + create_appointment(patient, new_practitioner, nowdate()) + + roles = [{"doctype": "Has Role", "role": "Physician"}] + user = create_user(roles=roles) + new_practitioner = frappe.get_doc("Healthcare Practitioner", new_practitioner) + new_practitioner.user_id = user.email + new_practitioner.save() + + frappe.set_user(user.name) + appointments = frappe.get_list("Patient Appointment") + assert len(appointments) == 1 + + frappe.set_user("Administrator") + appointments = frappe.get_list("Patient Appointment") + assert len(appointments) == 2 + + +def create_healthcare_docs(id=0): + patient = create_patient(id) + practitioner = create_practitioner(id) + + return patient, practitioner + + +def create_patient( + id=0, patient_name=None, email=None, mobile=None, customer=None, create_user=False +): + if frappe.db.exists("Patient", {"firstname": f"_Test Patient {str(id)}"}): + patient = frappe.db.get_value("Patient", {"first_name": f"_Test Patient {str(id)}"}, ["name"]) + return patient + + patient = frappe.new_doc("Patient") + patient.first_name = patient_name if patient_name else f"_Test Patient {str(id)}" + patient.sex = "Female" + patient.mobile = mobile + patient.email = email + patient.customer = customer + patient.invite_user = create_user + patient.save(ignore_permissions=True) + + return patient.name + + +def create_medical_department(id=0): + if frappe.db.exists("Medical Department", f"_Test Medical Department {str(id)}"): + return f"_Test Medical Department {str(id)}" + + medical_department = frappe.new_doc("Medical Department") + medical_department.department = f"_Test Medical Department {str(id)}" + medical_department.save(ignore_permissions=True) + + return medical_department.name + + +def create_practitioner(id=0, medical_department=None): + if frappe.db.exists( + "Healthcare Practitioner", {"firstname": f"_Test Healthcare Practitioner {str(id)}"} + ): + practitioner = frappe.db.get_value( + "Healthcare Practitioner", {"firstname": f"_Test Healthcare Practitioner {str(id)}"}, ["name"] + ) + return practitioner + + practitioner = frappe.new_doc("Healthcare Practitioner") + practitioner.first_name = f"_Test Healthcare Practitioner {str(id)}" + practitioner.gender = "Female" + practitioner.department = medical_department or create_medical_department(id) + practitioner.op_consulting_charge = 500 + practitioner.inpatient_visit_charge = 500 + practitioner.save(ignore_permissions=True) + + return practitioner.name def create_encounter(appointment): diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py index 6cb2a24e6af1..44f0a9785c44 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py @@ -6,8 +6,6 @@ from frappe.model.document import Document from frappe.utils import flt -from erpnext import get_company_currency - class TherapyPlan(Document): def validate(self): @@ -74,9 +72,6 @@ def make_sales_invoice(reference_name, patient, company, therapy_plan_template): si.company = company si.patient = patient si.customer = frappe.db.get_value("Patient", patient, "customer") - si.currency = frappe.get_value( - "Customer", si.customer, "default_currency" - ) or get_company_currency(si.company) item = frappe.db.get_value("Therapy Plan Template", therapy_plan_template, "linked_item") price_list, price_list_currency = frappe.db.get_values( From 611d1af526839ff4ceb5628be5fff385c37c9294 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 25 May 2022 17:58:30 +0530 Subject: [PATCH 41/48] fix: Loan Doc query in Bank Reconciliation Statement (cherry picked from commit 147fc8fde704bc8c96dac2a24686cf7af5f7712b) --- .../bank_reconciliation_statement.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py index f3ccc868c4cb..e5950b764ee3 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py @@ -198,11 +198,12 @@ def get_loan_entries(filters): amount_field = (loan_doc.disbursed_amount).as_("credit") posting_date = (loan_doc.disbursement_date).as_("posting_date") account = loan_doc.disbursement_account + salary_condition = loan_doc.docstatus == 1 else: amount_field = (loan_doc.amount_paid).as_("debit") posting_date = (loan_doc.posting_date).as_("posting_date") account = loan_doc.payment_account - + salary_condition = loan_doc.repay_from_salary == 0 query = ( frappe.qb.from_(loan_doc) .select( @@ -214,15 +215,13 @@ def get_loan_entries(filters): posting_date, ) .where(loan_doc.docstatus == 1) + .where(salary_condition) .where(account == filters.get("account")) .where(posting_date <= getdate(filters.get("report_date"))) .where(ifnull(loan_doc.clearance_date, "4000-01-01") > getdate(filters.get("report_date"))) ) - if doctype == "Loan Repayment": - query.where(loan_doc.repay_from_salary == 0) - - entries = query.run(as_dict=1) + entries = query.run(as_dict=1, debug=1) loan_docs.extend(entries) return loan_docs @@ -267,15 +266,17 @@ def get_loan_amount(filters): amount_field = Sum(loan_doc.disbursed_amount) posting_date = (loan_doc.disbursement_date).as_("posting_date") account = loan_doc.disbursement_account + salary_condition = loan_doc.docstatus == 1 else: amount_field = Sum(loan_doc.amount_paid) posting_date = (loan_doc.posting_date).as_("posting_date") account = loan_doc.payment_account - + salary_condition = loan_doc.repay_from_salary == 0 amount = ( frappe.qb.from_(loan_doc) .select(amount_field) .where(loan_doc.docstatus == 1) + .where(salary_condition) .where(account == filters.get("account")) .where(posting_date > getdate(filters.get("report_date"))) .where(ifnull(loan_doc.clearance_date, "4000-01-01") <= getdate(filters.get("report_date"))) From 74ddf261e92ba6d85afac1d86b9e3316c2f01f82 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 25 May 2022 17:59:40 +0530 Subject: [PATCH 42/48] chore: Linting Issues (cherry picked from commit a1f53f8d31aa22e56fded4b75c9d53839043c3d0) --- .../bank_reconciliation_statement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py index e5950b764ee3..5c70a404ef7c 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py @@ -221,7 +221,7 @@ def get_loan_entries(filters): .where(ifnull(loan_doc.clearance_date, "4000-01-01") > getdate(filters.get("report_date"))) ) - entries = query.run(as_dict=1, debug=1) + entries = query.run(as_dict=1) loan_docs.extend(entries) return loan_docs From cd00cb2fb730d59708b71b0b526af47fd3b8e7c5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 25 May 2022 18:10:42 +0530 Subject: [PATCH 43/48] chore: Linting Issues (cherry picked from commit 9e4a36089eef11902f1f3c8b5759ab42e50b8b00) --- .../bank_reconciliation_statement.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py index 5c70a404ef7c..c41d0d10ffee 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py @@ -204,6 +204,7 @@ def get_loan_entries(filters): posting_date = (loan_doc.posting_date).as_("posting_date") account = loan_doc.payment_account salary_condition = loan_doc.repay_from_salary == 0 + query = ( frappe.qb.from_(loan_doc) .select( From f14e9b7502aebde4c77305253cb704730a9accd1 Mon Sep 17 00:00:00 2001 From: xdlumertz Date: Wed, 25 May 2022 11:09:59 -0300 Subject: [PATCH 44/48] fix: Chart data for monthly periodicity in Cash Flow report (#31039) fix: Chart data for monthly periodicity in Cash Flow report (cherry picked from commit c5e922c76bced414679fbaad33d9a50d0b2c62f4) --- erpnext/accounts/report/cash_flow/cash_flow.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py index 7929d4aa2aef..ee924f86a6a1 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.py +++ b/erpnext/accounts/report/cash_flow/cash_flow.py @@ -263,7 +263,10 @@ def get_report_summary(summary_data, currency): def get_chart_data(columns, data): labels = [d.get("label") for d in columns[2:]] datasets = [ - {"name": account.get("account").replace("'", ""), "values": [account.get("total")]} + { + "name": account.get("account").replace("'", ""), + "values": [account.get(d.get("fieldname")) for d in columns[2:]], + } for account in data if account.get("parent_account") == None and account.get("currency") ] From 14422eaf59e24d2d1b6dbcd3dfd9825f1adea2fd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 25 May 2022 20:06:31 +0530 Subject: [PATCH 45/48] chore: Update test cases --- erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py | 2 +- erpnext/healthcare/doctype/lab_test/test_lab_test.py | 1 + .../doctype/patient_appointment/patient_appointment.py | 4 ++++ erpnext/healthcare/doctype/therapy_plan/therapy_plan.py | 5 +++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 4b81a7d6a239..5701402811ec 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -712,7 +712,7 @@ def test_multiple_pricing_rules_with_min_qty(self): title="_Test Pricing Rule with Min Qty - 2", ) - si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1, currency="USD") + si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1) item = si.items[0] item.stock_qty = 1 si.save() diff --git a/erpnext/healthcare/doctype/lab_test/test_lab_test.py b/erpnext/healthcare/doctype/lab_test/test_lab_test.py index 06c02d1ea079..c08820f36be0 100644 --- a/erpnext/healthcare/doctype/lab_test/test_lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/test_lab_test.py @@ -164,6 +164,7 @@ def create_sales_invoice(): sales_invoice.customer = frappe.db.get_value("Patient", patient, "customer") sales_invoice.due_date = getdate() sales_invoice.company = "_Test Company" + sales_invoice.currency = "INR" sales_invoice.debit_to = get_receivable_account("_Test Company") tests = [insulin_resistance_template, blood_test_template] diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index b6e30060437e..34aa8650e197 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -12,6 +12,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.utils import flt, get_link_to_form, get_time, getdate +from erpnext import get_company_currency from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import ( get_income_account, get_receivable_account, @@ -252,6 +253,9 @@ def create_sales_invoice(appointment_doc): sales_invoice = frappe.new_doc("Sales Invoice") sales_invoice.patient = appointment_doc.patient sales_invoice.customer = frappe.get_value("Patient", appointment_doc.patient, "customer") + sales_invoice.currency = frappe.get_value( + "Customer", sales_invoice.customer, "default_currency" + ) or get_company_currency(appointment_doc.currency) sales_invoice.appointment = appointment_doc.name sales_invoice.due_date = getdate() sales_invoice.company = appointment_doc.company diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py index 44f0a9785c44..6cb2a24e6af1 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py @@ -6,6 +6,8 @@ from frappe.model.document import Document from frappe.utils import flt +from erpnext import get_company_currency + class TherapyPlan(Document): def validate(self): @@ -72,6 +74,9 @@ def make_sales_invoice(reference_name, patient, company, therapy_plan_template): si.company = company si.patient = patient si.customer = frappe.db.get_value("Patient", patient, "customer") + si.currency = frappe.get_value( + "Customer", si.customer, "default_currency" + ) or get_company_currency(si.company) item = frappe.db.get_value("Therapy Plan Template", therapy_plan_template, "linked_item") price_list, price_list_currency = frappe.db.get_values( From 3d51d125bfeac08d47c5bb61193e7874ad7fdc9e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 25 May 2022 23:52:40 +0530 Subject: [PATCH 46/48] chore: Update test case --- .../doctype/patient_appointment/patient_appointment.py | 3 ++- .../doctype/patient_appointment/test_patient_appointment.py | 1 + erpnext/projects/doctype/timesheet/test_timesheet.py | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index 34aa8650e197..0d98fff04ff7 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -255,7 +255,8 @@ def create_sales_invoice(appointment_doc): sales_invoice.customer = frappe.get_value("Patient", appointment_doc.patient, "customer") sales_invoice.currency = frappe.get_value( "Customer", sales_invoice.customer, "default_currency" - ) or get_company_currency(appointment_doc.currency) + ) or get_company_currency(appointment_doc.company) + sales_invoice.appointment = appointment_doc.name sales_invoice.due_date = getdate() sales_invoice.company = appointment_doc.company diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 048547a93223..05e6b9cfe0d0 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -379,6 +379,7 @@ def create_patient( patient.mobile = mobile patient.email = email patient.customer = customer + patient.default_currency = "INR" patient.invite_user = create_user patient.save(ignore_permissions=True) diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index 57bfd5b60743..7298c037a709 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -84,7 +84,9 @@ def test_sales_invoice_from_timesheet(self): emp = make_employee("test_employee_6@salary.com") timesheet = make_timesheet(emp, simulate=True, is_billable=1) - sales_invoice = make_sales_invoice(timesheet.name, "_Test Item", "_Test Customer") + sales_invoice = make_sales_invoice( + timesheet.name, "_Test Item", "_Test Customer", currency="INR" + ) sales_invoice.due_date = nowdate() sales_invoice.submit() timesheet = frappe.get_doc("Timesheet", timesheet.name) From 7df829f9cc9c73a068d1c4f7940a2c74242c7705 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 26 May 2022 09:03:18 +0530 Subject: [PATCH 47/48] chore: Update test case --- .../erpnext_integrations/connectors/shopify_connection.py | 6 ++++++ .../doctype/shopify_settings/test_shopify_settings.py | 1 + 2 files changed, 7 insertions(+) diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py index 4579a274ffa5..f28afbcd83a2 100644 --- a/erpnext/erpnext_integrations/connectors/shopify_connection.py +++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py @@ -4,6 +4,7 @@ from frappe import _ from frappe.utils import cint, cstr, flt, get_datetime, get_request_session, getdate, nowdate +from erpnext import get_company_currency from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import ( dump_request_data, make_shopify_log, @@ -143,6 +144,10 @@ def create_sales_order(shopify_order, shopify_settings, company=None): "taxes": get_order_taxes(shopify_order, shopify_settings), "apply_discount_on": "Grand Total", "discount_amount": get_discounted_amount(shopify_order), + "currency": frappe.get_value( + "Customer", customer or shopify_settings.default_customer, "default_currency" + ) + or get_company_currency(shopify_settings.company), } ) @@ -178,6 +183,7 @@ def create_sales_invoice(shopify_order, shopify_settings, so, old_order_sync=Fal si.set_posting_time = 1 si.posting_date = posting_date si.due_date = posting_date + si.currency = so.currency si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-" si.flags.ignore_mandatory = True set_cost_center(si.items, shopify_settings.cost_center) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py index 7cc45d2115f4..47d6d438b065 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py @@ -58,6 +58,7 @@ def setup_shopify(cls): "warehouse": "_Test Warehouse - _TC", "cash_bank_account": "Cash - _TC", "account": "Cash - _TC", + "company": "_Test Company", "customer_group": "_Test Customer Group", "cost_center": "Main - _TC", "taxes": [{"shopify_tax": "International Shipping", "tax_account": "Legal Expenses - _TC"}], From 8c2f2033615ec14a042ed4e69394b9933cbc450a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 26 May 2022 10:32:10 +0530 Subject: [PATCH 48/48] fix: change project's actual_start_date fieldtype from Data to Date (backport #31085) (#31135) Co-authored-by: sersaber <93864988+sersaber@users.noreply.github.com> Co-authored-by: Rucha Mahabal --- erpnext/projects/doctype/project/project.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 2570df70261f..4aeef81cbfbb 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -234,7 +234,7 @@ }, { "fieldname": "actual_start_date", - "fieldtype": "Data", + "fieldtype": "Date", "label": "Actual Start Date", "read_only": 1 }, @@ -458,7 +458,7 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 4, - "modified": "2021-04-28 16:36:11.654632", + "modified": "2022-05-25 22:45:06.108499", "modified_by": "Administrator", "module": "Projects", "name": "Project", @@ -502,4 +502,4 @@ "timeline_field": "customer", "title_field": "project_name", "track_seen": 1 -} \ No newline at end of file +}