diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index bcd07718a59..71957e67a3c 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -374,12 +374,15 @@ def make_gl_entries(doc, credit_account, debit_account, against, try: make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True) frappe.db.commit() - except Exception: - frappe.db.rollback() - traceback = frappe.get_traceback() - frappe.log_error(message=traceback) + except Exception as e: + if frappe.flags.in_test: + raise e + else: + frappe.db.rollback() + traceback = frappe.get_traceback() + frappe.log_error(message=traceback) - frappe.flags.deferred_accounting_error = True + frappe.flags.deferred_accounting_error = True def send_mail(deferred_process): title = _("Error while processing deferred accounting for {0}").format(deferred_process) diff --git a/erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json b/erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json index 3eb0d74ed33..67acb26c7ee 100644 --- a/erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json +++ b/erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json @@ -1,63 +1,33 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2014-10-02 13:35:44.155278", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, + "actions": [], + "creation": "2014-10-02 13:35:44.155278", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "company", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Company", + "options": "Company" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2016-07-11 03:28:00.505946", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Fiscal Year Company", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_seen": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-28 18:01:53.495929", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Fiscal Year Company", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index b7bbb74ce94..20678d787b4 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -13,10 +13,12 @@ "voucher_type", "naming_series", "finance_book", + "tax_withholding_category", "column_break1", "from_template", "company", "posting_date", + "apply_tds", "2_add_edit_gl_entries", "accounts", "section_break99", @@ -498,16 +500,32 @@ "options": "Journal Entry Template", "print_hide": 1, "report_hide": 1 + }, + { + "depends_on": "eval:doc.apply_tds", + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "label": "Tax Withholding Category", + "mandatory_depends_on": "eval:doc.apply_tds", + "options": "Tax Withholding Category" + }, + { + "default": "0", + "depends_on": "eval:['Credit Note', 'Debit Note'].includes(doc.voucher_type)", + "fieldname": "apply_tds", + "fieldtype": "Check", + "label": "Apply Tax Withholding Amount " } ], "icon": "fa fa-file-text", "idx": 176, "is_submittable": 1, "links": [], - "modified": "2020-10-30 13:56:01.121995", + "modified": "2021-09-09 15:31:14.484029", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 24368f04419..e568a827617 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -15,6 +15,9 @@ from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import ( get_party_account_based_on_invoice_discounting, ) +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.utils import ( check_if_stock_and_account_balance_synced, @@ -57,7 +60,8 @@ class JournalEntry(AccountsController): self.validate_against_jv() self.validate_reference_doc() - self.set_against_account() + if self.docstatus == 0: + self.set_against_account() self.create_remarks() self.set_print_format_fields() self.validate_expense_claim() @@ -66,6 +70,10 @@ class JournalEntry(AccountsController): self.set_account_and_party_balance() self.validate_inter_company_accounts() self.validate_stock_accounts() + + if self.docstatus == 0: + self.apply_tax_withholding() + if not self.title: self.title = self.get_title() @@ -139,6 +147,72 @@ class JournalEntry(AccountsController): frappe.throw(_("Account: {0} can only be updated via Stock Transactions") .format(account), StockAccountInvalidTransaction) + def apply_tax_withholding(self): + from erpnext.accounts.report.general_ledger.general_ledger import get_account_type_map + + if not self.apply_tds or self.voucher_type not in ('Debit Note', 'Credit Note'): + return + + parties = [d.party for d in self.get('accounts') if d.party] + parties = list(set(parties)) + + if len(parties) > 1: + frappe.throw(_("Cannot apply TDS against multiple parties in one entry")) + + account_type_map = get_account_type_map(self.company) + party_type = 'supplier' if self.voucher_type == 'Credit Note' else 'customer' + doctype = 'Purchase Invoice' if self.voucher_type == 'Credit Note' else 'Sales Invoice' + debit_or_credit = 'debit_in_account_currency' if self.voucher_type == 'Credit Note' else 'credit_in_account_currency' + rev_debit_or_credit = 'credit_in_account_currency' if debit_or_credit == 'debit_in_account_currency' else 'debit_in_account_currency' + + party_account = get_party_account(party_type.title(), parties[0], self.company) + + net_total = sum(d.get(debit_or_credit) for d in self.get('accounts') if account_type_map.get(d.account) + not in ('Tax', 'Chargeable')) + + party_amount = sum(d.get(rev_debit_or_credit) for d in self.get('accounts') if d.account == party_account) + + inv = frappe._dict({ + party_type: parties[0], + 'doctype': doctype, + 'company': self.company, + 'posting_date': self.posting_date, + 'net_total': net_total + }) + + tax_withholding_details = get_party_tax_withholding_details(inv, self.tax_withholding_category) + + if not tax_withholding_details: + return + + accounts = [] + for d in self.get('accounts'): + if d.get('account') == tax_withholding_details.get("account_head"): + d.update({ + 'account': tax_withholding_details.get("account_head"), + debit_or_credit: tax_withholding_details.get('tax_amount') + }) + + accounts.append(d.get('account')) + + if d.get('account') == party_account: + d.update({ + rev_debit_or_credit: party_amount - tax_withholding_details.get('tax_amount') + }) + + if not accounts or tax_withholding_details.get("account_head") not in accounts: + self.append("accounts", { + 'account': tax_withholding_details.get("account_head"), + rev_debit_or_credit: tax_withholding_details.get('tax_amount'), + 'against_account': parties[0] + }) + + to_remove = [d for d in self.get('accounts') + if not d.get(rev_debit_or_credit) and d.account == tax_withholding_details.get("account_head")] + + for d in to_remove: + self.remove(d) + def update_inter_company_jv(self): if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference: frappe.db.set_value("Journal Entry", self.inter_company_journal_entry_reference,\ diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index 43eb0b6e2aa..8961167f018 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -93,6 +93,7 @@ "options": "Payment Term" }, { + "depends_on": "exchange_gain_loss", "fieldname": "exchange_gain_loss", "fieldtype": "Currency", "label": "Exchange Gain/Loss", @@ -103,7 +104,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-21 13:30:11.605388", + "modified": "2021-09-26 17:06:55.597389", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index dde0328130f..55e288eeef9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1303,6 +1303,7 @@ "fetch_from": "supplier.is_internal_supplier", "fieldname": "is_internal_supplier", "fieldtype": "Check", + "ignore_user_permissions": 1, "label": "Is Internal Supplier", "read_only": 1 }, @@ -1400,7 +1401,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-09-21 09:27:39.967811", + "modified": "2021-09-28 13:10:28.351810", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index dd4a0058938..1c9943fd224 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1146,6 +1146,12 @@ class PurchaseInvoice(BuyingController): if not self.apply_tds: return + if self.apply_tds and not self.get('tax_withholding_category'): + self.tax_withholding_category = frappe.db.get_value('Supplier', self.supplier, 'tax_withholding_category') + + if not self.tax_withholding_category: + return + tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category) if not tax_withholding_details: diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json index 63dfff8921f..9fcbf5c6339 100644 --- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json +++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json @@ -97,6 +97,7 @@ "width": "100px" }, { + "depends_on": "exchange_gain_loss", "fieldname": "exchange_gain_loss", "fieldtype": "Currency", "label": "Exchange Gain/Loss", @@ -104,6 +105,7 @@ "read_only": 1 }, { + "depends_on": "exchange_gain_loss", "fieldname": "ref_exchange_rate", "fieldtype": "Float", "label": "Reference Exchange Rate", @@ -115,7 +117,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-04-20 16:26:53.820530", + "modified": "2021-09-26 15:47:28.167371", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Advance", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index f383b52b409..2d6c04ebf9b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1953,6 +1953,7 @@ "fetch_from": "customer.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -2022,7 +2023,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-09-21 09:27:50.191854", + "modified": "2021-09-28 13:09:34.391799", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 619dc7b0ada..8a2e9450e97 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1800,6 +1800,47 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, si.name, expected_gle, "2019-01-30") + def test_deferred_revenue_post_account_freeze_upto_by_admin(self): + frappe.set_user("Administrator") + + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None) + + deferred_account = create_account(account_name="Deferred Revenue", + parent_account="Current Liabilities - _TC", company="_Test Company") + + item = create_item("_Test Item for Deferred Accounting") + item.enable_deferred_revenue = 1 + item.deferred_revenue_account = deferred_account + item.no_of_months = 12 + item.save() + + si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_save=True) + si.items[0].enable_deferred_revenue = 1 + si.items[0].service_start_date = "2019-01-10" + si.items[0].service_end_date = "2019-03-15" + si.items[0].deferred_revenue_account = deferred_account + si.save() + si.submit() + + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31')) + frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', 'System Manager') + + pda1 = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date=nowdate(), + start_date="2019-01-01", + end_date="2019-03-31", + type="Income", + company="_Test Company" + )) + + pda1.insert() + self.assertRaises(frappe.ValidationError, pda1.submit) + + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None) + def test_fixed_deferred_revenue(self): deferred_account = create_account(account_name="Deferred Revenue", parent_account="Current Liabilities - _TC", company="_Test Company") @@ -2352,6 +2393,7 @@ def make_test_address_for_ewaybill(): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): address = frappe.get_doc({ "address_line1": "_Test Address Line 1", + "address_line2": "_Test Address Line 2", "address_title": "_Test Address for Eway bill", "address_type": "Billing", "city": "_Test City", @@ -2373,11 +2415,12 @@ def make_test_address_for_ewaybill(): address.save() - if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'): + if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Billing'): address = frappe.get_doc({ "address_line1": "_Test Address Line 1", + "address_line2": "_Test Address Line 2", "address_title": "_Test Customer-Address for Eway bill", - "address_type": "Shipping", + "address_type": "Billing", "city": "_Test City", "state": "Test State", "country": "India", @@ -2397,9 +2440,34 @@ def make_test_address_for_ewaybill(): address.save() + if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_line2": "_Test Address Line 2", + "address_title": "_Test Customer-Address for Eway bill", + "address_type": "Shipping", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+910000000000", + "gst_state": "Maharashtra", + "gst_state_number": "27", + "pincode": "410098" + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test Customer" + }) + + address.save() + if not frappe.db.exists('Address', '_Test Dispatch-Address for Eway bill-Shipping'): address = frappe.get_doc({ "address_line1": "_Test Dispatch Address Line 1", + "address_line2": "_Test Dispatch Address Line 2", "address_title": "_Test Dispatch-Address for Eway bill", "address_type": "Shipping", "city": "_Test City", @@ -2414,11 +2482,6 @@ def make_test_address_for_ewaybill(): "pincode": "1100101" }).insert() - address.append("links", { - "link_doctype": "Company", - "link_name": "_Test Company" - }) - address.save() def make_test_transporter_for_ewaybill(): @@ -2458,7 +2521,8 @@ def make_sales_invoice_for_ewaybill(): si.distance = 2000 si.company_address = "_Test Address for Eway bill-Billing" - si.customer_address = "_Test Customer-Address for Eway bill-Shipping" + si.customer_address = "_Test Customer-Address for Eway bill-Billing" + si.shipping_address_name = "_Test Customer-Address for Eway bill-Shipping" si.dispatch_address_name = "_Test Dispatch-Address for Eway bill-Shipping" si.vehicle_no = "KA12KA1234" si.gst_category = "Registered Regular" diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json index 29422d68cf6..f92b57a45e1 100644 --- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json +++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json @@ -98,6 +98,7 @@ "width": "120px" }, { + "depends_on": "exchange_gain_loss", "fieldname": "exchange_gain_loss", "fieldtype": "Currency", "label": "Exchange Gain/Loss", @@ -105,6 +106,7 @@ "read_only": 1 }, { + "depends_on": "exchange_gain_loss", "fieldname": "ref_exchange_rate", "fieldtype": "Float", "label": "Reference Exchange Rate", @@ -116,7 +118,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-04 20:25:49.832052", + "modified": "2021-09-26 15:47:46.911595", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Advance", diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index fa4ea218e90..16ef5fc9745 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -100,6 +100,7 @@ def get_tax_withholding_details(tax_withholding_category, posting_date, company) for account_detail in tax_withholding.accounts: if company == account_detail.company: return frappe._dict({ + "tax_withholding_category": tax_withholding_category, "account_head": account_detail.account, "rate": tax_rate_detail.tax_withholding_rate, "from_date": tax_rate_detail.from_date, @@ -206,18 +207,39 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'): dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit' + doctype = 'Purchase Invoice' if party_type == 'Supplier' else 'Sales Invoice' filters = { - dr_or_cr: ['>', 0], 'company': company, - 'party_type': party_type, - 'party': ['in', parties], + frappe.scrub(party_type): ['in', parties], 'posting_date': ['between', (tax_details.from_date, tax_details.to_date)], 'is_opening': 'No', - 'is_cancelled': 0 + 'docstatus': 1 } - return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""] + if not tax_details.get('consider_party_ledger_amount') and doctype != "Sales Invoice": + filters.update({ + 'apply_tds': 1, + 'tax_withholding_category': tax_details.get('tax_withholding_category') + }) + + invoices = frappe.get_all(doctype, filters=filters, pluck="name") or [""] + + journal_entries = frappe.db.sql(""" + SELECT j.name + FROM `tabJournal Entry` j, `tabJournal Entry Account` ja + WHERE + j.docstatus = 1 + AND j.is_opening = 'No' + AND j.posting_date between %s and %s + AND ja.{dr_or_cr} > 0 + AND ja.party in %s + """.format(dr_or_cr=dr_or_cr), (tax_details.from_date, tax_details.to_date, tuple(parties)), as_list=1) + + if journal_entries: + journal_entries = journal_entries[0] + + return invoices + journal_entries def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type='Supplier'): # for advance vouchers, debit and credit is reversed diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 8a88d798d8b..84b364b3427 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -176,6 +176,29 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in invoices: d.cancel() + def test_multi_category_single_supplier(self): + frappe.db.set_value("Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category") + invoices = [] + + pi = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 500, do_not_save=True) + pi.tax_withholding_category = "Test Service Category" + pi.save() + pi.submit() + invoices.append(pi) + + # Second Invoice will apply TDS checked + pi1 = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 2500, do_not_save=True) + pi1.tax_withholding_category = "Test Goods Category" + pi1.save() + pi1.submit() + invoices.append(pi1) + + self.assertEqual(pi1.taxes[0].tax_amount, 250) + + #delete invoices to avoid clashing + for d in invoices: + d.cancel() + def cancel_invoices(): purchase_invoices = frappe.get_all("Purchase Invoice", { 'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']], @@ -251,7 +274,8 @@ def create_sales_invoice(**args): def create_records(): # create a new suppliers - for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3', 'Test TDS Supplier4']: + for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3', + 'Test TDS Supplier4', 'Test TDS Supplier5']: if frappe.db.exists('Supplier', name): continue @@ -390,3 +414,39 @@ def create_tax_with_holding_category(): 'account': 'TDS - _TC' }] }).insert() + + if not frappe.db.exists("Tax Withholding Category", "Test Service Category"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "Test Service Category", + "category_name": "Test Service Category", + "rates": [{ + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], + 'tax_withholding_rate': 10, + 'single_threshold': 2000, + 'cumulative_threshold': 2000 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TDS - _TC' + }] + }).insert() + + if not frappe.db.exists("Tax Withholding Category", "Test Goods Category"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "Test Goods Category", + "category_name": "Test Goods Category", + "rates": [{ + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], + 'tax_withholding_rate': 10, + 'single_threshold': 2000, + 'cumulative_threshold': 2000 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TDS - _TC' + }] + }).insert() diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 4bf2b828edd..0cee6f5b3aa 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -284,13 +284,16 @@ def check_freezing_date(posting_date, adv_adj=False): """ Nobody can do GL Entries where posting date is before freezing date except authorized person + + Administrator has all the roles so this check will be bypassed if any role is allowed to post + Hence stop admin to bypass if accounts are freezed """ if not adv_adj: acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') if acc_frozen_upto: frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') if getdate(posting_date) <= getdate(acc_frozen_upto) \ - and not frozen_accounts_modifier in frappe.get_roles(): + and not frozen_accounts_modifier in frappe.get_roles() or frappe.session.user == 'Administrator': frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto))) def set_as_cancel(voucher_type, voucher_no): diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json index dfc4b18e07d..91f079824d2 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json @@ -1,12 +1,15 @@ { - "add_total_row": 0, + "add_total_row": 1, + "columns": [], "creation": "2018-08-21 11:25:00.551823", + "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [], "idx": 0, "is_standard": "Yes", - "modified": "2018-09-21 11:25:00.551823", + "modified": "2021-09-20 17:43:39.518851", "modified_by": "Administrator", "module": "Accounts", "name": "TDS Computation Summary", diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py index c4a8c7a899a..536df1f1a17 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py @@ -2,11 +2,10 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt -from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( - get_advance_vouchers, - get_debit_note_amount, +from erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly import ( + get_result, + get_tds_docs, ) from erpnext.accounts.utils import get_fiscal_year @@ -17,9 +16,12 @@ def execute(filters=None): filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name') columns = get_columns(filters) - res = get_result(filters) + tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters) - return columns, res + res = get_result(filters, tds_docs, tds_accounts, tax_category_map) + final_result = group_by_supplier_and_category(res) + + return columns, final_result def validate_filters(filters): ''' Validate if dates are properly set and lie in the same fiscal year''' @@ -33,81 +35,39 @@ def validate_filters(filters): filters["fiscal_year"] = from_year -def get_result(filters): - # if no supplier selected, fetch data for all tds applicable supplier - # else fetch relevant data for selected supplier - pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" - fields = ["name", pan+" as pan", "tax_withholding_category", "supplier_type", "supplier_name"] +def group_by_supplier_and_category(data): + supplier_category_wise_map = {} - if filters.supplier: - filters.supplier = frappe.db.get_list('Supplier', - {"name": filters.supplier}, fields) - else: - filters.supplier = frappe.db.get_list('Supplier', - {"tax_withholding_category": ["!=", ""]}, fields) + for row in data: + supplier_category_wise_map.setdefault((row.get('supplier'), row.get('section_code')), { + 'pan': row.get('pan'), + 'supplier': row.get('supplier'), + 'supplier_name': row.get('supplier_name'), + 'section_code': row.get('section_code'), + 'entity_type': row.get('entity_type'), + 'tds_rate': row.get('tds_rate'), + 'total_amount_credited': 0.0, + 'tds_deducted': 0.0 + }) + supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['total_amount_credited'] += \ + row.get('total_amount_credited', 0.0) + + supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['tds_deducted'] += \ + row.get('tds_deducted', 0.0) + + final_result = get_final_result(supplier_category_wise_map) + + return final_result + + +def get_final_result(supplier_category_wise_map): out = [] - for supplier in filters.supplier: - tds = frappe.get_doc("Tax Withholding Category", supplier.tax_withholding_category) - rate = [d.tax_withholding_rate for d in tds.rates if d.fiscal_year == filters.fiscal_year] - - if rate: - rate = rate[0] - - try: - account = [d.account for d in tds.accounts if d.company == filters.company][0] - - except IndexError: - account = [] - total_invoiced_amount, tds_deducted = get_invoice_and_tds_amount(supplier.name, account, - filters.company, filters.from_date, filters.to_date, filters.fiscal_year) - - if total_invoiced_amount or tds_deducted: - row = [supplier.pan, supplier.name] - - if filters.naming_series == 'Naming Series': - row.append(supplier.supplier_name) - - row.extend([tds.name, supplier.supplier_type, rate, total_invoiced_amount, tds_deducted]) - out.append(row) + for key, value in supplier_category_wise_map.items(): + out.append(value) return out -def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, fiscal_year): - ''' calculate total invoice amount and total tds deducted for given supplier ''' - - entries = frappe.db.sql(""" - select voucher_no, credit - from `tabGL Entry` - where party in (%s) and credit > 0 - and company=%s and is_cancelled = 0 - and posting_date between %s and %s - """, (supplier, company, from_date, to_date), as_dict=1) - - supplier_credit_amount = flt(sum(d.credit for d in entries)) - - vouchers = [d.voucher_no for d in entries] - vouchers += get_advance_vouchers([supplier], company=company, - from_date=from_date, to_date=to_date) - - tds_deducted = 0 - if vouchers: - tds_deducted = flt(frappe.db.sql(""" - select sum(credit) - from `tabGL Entry` - where account=%s and posting_date between %s and %s - and company=%s and credit > 0 and voucher_no in ({0}) - """.format(', '.join("'%s'" % d for d in vouchers)), - (account, from_date, to_date, company))[0][0]) - - date_range_filter = [fiscal_year, from_date, to_date] - - debit_note_amount = get_debit_note_amount([supplier], date_range_filter, company=company) - - total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount - - return total_invoiced_amount, tds_deducted - def get_columns(filters): columns = [ { @@ -149,7 +109,7 @@ def get_columns(filters): { "label": _("TDS Rate %"), "fieldname": "tds_rate", - "fieldtype": "Float", + "fieldtype": "Percent", "width": 90 }, { diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js index 72de318a48c..ff2aa306017 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js @@ -16,69 +16,6 @@ frappe.query_reports["TDS Payable Monthly"] = { "label": __("Supplier"), "fieldtype": "Link", "options": "Supplier", - "get_query": function() { - return { - "filters": { - "tax_withholding_category": ["!=", ""], - } - } - }, - on_change: function() { - frappe.query_report.set_filter_value("purchase_invoice", ""); - frappe.query_report.refresh(); - } - }, - { - "fieldname":"purchase_invoice", - "label": __("Purchase Invoice"), - "fieldtype": "Link", - "options": "Purchase Invoice", - "get_query": function() { - return { - "filters": { - "name": ["in", frappe.query_report.invoices] - } - } - }, - on_change: function() { - let supplier = frappe.query_report.get_filter_value('supplier'); - if(!supplier) return; // return if no supplier selected - - // filter invoices based on selected supplier - let invoices = []; - frappe.query_report.invoice_data.map(d => { - if(d.supplier==supplier) - invoices.push(d.name) - }); - frappe.query_report.invoices = invoices; - frappe.query_report.refresh(); - } - }, - { - "fieldname":"purchase_order", - "label": __("Purchase Order"), - "fieldtype": "Link", - "options": "Purchase Order", - "get_query": function() { - return { - "filters": { - "name": ["in", frappe.query_report.invoices] - } - } - }, - on_change: function() { - let supplier = frappe.query_report.get_filter_value('supplier'); - if(!supplier) return; // return if no supplier selected - - // filter invoices based on selected supplier - let invoices = []; - frappe.query_report.invoice_data.map(d => { - if(d.supplier==supplier) - invoices.push(d.name) - }); - frappe.query_report.invoices = invoices; - frappe.query_report.refresh(); - } }, { "fieldname":"from_date", @@ -96,23 +33,5 @@ frappe.query_reports["TDS Payable Monthly"] = { "reqd": 1, "width": "60px" } - ], - - onload: function(report) { - // fetch all tds applied invoices - frappe.call({ - "method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices_and_orders", - callback: function(r) { - let invoices = []; - - r.message.map(d => { - invoices.push(d.name); - }); - - report["invoice_data"] = r.message.invoices; - report["invoices"] = invoices; - - } - }); - } + ] } diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json index 557a62d8fea..4d555bd8ba1 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json @@ -1,13 +1,15 @@ { "add_total_row": 1, + "columns": [], "creation": "2018-08-21 11:32:30.874923", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [], "idx": 0, "is_standard": "Yes", - "modified": "2019-09-24 13:46:16.473711", + "modified": "2021-09-20 12:05:50.387572", "modified_by": "Administrator", "module": "Accounts", "name": "TDS Payable Monthly", diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 9e1382b9222..621b697aca4 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -8,19 +8,12 @@ from frappe import _ def execute(filters=None): - filters["invoices"] = frappe.cache().hget("invoices", frappe.session.user) validate_filters(filters) - set_filters(filters) - - # TDS payment entries - payment_entries = get_payment_entires(filters) + tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters) columns = get_columns(filters) - if not filters.get("invoices"): - return columns, [] - - res = get_result(filters, payment_entries) + res = get_result(filters, tds_docs, tds_accounts, tax_category_map) return columns, res def validate_filters(filters): @@ -28,109 +21,59 @@ def validate_filters(filters): if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) -def set_filters(filters): - invoices = [] - - if not filters.get("invoices"): - filters["invoices"] = get_tds_invoices_and_orders() - - if filters.supplier and filters.purchase_invoice: - for d in filters["invoices"]: - if d.name == filters.purchase_invoice and d.supplier == filters.supplier: - invoices.append(d) - elif filters.supplier and not filters.purchase_invoice: - for d in filters["invoices"]: - if d.supplier == filters.supplier: - invoices.append(d) - elif filters.purchase_invoice and not filters.supplier: - for d in filters["invoices"]: - if d.name == filters.purchase_invoice: - invoices.append(d) - elif filters.supplier and filters.purchase_order: - for d in filters.get("invoices"): - if d.name == filters.purchase_order and d.supplier == filters.supplier: - invoices.append(d) - elif filters.supplier and not filters.purchase_order: - for d in filters.get("invoices"): - if d.supplier == filters.supplier: - invoices.append(d) - elif filters.purchase_order and not filters.supplier: - for d in filters.get("invoices"): - if d.name == filters.purchase_order: - invoices.append(d) - - filters["invoices"] = invoices if invoices else filters["invoices"] - filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name') - - #print(filters.get('invoices')) - -def get_result(filters, payment_entries): - supplier_map, tds_docs = get_supplier_map(filters, payment_entries) - documents = [d.get('name') for d in filters.get('invoices')] + [d.get('name') for d in payment_entries] - - gle_map = get_gle_map(filters, documents) +def get_result(filters, tds_docs, tds_accounts, tax_category_map): + supplier_map = get_supplier_pan_map() + tax_rate_map = get_tax_rate_map(filters) + gle_map = get_gle_map(filters, tds_docs) out = [] - for d in gle_map: + for name, details in gle_map.items(): tds_deducted, total_amount_credited = 0, 0 - supplier = supplier_map[d] + tax_withholding_category = tax_category_map.get(name) + rate = tax_rate_map.get(tax_withholding_category) - tds_doc = tds_docs[supplier.tax_withholding_category] - account_list = [i.account for i in tds_doc.accounts if i.company == filters.company] + for entry in details: + supplier = entry.party or entry.against + posting_date = entry.posting_date + voucher_type = entry.voucher_type - if account_list: - account = account_list[0] + if entry.account in tds_accounts: + tds_deducted += (entry.credit - entry.debit) - for k in gle_map[d]: - if k.party == supplier_map[d] and k.credit > 0: - total_amount_credited += (k.credit - k.debit) - elif account_list and k.account == account and (k.credit - k.debit) > 0: - tds_deducted = (k.credit - k.debit) - total_amount_credited += (k.credit - k.debit) - voucher_type = k.voucher_type + total_amount_credited += (entry.credit - entry.debit) - rate = [i.tax_withholding_rate for i in tds_doc.rates - if i.fiscal_year == gle_map[d][0].fiscal_year] - - if rate and len(rate) > 0 and tds_deducted: - rate = rate[0] - - row = [supplier.pan, supplier.name] + if rate and tds_deducted: + row = { + 'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier).pan, + 'supplier': supplier_map.get(supplier).name + } if filters.naming_series == 'Naming Series': - row.append(supplier.supplier_name) + row.update({'supplier_name': supplier_map.get(supplier).supplier_name}) + + row.update({ + 'section_code': tax_withholding_category, + 'entity_type': supplier_map.get(supplier).supplier_type, + 'tds_rate': rate, + 'total_amount_credited': total_amount_credited, + 'tds_deducted': tds_deducted, + 'transaction_date': posting_date, + 'transaction_type': voucher_type, + 'ref_no': name + }) - row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited, - tds_deducted, gle_map[d][0].posting_date, voucher_type, d]) out.append(row) return out -def get_supplier_map(filters, payment_entries): - # create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}} - # pre-fetch all distinct applicable tds docs - supplier_map, tds_docs = {}, {} - pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" - supplier_list = [d.supplier for d in filters["invoices"]] +def get_supplier_pan_map(): + supplier_map = frappe._dict() + suppliers = frappe.db.get_all('Supplier', fields=['name', 'pan', 'supplier_type', 'supplier_name']) - supplier_detail = frappe.db.get_all('Supplier', - {"name": ["in", supplier_list]}, - ["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"]) + for d in suppliers: + supplier_map[d.name] = d - for d in filters["invoices"]: - supplier_map[d.get("name")] = [k for k in supplier_detail - if k.name == d.get("supplier")][0] - - for d in payment_entries: - supplier_map[d.get("name")] = [k for k in supplier_detail - if k.name == d.get("supplier")][0] - - for d in supplier_detail: - if d.get("tax_withholding_category") not in tds_docs: - tds_docs[d.get("tax_withholding_category")] = \ - frappe.get_doc("Tax Withholding Category", d.get("tax_withholding_category")) - - return supplier_map, tds_docs + return supplier_map def get_gle_map(filters, documents): # create gle_map of the form @@ -140,10 +83,9 @@ def get_gle_map(filters, documents): gle = frappe.db.get_all('GL Entry', { "voucher_no": ["in", documents], - 'is_cancelled': 0, - 'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]), + "credit": (">", 0) }, - ["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date", "voucher_type"], + ["credit", "debit", "account", "voucher_no", "posting_date", "voucher_type", "against", "party"], ) for d in gle: @@ -233,39 +175,57 @@ def get_columns(filters): return columns -def get_payment_entires(filters): - filter_dict = { - 'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]), - 'party_type': 'Supplier', - 'apply_tax_withholding_amount': 1 +def get_tds_docs(filters): + tds_documents = [] + purchase_invoices = [] + payment_entries = [] + journal_entries = [] + tax_category_map = {} + + tds_accounts = frappe.get_all("Tax Withholding Account", {'company': filters.get('company')}, + pluck="account") + + query_filters = { + "credit": ('>', 0), + "account": ("in", tds_accounts), + "posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]), + "is_cancelled": 0 } - if filters.get('purchase_invoice') or filters.get('purchase_order'): - parent = frappe.db.get_all('Payment Entry Reference', - {'reference_name': ('in', [d.get('name') for d in filters.get('invoices')])}, ['parent']) + if filters.get('supplier'): + query_filters.update({'against': filters.get('supplier')}) - filter_dict.update({'name': ('in', [d.get('parent') for d in parent])}) + tds_docs = frappe.get_all("GL Entry", query_filters, ["voucher_no", "voucher_type", "against", "party"]) - payment_entries = frappe.get_all('Payment Entry', fields=['name', 'party_name as supplier'], - filters=filter_dict) + for d in tds_docs: + if d.voucher_type == "Purchase Invoice": + purchase_invoices.append(d.voucher_no) + elif d.voucher_type == "Payment Entry": + payment_entries.append(d.voucher_no) + elif d.voucher_type == "Journal Entry": + journal_entries.append(d.voucher_no) - return payment_entries + tds_documents.append(d.voucher_no) -@frappe.whitelist() -def get_tds_invoices_and_orders(): - # fetch tds applicable supplier and fetch invoices for these suppliers - suppliers = [d.name for d in frappe.db.get_list("Supplier", - {"tax_withholding_category": ["!=", ""]}, ["name"])] + if purchase_invoices: + get_tax_category_map(purchase_invoices, 'Purchase Invoice', tax_category_map) - invoices = frappe.db.get_list("Purchase Invoice", - {"supplier": ["in", suppliers]}, ["name", "supplier"]) + if payment_entries: + get_tax_category_map(payment_entries, 'Payment Entry', tax_category_map) - orders = frappe.db.get_list("Purchase Order", - {"supplier": ["in", suppliers]}, ["name", "supplier"]) + if journal_entries: + get_tax_category_map(journal_entries, 'Journal Entry', tax_category_map) - invoices = invoices + orders - invoices = [d for d in invoices if d.supplier] + return tds_documents, tds_accounts, tax_category_map - frappe.cache().hset("invoices", frappe.session.user, invoices) +def get_tax_category_map(vouchers, doctype, tax_category_map): + tax_category_map.update(frappe._dict(frappe.get_all(doctype, + filters = {'name': ('in', vouchers)}, fields=['name', 'tax_withholding_category'], as_list=1))) - return invoices +def get_tax_rate_map(filters): + rate_map = frappe.get_all('Tax Withholding Rate', filters={ + 'from_date': ('<=', filters.get('from_date')), + 'to_date': ('>=', filters.get('to_date')) + }, fields=['parent', 'tax_withholding_rate'], as_list=1) + + return frappe._dict(rate_map) \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 8ff4f9790aa..39f102e1430 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -394,10 +394,6 @@ class Asset(AccountsController): if cint(self.number_of_depreciations_booked) > cint(row.total_number_of_depreciations): frappe.throw(_("Number of Depreciations Booked cannot be greater than Total Number of Depreciations")) - if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(nowdate()): - frappe.msgprint(_("Depreciation Row {0}: Depreciation Start Date is entered as past date") - .format(row.idx), title=_('Warning'), indicator='red') - if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date): frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date") .format(row.idx)) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 4cc9be5b05d..7183ee7e369 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -645,12 +645,18 @@ class TestAsset(unittest.TestCase): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=8000.0, location="Test Location") + finance_book = frappe.new_doc('Finance Book') + finance_book.finance_book_name = 'Income Tax' + finance_book.for_income_tax = 1 + finance_book.insert(ignore_if_duplicate=1) + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 asset.available_for_use_date = '2030-07-12' asset.purchase_date = '2030-01-01' asset.append("finance_books", { + "finance_book": finance_book.name, "expected_value_after_useful_life": 1000, "depreciation_method": "Written Down Value", "total_number_of_depreciations": 3, diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index ef54538fcd4..896208f25e1 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1121,6 +1121,7 @@ "fetch_from": "supplier.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1143,7 +1144,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-08-30 20:03:14.008804", + "modified": "2021-09-28 13:10:47.955401", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js index 701da4380aa..ca3be03da6e 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js @@ -30,7 +30,14 @@ frappe.query_reports["Purchase Order Analysis"] = { "default": frappe.datetime.get_today() }, { - "fieldname": "purchase_order", + "fieldname":"project", + "label": __("Project"), + "fieldtype": "Link", + "width": "80", + "options": "Project" + }, + { + "fieldname": "name", "label": __("Purchase Order"), "fieldtype": "Link", "width": "80", diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index 5d59456550b..1b25dd45d2d 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -41,14 +41,12 @@ def get_conditions(filters): if filters.get("from_date") and filters.get("to_date"): conditions += " and po.transaction_date between %(from_date)s and %(to_date)s" - if filters.get("company"): - conditions += " and po.company = %(company)s" + for field in ['company', 'name', 'status']: + if filters.get(field): + conditions += f" and po.{field} = %({field})s" - if filters.get("purchase_order"): - conditions += " and po.name = %(purchase_order)s" - - if filters.get("status"): - conditions += " and po.status in %(status)s" + if filters.get('project'): + conditions += " and poi.project = %(project)s" return conditions @@ -57,6 +55,7 @@ def get_data(conditions, filters): SELECT po.transaction_date as date, poi.schedule_date as required_date, + poi.project, po.name as purchase_order, po.status, po.supplier, poi.item_code, poi.qty, poi.received_qty, @@ -175,6 +174,12 @@ def get_columns(filters): "fieldtype": "Link", "options": "Supplier", "width": 130 + },{ + "label": _("Project"), + "fieldname": "project", + "fieldtype": "Link", + "options": "Project", + "width": 130 }] if not filters.get("group_by_po"): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 87d5648f6ed..e9b531ecb86 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -685,13 +685,17 @@ class AccountsController(TransactionBase): .format(d.reference_name, d.against_order)) def set_advance_gain_or_loss(self): - if not self.get("advances"): + if self.get('conversion_rate') == 1 or not self.get("advances"): + return + + is_purchase_invoice = self.doctype == 'Purchase Invoice' + party_account = self.credit_to if is_purchase_invoice else self.debit_to + if get_account_currency(party_account) != self.currency: return for d in self.get("advances"): advance_exchange_rate = d.ref_exchange_rate - if (d.allocated_amount and self.conversion_rate != 1 - and self.conversion_rate != advance_exchange_rate): + if (d.allocated_amount and self.conversion_rate != advance_exchange_rate): base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount @@ -710,7 +714,7 @@ class AccountsController(TransactionBase): gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account') if not gain_loss_account: - frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}") + frappe.throw(_("Please set default Exchange Gain/Loss Account in Company {}") .format(self.get('company'))) account_currency = get_account_currency(gain_loss_account) if account_currency != self.company_currency: @@ -729,7 +733,7 @@ class AccountsController(TransactionBase): "against": party, dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss), dr_or_cr: abs(d.exchange_gain_loss), - "cost_center": self.cost_center, + "cost_center": self.cost_center or erpnext.get_default_cost_center(self.company), "project": self.project }, item=d) ) diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py index 7c751a47a6b..1a1bcb2e20f 100644 --- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py +++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py @@ -55,8 +55,7 @@ def mark_employee_attendance(employee_list, status, date, leave_type=None, compa else: leave_type = None - if not company: - company = frappe.db.get_value("Employee", employee['employee'], "Company") + company = frappe.db.get_value("Employee", employee['employee'], "Company", cache=True) attendance=frappe.get_doc(dict( doctype='Attendance', @@ -68,4 +67,4 @@ def mark_employee_attendance(employee_list, status, date, leave_type=None, compa company=company )) attendance.insert() - attendance.submit() + attendance.submit() \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 232e3a0b0ff..7cfec974fc8 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1133,7 +1133,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): query_filters["has_variants"] = 0 if filters and filters.get("is_stock_item"): - query_filters["is_stock_item"] = 1 + or_cond_filters["is_stock_item"] = 1 + or_cond_filters["has_variants"] = 1 return frappe.get_list("Item", fields = fields, filters=query_filters, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index cee796efbc2..a4819965810 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -309,3 +309,6 @@ erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes +erpnext.patches.v13_0.create_custom_field_for_finance_book +erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries +erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry diff --git a/erpnext/patches/v13_0/create_custom_field_for_finance_book.py b/erpnext/patches/v13_0/create_custom_field_for_finance_book.py new file mode 100644 index 00000000000..313b0e9a2eb --- /dev/null +++ b/erpnext/patches/v13_0/create_custom_field_for_finance_book.py @@ -0,0 +1,21 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_field = { + 'Finance Book': [ + { + 'fieldname': 'for_income_tax', + 'label': 'For Income Tax', + 'fieldtype': 'Check', + 'insert_after': 'finance_book_name', + 'description': 'If the asset is put to use for less than 180 days, the first Depreciation Rate will be reduced by 50%.' + } + ] + } + create_custom_fields(custom_field, update=1) diff --git a/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py b/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py new file mode 100644 index 00000000000..aeb8d8eb588 --- /dev/null +++ b/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py @@ -0,0 +1,76 @@ +from typing import List, NewType + +import frappe + +StockEntryCode = NewType("StockEntryCode", str) + + +def execute(): + stock_entry_codes = find_broken_stock_entries() + + for stock_entry_code in stock_entry_codes: + patched_stock_entry = patch_additional_cost(stock_entry_code) + create_repost_item_valuation(patched_stock_entry) + + +def find_broken_stock_entries() -> List[StockEntryCode]: + period_closing_date = frappe.db.get_value( + "Period Closing Voucher", {"docstatus": 1}, "posting_date", order_by="posting_date desc" + ) + + stock_entries_to_patch = frappe.db.sql( + """ + select se.name, sum(sed.additional_cost) as item_additional_cost, se.total_additional_costs + from `tabStock Entry` se + join `tabStock Entry Detail` sed + on sed.parent = se.name + where + se.docstatus = 1 and + se.posting_date > %s + group by + sed.parent + having + item_additional_cost != se.total_additional_costs + """, + period_closing_date, + as_dict=True, + ) + + return [d.name for d in stock_entries_to_patch] + + +def patch_additional_cost(code: StockEntryCode): + stock_entry = frappe.get_doc("Stock Entry", code) + stock_entry.distribute_additional_costs() + stock_entry.update_valuation_rate() + stock_entry.set_total_incoming_outgoing_value() + stock_entry.set_total_amount() + stock_entry.db_update() + for item in stock_entry.items: + item.db_update() + return stock_entry + + +def create_repost_item_valuation(stock_entry): + from erpnext.controllers.stock_controller import create_repost_item_valuation_entry + + # turn on recalculate flag so reposting corrects the incoming/outgoing rates. + frappe.db.set_value( + "Stock Ledger Entry", + {"voucher_no": stock_entry.name, "actual_qty": (">", 0)}, + "recalculate_rate", + 1, + update_modified=False, + ) + + create_repost_item_valuation_entry( + args=frappe._dict( + { + "posting_date": stock_entry.posting_date, + "posting_time": stock_entry.posting_time, + "voucher_type": stock_entry.doctype, + "voucher_no": stock_entry.name, + "company": stock_entry.company, + } + ) + ) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py new file mode 100644 index 00000000000..fa8a86437d0 --- /dev/null +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -0,0 +1,49 @@ +from __future__ import unicode_literals + +import json + +import frappe + + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'purchase_invoice_advance') + frappe.reload_doc('accounts', 'doctype', 'sales_invoice_advance') + + purchase_invoices = frappe.db.sql(""" + select + parenttype as type, parent as name + from + `tabPurchase Invoice Advance` + where + ref_exchange_rate = 1 + and docstatus = 1 + and ifnull(exchange_gain_loss, '') != '' + group by + parent + """, as_dict=1) + + sales_invoices = frappe.db.sql(""" + select + parenttype as type, parent as name + from + `tabSales Invoice Advance` + where + ref_exchange_rate = 1 + and docstatus = 1 + and ifnull(exchange_gain_loss, '') != '' + group by + parent + """, as_dict=1) + + if purchase_invoices + sales_invoices: + frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") + + for invoice in purchase_invoices + sales_invoices: + doc = frappe.get_doc(invoice.type, invoice.name) + doc.docstatus = 2 + doc.make_gl_entries() + for advance in doc.advances: + if advance.ref_exchange_rate == 1: + advance.db_set('exchange_gain_loss', 0, False) + doc.docstatus = 1 + doc.make_gl_entries() \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 2a8870b2305..df970f34c2b 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -144,6 +144,9 @@ class Project(Document): if self.sales_order: frappe.db.set_value("Sales Order", self.sales_order, "project", self.name) + def on_trash(self): + frappe.db.set_value("Sales Order", {"project": self.name}, "project", "") + def update_percent_complete(self): if self.percent_complete_method == "Manual": if self.status == "Completed": diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index ebc132626ca..c64ac8d0ea8 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -9,6 +9,8 @@ from frappe.utils import add_days, getdate, nowdate from erpnext.projects.doctype.project_template.test_project_template import make_project_template from erpnext.projects.doctype.task.test_task import create_task +from erpnext.selling.doctype.sales_order.sales_order import make_project as make_project_from_so +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order test_records = frappe.get_test_records('Project') test_ignore = ["Sales Order"] @@ -96,6 +98,21 @@ class TestProject(unittest.TestCase): self.assertEqual(len(tasks), 2) + def test_project_linking_with_sales_order(self): + so = make_sales_order() + project = make_project_from_so(so.name) + + project.save() + self.assertEqual(project.sales_order, so.name) + + so.reload() + self.assertEqual(so.project, project.name) + + project.delete() + + so.reload() + self.assertFalse(so.project) + def get_project(name, template): project = frappe.get_doc(dict( diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 149582f48bf..63fd8a1c67c 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -652,6 +652,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe me.frm.script_manager.trigger('qty', item.doctype, item.name); if (!me.frm.doc.set_warehouse) me.frm.script_manager.trigger('warehouse', item.doctype, item.name); + me.apply_price_list(item, true); }, undefined, !frappe.flags.hide_serial_batch_dialog); } }, diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 633064cf094..4a2c577e55b 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -663,6 +663,15 @@ def make_custom_fields(update=True): 'fieldtype': 'Data', 'insert_after': 'email' } + ], + 'Finance Book': [ + { + 'fieldname': 'for_income_tax', + 'label': 'For Income Tax', + 'fieldtype': 'Check', + 'insert_after': 'finance_book_name', + 'description': 'If the asset is put to use for less than 180 days, the first Depreciation Rate will be reduced by 50%.' + } ] } create_custom_fields(custom_fields, update=update) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 091cc8847cb..0faf80b0024 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -857,12 +857,13 @@ def get_depreciation_amount(asset, depreciable_value, row): rate_of_depreciation = row.rate_of_depreciation # if its the first depreciation if depreciable_value == asset.gross_purchase_amount: - # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 - diff = date_diff(row.depreciation_start_date, asset.available_for_use_date) - if diff <= 180: - rate_of_depreciation = rate_of_depreciation / 2 - frappe.msgprint( - _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.')) + if row.finance_book and frappe.db.get_value('Finance Book', row.finance_book, 'for_income_tax'): + # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 + diff = date_diff(row.depreciation_start_date, asset.available_for_use_date) + if diff <= 180: + rate_of_depreciation = rate_of_depreciation / 2 + frappe.msgprint( + _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.')) depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100)) diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 85282ca1a07..7c7ed9a9604 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1480,6 +1480,7 @@ "fetch_from": "customer.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1512,7 +1513,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-09-01 15:12:24.115483", + "modified": "2021-09-28 13:09:51.515542", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index fdc8763baa6..9bf142c4b44 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1277,6 +1277,7 @@ "fetch_from": "customer.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1308,7 +1309,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2021-08-27 20:14:40.215231", + "modified": "2021-09-28 13:10:09.761714", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json index 8665986004d..805286ddcc0 100644 --- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json +++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json @@ -36,7 +36,8 @@ "fieldname": "qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Qty" + "label": "Qty", + "reqd": 1 }, { "fieldname": "picked_qty", @@ -180,7 +181,7 @@ ], "istable": 1, "links": [], - "modified": "2020-06-24 17:18:57.357120", + "modified": "2021-09-28 12:02:16.923056", "modified_by": "Administrator", "module": "Stock", "name": "Pick List Item", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 1a597343c0a..112ddedac29 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -1140,6 +1140,7 @@ "fetch_from": "supplier.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1149,7 +1150,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2021-08-17 20:16:40.849885", + "modified": "2021-09-28 13:11:10.181328", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 7cb9665e857..157904bc34e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -548,44 +548,7 @@ frappe.ui.form.on('Stock Entry', { calculate_basic_amount: function(frm, item) { item.basic_amount = flt(flt(item.transfer_qty) * flt(item.basic_rate), precision("basic_amount", item)); - - frm.events.calculate_amount(frm); - }, - - calculate_amount: function(frm) { frm.events.calculate_total_additional_costs(frm); - let total_basic_amount = 0; - if (in_list(["Repack", "Manufacture"], frm.doc.purpose)) { - total_basic_amount = frappe.utils.sum( - (frm.doc.items || []).map(function(i) { - return i.is_finished_item ? flt(i.basic_amount) : 0; - }) - ); - } else { - total_basic_amount = frappe.utils.sum( - (frm.doc.items || []).map(function(i) { - return i.t_warehouse ? flt(i.basic_amount) : 0; - }) - ); - } - for (let i in frm.doc.items) { - let item = frm.doc.items[i]; - - if (((in_list(["Repack", "Manufacture"], frm.doc.purpose) && item.is_finished_item) || item.t_warehouse) && total_basic_amount) { - item.additional_cost = (flt(item.basic_amount) / total_basic_amount) * frm.doc.total_additional_costs; - } else { - item.additional_cost = 0; - } - - item.amount = flt(item.basic_amount + flt(item.additional_cost), precision("amount", item)); - - if (flt(item.transfer_qty)) { - item.valuation_rate = flt(flt(item.basic_rate) + (flt(item.additional_cost) / flt(item.transfer_qty)), - precision("valuation_rate", item)); - } - } - - refresh_field('items'); }, calculate_total_additional_costs: function(frm) { @@ -781,11 +744,6 @@ frappe.ui.form.on('Landed Cost Taxes and Charges', { amount: function(frm, cdt, cdn) { frm.events.set_base_amount(frm, cdt, cdn); - // Adding this check because same table in used in LCV - // This causes an error if you try to post an LCV immediately after a Stock Entry - if (frm.doc.doctype == 'Stock Entry') { - frm.events.calculate_amount(frm); - } }, expense_account: function(frm, cdt, cdn) { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 1c9b9614f67..bd7d22bcbc0 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -555,22 +555,27 @@ class StockEntry(StockController): def distribute_additional_costs(self): # If no incoming items, set additional costs blank - if not any([d.item_code for d in self.items if d.t_warehouse]): + if not any(d.item_code for d in self.items if d.t_warehouse): self.additional_costs = [] - self.total_additional_costs = sum([flt(t.base_amount) for t in self.get("additional_costs")]) + self.total_additional_costs = sum(flt(t.base_amount) for t in self.get("additional_costs")) if self.purpose in ("Repack", "Manufacture"): - incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.is_finished_item]) + incoming_items_cost = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item) else: - incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse]) + incoming_items_cost = sum(flt(t.basic_amount) for t in self.get("items") if t.t_warehouse) - if incoming_items_cost: - for d in self.get("items"): - if (self.purpose in ("Repack", "Manufacture") and d.is_finished_item) or d.t_warehouse: - d.additional_cost = (flt(d.basic_amount) / incoming_items_cost) * self.total_additional_costs - else: - d.additional_cost = 0 + if not incoming_items_cost: + return + + for d in self.get("items"): + if self.purpose in ("Repack", "Manufacture") and not d.is_finished_item: + d.additional_cost = 0 + continue + elif not d.t_warehouse: + d.additional_cost = 0 + continue + d.additional_cost = (flt(d.basic_amount) / incoming_items_cost) * self.total_additional_costs def update_valuation_rate(self): for d in self.get("items"): @@ -805,7 +810,11 @@ class StockEntry(StockController): def get_gl_entries(self, warehouse_account): gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account) - total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse]) + if self.purpose in ("Repack", "Manufacture"): + total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item) + else: + total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.t_warehouse) + divide_based_on = total_basic_amount if self.get("additional_costs") and not total_basic_amount: @@ -816,20 +825,24 @@ class StockEntry(StockController): for t in self.get("additional_costs"): for d in self.get("items"): - if d.t_warehouse: - item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) - item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, { - "amount": 0.0, - "base_amount": 0.0 - }) + if self.purpose in ("Repack", "Manufacture") and not d.is_finished_item: + continue + elif not d.t_warehouse: + continue - multiply_based_on = d.basic_amount if total_basic_amount else d.qty + item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) + item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, { + "amount": 0.0, + "base_amount": 0.0 + }) - item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += \ - flt(t.amount * multiply_based_on) / divide_based_on + multiply_based_on = d.basic_amount if total_basic_amount else d.qty - item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += \ - flt(t.base_amount * multiply_based_on) / divide_based_on + item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += \ + flt(t.amount * multiply_based_on) / divide_based_on + + item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += \ + flt(t.base_amount * multiply_based_on) / divide_based_on if item_account_wise_additional_cost: for d in self.get("items"): diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 46619eb1f35..c9d0af5f3b1 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -837,6 +837,39 @@ class TestStockEntry(unittest.TestCase): frappe.db.set_default("allow_negative_stock", 0) + def test_additional_cost_distribution_manufacture(self): + se = frappe.get_doc( + doctype="Stock Entry", + purpose="Manufacture", + additional_costs=[frappe._dict(base_amount=100)], + items=[ + frappe._dict(item_code="RM", basic_amount=10), + frappe._dict(item_code="FG", basic_amount=20, t_warehouse="X", is_finished_item=1), + frappe._dict(item_code="scrap", basic_amount=30, t_warehouse="X") + ], + ) + + se.distribute_additional_costs() + + distributed_costs = [d.additional_cost for d in se.items] + self.assertEqual([0.0, 100.0, 0.0], distributed_costs) + + def test_additional_cost_distribution_non_manufacture(self): + se = frappe.get_doc( + doctype="Stock Entry", + purpose="Material Receipt", + additional_costs=[frappe._dict(base_amount=100)], + items=[ + frappe._dict(item_code="RECEIVED_1", basic_amount=20, t_warehouse="X"), + frappe._dict(item_code="RECEIVED_2", basic_amount=30, t_warehouse="X") + ], + ) + + se.distribute_additional_costs() + + distributed_costs = [d.additional_cost for d in se.items] + self.assertEqual([40.0, 60.0], distributed_costs) + def make_serialized_item(**args): args = frappe._dict(args) se = frappe.copy_doc(test_records[0]) diff --git a/erpnext/templates/print_formats/includes/total.html b/erpnext/templates/print_formats/includes/total.html index 81799809ba7..879203bbf25 100644 --- a/erpnext/templates/print_formats/includes/total.html +++ b/erpnext/templates/print_formats/includes/total.html @@ -7,7 +7,7 @@ {% else %}
-
+
{{ doc.get_formatted("total", doc) }}