Merge branch 'develop' into scan-batch
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index bcd0771..71957e6 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -374,12 +374,15 @@
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 3eb0d74..67acb26 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 b7bbb74..20678d7 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 24368f0..e568a82 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.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 @@
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 @@
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 @@
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 43eb0b6..8961167 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 dde0328..55e288e 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 dd4a005..1c9943f 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -1146,6 +1146,12 @@
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 63dfff8..9fcbf5c 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 f383b52..2d6c04e 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 619dc7b..8a2e945 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 @@
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 @@
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 @@
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 @@
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 @@
"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 @@
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 29422d6..f92b57a 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 fa4ea21..16ef5fc 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 @@
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_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 8a88d79..84b364b 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 @@
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_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 @@
'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 4bf2b82..0cee6f5 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -284,13 +284,16 @@
"""
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 dfc4b18..91f0798 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 c4a8c7a..536df1f 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 @@
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 @@
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 @@
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 @@
{
"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 72de318..ff2aa30 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 @@
"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 @@
"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 557a62d..4d555bd 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 9e1382b..621b697 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 @@
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 @@
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.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited,
- tds_deducted, gle_map[d][0].posting_date, voucher_type, d])
+ 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
+ })
+
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 @@
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 @@
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 8ff4f97..39f102e 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -394,10 +394,6 @@
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 4cc9be5..7183ee7 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -645,12 +645,18 @@
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 ef54538..896208f 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 701da43..ca3be03 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 @@
"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 5d59456..1b25dd4 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 @@
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 @@
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 @@
"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 87d5648..e9b531e 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -685,13 +685,17 @@
.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 @@
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 @@
"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 7c751a4..1a1bcb2 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 @@
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 @@
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 232e3a0..7cfec97 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -1133,7 +1133,8 @@
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 cee796e..a481996 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -309,3 +309,6 @@
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 0000000..313b0e9
--- /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 0000000..aeb8d8e
--- /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 0000000..fa8a864
--- /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 2a8870b..df970f3 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -144,6 +144,9 @@
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 ebc1326..c64ac8d 100644
--- a/erpnext/projects/doctype/project/test_project.py
+++ b/erpnext/projects/doctype/project/test_project.py
@@ -9,6 +9,8 @@
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 @@
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 149582f..63fd8a1 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -652,6 +652,7 @@
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 633064c..4a2c577 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -663,6 +663,15 @@
'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 091cc884..0faf80b 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -857,12 +857,13 @@
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 85282ca..7c7ed9a 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 fdc8763..9bf142c 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 8665986..805286d 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 1a59734..112dded 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 7cb9665..157904b 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -548,44 +548,7 @@
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 @@
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 1c9b961..bd7d22b 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -555,22 +555,27 @@
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 @@
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 @@
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 46619eb..c9d0af5 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 @@
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 8179980..879203b 100644
--- a/erpnext/templates/print_formats/includes/total.html
+++ b/erpnext/templates/print_formats/includes/total.html
@@ -7,7 +7,7 @@
</div>
{% else %}
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
- <label>{{ _(doc.meta.get_label('total')) }}</label></div>
+ <label>{{ _(df.label) }}</label></div>
<div class="col-xs-7 text-right">
{{ doc.get_formatted("total", doc) }}
</div>