fix: conflicts
This commit is contained in:
commit
343143037c
@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '13.7.1'
|
||||
__version__ = '13.8.0'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
@ -391,5 +391,5 @@ def set_default_accounts(company):
|
||||
})
|
||||
|
||||
company.save()
|
||||
install_country_fixtures(company.name)
|
||||
install_country_fixtures(company.name, company.country)
|
||||
company.create_default_tax_template()
|
||||
|
@ -306,5 +306,5 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
jv.flags.ignore_mandatory = True
|
||||
jv.submit()
|
@ -168,7 +168,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
||||
frappe.throw(_("Invalid {0}").format(args.get(field)))
|
||||
|
||||
parent_groups = frappe.db.sql_list("""select name from `tab%s`
|
||||
where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
|
||||
where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
|
||||
|
||||
if parenttype in ["Customer Group", "Item Group", "Territory"]:
|
||||
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
|
||||
|
@ -1018,8 +1018,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 37500.0],
|
||||
["_Test Payable USD - _TC", -40000.0],
|
||||
["Exchange Gain/Loss - _TC", 2500.0]
|
||||
["_Test Payable USD - _TC", -35000.0],
|
||||
["Exchange Gain/Loss - _TC", -2500.0]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
@ -1049,8 +1049,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 36500.0],
|
||||
["_Test Payable USD - _TC", -38000.0],
|
||||
["Exchange Gain/Loss - _TC", 1500.0]
|
||||
["_Test Payable USD - _TC", -35000.0],
|
||||
["Exchange Gain/Loss - _TC", -1500.0]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
|
@ -1,263 +1,151 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "Prompt",
|
||||
"beta": 0,
|
||||
"creation": "2018-04-13 18:42:06.431683",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"category_details_section",
|
||||
"category_name",
|
||||
"round_off_tax_amount",
|
||||
"column_break_2",
|
||||
"consider_party_ledger_amount",
|
||||
"tax_on_excess_amount",
|
||||
"section_break_8",
|
||||
"rates",
|
||||
"section_break_7",
|
||||
"accounts"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "category_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Category Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Tax Withholding Rates",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rates",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Rates",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Tax Withholding Rate",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Account Details",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "accounts",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Accounts",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Tax Withholding Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "category_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Category Details",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
|
||||
"fieldname": "consider_party_ledger_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consider Entire Party Ledger Amount",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Tax will be withheld only for amount exceeding the cumulative threshold",
|
||||
"fieldname": "tax_on_excess_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Only Deduct Tax On Excess Amount ",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"description": "Checking this will round off the tax amount to the nearest integer",
|
||||
"fieldname": "round_off_tax_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Round Off Tax Amount",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-07-17 22:53:26.193179",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-27 21:47:34.396071",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Category",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt, getdate
|
||||
from frappe.utils import flt, getdate, cint
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
class TaxWithholdingCategory(Document):
|
||||
@ -86,7 +86,10 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
|
||||
"rate": tax_rate_detail.tax_withholding_rate,
|
||||
"threshold": tax_rate_detail.single_threshold,
|
||||
"cumulative_threshold": tax_rate_detail.cumulative_threshold,
|
||||
"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category
|
||||
"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category,
|
||||
"consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount,
|
||||
"tax_on_excess_amount": tax_withholding.tax_on_excess_amount,
|
||||
"round_off_tax_amount": tax_withholding.round_off_tax_amount
|
||||
})
|
||||
|
||||
def get_tax_withholding_rates(tax_withholding, fiscal_year):
|
||||
@ -145,6 +148,7 @@ def get_lower_deduction_certificate(fiscal_year, pan_no):
|
||||
def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
|
||||
fiscal_year = fiscal_year_details[0]
|
||||
|
||||
|
||||
vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
||||
advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
||||
taxable_vouchers = vouchers + advance_vouchers
|
||||
@ -235,10 +239,18 @@ def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details):
|
||||
|
||||
def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
|
||||
tds_amount = 0
|
||||
invoice_filters = {
|
||||
'name': ('in', vouchers),
|
||||
'docstatus': 1
|
||||
}
|
||||
|
||||
supp_credit_amt = frappe.db.get_value('Purchase Invoice', {
|
||||
'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1
|
||||
}, 'sum(net_total)') or 0.0
|
||||
field = 'sum(net_total)'
|
||||
|
||||
if not cint(tax_details.consider_party_ledger_amount):
|
||||
invoice_filters.update({'apply_tds': 1})
|
||||
field = 'sum(grand_total)'
|
||||
|
||||
supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.0
|
||||
|
||||
supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', {
|
||||
'parent': ('in', vouchers), 'docstatus': 1,
|
||||
@ -255,6 +267,13 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
||||
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
|
||||
|
||||
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
||||
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(tax_details.tax_on_excess_amount):
|
||||
# Get net total again as TDS is calculated on net total
|
||||
# Grand is used to just check for threshold breach
|
||||
net_total = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0
|
||||
net_total += inv.net_total
|
||||
supp_credit_amt = net_total - cumulative_threshold
|
||||
|
||||
if ldc and is_valid_certificate(
|
||||
ldc.valid_from, ldc.valid_upto,
|
||||
inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
|
||||
@ -264,6 +283,9 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
||||
else:
|
||||
tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
|
||||
|
||||
if cint(tax_details.round_off_tax_amount):
|
||||
tds_amount = round(tds_amount)
|
||||
|
||||
return tds_amount
|
||||
|
||||
def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv_vouchers):
|
||||
|
@ -87,6 +87,31 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def test_tax_withholding_category_checks(self):
|
||||
invoices = []
|
||||
frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category")
|
||||
|
||||
# First Invoice with no tds check
|
||||
pi = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000, do_not_save=True)
|
||||
pi.apply_tds = 0
|
||||
pi.save()
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
# Second Invoice will apply TDS checked
|
||||
pi1 = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000)
|
||||
pi1.submit()
|
||||
invoices.append(pi1)
|
||||
|
||||
# Cumulative threshold is 30000
|
||||
# Threshold calculation should be on both the invoices
|
||||
# TDS should be applied only on 1000
|
||||
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
|
||||
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
|
||||
def test_cumulative_threshold_tcs(self):
|
||||
frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
|
||||
invoices = []
|
||||
@ -195,7 +220,7 @@ def create_sales_invoice(**args):
|
||||
|
||||
def create_records():
|
||||
# create a new suppliers
|
||||
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']:
|
||||
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3']:
|
||||
if frappe.db.exists('Supplier', name):
|
||||
continue
|
||||
|
||||
@ -311,3 +336,23 @@ def create_tax_with_holding_category():
|
||||
'account': 'TDS - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
if not frappe.db.exists("Tax Withholding Category", "New TDS Category"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Tax Withholding Category",
|
||||
"name": "New TDS Category",
|
||||
"category_name": "New TDS Category",
|
||||
"round_off_tax_amount": 1,
|
||||
"consider_party_ledger_amount": 1,
|
||||
"tax_on_excess_amount": 1,
|
||||
"rates": [{
|
||||
'fiscal_year': fiscal_year,
|
||||
'tax_withholding_rate': 10,
|
||||
'single_threshold': 0,
|
||||
'cumulative_threshold': 30000
|
||||
}],
|
||||
"accounts": [{
|
||||
'company': '_Test Company',
|
||||
'account': 'TDS - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
|
@ -241,6 +241,7 @@ class GrossProfitGenerator(object):
|
||||
sle.voucher_detail_no == row.item_row:
|
||||
previous_stock_value = len(my_sle) > i+1 and \
|
||||
flt(my_sle[i+1].stock_value) or 0.0
|
||||
|
||||
if previous_stock_value:
|
||||
return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
|
||||
else:
|
||||
@ -335,7 +336,7 @@ class GrossProfitGenerator(object):
|
||||
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
|
||||
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
||||
from `tabStock Ledger Entry`
|
||||
where company=%(company)s
|
||||
where company=%(company)s and is_cancelled = 0
|
||||
order by
|
||||
item_code desc, warehouse desc, posting_date desc,
|
||||
posting_time desc, creation desc""", self.filters, as_dict=True)
|
||||
|
39
erpnext/change_log/v13/v13_8_0.md
Normal file
39
erpnext/change_log/v13/v13_8_0.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Version 13.8.0 Release Notes
|
||||
|
||||
### Features & Enhancements
|
||||
- Report to show COGS by item groups ([#26222](https://github.com/frappe/erpnext/pull/26222))
|
||||
- Enhancements in TDS ([#26677](https://github.com/frappe/erpnext/pull/26677))
|
||||
- API Endpoint to update halted Razorpay subscriptions ([#26564](https://github.com/frappe/erpnext/pull/26564))
|
||||
|
||||
### Fixes
|
||||
- Incorrect bom name ([#26600](https://github.com/frappe/erpnext/pull/26600))
|
||||
- Exchange rate revaluation posting date and precision fixes ([#26651](https://github.com/frappe/erpnext/pull/26651))
|
||||
- POS item cart dom updates ([#26460](https://github.com/frappe/erpnext/pull/26460))
|
||||
- General Ledger report not working with filter group by ([#26439](https://github.com/frappe/erpnext/pull/26438))
|
||||
- Tax calculation for Recurring additional salary ([#24206](https://github.com/frappe/erpnext/pull/24206))
|
||||
- Validation check for batch for stock reconciliation type in stock entry ([#26487](https://github.com/frappe/erpnext/pull/26487))
|
||||
- Improved UX for additional discount field ([#26502](https://github.com/frappe/erpnext/pull/26502))
|
||||
- Add missing cess amount in GSTR-3B report ([#26644](https://github.com/frappe/erpnext/pull/26644))
|
||||
- Optimized code for reposting item valuation ([#26431](https://github.com/frappe/erpnext/pull/26431))
|
||||
- FG item not fetched in manufacture entry ([#26508](https://github.com/frappe/erpnext/pull/26508))
|
||||
- Errors on parallel requests creation of company for India ([#26420](https://github.com/frappe/erpnext/pull/26420))
|
||||
- Incorrect valuation rate calculation in gross profit report ([#26558](https://github.com/frappe/erpnext/pull/26558))
|
||||
- Empty "against account" in Purchase Receipt GLE ([#26712](https://github.com/frappe/erpnext/pull/26712))
|
||||
- Remove cancelled entries from Stock and Account Value comparison report ([#26721](https://github.com/frappe/erpnext/pull/26721))
|
||||
- Remove manual permission checking ([#26691](https://github.com/frappe/erpnext/pull/26691))
|
||||
- Delete child docs when parent doc is deleted ([#26518](https://github.com/frappe/erpnext/pull/26518))
|
||||
- GST Reports timeout issue ([#26646](https://github.com/frappe/erpnext/pull/26646))
|
||||
- Parent condition in pricing rules ([#26727](https://github.com/frappe/erpnext/pull/26727))
|
||||
- Added Company filters for Loan ([#26294](https://github.com/frappe/erpnext/pull/26294))
|
||||
- Incorrect discount amount on amended document ([#26292](https://github.com/frappe/erpnext/pull/26292))
|
||||
- Exchange gain loss not set for advances linked with invoices ([#26436](https://github.com/frappe/erpnext/pull/26436))
|
||||
- Unallocated amount in Payment Entry after taxes ([#26412](https://github.com/frappe/erpnext/pull/26412))
|
||||
- Wrong operation time in Work Order ([#26613](https://github.com/frappe/erpnext/pull/26613))
|
||||
- Serial No and Batch validation ([#26614](https://github.com/frappe/erpnext/pull/26614))
|
||||
- Gl Entries for exchange gain loss ([#26734](https://github.com/frappe/erpnext/pull/26734))
|
||||
- TDS computation summary shows cancelled invoices ([#26485](https://github.com/frappe/erpnext/pull/26485))
|
||||
- Price List rate not fetched for return sales invoice fixed ([#26560](https://github.com/frappe/erpnext/pull/26560))
|
||||
- Included company in link document type filters for contact ([#26576](https://github.com/frappe/erpnext/pull/26576))
|
||||
- Ignore mandatory fields while creating payment reconciliation Journal Entry ([#26643](https://github.com/frappe/erpnext/pull/26643))
|
||||
- Unable to download GSTR-1 json ([#26418](https://github.com/frappe/erpnext/pull/26418))
|
||||
- Paging buttons not working on item group portal page ([#26498](https://github.com/frappe/erpnext/pull/26498))
|
@ -674,19 +674,24 @@ class AccountsController(TransactionBase):
|
||||
if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
|
||||
for d in self.get("advances"):
|
||||
if d.exchange_gain_loss:
|
||||
party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer
|
||||
party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to
|
||||
party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer"
|
||||
is_purchase_invoice = self.get('doctype') == 'Purchase Invoice'
|
||||
party = self.supplier if is_purchase_invoice else self.customer
|
||||
party_account = self.credit_to if is_purchase_invoice else self.debit_to
|
||||
party_type = "Supplier" if is_purchase_invoice else "Customer"
|
||||
|
||||
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 {}")
|
||||
.format(self.get('company')))
|
||||
account_currency = get_account_currency(gain_loss_account)
|
||||
if account_currency != self.company_currency:
|
||||
frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
|
||||
frappe.throw(_("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency))
|
||||
|
||||
# for purchase
|
||||
dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
|
||||
# just reverse for sales?
|
||||
dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
|
||||
if not is_purchase_invoice:
|
||||
# just reverse for sales?
|
||||
dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
|
@ -407,6 +407,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
|
||||
where
|
||||
batch.disabled = 0
|
||||
and sle.is_cancelled = 0
|
||||
and sle.item_code = %(item_code)s
|
||||
and sle.warehouse = %(warehouse)s
|
||||
and (sle.batch_no like %(txt)s
|
||||
|
@ -14,7 +14,7 @@ frappe.ui.form.on('Loan Application', {
|
||||
refresh: function(frm) {
|
||||
frm.trigger("toggle_fields");
|
||||
frm.trigger("add_toolbar_buttons");
|
||||
frm.set_query("loan_type", () => {
|
||||
frm.set_query('loan_type', () => {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
|
@ -748,7 +748,7 @@ def get_valuation_rate(args):
|
||||
if valuation_rate <= 0:
|
||||
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
||||
from `tabStock Ledger Entry`
|
||||
where item_code = %s and valuation_rate > 0
|
||||
where item_code = %s and valuation_rate > 0 and is_cancelled = 0
|
||||
order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code'])
|
||||
|
||||
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
|
||||
|
@ -297,3 +297,5 @@ erpnext.patches.v13_0.update_level_in_bom #1234sswef
|
||||
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
|
||||
erpnext.patches.v13_0.update_subscription_status_in_memberships
|
||||
erpnext.patches.v13_0.update_amt_in_work_order_required_items
|
||||
erpnext.patches.v13_0.update_export_type_for_gst
|
||||
erpnext.patches.v13_0.update_tds_check_field #3
|
||||
|
24
erpnext/patches/v13_0/update_export_type_for_gst.py
Normal file
24
erpnext/patches/v13_0/update_export_type_for_gst.py
Normal file
@ -0,0 +1,24 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||
if not company:
|
||||
return
|
||||
|
||||
# Update custom fields
|
||||
fieldname = frappe.db.get_value('Custom Field', {'dt': 'Customer', 'fieldname': 'export_type'})
|
||||
if fieldname:
|
||||
frappe.db.set_value('Custom Field', fieldname, 'default', '')
|
||||
|
||||
fieldname = frappe.db.get_value('Custom Field', {'dt': 'Supplier', 'fieldname': 'export_type'})
|
||||
if fieldname:
|
||||
frappe.db.set_value('Custom Field', fieldname, 'default', '')
|
||||
|
||||
# Update Customer/Supplier Masters
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabCustomer` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas', 'Deemed Export')
|
||||
""")
|
||||
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabSupplier` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas')
|
||||
""")
|
9
erpnext/patches/v13_0/update_tds_check_field.py
Normal file
9
erpnext/patches/v13_0/update_tds_check_field.py
Normal file
@ -0,0 +1,9 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
if frappe.db.has_table("Tax Withholding Category") \
|
||||
and frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"):
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0
|
||||
WHERE round_off_tax_amount IS NULL
|
||||
""")
|
@ -112,11 +112,11 @@ class AdditionalSalary(Document):
|
||||
no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1
|
||||
return amount_per_day * no_of_days
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_additional_salaries(employee, start_date, end_date, component_type):
|
||||
additional_salary_list = frappe.db.sql("""
|
||||
select name, salary_component as component, type, amount,
|
||||
overwrite_salary_structure_amount as overwrite,
|
||||
deduct_full_tax_on_selected_payroll_date
|
||||
select name, salary_component as component, type, amount, overwrite_salary_structure_amount as overwrite,
|
||||
deduct_full_tax_on_selected_payroll_date, is_recurring
|
||||
from `tabAdditional Salary`
|
||||
where employee=%(employee)s
|
||||
and docstatus = 1
|
||||
|
@ -12,6 +12,7 @@
|
||||
"year_to_date",
|
||||
"section_break_5",
|
||||
"additional_salary",
|
||||
"is_recurring_additional_salary",
|
||||
"statistical_component",
|
||||
"depends_on_payment_days",
|
||||
"exempted_from_income_tax",
|
||||
@ -235,11 +236,19 @@
|
||||
"label": "Year To Date",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.parenttype=='Salary Slip' && doc.parentfield=='earnings' && doc.additional_salary",
|
||||
"fieldname": "is_recurring_additional_salary",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Recurring Additional Salary",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-14 13:39:15.847158",
|
||||
"modified": "2021-03-14 13:39:15.847158",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Detail",
|
||||
|
@ -7,12 +7,12 @@ import datetime, math
|
||||
|
||||
from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
|
||||
from frappe import msgprint, _
|
||||
from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries
|
||||
from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period
|
||||
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount
|
||||
@ -618,7 +618,8 @@ class SalarySlip(TransactionBase):
|
||||
get_salary_component_data(additional_salary.component),
|
||||
additional_salary.amount,
|
||||
component_type,
|
||||
additional_salary
|
||||
additional_salary,
|
||||
is_recurring = additional_salary.is_recurring
|
||||
)
|
||||
|
||||
def add_tax_components(self, payroll_period):
|
||||
@ -639,7 +640,7 @@ class SalarySlip(TransactionBase):
|
||||
tax_row = get_salary_component_data(d)
|
||||
self.update_component_row(tax_row, tax_amount, "deductions")
|
||||
|
||||
def update_component_row(self, component_data, amount, component_type, additional_salary=None):
|
||||
def update_component_row(self, component_data, amount, component_type, additional_salary=None, is_recurring = 0):
|
||||
component_row = None
|
||||
for d in self.get(component_type):
|
||||
if d.salary_component != component_data.salary_component:
|
||||
@ -677,6 +678,7 @@ class SalarySlip(TransactionBase):
|
||||
component_row.set(attr, component_data.get(attr))
|
||||
|
||||
if additional_salary:
|
||||
component_row.is_recurring_additional_salary = is_recurring
|
||||
component_row.default_amount = 0
|
||||
component_row.additional_amount = amount
|
||||
component_row.additional_salary = additional_salary.name
|
||||
@ -710,6 +712,7 @@ class SalarySlip(TransactionBase):
|
||||
# get remaining numbers of sub-period (period for which one salary is processed)
|
||||
remaining_sub_periods = get_period_factor(self.employee,
|
||||
self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1]
|
||||
|
||||
# get taxable_earnings, paid_taxes for previous period
|
||||
previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date,
|
||||
self.start_date, tax_slab.allow_tax_exemption)
|
||||
@ -869,8 +872,16 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
if earning.is_tax_applicable:
|
||||
if additional_amount:
|
||||
taxable_earnings += (amount - additional_amount)
|
||||
additional_income += additional_amount
|
||||
if not earning.is_recurring_additional_salary:
|
||||
taxable_earnings += (amount - additional_amount)
|
||||
additional_income += additional_amount
|
||||
else:
|
||||
to_date = frappe.db.get_value("Additional Salary", earning.additional_salary, 'to_date')
|
||||
period = (getdate(to_date).month - getdate(self.start_date).month) + 1
|
||||
if period > 0:
|
||||
taxable_earnings += (amount - additional_amount) * period
|
||||
additional_income += additional_amount * period
|
||||
|
||||
if earning.deduct_full_tax_on_selected_payroll_date:
|
||||
additional_income_with_full_tax += additional_amount
|
||||
continue
|
||||
|
@ -129,7 +129,7 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None,
|
||||
"earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
|
||||
"payroll_frequency": payroll_frequency,
|
||||
"payment_account": get_random("Account", filters={"account_currency": currency}),
|
||||
"payment_account": get_random("Account", filters={'account_currency': currency}),
|
||||
"currency": currency
|
||||
}
|
||||
if other_details and isinstance(other_details, dict):
|
||||
|
@ -280,9 +280,15 @@ class GSTR3BReport(Document):
|
||||
if self.get('invoice_items'):
|
||||
# Build itemised tax for export invoices, nil and exempted where tax table is blank
|
||||
for invoice, items in iteritems(self.invoice_items):
|
||||
if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type')
|
||||
== "Without Payment of Tax"):
|
||||
if invoice not in self.items_based_on_tax_rate and self.invoice_detail_map.get(invoice, {}).get('export_type') \
|
||||
== "Without Payment of Tax" and self.invoice_detail_map.get(invoice, {}).get('gst_category') == "Overseas":
|
||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
||||
else:
|
||||
for item in items.keys():
|
||||
if item in self.is_nil_exempt + self.is_non_gst and \
|
||||
item not in self.items_based_on_tax_rate.get(invoice, {}).get(0, []):
|
||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, [])
|
||||
self.items_based_on_tax_rate[invoice][0].append(item)
|
||||
|
||||
def set_outward_taxable_supplies(self):
|
||||
inter_state_supply_details = {}
|
||||
|
@ -641,7 +641,6 @@ def make_custom_fields(update=True):
|
||||
'label': 'Export Type',
|
||||
'fieldtype': 'Select',
|
||||
'insert_after': 'gst_category',
|
||||
'default': 'Without Payment of Tax',
|
||||
'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)',
|
||||
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
||||
}
|
||||
@ -660,7 +659,6 @@ def make_custom_fields(update=True):
|
||||
'label': 'Export Type',
|
||||
'fieldtype': 'Select',
|
||||
'insert_after': 'gst_category',
|
||||
'default': 'Without Payment of Tax',
|
||||
'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
|
||||
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
||||
}
|
||||
|
@ -286,7 +286,8 @@ class Gstr1Report(object):
|
||||
# Build itemised tax for export invoices where tax table is blank
|
||||
for invoice, items in iteritems(self.invoice_items):
|
||||
if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \
|
||||
and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax":
|
||||
and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \
|
||||
and self.invoices.get(invoice, {}).get('gst_category') == "Overseas":
|
||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
||||
|
||||
def get_columns(self):
|
||||
|
@ -18,7 +18,7 @@ class TransactionDeletionRecord(Document):
|
||||
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
||||
for doctype in self.doctypes_to_be_ignored:
|
||||
if doctype.doctype_name not in doctypes_to_be_ignored_list:
|
||||
frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "),
|
||||
frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it."),
|
||||
title=_("Not Allowed"))
|
||||
|
||||
def before_submit(self):
|
||||
|
@ -162,19 +162,19 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=No
|
||||
|
||||
out = float(frappe.db.sql("""select sum(actual_qty)
|
||||
from `tabStock Ledger Entry`
|
||||
where warehouse=%s and batch_no=%s {0}""".format(cond),
|
||||
where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(cond),
|
||||
(warehouse, batch_no))[0][0] or 0)
|
||||
|
||||
if batch_no and not warehouse:
|
||||
out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty
|
||||
from `tabStock Ledger Entry`
|
||||
where batch_no=%s
|
||||
where is_cancelled = 0 and batch_no=%s
|
||||
group by warehouse''', batch_no, as_dict=1)
|
||||
|
||||
if not batch_no and item_code and warehouse:
|
||||
out = frappe.db.sql('''select batch_no, sum(actual_qty) as qty
|
||||
from `tabStock Ledger Entry`
|
||||
where item_code = %s and warehouse=%s
|
||||
where is_cancelled = 0 and item_code = %s and warehouse=%s
|
||||
group by batch_no''', (item_code, warehouse), as_dict=1)
|
||||
|
||||
return out
|
||||
|
@ -239,6 +239,7 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re
|
||||
and sle.`item_code`=%(item_code)s
|
||||
and sle.`company` = %(company)s
|
||||
and batch.disabled = 0
|
||||
and sle.is_cancelled=0
|
||||
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
|
||||
{warehouse_condition}
|
||||
GROUP BY
|
||||
|
@ -1789,7 +1789,7 @@ def get_expired_batch_items():
|
||||
from `tabBatch` b, `tabStock Ledger Entry` sle
|
||||
where b.expiry_date <= %s
|
||||
and b.expiry_date is not NULL
|
||||
and b.batch_id = sle.batch_no
|
||||
and b.batch_id = sle.batch_no and sle.is_cancelled = 0
|
||||
group by sle.warehouse, sle.item_code, sle.batch_no""",(nowdate()), as_dict=1)
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -60,7 +60,7 @@ class StockLedgerEntry(Document):
|
||||
if self.batch_no and not self.get("allow_negative_stock"):
|
||||
batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty)
|
||||
from `tabStock Ledger Entry`
|
||||
where warehouse=%s and item_code=%s and batch_no=%s""",
|
||||
where is_cancelled =0 and warehouse=%s and item_code=%s and batch_no=%s""",
|
||||
(self.warehouse, self.item_code, self.batch_no))[0][0])
|
||||
|
||||
if batch_bal_after_transaction < 0:
|
||||
@ -152,7 +152,7 @@ class StockLedgerEntry(Document):
|
||||
last_transaction_time = frappe.db.sql("""
|
||||
select MAX(timestamp(posting_date, posting_time)) as posting_time
|
||||
from `tabStock Ledger Entry`
|
||||
where docstatus = 1 and item_code = %s
|
||||
where docstatus = 1 and is_cancelled = 0 and item_code = %s
|
||||
and warehouse = %s""", (self.item_code, self.warehouse))[0][0]
|
||||
|
||||
cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
|
||||
|
0
erpnext/stock/report/cogs_by_item_group/__init__.py
Normal file
0
erpnext/stock/report/cogs_by_item_group/__init__.py
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
|
||||
frappe.query_reports["COGS By Item Group"] = {
|
||||
filters: [
|
||||
{
|
||||
label: __("Company"),
|
||||
fieldname: "company",
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
mandatory: true,
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
},
|
||||
{
|
||||
label: __("From Date"),
|
||||
fieldname: "from_date",
|
||||
fieldtype: "Date",
|
||||
mandatory: true,
|
||||
default: frappe.datetime.year_start(),
|
||||
},
|
||||
{
|
||||
label: __("To Date"),
|
||||
fieldname: "to_date",
|
||||
fieldtype: "Date",
|
||||
mandatory: true,
|
||||
default: frappe.datetime.get_today(),
|
||||
},
|
||||
]
|
||||
};
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2021-06-02 18:59:19.830928",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2021-06-02 18:59:55.470621",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "COGS By Item Group",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "COGS By Item Group",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Auditor"
|
||||
}
|
||||
]
|
||||
}
|
188
erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
Normal file
188
erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
Normal file
@ -0,0 +1,188 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from collections import OrderedDict
|
||||
import datetime
|
||||
from typing import Dict, List, Tuple, Union
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import date_diff
|
||||
|
||||
from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries
|
||||
|
||||
|
||||
Filters = frappe._dict
|
||||
Row = frappe._dict
|
||||
Data = List[Row]
|
||||
Columns = List[Dict[str, str]]
|
||||
DateTime = Union[datetime.date, datetime.datetime]
|
||||
FilteredEntries = List[Dict[str, Union[str, float, DateTime, None]]]
|
||||
ItemGroupsDict = Dict[Tuple[int, int], Dict[str, Union[str, int]]]
|
||||
SVDList = List[frappe._dict]
|
||||
|
||||
|
||||
def execute(filters: Filters) -> Tuple[Columns, Data]:
|
||||
update_filters_with_account(filters)
|
||||
validate_filters(filters)
|
||||
columns = get_columns()
|
||||
data = get_data(filters)
|
||||
return columns, data
|
||||
|
||||
|
||||
def update_filters_with_account(filters: Filters) -> None:
|
||||
account = frappe.get_value("Company", filters.get("company"), "default_expense_account")
|
||||
filters.update(dict(account=account))
|
||||
|
||||
|
||||
def validate_filters(filters: Filters) -> None:
|
||||
if filters.from_date > filters.to_date:
|
||||
frappe.throw(_("From Date must be before To Date"))
|
||||
|
||||
|
||||
def get_columns() -> Columns:
|
||||
return [
|
||||
{
|
||||
'label': 'Item Group',
|
||||
'fieldname': 'item_group',
|
||||
'fieldtype': 'Data',
|
||||
'width': '200'
|
||||
},
|
||||
{
|
||||
'label': 'COGS Debit',
|
||||
'fieldname': 'cogs_debit',
|
||||
'fieldtype': 'Currency',
|
||||
'width': '200'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def get_data(filters: Filters) -> Data:
|
||||
filtered_entries = get_filtered_entries(filters)
|
||||
svd_list = get_stock_value_difference_list(filtered_entries)
|
||||
leveled_dict = get_leveled_dict()
|
||||
|
||||
assign_self_values(leveled_dict, svd_list)
|
||||
assign_agg_values(leveled_dict)
|
||||
|
||||
data = []
|
||||
for item in leveled_dict.items():
|
||||
i = item[1]
|
||||
if i['agg_value'] == 0:
|
||||
continue
|
||||
data.append(get_row(i['name'], i['agg_value'], i['is_group'], i['level']))
|
||||
if i['self_value'] < i['agg_value'] and i['self_value'] > 0:
|
||||
data.append(get_row(i['name'], i['self_value'], 0, i['level'] + 1))
|
||||
return data
|
||||
|
||||
|
||||
def get_filtered_entries(filters: Filters) -> FilteredEntries:
|
||||
gl_entries = get_gl_entries(filters, [])
|
||||
filtered_entries = []
|
||||
for entry in gl_entries:
|
||||
posting_date = entry.get('posting_date')
|
||||
from_date = filters.get('from_date')
|
||||
if date_diff(from_date, posting_date) > 0:
|
||||
continue
|
||||
filtered_entries.append(entry)
|
||||
return filtered_entries
|
||||
|
||||
|
||||
def get_stock_value_difference_list(filtered_entries: FilteredEntries) -> SVDList:
|
||||
voucher_nos = [fe.get('voucher_no') for fe in filtered_entries]
|
||||
svd_list = frappe.get_list(
|
||||
'Stock Ledger Entry', fields=['item_code','stock_value_difference'],
|
||||
filters=[('voucher_no', 'in', voucher_nos)]
|
||||
)
|
||||
assign_item_groups_to_svd_list(svd_list)
|
||||
return svd_list
|
||||
|
||||
|
||||
def get_leveled_dict() -> OrderedDict:
|
||||
item_groups_dict = get_item_groups_dict()
|
||||
lr_list = sorted(item_groups_dict, key=lambda x : int(x[0]))
|
||||
leveled_dict = OrderedDict()
|
||||
current_level = 0
|
||||
nesting_r = []
|
||||
for l, r in lr_list:
|
||||
while current_level > 0 and nesting_r[-1] < l:
|
||||
nesting_r.pop()
|
||||
current_level -= 1
|
||||
|
||||
leveled_dict[(l,r)] = {
|
||||
'level' : current_level,
|
||||
'name' : item_groups_dict[(l,r)]['name'],
|
||||
'is_group' : item_groups_dict[(l,r)]['is_group']
|
||||
}
|
||||
|
||||
if int(r) - int(l) > 1:
|
||||
current_level += 1
|
||||
nesting_r.append(r)
|
||||
|
||||
update_leveled_dict(leveled_dict)
|
||||
return leveled_dict
|
||||
|
||||
|
||||
def assign_self_values(leveled_dict: OrderedDict, svd_list: SVDList) -> None:
|
||||
key_dict = {v['name']:k for k, v in leveled_dict.items()}
|
||||
for item in svd_list:
|
||||
key = key_dict[item.get("item_group")]
|
||||
leveled_dict[key]['self_value'] += -item.get("stock_value_difference")
|
||||
|
||||
|
||||
def assign_agg_values(leveled_dict: OrderedDict) -> None:
|
||||
keys = list(leveled_dict.keys())[::-1]
|
||||
prev_level = leveled_dict[keys[-1]]['level']
|
||||
accu = [0]
|
||||
for k in keys[:-1]:
|
||||
curr_level = leveled_dict[k]['level']
|
||||
if curr_level == prev_level:
|
||||
accu[-1] += leveled_dict[k]['self_value']
|
||||
leveled_dict[k]['agg_value'] = leveled_dict[k]['self_value']
|
||||
|
||||
elif curr_level > prev_level:
|
||||
accu.append(leveled_dict[k]['self_value'])
|
||||
leveled_dict[k]['agg_value'] = accu[-1]
|
||||
|
||||
elif curr_level < prev_level:
|
||||
accu[-1] += leveled_dict[k]['self_value']
|
||||
leveled_dict[k]['agg_value'] = accu[-1]
|
||||
|
||||
prev_level = curr_level
|
||||
|
||||
# root node
|
||||
rk = keys[-1]
|
||||
leveled_dict[rk]['agg_value'] = sum(accu) + leveled_dict[rk]['self_value']
|
||||
|
||||
|
||||
def get_row(name:str, value:float, is_bold:int, indent:int) -> Row:
|
||||
item_group = name
|
||||
if is_bold:
|
||||
item_group = frappe.bold(item_group)
|
||||
return frappe._dict(item_group=item_group, cogs_debit=value, indent=indent)
|
||||
|
||||
|
||||
def assign_item_groups_to_svd_list(svd_list: SVDList) -> None:
|
||||
ig_map = get_item_groups_map(svd_list)
|
||||
for item in svd_list:
|
||||
item.item_group = ig_map[item.get("item_code")]
|
||||
|
||||
|
||||
def get_item_groups_map(svd_list: SVDList) -> Dict[str, str]:
|
||||
item_codes = set(i['item_code'] for i in svd_list)
|
||||
ig_list = frappe.get_list(
|
||||
'Item', fields=['item_code','item_group'],
|
||||
filters=[('item_code', 'in', item_codes)]
|
||||
)
|
||||
return {i['item_code']:i['item_group'] for i in ig_list}
|
||||
|
||||
|
||||
def get_item_groups_dict() -> ItemGroupsDict:
|
||||
item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt"))
|
||||
return {(i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']}
|
||||
for i in item_groups_list}
|
||||
|
||||
|
||||
def update_leveled_dict(leveled_dict: OrderedDict) -> None:
|
||||
for k in leveled_dict:
|
||||
leveled_dict[k].update({'self_value':0, 'agg_value':0})
|
@ -69,7 +69,7 @@ def get_consumed_details(filters):
|
||||
i.stock_uom, sle.actual_qty, sle.stock_value_difference,
|
||||
sle.voucher_no, sle.voucher_type
|
||||
from `tabStock Ledger Entry` sle, `tabItem` i
|
||||
where sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1):
|
||||
where sle.is_cancelled = 0 and sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1):
|
||||
consumed_details.setdefault(d.item_code, []).append(d)
|
||||
|
||||
return consumed_details
|
||||
|
Loading…
x
Reference in New Issue
Block a user