diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 054afe5bbb..6d388c4aaa 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -12,6 +12,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.doctype.item.test_item import make_item class TestPOSInvoice(unittest.TestCase): + @classmethod + def setUpClass(cls): + frappe.db.sql("delete from `tabTax Rule`") + def tearDown(self): if frappe.session.user != "Administrator": frappe.set_user("Administrator") diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py index 632e30db45..ac1ffd9e75 100644 --- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py @@ -14,10 +14,15 @@ test_records = frappe.get_test_records('Tax Rule') from six import iteritems class TestTaxRule(unittest.TestCase): - def setUp(self): + @classmethod + def setUpClass(cls): + frappe.db.set_value("Shopping Cart Settings", None, "enabled", 0) + + @classmethod + def tearDownClass(cls): frappe.db.sql("delete from `tabTax Rule`") - def tearDown(self): + def setUp(self): frappe.db.sql("delete from `tabTax Rule`") def test_conflict(self): diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 90d064604e..ef9372eeb6 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -368,7 +368,6 @@ def make_purchase_receipt(source_name, target_doc=None): "Purchase Order": { "doctype": "Purchase Receipt", "field_map": { - "per_billed": "per_billed", "supplier_warehouse":"supplier_warehouse" }, "validation": { diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index e329b325b3..5f73c55836 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -113,10 +113,10 @@ class calculate_taxes_and_totals(object): item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) if flt(item.rate_with_margin) > 0: item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) - if not item.discount_amount: - item.discount_amount = item.rate_with_margin - item.rate - elif not item.discount_percentage: + if item.discount_amount and not item.discount_percentage: item.rate -= item.discount_amount + else: + item.discount_amount = item.rate_with_margin - item.rate elif flt(item.price_list_rate) > 0: item.discount_amount = item.price_list_rate - item.rate elif flt(item.price_list_rate) > 0 and not item.discount_amount: @@ -808,4 +808,4 @@ class init_landed_taxes_and_totals(object): def set_amounts_in_company_currency(self): for d in self.doc.get(self.tax_field): d.amount = flt(d.amount, d.precision("amount")) - d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount")) \ No newline at end of file + d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount")) diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py b/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py index a21caca8ff..21776d2380 100644 --- a/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py +++ b/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py @@ -81,15 +81,8 @@ class TestInpatientMedicationOrder(unittest.TestCase): self.ip_record.reload() discharge_patient(self.ip_record) - for entry in frappe.get_all('Inpatient Medication Entry'): - doc = frappe.get_doc('Inpatient Medication Entry', entry.name) - doc.cancel() - doc.delete() - - for entry in frappe.get_all('Inpatient Medication Order'): - doc = frappe.get_doc('Inpatient Medication Order', entry.name) - doc.cancel() - doc.delete() + for doctype in ["Inpatient Medication Entry", "Inpatient Medication Order"]: + frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) def create_dosage_form(): if not frappe.db.exists('Dosage Form', 'Tablet'): diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index b335c48594..48bfa0c0aa 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -56,6 +56,7 @@ class TestLeaveApplication(unittest.TestCase): @classmethod def setUpClass(cls): set_leave_approver() + frappe.db.sql("delete from tabAttendance where employee='_T-Employee-00001'") def tearDown(self): frappe.set_user("Administrator") @@ -230,8 +231,9 @@ class TestLeaveApplication(unittest.TestCase): def test_optional_leave(self): leave_period = get_leave_period() today = nowdate() - from datetime import date holiday_list = 'Test Holiday List for Optional Holiday' + optional_leave_date = add_days(today, 7) + if not frappe.db.exists('Holiday List', holiday_list): frappe.get_doc(dict( doctype = 'Holiday List', @@ -239,7 +241,7 @@ class TestLeaveApplication(unittest.TestCase): from_date = add_months(today, -6), to_date = add_months(today, 6), holidays = [ - dict(holiday_date = today, description = 'Test') + dict(holiday_date = optional_leave_date, description = 'Test') ] )).insert() employee = get_employee() @@ -255,7 +257,7 @@ class TestLeaveApplication(unittest.TestCase): allocate_leaves(employee, leave_period, leave_type, 10) - date = add_days(today, - 1) + date = add_days(today, 6) leave_application = frappe.get_doc(dict( doctype = 'Leave Application', @@ -270,14 +272,14 @@ class TestLeaveApplication(unittest.TestCase): # can only apply on optional holidays self.assertRaises(NotAnOptionalHoliday, leave_application.insert) - leave_application.from_date = today - leave_application.to_date = today + leave_application.from_date = optional_leave_date + leave_application.to_date = optional_leave_date leave_application.status = "Approved" leave_application.insert() leave_application.submit() # check leave balance is reduced - self.assertEqual(get_leave_balance_on(employee.name, leave_type, today), 9) + self.assertEqual(get_leave_balance_on(employee.name, leave_type, optional_leave_date), 9) def test_leaves_allowed(self): employee = get_employee() @@ -341,7 +343,7 @@ class TestLeaveApplication(unittest.TestCase): to_date = add_days(date, 4), company = "_Test Company", docstatus = 1, - status = "Approved" + status = "Approved" )) self.assertRaises(frappe.ValidationError, leave_application.insert) @@ -363,7 +365,7 @@ class TestLeaveApplication(unittest.TestCase): to_date = add_days(date, 4), company = "_Test Company", docstatus = 1, - status = "Approved" + status = "Approved" )) self.assertTrue(leave_application.insert()) @@ -393,7 +395,7 @@ class TestLeaveApplication(unittest.TestCase): to_date = add_days(date, 4), company = "_Test Company", docstatus = 1, - status = "Approved" + status = "Approved" )) self.assertRaises(frappe.ValidationError, leave_application.insert) @@ -508,7 +510,7 @@ class TestLeaveApplication(unittest.TestCase): description = "_Test Reason", company = "_Test Company", docstatus = 1, - status = "Approved" + status = "Approved" )) leave_application.submit() leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name)) @@ -540,7 +542,7 @@ class TestLeaveApplication(unittest.TestCase): description = "_Test Reason", company = "_Test Company", docstatus = 1, - status = "Approved" + status = "Approved" )) leave_application.submit() diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 63559c4f5a..66dced4cc6 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -52,7 +52,9 @@ def create_leave_ledger_entry(ref_doc, args, submit=True): ledger.update(args) if submit: - frappe.get_doc(ledger).submit() + doc = frappe.get_doc(ledger) + doc.flags.ignore_permissions = 1 + doc.submit() else: delete_ledger_entry(ledger) diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json index 102bc0d71d..99b5c72b2d 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "LM-LSS-.#####", "creation": "2019-09-06 11:33:34.709540", "doctype": "DocType", @@ -14,6 +15,7 @@ "shortfall_amount", "column_break_8", "security_value", + "shortfall_percentage", "section_break_8", "process_loan_security_shortfall" ], @@ -85,10 +87,18 @@ { "fieldname": "column_break_8", "fieldtype": "Column Break" + }, + { + "fieldname": "shortfall_percentage", + "fieldtype": "Percent", + "label": "Shortfall Percentage", + "read_only": 1 } ], "in_create": 1, - "modified": "2019-10-24 06:24:26.128997", + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-04-01 08:13:43.263772", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Shortfall", diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py index b5e78981d0..653943629e 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py @@ -22,7 +22,9 @@ def update_shortfall_status(loan, security_value): if security_value >= loan_security_shortfall.shortfall_amount: frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, { "status": "Completed", - "shortfall_amount": loan_security_shortfall.shortfall_amount}) + "shortfall_amount": loan_security_shortfall.shortfall_amount, + "shortfall_percentage": 0 + }) else: frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, "shortfall_amount", loan_security_shortfall.shortfall_amount - security_value) @@ -65,7 +67,8 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): outstanding_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \ - flt(loan.total_principal_paid) else: - outstanding_amount = loan.disbursed_amount + outstanding_amount = flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \ + - flt(loan.total_principal_paid) pledged_securities = get_pledged_security_qty(loan.name) ltv_ratio = '' @@ -81,14 +84,15 @@ def check_for_ltv_shortfall(process_loan_security_shortfall): if current_ratio > ltv_ratio: shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100) create_loan_security_shortfall(loan.name, outstanding_amount, security_value, shortfall_amount, - process_loan_security_shortfall) + current_ratio, process_loan_security_shortfall) elif loan_shortfall_map.get(loan.name): shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100) if shortfall_amount <= 0: shortfall = loan_shortfall_map.get(loan.name) update_pending_shortfall(shortfall) -def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall): +def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, shortfall_ratio, + process_loan_security_shortfall): existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name") if existing_shortfall: @@ -101,6 +105,7 @@ def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_ ltv_shortfall.loan_amount = loan_amount ltv_shortfall.security_value = security_value ltv_shortfall.shortfall_amount = shortfall_amount + ltv_shortfall.shortfall_percentage = shortfall_ratio ltv_shortfall.process_loan_security_shortfall = process_loan_security_shortfall ltv_shortfall.save() @@ -114,6 +119,7 @@ def update_pending_shortfall(shortfall): frappe.db.set_value("Loan Security Shortfall", shortfall, { "status": "Completed", - "shortfall_amount": 0 + "shortfall_amount": 0, + "shortfall_percentage": 0 }) diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py index 0f72c3cce7..2a74a1eb85 100644 --- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py +++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py @@ -63,9 +63,11 @@ def get_active_loan_details(filters): currency = erpnext.get_company_currency(filters.get('company')) for loan in loan_details: + total_payment = loan.total_payment if loan.status == 'Disbursed' else loan.disbursed_amount + loan.update({ "sanctioned_amount": flt(sanctioned_amount_map.get(loan.applicant_name)), - "principal_outstanding": flt(loan.total_payment) - flt(loan.total_principal_paid) \ + "principal_outstanding": flt(total_payment) - flt(loan.total_principal_paid) \ - flt(loan.total_interest_payable) - flt(loan.written_off_amount), "total_repayment": flt(payments.get(loan.loan)), "accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")), diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 3239478872..4050a7d3ed 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -134,7 +134,13 @@ class TestBOM(unittest.TestCase): bom.items[0].conversion_factor = 6 bom.insert() - reset_item_valuation_rate(item_code='_Test Item', qty=200, rate=200) + reset_item_valuation_rate( + item_code='_Test Item', + warehouse_list=frappe.get_all("Warehouse", + {"is_group":0, "company": bom.company}, pluck="name"), + qty=200, + rate=200 + ) bom.update_cost() diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py index 7071bc1ab0..6a38dcfa03 100644 --- a/erpnext/manufacturing/doctype/routing/test_routing.py +++ b/erpnext/manufacturing/doctype/routing/test_routing.py @@ -13,8 +13,15 @@ from erpnext.manufacturing.doctype.workstation.test_workstation import make_work from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record class TestRouting(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.item_code = "Test Routing Item - A" + + @classmethod + def tearDownClass(cls): + frappe.db.sql('delete from tabBOM where item=%s', cls.item_code) + def test_sequence_id(self): - item_code = "Test Routing Item - A" operations = [{"operation": "Test Operation A", "workstation": "Test Workstation A", "time_in_mins": 30}, {"operation": "Test Operation B", "workstation": "Test Workstation A", "time_in_mins": 20}] @@ -22,8 +29,8 @@ class TestRouting(unittest.TestCase): setup_operations(operations) routing_doc = create_routing(routing_name="Testing Route", operations=operations) - bom_doc = setup_bom(item_code=item_code, routing=routing_doc.name) - wo_doc = make_wo_order_test_record(production_item = item_code, bom_no=bom_doc.name) + bom_doc = setup_bom(item_code=self.item_code, routing=routing_doc.name) + wo_doc = make_wo_order_test_record(production_item = self.item_code, bom_no=bom_doc.name) for row in routing_doc.operations: self.assertEqual(row.sequence_id, row.idx) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 08291d1eae..6b1fafe5f4 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -371,14 +371,14 @@ class TestWorkOrder(unittest.TestCase): def test_job_card(self): stock_entries = [] - data = frappe.get_cached_value('BOM', - {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) + bom = frappe.get_doc('BOM', { + 'docstatus': 1, + 'with_operations': 1, + 'company': '_Test Company' + }) - bom, bom_item = data - - bom_doc = frappe.get_doc('BOM', bom) - work_order = make_wo_order_test_record(item=bom_item, qty=1, - bom_no=bom, source_warehouse="_Test Warehouse - _TC") + work_order = make_wo_order_test_record(item=bom.item, qty=1, + bom_no=bom.name, source_warehouse="_Test Warehouse - _TC") for row in work_order.required_items: stock_entry_doc = test_stock_entry.make_stock_entry(item_code=row.item_code, @@ -390,14 +390,14 @@ class TestWorkOrder(unittest.TestCase): stock_entries.append(ste) job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name}) - self.assertEqual(len(job_cards), len(bom_doc.operations)) + self.assertEqual(len(job_cards), len(bom.operations)) for i, job_card in enumerate(job_cards): doc = frappe.get_doc("Job Card", job_card) doc.append("time_logs", { - "from_time": now(), - "hours": i, - "to_time": add_to_date(now(), i), + "from_time": add_to_date(None, i), + "hours": 1, + "to_time": add_to_date(None, i + 1), "completed_qty": doc.for_quantity }) doc.submit() diff --git a/erpnext/patches.txt b/erpnext/patches.txt index aabefb85e7..16863142bc 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -763,3 +763,4 @@ erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae erpnext.patches.v13_0.setup_uae_vat_fields execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') erpnext.patches.v13_0.rename_discharge_date_in_ip_record +erpnext.patches.v12_0.purchase_receipt_status diff --git a/erpnext/patches/v12_0/purchase_receipt_status.py b/erpnext/patches/v12_0/purchase_receipt_status.py new file mode 100644 index 0000000000..1a99b3163b --- /dev/null +++ b/erpnext/patches/v12_0/purchase_receipt_status.py @@ -0,0 +1,30 @@ +""" This patch fixes old purchase receipts (PR) where even after submitting + the PR, the `status` remains "Draft". `per_billed` field was copied over from previous + doc (PO), hence it is recalculated for setting new correct status of PR. +""" + +import frappe + +logger = frappe.logger("patch", allow_site=True, file_count=50) + +def execute(): + affected_purchase_receipts = frappe.db.sql( + """select name from `tabPurchase Receipt` + where status = 'Draft' and per_billed = 100 and docstatus = 1""" + ) + + if not affected_purchase_receipts: + return + + logger.info("purchase_receipt_status: begin patch, PR count: {}" + .format(len(affected_purchase_receipts))) + + + for pr in affected_purchase_receipts: + pr_name = pr[0] + logger.info("purchase_receipt_status: patching PR - {}".format(pr_name)) + + pr_doc = frappe.get_doc("Purchase Receipt", pr_name) + + pr_doc.update_billing_status(update_modified=False) + pr_doc.set_status(update=True, update_modified=False) diff --git a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py index de08aa26b3..2d3b096915 100644 --- a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py +++ b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py @@ -6,6 +6,8 @@ def execute(): if "Healthcare" not in frappe.get_active_domains(): return + frappe.reload_doc("healthcare", "doctype", "Therapy Session") + frappe.reload_doc("healthcare", "doctype", "Inpatient Medication Order") frappe.reload_doc("healthcare", "doctype", "Patient History Settings") frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type") frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type") diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 9abe57cd65..aa9acd8bd0 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -618,13 +618,16 @@ class SalarySlip(TransactionBase): component_row = self.append(component_type) for attr in ( - 'depends_on_payment_days', 'salary_component', 'abbr' + 'depends_on_payment_days', 'salary_component', 'do_not_include_in_total', 'is_tax_applicable', 'is_flexible_benefit', 'variable_based_on_taxable_salary', 'exempted_from_income_tax' ): component_row.set(attr, component_data.get(attr)) + abbr = component_data.get('abbr') or component_data.get('salary_component_abbr') + component_row.set('abbr', abbr) + if additional_salary: component_row.default_amount = 0 component_row.additional_amount = amount diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py index 1712081550..352c1804f0 100644 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py @@ -100,7 +100,7 @@ class SalaryStructure(Document): from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab) else: assign_salary_structure_for_employees(employees, self, - payroll_payable_account=payroll_payable_account, + payroll_payable_account=payroll_payable_account, from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab) else: frappe.msgprint(_("No Employee Found")) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 433ef18e99..8eccc3f565 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -87,10 +87,10 @@ def get_doc_details(invoice): invoice_date=invoice_date )) -def get_party_details(address_name): +def get_party_details(address_name, company_address=None, billing_address=None, shipping_address=None): d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] - if (not d.gstin + if ((not d.gstin and not shipping_address) or not d.city or not d.pincode or not d.address_title @@ -108,8 +108,7 @@ def get_party_details(address_name): # according to einvoice standard pincode = 999999 - return frappe._dict(dict( - gstin=d.gstin, + party_address_details = frappe._dict(dict( legal_name=sanitize_for_json(d.address_title), location=sanitize_for_json(d.city), pincode=d.pincode, @@ -117,6 +116,9 @@ def get_party_details(address_name): address_line1=sanitize_for_json(d.address_line1), address_line2=sanitize_for_json(d.address_line2) )) + if d.gstin: + party_address_details.gstin = d.gstin + return party_address_details def get_gstin_details(gstin): if not hasattr(frappe.local, 'gstin_cache'): @@ -328,12 +330,12 @@ def make_einvoice(invoice): item_list = get_item_list(invoice) doc_details = get_doc_details(invoice) invoice_value_details = get_invoice_value_details(invoice) - seller_details = get_party_details(invoice.company_address) + seller_details = get_party_details(invoice.company_address, company_address=1) if invoice.gst_category == 'Overseas': buyer_details = get_overseas_address_details(invoice.customer_address) else: - buyer_details = get_party_details(invoice.customer_address) + buyer_details = get_party_details(invoice.customer_address, billing_address=1) place_of_supply = get_place_of_supply(invoice, invoice.doctype) if place_of_supply: place_of_supply = place_of_supply.split('-')[0] @@ -346,7 +348,7 @@ def make_einvoice(invoice): if invoice.gst_category == 'Overseas': shipping_details = get_overseas_address_details(invoice.shipping_address_name) else: - shipping_details = get_party_details(invoice.shipping_address_name) + shipping_details = get_party_details(invoice.shipping_address_name, shipping_address=1) if invoice.is_pos and invoice.base_paid_amount: payment_details = get_payment_details(invoice) @@ -394,7 +396,9 @@ def safe_json_load(json_string): snippet = json_string[start:end] frappe.throw(_("Error in input data. Please check for any special characters near following input:
{}").format(snippet)) -def validate_einvoice(validations, einvoice, errors=[]): +def validate_einvoice(validations, einvoice, errors=None): + if errors is None: + errors = [] for fieldname, field_validation in validations.items(): value = einvoice.get(fieldname, None) if not value or value == "None": diff --git a/erpnext/shopping_cart/test_shopping_cart.py b/erpnext/shopping_cart/test_shopping_cart.py index cf59a52b5b..d857bf5f5c 100644 --- a/erpnext/shopping_cart/test_shopping_cart.py +++ b/erpnext/shopping_cart/test_shopping_cart.py @@ -16,6 +16,11 @@ class TestShoppingCart(unittest.TestCase): Note: Shopping Cart == Quotation """ + + @classmethod + def tearDownClass(cls): + frappe.db.sql("delete from `tabTax Rule`") + def setUp(self): frappe.set_user("Administrator") create_test_contact_and_address() @@ -51,8 +56,8 @@ class TestShoppingCart(unittest.TestCase): def test_add_to_cart(self): self.login_as_customer() - # remove from cart - self.remove_all_items_from_cart() + # clear existing quotations + self.clear_existing_quotations() # add first item update_cart("_Test Item", 1) @@ -100,6 +105,7 @@ class TestShoppingCart(unittest.TestCase): self.assertEqual(len(quotation.get("items")), 1) def test_tax_rule(self): + self.create_tax_rule() self.login_as_customer() quotation = self.create_quotation() @@ -115,6 +121,13 @@ class TestShoppingCart(unittest.TestCase): self.remove_test_quotation(quotation) + def create_tax_rule(self): + tax_rule = frappe.get_test_records("Tax Rule")[0] + try: + frappe.get_doc(tax_rule).insert() + except frappe.DuplicateEntryError: + pass + def create_quotation(self): quotation = frappe.new_doc("Quotation") @@ -195,10 +208,15 @@ class TestShoppingCart(unittest.TestCase): "_Test Contact For _Test Customer") frappe.set_user("test_contact_customer@example.com") - def remove_all_items_from_cart(self): - quotation = _get_cart_quotation() - quotation.flags.ignore_permissions=True - quotation.delete() + def clear_existing_quotations(self): + quotations = frappe.get_all("Quotation", filters={ + "party_name": get_party().name, + "order_type": "Shopping Cart", + "docstatus": 0 + }, order_by="modified desc", pluck="name") + + for quotation in quotations: + frappe.delete_doc("Quotation", quotation, ignore_permissions=True, force=True) def create_user_if_not_exists(self, email, first_name = None): if frappe.db.exists("User", email): diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index a762e9763e..c4da05a6d4 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -23,7 +23,7 @@ class TestPickList(unittest.TestCase): 'purpose': 'Opening Stock', 'expense_account': 'Temporary Opening - _TC', 'items': [{ - 'item_code': '_Test Item Home Desktop 100', + 'item_code': '_Test Item', 'warehouse': '_Test Warehouse - _TC', 'valuation_rate': 100, 'qty': 5 @@ -38,7 +38,7 @@ class TestPickList(unittest.TestCase): 'customer': '_Test Customer', 'items_based_on': 'Sales Order', 'locations': [{ - 'item_code': '_Test Item Home Desktop 100', + 'item_code': '_Test Item', 'qty': 5, 'stock_qty': 5, 'conversion_factor': 1, @@ -48,7 +48,7 @@ class TestPickList(unittest.TestCase): }) pick_list.set_item_locations() - self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100') + self.assertEqual(pick_list.locations[0].item_code, '_Test Item') self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC') self.assertEqual(pick_list.locations[0].qty, 5) @@ -238,7 +238,7 @@ class TestPickList(unittest.TestCase): 'purpose': 'Opening Stock', 'expense_account': 'Temporary Opening - _TC', 'items': [{ - 'item_code': '_Test Item Home Desktop 100', + 'item_code': '_Test Item', 'warehouse': '_Test Warehouse - _TC', 'valuation_rate': 100, 'qty': 10 @@ -252,7 +252,7 @@ class TestPickList(unittest.TestCase): 'customer': '_Test Customer', 'company': '_Test Company', 'items': [{ - 'item_code': '_Test Item Home Desktop 100', + 'item_code': '_Test Item', 'qty': 10, 'delivery_date': frappe.utils.today() }], @@ -265,14 +265,14 @@ class TestPickList(unittest.TestCase): 'customer': '_Test Customer', 'items_based_on': 'Sales Order', 'locations': [{ - 'item_code': '_Test Item Home Desktop 100', + 'item_code': '_Test Item', 'qty': 5, 'stock_qty': 5, 'conversion_factor': 1, 'sales_order': '_T-Sales Order-1', 'sales_order_item': '_T-Sales Order-1_item', }, { - 'item_code': '_Test Item Home Desktop 100', + 'item_code': '_Test Item', 'qty': 5, 'stock_qty': 5, 'conversion_factor': 1, @@ -282,12 +282,12 @@ class TestPickList(unittest.TestCase): }) pick_list.set_item_locations() - self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100') + self.assertEqual(pick_list.locations[0].item_code, '_Test Item') self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC') self.assertEqual(pick_list.locations[0].qty, 5) self.assertEqual(pick_list.locations[0].sales_order_item, '_T-Sales Order-1_item') - self.assertEqual(pick_list.locations[1].item_code, '_Test Item Home Desktop 100') + self.assertEqual(pick_list.locations[1].item_code, '_Test Item') self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse - _TC') self.assertEqual(pick_list.locations[1].qty, 5) self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name) @@ -358,4 +358,4 @@ class TestPickList(unittest.TestCase): # pass # def test_pick_list_from_material_request(self): - # pass \ No newline at end of file + # pass diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 70687bdac2..5d7597b2db 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -176,7 +176,7 @@ class PurchaseReceipt(BuyingController): if flt(self.per_billed) < 100: self.update_billing_status() else: - self.status = "Completed" + self.db_set("status", "Completed") # Updating stock ledger should always be called after updating prevdoc status, diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 7741ee7f60..7f0c3fa801 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -191,7 +191,7 @@ class TestPurchaseReceipt(unittest.TestCase): rm_supp_cost = sum([d.amount for d in pr.get("supplied_items")]) self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2)) - + pr.cancel() def test_subcontracting_gle_fg_item_rate_zero(self): @@ -912,6 +912,57 @@ class TestPurchaseReceipt(unittest.TestCase): ste1.cancel() po.cancel() + + def test_po_to_pi_and_po_to_pr_worflow_full(self): + """Test following behaviour: + - Create PO + - Create PI from PO and submit + - Create PR from PO and submit + """ + from erpnext.buying.doctype.purchase_order import test_purchase_order + from erpnext.buying.doctype.purchase_order import purchase_order + + po = test_purchase_order.create_purchase_order() + + pi = purchase_order.make_purchase_invoice(po.name) + pi.submit() + + pr = purchase_order.make_purchase_receipt(po.name) + pr.submit() + + pr.load_from_db() + + self.assertEqual(pr.status, "Completed") + self.assertEqual(pr.per_billed, 100) + + def test_po_to_pi_and_po_to_pr_worflow_partial(self): + """Test following behaviour: + - Create PO + - Create partial PI from PO and submit + - Create PR from PO and submit + """ + from erpnext.buying.doctype.purchase_order import test_purchase_order + from erpnext.buying.doctype.purchase_order import purchase_order + + po = test_purchase_order.create_purchase_order() + + pi = purchase_order.make_purchase_invoice(po.name) + pi.items[0].qty /= 2 # roughly 50%, ^ this function only creates PI with 1 item. + pi.submit() + + pr = purchase_order.make_purchase_receipt(po.name) + pr.save() + # per_billed is only updated after submission. + self.assertEqual(flt(pr.per_billed), 0) + + pr.submit() + + pi.load_from_db() + pr.load_from_db() + + self.assertEqual(pr.status, "To Bill") + self.assertAlmostEqual(pr.per_billed, 50.0, places=2) + def get_sl_entries(voucher_type, voucher_no): return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 64dcbed1d8..98246fb024 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -100,6 +100,13 @@ frappe.ui.form.on('Stock Entry', { frm.add_fetch("bom_no", "inspection_required", "inspection_required"); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); + + frappe.db.get_single_value('Stock Settings', 'disable_serial_no_and_batch_selector') + .then((value) => { + if (value) { + frappe.flags.hide_serial_batch_dialog = true; + } + }); }, setup_quality_inspection: function(frm) { @@ -721,7 +728,7 @@ frappe.ui.form.on('Stock Entry Detail', { no_batch_serial_number_value = !d.batch_no; } - if (no_batch_serial_number_value) { + if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog) { erpnext.stock.select_batch_and_serial_no(frm, d); } } diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 7ebd4e6cb2..349d8ae679 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -313,8 +313,8 @@ class TestStockLedgerEntry(unittest.TestCase): # Set User with Stock User role but not Stock Manager try: - frappe.set_user("test@example.com") user = frappe.get_doc("User", "test@example.com") + frappe.set_user(user.name) user.add_roles("Stock User") user.remove_roles("Stock Manager") @@ -325,7 +325,9 @@ class TestStockLedgerEntry(unittest.TestCase): # Block back-dated entry self.assertRaises(BackDatedStockTransaction, back_dated_se_1.submit) + frappe.set_user("Administrator") user.add_roles("Stock Manager") + frappe.set_user(user.name) # Back dated entry allowed to Stock Manager back_dated_se_2 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100, @@ -337,6 +339,7 @@ class TestStockLedgerEntry(unittest.TestCase): finally: frappe.db.set_value("Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None) frappe.set_user("Administrator") + user.remove_roles("Stock Manager") def create_repack_entry(**args): @@ -400,4 +403,4 @@ def create_items(): make_item(d, properties=properties) - return items \ No newline at end of file + return items