diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
index 45373741f39..e711ae0de2b 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
@@ -49,6 +49,7 @@
"column_break_21",
"start_date",
"section_break_33",
+ "pdf_name",
"subject",
"column_break_28",
"cc_to",
@@ -273,7 +274,7 @@
"fieldname": "help_text",
"fieldtype": "HTML",
"label": "Help Text",
- "options": "
\n
Statement Of Accounts for {{ customer.name }}Hello {{ customer.name }},
PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}. Statement Of Accounts for {{ customer.customer_name }}Hello {{ customer.customer_name }},
PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}. {{ _("Printed On ") }}{{ frappe.utils.now() }}
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py index c281040aaf2..fb0d8d152f0 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py @@ -1,9 +1,42 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe import unittest +import frappe +from frappe.utils import add_days, getdate, today + +from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import ( + send_emails, +) +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + class TestProcessStatementOfAccounts(unittest.TestCase): - pass + def setUp(self): + self.si = create_sales_invoice() + self.process_soa = create_process_soa() + + def test_auto_email_for_process_soa_ar(self): + send_emails(self.process_soa.name, from_scheduler=True) + self.process_soa.load_from_db() + self.assertEqual(self.process_soa.posting_date, getdate(add_days(today(), 7))) + + def tearDown(self): + frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA") + + +def create_process_soa(): + frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA") + process_soa = frappe.new_doc("Process Statement Of Accounts") + soa_dict = { + "name": "Test Process SOA", + "company": "_Test Company", + } + process_soa.update(soa_dict) + process_soa.set("customers", [{"customer": "_Test Customer"}]) + process_soa.enable_auto_email = 1 + process_soa.frequency = "Weekly" + process_soa.report = "Accounts Receivable" + process_soa.save() + return process_soa diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 050e6bc5d2f..ce1a62d0065 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -287,7 +287,7 @@ def get_conditions(filters): conditions = "" for opts in ( - ("company", " and company=%(company)s"), + ("company", " and `tabPurchase Invoice`.company=%(company)s"), ("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"), ("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"), ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"), diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 4d24dd90762..19bb449cd94 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -332,7 +332,7 @@ def get_conditions(filters, additional_conditions=None): conditions = "" for opts in ( - ("company", " and company=%(company)s"), + ("company", " and `tabSales Invoice`.company=%(company)s"), ("customer", " and `tabSales Invoice`.customer = %(customer)s"), ("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"), ("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"), diff --git a/erpnext/accounts/report/tds_payable_monthly/test_tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/test_tds_payable_monthly.py new file mode 100644 index 00000000000..89ecef1904c --- /dev/null +++ b/erpnext/accounts/report/tds_payable_monthly/test_tds_payable_monthly.py @@ -0,0 +1,111 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import today + +from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center +from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.doctype.tax_withholding_category.test_tax_withholding_category import ( + create_tax_withholding_category, +) +from erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly import execute +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.accounts.utils import get_fiscal_year + + +class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase): + def setUp(self): + self.create_company() + self.clear_old_entries() + create_tax_accounts() + create_tcs_category() + + def test_tax_withholding_for_customers(self): + si = create_sales_invoice(rate=1000) + pe = create_tcs_payment_entry() + filters = frappe._dict( + company="_Test Company", party_type="Customer", from_date=today(), to_date=today() + ) + result = execute(filters)[1] + expected_values = [ + [pe.name, "TCS", 0.075, 2550, 0.53, 2550.53], + [si.name, "TCS", 0.075, 1000, 0.53, 1000.53], + ] + self.check_expected_values(result, expected_values) + + def check_expected_values(self, result, expected_values): + for i in range(len(result)): + voucher = frappe._dict(result[i]) + voucher_expected_values = expected_values[i] + self.assertEqual(voucher.ref_no, voucher_expected_values[0]) + self.assertEqual(voucher.section_code, voucher_expected_values[1]) + self.assertEqual(voucher.rate, voucher_expected_values[2]) + self.assertEqual(voucher.base_total, voucher_expected_values[3]) + self.assertEqual(voucher.tax_amount, voucher_expected_values[4]) + self.assertEqual(voucher.grand_total, voucher_expected_values[5]) + + def tearDown(self): + self.clear_old_entries() + + +def create_tax_accounts(): + account_names = ["TCS", "TDS"] + for account in account_names: + frappe.get_doc( + { + "doctype": "Account", + "company": "_Test Company", + "account_name": account, + "parent_account": "Duties and Taxes - _TC", + "report_type": "Balance Sheet", + "root_type": "Liability", + } + ).insert(ignore_if_duplicate=True) + + +def create_tcs_category(): + fiscal_year = get_fiscal_year(today(), company="_Test Company") + from_date = fiscal_year[1] + to_date = fiscal_year[2] + + tax_category = create_tax_withholding_category( + category_name="TCS", + rate=0.075, + from_date=from_date, + to_date=to_date, + account="TCS - _TC", + cumulative_threshold=300, + ) + + customer = frappe.get_doc("Customer", "_Test Customer") + customer.tax_withholding_category = "TCS" + customer.save() + + +def create_tcs_payment_entry(): + payment_entry = create_payment_entry( + payment_type="Receive", + party_type="Customer", + party="_Test Customer", + paid_from="Debtors - _TC", + paid_to="Cash - _TC", + paid_amount=2550, + ) + + payment_entry.append( + "taxes", + { + "account_head": "TCS - _TC", + "charge_type": "Actual", + "tax_amount": 0.53, + "add_deduct_tax": "Add", + "description": "Test", + "cost_center": "Main - _TC", + }, + ) + payment_entry.submit() + return payment_entry diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 34d5430210c..e72ddcb12a0 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -40,6 +40,7 @@ class Asset(AccountsController): self.validate_item() self.validate_cost_center() self.set_missing_values() + self.validate_finance_books() if not self.split_from: self.prepare_depreciation_data() self.validate_gross_and_purchase_amount() @@ -206,6 +207,27 @@ class Asset(AccountsController): finance_books = get_item_details(self.item_code, self.asset_category) self.set("finance_books", finance_books) + def validate_finance_books(self): + if not self.calculate_depreciation or len(self.finance_books) == 1: + return + + finance_books = set() + + for d in self.finance_books: + if d.finance_book in finance_books: + frappe.throw( + _("Row #{}: Please use a different Finance Book.").format(d.idx), + title=_("Duplicate Finance Book"), + ) + else: + finance_books.add(d.finance_book) + + if not d.finance_book: + frappe.throw( + _("Row #{}: Finance Book should not be empty since you're using multiple.").format(d.idx), + title=_("Missing Finance Book"), + ) + def validate_asset_values(self): if not self.asset_category: self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category") @@ -1363,26 +1385,41 @@ def get_straight_line_or_manual_depr_amount( daily_depr_amount = ( flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) ) / date_diff( - add_months( - row.depreciation_start_date, - flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) - * row.frequency_of_depreciation, - ), - add_months( - row.depreciation_start_date, - flt( - row.total_number_of_depreciations - - asset.number_of_depreciations_booked - - number_of_pending_depreciations + get_last_day( + add_months( + row.depreciation_start_date, + flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) + * row.frequency_of_depreciation, ) - * row.frequency_of_depreciation, + ), + add_days( + get_last_day( + add_months( + row.depreciation_start_date, + flt( + row.total_number_of_depreciations + - asset.number_of_depreciations_booked + - number_of_pending_depreciations + - 1 + ) + * row.frequency_of_depreciation, + ) + ), + 1, ), ) - to_date = add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) - from_date = add_months( - row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation + + to_date = get_last_day( + add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) ) - return daily_depr_amount * date_diff(to_date, from_date) + from_date = add_days( + get_last_day( + add_months(row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation) + ), + 1, + ) + + return daily_depr_amount * (date_diff(to_date, from_date) + 1) else: return ( flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) @@ -1395,18 +1432,29 @@ def get_straight_line_or_manual_depr_amount( - flt(asset.opening_accumulated_depreciation) - flt(row.expected_value_after_useful_life) ) / date_diff( - add_months( - row.depreciation_start_date, - flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked) - * row.frequency_of_depreciation, + get_last_day( + add_months( + row.depreciation_start_date, + flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) + * row.frequency_of_depreciation, + ) + ), + add_days( + get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)), 1 ), - row.depreciation_start_date, ) - to_date = add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) - from_date = add_months( - row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation + + to_date = get_last_day( + add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) ) - return daily_depr_amount * date_diff(to_date, from_date) + from_date = add_days( + get_last_day( + add_months(row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation) + ), + 1, + ) + + return daily_depr_amount * (date_diff(to_date, from_date) + 1) else: return ( flt(asset.gross_purchase_amount) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index a2826d929b8..1adbeed65a5 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -743,18 +743,18 @@ class TestDepreciationMethods(AssetSetup): ) expected_schedules = [ - ["2023-01-31", 1019.18, 1019.18], - ["2023-02-28", 920.55, 1939.73], - ["2023-03-31", 1019.18, 2958.91], - ["2023-04-30", 986.3, 3945.21], - ["2023-05-31", 1019.18, 4964.39], - ["2023-06-30", 986.3, 5950.69], - ["2023-07-31", 1019.18, 6969.87], - ["2023-08-31", 1019.18, 7989.05], - ["2023-09-30", 986.3, 8975.35], - ["2023-10-31", 1019.18, 9994.53], - ["2023-11-30", 986.3, 10980.83], - ["2023-12-31", 1019.17, 12000.0], + ["2023-01-31", 1021.98, 1021.98], + ["2023-02-28", 923.08, 1945.06], + ["2023-03-31", 1021.98, 2967.04], + ["2023-04-30", 989.01, 3956.05], + ["2023-05-31", 1021.98, 4978.03], + ["2023-06-30", 989.01, 5967.04], + ["2023-07-31", 1021.98, 6989.02], + ["2023-08-31", 1021.98, 8011.0], + ["2023-09-30", 989.01, 9000.01], + ["2023-10-31", 1021.98, 10021.99], + ["2023-11-30", 989.01, 11011.0], + ["2023-12-31", 989.0, 12000.0], ] schedules = [ @@ -1332,6 +1332,7 @@ class TestDepreciationBasics(AssetSetup): asset.append( "finance_books", { + "finance_book": "Test Finance Book 1", "depreciation_method": "Straight Line", "frequency_of_depreciation": 1, "total_number_of_depreciations": 3, @@ -1342,6 +1343,7 @@ class TestDepreciationBasics(AssetSetup): asset.append( "finance_books", { + "finance_book": "Test Finance Book 2", "depreciation_method": "Straight Line", "frequency_of_depreciation": 1, "total_number_of_depreciations": 6, @@ -1352,6 +1354,7 @@ class TestDepreciationBasics(AssetSetup): asset.append( "finance_books", { + "finance_book": "Test Finance Book 3", "depreciation_method": "Straight Line", "frequency_of_depreciation": 12, "total_number_of_depreciations": 3, @@ -1381,6 +1384,7 @@ class TestDepreciationBasics(AssetSetup): asset.append( "finance_books", { + "finance_book": "Test Finance Book 1", "depreciation_method": "Straight Line", "frequency_of_depreciation": 12, "total_number_of_depreciations": 3, @@ -1391,6 +1395,7 @@ class TestDepreciationBasics(AssetSetup): asset.append( "finance_books", { + "finance_book": "Test Finance Book 2", "depreciation_method": "Straight Line", "frequency_of_depreciation": 12, "total_number_of_depreciations": 6, @@ -1647,6 +1652,15 @@ def create_asset_data(): if not frappe.db.exists("Location", "Test Location"): frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert() + if not frappe.db.exists("Finance Book", "Test Finance Book 1"): + frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 1"}).insert() + + if not frappe.db.exists("Finance Book", "Test Finance Book 2"): + frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 2"}).insert() + + if not frappe.db.exists("Finance Book", "Test Finance Book 3"): + frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 3"}).insert() + def create_asset(**args): args = frappe._dict(args) diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py index 83031415ec3..5c40072086e 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py @@ -80,14 +80,16 @@ def calculate_next_due_date( next_due_date = add_days(start_date, 7) if periodicity == "Monthly": next_due_date = add_months(start_date, 1) + if periodicity == "Quarterly": + next_due_date = add_months(start_date, 3) + if periodicity == "Half-yearly": + next_due_date = add_months(start_date, 6) if periodicity == "Yearly": next_due_date = add_years(start_date, 1) if periodicity == "2 Yearly": next_due_date = add_years(start_date, 2) if periodicity == "3 Yearly": next_due_date = add_years(start_date, 3) - if periodicity == "Quarterly": - next_due_date = add_months(start_date, 3) if end_date and ( (start_date and start_date >= end_date) or (last_completion_date and last_completion_date >= end_date) diff --git a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json index b7cb23e6687..80d90c63473 100644 --- a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json +++ b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json @@ -71,7 +71,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Periodicity", - "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly\n3 Yearly", + "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf-yearly\nYearly\n2 Yearly\n3 Yearly", "reqd": 1 }, { @@ -153,4 +153,4 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 48086dde93f..f0e113b0e7b 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -408,6 +408,16 @@ class LoanRepayment(AccountsController): else: payment_account = self.payment_account + payment_party_type = "" + payment_party = "" + + if ( + hasattr(self, "process_payroll_accounting_entry_based_on_employee") + and self.process_payroll_accounting_entry_based_on_employee + ): + payment_party_type = "Employee" + payment_party = self.applicant + if self.total_penalty_paid: gle_map.append( self.get_gl_dict( @@ -455,6 +465,8 @@ class LoanRepayment(AccountsController): "remarks": _(remarks), "cost_center": self.cost_center, "posting_date": getdate(self.posting_date), + "party_type": payment_party_type, + "party": payment_party, } ) ) @@ -493,6 +505,7 @@ def create_repayment_entry( amount_paid, penalty_amount=None, payroll_payable_account=None, + process_payroll_accounting_entry_based_on_employee=0, ): lr = frappe.get_doc( @@ -509,6 +522,7 @@ def create_repayment_entry( "amount_paid": amount_paid, "loan_type": loan_type, "payroll_payable_account": payroll_payable_account, + "process_payroll_accounting_entry_based_on_employee": process_payroll_accounting_entry_based_on_employee, } ).insert() diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 09bf1d8a736..d07bf0fa66b 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -10,22 +10,25 @@ "warehouse", "item_name", "material_request_type", - "actual_qty", - "ordered_qty", + "quantity", "required_bom_qty", "column_break_4", - "quantity", + "schedule_date", "uom", "conversion_factor", - "projected_qty", - "reserved_qty_for_production", - "safety_stock", "item_details", "description", "min_order_qty", "section_break_8", "sales_order", - "requested_qty" + "bin_qty_section", + "actual_qty", + "requested_qty", + "reserved_qty_for_production", + "column_break_yhelv", + "ordered_qty", + "projected_qty", + "safety_stock" ], "fields": [ { @@ -65,7 +68,7 @@ "fieldtype": "Column Break" }, { - "columns": 1, + "columns": 2, "fieldname": "quantity", "fieldtype": "Float", "in_list_view": 1, @@ -80,12 +83,12 @@ "read_only": 1 }, { - "columns": 2, + "columns": 1, "default": "0", "fieldname": "actual_qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Available Qty", + "label": "Qty In Stock", "no_copy": 1, "read_only": 1 }, @@ -176,11 +179,27 @@ "fieldtype": "Float", "label": "Conversion Factor", "read_only": 1 + }, + { + "columns": 1, + "fieldname": "schedule_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Required By" + }, + { + "fieldname": "bin_qty_section", + "fieldtype": "Section Break", + "label": "BIN Qty" + }, + { + "fieldname": "column_break_yhelv", + "fieldtype": "Column Break" } ], "istable": 1, "links": [], - "modified": "2023-05-03 12:43:29.895754", + "modified": "2023-09-12 12:09:08.358326", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a494550423f..795cb97fffa 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -725,7 +725,7 @@ class ProductionPlan(Document): # key for Sales Order:Material Request Type:Customer key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "") - schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days)) + schedule_date = item.schedule_date or add_days(nowdate(), cint(item_doc.lead_time_days)) if not key in material_request_map: # make a new MR for the combination diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 2871a29d768..f5778847b9c 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -2,7 +2,7 @@ # See license.txt import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_to_date, flt, now_datetime, nowdate +from frappe.utils import add_to_date, flt, getdate, now_datetime, nowdate from erpnext.controllers.item_variant import create_variant from erpnext.manufacturing.doctype.production_plan.production_plan import ( @@ -58,6 +58,9 @@ class TestProductionPlan(FrappeTestCase): pln = create_production_plan(item_code="Test Production Item 1") self.assertTrue(len(pln.mr_items), 2) + for row in pln.mr_items: + row.schedule_date = add_to_date(nowdate(), days=10) + pln.make_material_request() pln.reload() self.assertTrue(pln.status, "Material Requested") @@ -71,6 +74,13 @@ class TestProductionPlan(FrappeTestCase): self.assertTrue(len(material_requests), 2) + for row in material_requests: + mr_schedule_date = getdate(frappe.db.get_value("Material Request", row[0], "schedule_date")) + + expected_date = getdate(add_to_date(nowdate(), days=10)) + + self.assertEqual(mr_schedule_date, expected_date) + pln.make_work_order() work_orders = frappe.get_all( "Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1 diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index d80133c988a..082ba915207 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -84,6 +84,7 @@ class Project(Document): issue=task_details.issue, is_group=task_details.is_group, color=task_details.color, + template_task=task_details.name, ) ).insert() @@ -103,9 +104,13 @@ class Project(Document): return date def dependency_mapping(self, template_tasks, project_tasks): - for template_task in template_tasks: - project_task = list(filter(lambda x: x.subject == template_task.subject, project_tasks))[0] - project_task = frappe.get_doc("Task", project_task.name) + for project_task in project_tasks: + if project_task.get("template_task"): + template_task = frappe.get_doc("Task", project_task.template_task) + else: + template_task = list(filter(lambda x: x.subject == project_task.subject, template_tasks))[0] + template_task = frappe.get_doc("Task", template_task.name) + self.check_depends_on_value(template_task, project_task, project_tasks) self.check_for_parent_tasks(template_task, project_task, project_tasks) @@ -117,6 +122,7 @@ class Project(Document): filter(lambda x: x.subject == child_task_subject, project_tasks) ) if len(corresponding_project_task): + project_task.reload() # reload, as it might have been updated in the previous iteration project_task.append("depends_on", {"task": corresponding_project_task[0].name}) project_task.save() diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 8a599cef753..e49fecd1f47 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -1,9 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import unittest - import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, getdate, nowdate from erpnext.projects.doctype.project_template.test_project_template import make_project_template @@ -15,7 +14,7 @@ test_records = frappe.get_test_records("Project") test_ignore = ["Sales Order"] -class TestProject(unittest.TestCase): +class TestProject(FrappeTestCase): def test_project_with_template_having_no_parent_and_depend_tasks(self): project_name = "Test Project with Template - No Parent and Dependend Tasks" frappe.db.sql(""" delete from tabTask where project = %s """, project_name) @@ -155,6 +154,50 @@ class TestProject(unittest.TestCase): so.reload() self.assertFalse(so.project) + def test_project_with_template_tasks_having_common_name(self): + # Step - 1: Create Template Parent Tasks + template_parent_task1 = create_task(subject="Parent Task - 1", is_template=1, is_group=1) + template_parent_task2 = create_task(subject="Parent Task - 2", is_template=1, is_group=1) + template_parent_task3 = create_task(subject="Parent Task - 1", is_template=1, is_group=1) + + # Step - 2: Create Template Child Tasks + template_task1 = create_task( + subject="Task - 1", is_template=1, parent_task=template_parent_task1.name + ) + template_task2 = create_task( + subject="Task - 2", is_template=1, parent_task=template_parent_task2.name + ) + template_task3 = create_task( + subject="Task - 1", is_template=1, parent_task=template_parent_task3.name + ) + + # Step - 3: Create Project Template + template_tasks = [ + template_parent_task1, + template_task1, + template_parent_task2, + template_task2, + template_parent_task3, + template_task3, + ] + project_template = make_project_template( + "Project template with common Task Subject", template_tasks + ) + + # Step - 4: Create Project against the Project Template + project = get_project("Project with common Task Subject", project_template) + project_tasks = frappe.get_all( + "Task", {"project": project.name}, ["subject", "parent_task", "is_group"] + ) + + # Test - 1: No. of Project Tasks should be equal to No. of Template Tasks + self.assertEquals(len(project_tasks), len(template_tasks)) + + # Test - 2: All child Project Tasks should have Parent Task linked + for pt in project_tasks: + if not pt.is_group: + self.assertIsNotNone(pt.parent_task) + def get_project(name, template): diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 141a99e612b..33a8799f96c 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -52,13 +52,15 @@ "company", "lft", "rgt", - "old_parent" + "old_parent", + "template_task" ], "fields": [ { "fieldname": "subject", "fieldtype": "Data", "in_global_search": 1, + "in_list_view": 1, "in_standard_filter": 1, "label": "Subject", "reqd": 1, @@ -138,6 +140,7 @@ "fieldname": "parent_task", "fieldtype": "Link", "ignore_user_permissions": 1, + "in_list_view": 1, "label": "Parent Task", "options": "Task", "search_index": 1 @@ -382,6 +385,12 @@ "fieldtype": "Date", "label": "Completed On", "mandatory_depends_on": "eval: doc.status == \"Completed\"" + }, + { + "fieldname": "template_task", + "fieldtype": "Data", + "hidden": 1, + "label": "Template Task" } ], "icon": "fa fa-check", @@ -389,7 +398,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2022-06-23 16:58:47.005241", + "modified": "2023-09-06 13:52:05.861175", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/setup/doctype/employee/test_employee.py b/erpnext/setup/doctype/employee/test_employee.py index 071c336326f..5a693c5effb 100644 --- a/erpnext/setup/doctype/employee/test_employee.py +++ b/erpnext/setup/doctype/employee/test_employee.py @@ -66,5 +66,8 @@ def make_employee(user, company=None, **kwargs): employee.insert() return employee.name else: - frappe.db.set_value("Employee", {"employee_name": user}, "status", "Active") - return frappe.get_value("Employee", {"employee_name": user}, "name") + employee = frappe.get_doc("Employee", {"employee_name": user}) + employee.update(kwargs) + employee.status = "Active" + employee.save() + return employee.name diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index eea28791a9f..05fa2324dd4 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -6,6 +6,14 @@ cur_frm.cscript.refresh = cur_frm.cscript.inspection_type; frappe.ui.form.on("Quality Inspection", { setup: function(frm) { + frm.set_query("reference_name", function() { + return { + filters: { + "docstatus": ["!=", 2], + } + } + }); + frm.set_query("batch_no", function() { return { filters: { diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 7e81a72028f..80bf8508cf3 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -323,8 +323,10 @@ class StockBalanceReport(object): for field in ["item_code", "brand"]: if not self.filters.get(field): continue - - query = query.where(item_table[field] == self.filters.get(field)) + elif field == "item_code": + query = query.where(item_table.name == self.filters.get(field)) + else: + query = query.where(item_table[field] == self.filters.get(field)) return query