mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-18 12:39:18 +00:00
Compare commits
61 Commits
v14.92.4
...
mergify/bp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed2ff8b2fe | ||
|
|
c5ed69c2c2 | ||
|
|
21311dab86 | ||
|
|
9dc88a1283 | ||
|
|
a6eadb18c9 | ||
|
|
f6d3a811ed | ||
|
|
e1674d2017 | ||
|
|
65d7c6b882 | ||
|
|
6f1616bc95 | ||
|
|
07ac93d06a | ||
|
|
824beaa893 | ||
|
|
b4dc1be5ed | ||
|
|
cf49c32b94 | ||
|
|
d54f45469a | ||
|
|
ad5181c52f | ||
|
|
6e03d45034 | ||
|
|
7a18f86dd5 | ||
|
|
39e1bddce6 | ||
|
|
d09de53d1d | ||
|
|
90d88b1c26 | ||
|
|
22eec09175 | ||
|
|
dbfd4ea4d3 | ||
|
|
8d28dcb18b | ||
|
|
3f186b24f3 | ||
|
|
4b0ddbf73a | ||
|
|
cddf1e1ee5 | ||
|
|
22e5aba02b | ||
|
|
21d13859a0 | ||
|
|
6267b9aea5 | ||
|
|
6499a1dae0 | ||
|
|
478992812f | ||
|
|
634dd11d72 | ||
|
|
0e6586734a | ||
|
|
5e129374b9 | ||
|
|
55d2a54785 | ||
|
|
3a6d056fd3 | ||
|
|
d713f39fce | ||
|
|
1dee10077c | ||
|
|
c86cf5b9c6 | ||
|
|
5c9e0bba2f | ||
|
|
793688710a | ||
|
|
98414385e5 | ||
|
|
33f7b742ec | ||
|
|
e9c1d0ad52 | ||
|
|
67c0d08569 | ||
|
|
6f6cbb717e | ||
|
|
387657fa92 | ||
|
|
ef03d90a08 | ||
|
|
a15b010868 | ||
|
|
9d00afe1b1 | ||
|
|
6ea7393f7d | ||
|
|
8de0d60581 | ||
|
|
a67092894f | ||
|
|
77b9044d8d | ||
|
|
ba9f571886 | ||
|
|
a299392128 | ||
|
|
bc58fd1fa4 | ||
|
|
10f1c9c3ac | ||
|
|
a3b120b3b2 | ||
|
|
5e05873226 | ||
|
|
db828c08b6 |
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.92.4"
|
||||
__version__ = "14.67.1"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -19,7 +19,7 @@ frappe.ui.form.on("Currency Exchange Settings", {
|
||||
to: "{to_currency}",
|
||||
};
|
||||
add_param(frm, r.message, params, result);
|
||||
} else if (frm.doc.service_provider == "frankfurter.app") {
|
||||
} else if (["frankfurter.app", "frankfurter.dev"].includes(frm.doc.service_provider)) {
|
||||
let result = ["rates", "{to_currency}"];
|
||||
let params = {
|
||||
base: "{from_currency}",
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
"fieldname": "service_provider",
|
||||
"fieldtype": "Select",
|
||||
"label": "Service Provider",
|
||||
"options": "frankfurter.app\nexchangerate.host\nCustom",
|
||||
"options": "frankfurter.dev\nexchangerate.host\nCustom",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -104,7 +104,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-18 08:32:26.895076",
|
||||
"modified": "2025-11-25 13:03:41.896424",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Currency Exchange Settings",
|
||||
@@ -141,8 +141,9 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class CurrencyExchangeSettings(Document):
|
||||
self.append("req_params", {"key": "date", "value": "{transaction_date}"})
|
||||
self.append("req_params", {"key": "from", "value": "{from_currency}"})
|
||||
self.append("req_params", {"key": "to", "value": "{to_currency}"})
|
||||
elif self.service_provider == "frankfurter.app":
|
||||
elif self.service_provider in ("frankfurter.dev", "frankfurter.app"):
|
||||
self.set("result_key", [])
|
||||
self.set("req_params", [])
|
||||
|
||||
@@ -80,11 +80,13 @@ class CurrencyExchangeSettings(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_api_endpoint(service_provider: str | None = None, use_http: bool = False):
|
||||
if service_provider and service_provider in ["exchangerate.host", "frankfurter.app"]:
|
||||
if service_provider and service_provider in ["exchangerate.host", "frankfurter.dev", "frankfurter.app"]:
|
||||
if service_provider == "exchangerate.host":
|
||||
api = "api.exchangerate.host/convert"
|
||||
elif service_provider == "frankfurter.app":
|
||||
api = "api.frankfurter.app/{transaction_date}"
|
||||
elif service_provider == "frankfurter.dev":
|
||||
api = "api.frankfurter.dev/v1/{transaction_date}"
|
||||
|
||||
protocol = "https://"
|
||||
if use_http:
|
||||
|
||||
@@ -11,9 +11,9 @@ frappe.ui.form.on("Period Closing Voucher", {
|
||||
return {
|
||||
filters: [
|
||||
["Account", "company", "=", frm.doc.company],
|
||||
["Account", "is_group", "=", "0"],
|
||||
["Account", "is_group", "=", 0],
|
||||
["Account", "freeze_account", "=", "No"],
|
||||
["Account", "root_type", "in", "Liability, Equity"],
|
||||
["Account", "root_type", "in", ["Liability", "Equity"]],
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2828,6 +2828,60 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
|
||||
self.assertEqual(sales_invoice.items[0].item_tax_rate, item_tax_map)
|
||||
|
||||
def test_item_tax_template_change_with_grand_total_discount(self):
|
||||
"""
|
||||
Test that when item tax template changes due to discount on Grand Total,
|
||||
the tax calculations are consistent.
|
||||
"""
|
||||
item = create_item("Test Item With Multiple Tax Templates")
|
||||
|
||||
item.set("taxes", [])
|
||||
item.append(
|
||||
"taxes",
|
||||
{
|
||||
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
|
||||
"minimum_net_rate": 0,
|
||||
"maximum_net_rate": 500,
|
||||
},
|
||||
)
|
||||
|
||||
item.append(
|
||||
"taxes",
|
||||
{
|
||||
"item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
|
||||
"minimum_net_rate": 501,
|
||||
"maximum_net_rate": 1000,
|
||||
},
|
||||
)
|
||||
|
||||
item.save()
|
||||
|
||||
si = create_sales_invoice(item=item.name, rate=700, do_not_save=True)
|
||||
si.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account Excise Duty - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Excise Duty",
|
||||
"rate": 0,
|
||||
},
|
||||
)
|
||||
si.insert()
|
||||
|
||||
self.assertEqual(si.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC")
|
||||
|
||||
si.apply_discount_on = "Grand Total"
|
||||
si.discount_amount = 300
|
||||
si.save()
|
||||
|
||||
# Verify template changed to 10%
|
||||
self.assertEqual(si.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
|
||||
self.assertEqual(si.taxes[0].tax_amount, 70) # 10% of 700
|
||||
self.assertEqual(si.grand_total, 470) # 700 + 70 - 300
|
||||
|
||||
si.submit()
|
||||
|
||||
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
|
||||
def test_sales_invoice_with_discount_accounting_enabled(self):
|
||||
discount_account = create_account(
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Int",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
@@ -102,7 +102,7 @@
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -199,7 +199,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Int",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
@@ -221,7 +221,7 @@
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -324,7 +324,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-01-10 18:32:36.201124",
|
||||
"modified": "2025-12-10 08:06:40.611761",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Share Balance",
|
||||
@@ -339,4 +339,4 @@
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
<<<<<<< HEAD
|
||||
from erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly import (
|
||||
=======
|
||||
from erpnext.accounts.report.tax_withholding_details.tax_withholding_details import (
|
||||
_get_twc_additional_columns,
|
||||
>>>>>>> e22326065d (feat: enhance tax withholding details report with additional columns support (backport #54409) (#54432))
|
||||
get_result,
|
||||
get_tds_docs,
|
||||
)
|
||||
@@ -9,6 +14,10 @@ from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
return _execute(filters)
|
||||
|
||||
|
||||
def _execute(filters=None, additional_table_columns=None):
|
||||
if filters.get("party_type") == "Customer":
|
||||
party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
||||
else:
|
||||
@@ -18,7 +27,7 @@ def execute(filters=None):
|
||||
|
||||
validate_filters(filters)
|
||||
|
||||
columns = get_columns(filters)
|
||||
columns = get_columns(filters, additional_table_columns)
|
||||
(
|
||||
tds_docs,
|
||||
tds_accounts,
|
||||
@@ -27,10 +36,15 @@ def execute(filters=None):
|
||||
invoice_total_map,
|
||||
) = get_tds_docs(filters)
|
||||
|
||||
<<<<<<< HEAD
|
||||
res = get_result(
|
||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_total_map
|
||||
)
|
||||
final_result = group_by_party_and_category(res, filters)
|
||||
=======
|
||||
res = get_result(filters, tds_accounts, tax_category_map, net_total_map, additional_table_columns)
|
||||
final_result = group_by_party_and_category(res, filters, additional_table_columns)
|
||||
>>>>>>> e22326065d (feat: enhance tax withholding details report with additional columns support (backport #54409) (#54432))
|
||||
|
||||
return columns, final_result
|
||||
|
||||
@@ -48,32 +62,33 @@ def validate_filters(filters):
|
||||
filters["fiscal_year"] = from_year
|
||||
|
||||
|
||||
def group_by_party_and_category(data, filters):
|
||||
def group_by_party_and_category(data, filters, additional_table_columns=None):
|
||||
party_category_wise_map = {}
|
||||
twc_additional_columns = _get_twc_additional_columns(additional_table_columns)
|
||||
|
||||
for row in data:
|
||||
party_category_wise_map.setdefault(
|
||||
(row.get("party"), row.get("section_code")),
|
||||
{
|
||||
"pan": row.get("pan"),
|
||||
"tax_id": row.get("tax_id"),
|
||||
"party": row.get("party"),
|
||||
"party_name": row.get("party_name"),
|
||||
"section_code": row.get("section_code"),
|
||||
"entity_type": row.get("entity_type"),
|
||||
"rate": row.get("rate"),
|
||||
"total_amount": 0.0,
|
||||
"tax_amount": 0.0,
|
||||
},
|
||||
)
|
||||
key = (row.get("party"), row.get("tax_withholding_category"))
|
||||
default_row = {
|
||||
"pan": row.get("pan"),
|
||||
"tax_id": row.get("tax_id"),
|
||||
"party": row.get("party"),
|
||||
"party_name": row.get("party_name"),
|
||||
"tax_withholding_category": row.get("tax_withholding_category"),
|
||||
"party_entity_type": row.get("party_entity_type"),
|
||||
"rate": row.get("rate"),
|
||||
"total_amount": 0.0,
|
||||
"tax_amount": 0.0,
|
||||
}
|
||||
|
||||
party_category_wise_map.get((row.get("party"), row.get("section_code")))["total_amount"] += row.get(
|
||||
"total_amount", 0.0
|
||||
)
|
||||
if twc_additional_columns:
|
||||
for col in twc_additional_columns:
|
||||
default_row[col] = row.get(col)
|
||||
|
||||
party_category_wise_map.get((row.get("party"), row.get("section_code")))["tax_amount"] += row.get(
|
||||
"tax_amount", 0.0
|
||||
)
|
||||
party_category_wise_map.setdefault(key, default_row)
|
||||
|
||||
party_category_wise_map.get(key)["total_amount"] += row.get("total_amount", 0.0)
|
||||
|
||||
party_category_wise_map.get(key)["tax_amount"] += row.get("tax_amount", 0.0)
|
||||
|
||||
final_result = get_final_result(party_category_wise_map)
|
||||
|
||||
@@ -88,7 +103,7 @@ def get_final_result(party_category_wise_map):
|
||||
return out
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
def get_columns(filters, additional_table_columns=None):
|
||||
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
||||
columns = [
|
||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 90},
|
||||
@@ -111,16 +126,24 @@ def get_columns(filters):
|
||||
}
|
||||
)
|
||||
|
||||
if additional_table_columns:
|
||||
columns.extend(additional_table_columns)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("Section Code"),
|
||||
"label": _("Tax Withholding Category"),
|
||||
"options": "Tax Withholding Category",
|
||||
"fieldname": "section_code",
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"width": 180,
|
||||
},
|
||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 180},
|
||||
{
|
||||
"label": _(f"{filters.get('party_type', 'Party')} Type"),
|
||||
"fieldname": "party_entity_type",
|
||||
"fieldtype": "Data",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||
"fieldname": "rate",
|
||||
|
||||
@@ -8,6 +8,10 @@ from frappe.utils import getdate
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
return _execute(filters)
|
||||
|
||||
|
||||
def _execute(filters=None, additional_table_columns=None):
|
||||
if filters.get("party_type") == "Customer":
|
||||
party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
||||
else:
|
||||
@@ -24,11 +28,15 @@ def execute(filters=None):
|
||||
net_total_map,
|
||||
) = get_tds_docs(filters)
|
||||
|
||||
columns = get_columns(filters)
|
||||
columns = get_columns(filters, additional_table_columns)
|
||||
|
||||
<<<<<<< HEAD:erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
|
||||
res = get_result(
|
||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, net_total_map
|
||||
)
|
||||
=======
|
||||
res = get_result(filters, tds_accounts, tax_category_map, net_total_map, additional_table_columns)
|
||||
>>>>>>> e22326065d (feat: enhance tax withholding details report with additional columns support (backport #54409) (#54432)):erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
|
||||
return columns, res
|
||||
|
||||
|
||||
@@ -39,10 +47,21 @@ def validate_filters(filters):
|
||||
frappe.throw(_("From Date must be before To Date"))
|
||||
|
||||
|
||||
<<<<<<< HEAD:erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
|
||||
def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, net_total_map):
|
||||
party_map = get_party_pan_map(filters.get("party_type"))
|
||||
tax_rate_map = get_tax_rate_map(filters)
|
||||
gle_map = get_gle_map(tds_docs)
|
||||
=======
|
||||
def get_result(filters, tds_accounts, tax_category_map, net_total_map, additional_table_columns=None):
|
||||
party_names = {v.party for v in net_total_map.values() if v.party}
|
||||
party_map = get_party_pan_map(filters.get("party_type"), party_names)
|
||||
tax_rate_map = get_tax_rate_map(filters)
|
||||
gle_map = get_gle_map(net_total_map)
|
||||
precision = get_currency_precision()
|
||||
twc = get_tax_withholding_category_details(additional_table_columns)
|
||||
twc_additional_columns = _get_twc_additional_columns(additional_table_columns)
|
||||
>>>>>>> e22326065d (feat: enhance tax withholding details report with additional columns support (backport #54409) (#54432)):erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
|
||||
|
||||
out = []
|
||||
entries = {}
|
||||
@@ -106,8 +125,8 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
|
||||
|
||||
row.update(
|
||||
{
|
||||
"section_code": tax_withholding_category or "",
|
||||
"entity_type": party_map.get(party, {}).get(party_type),
|
||||
"tax_withholding_category": tax_withholding_category or "",
|
||||
"party_entity_type": party_map.get(party, {}).get(party_type),
|
||||
"rate": rate,
|
||||
"total_amount": total_amount,
|
||||
"grand_total": grand_total,
|
||||
@@ -121,18 +140,56 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
|
||||
}
|
||||
)
|
||||
|
||||
if tax_withholding_category:
|
||||
if twc_details := twc.get(tax_withholding_category, {}):
|
||||
for col in twc_additional_columns or []:
|
||||
row[col] = twc_details.get(col)
|
||||
|
||||
key = entry.voucher_no
|
||||
if key in entries:
|
||||
entries[key]["tax_amount"] += tax_amount
|
||||
else:
|
||||
entries[key] = row
|
||||
out = list(entries.values())
|
||||
<<<<<<< HEAD:erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
|
||||
out.sort(key=lambda x: (x["section_code"], x["transaction_date"]))
|
||||
=======
|
||||
out.sort(key=lambda x: (x["tax_withholding_category"], x["transaction_date"], x["ref_no"]))
|
||||
>>>>>>> e22326065d (feat: enhance tax withholding details report with additional columns support (backport #54409) (#54432)):erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
|
||||
|
||||
return out
|
||||
|
||||
|
||||
<<<<<<< HEAD:erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
|
||||
def get_party_pan_map(party_type):
|
||||
=======
|
||||
def get_tax_withholding_category_details(additional_table_columns=None):
|
||||
if not additional_table_columns:
|
||||
return {}
|
||||
|
||||
category_fields = _get_twc_additional_columns(additional_table_columns)
|
||||
|
||||
if not category_fields:
|
||||
return {}
|
||||
|
||||
rows = frappe.get_all("Tax Withholding Category", fields=["name", *category_fields])
|
||||
|
||||
return {row["name"]: row for row in rows}
|
||||
|
||||
|
||||
def _get_twc_additional_columns(additional_table_columns):
|
||||
if not additional_table_columns:
|
||||
return []
|
||||
|
||||
return [
|
||||
col.get("fieldname")
|
||||
for col in additional_table_columns
|
||||
if col.get("_doctype") == "Tax Withholding Category" and col.get("fieldname")
|
||||
]
|
||||
|
||||
|
||||
def get_party_pan_map(party_type, party_names):
|
||||
>>>>>>> e22326065d (feat: enhance tax withholding details report with additional columns support (backport #54409) (#54432)):erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
|
||||
party_map = frappe._dict()
|
||||
|
||||
fields = ["name", "tax_withholding_category"]
|
||||
@@ -173,19 +230,22 @@ def get_gle_map(documents):
|
||||
return gle_map
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
def get_columns(filters, additional_table_columns=None):
|
||||
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
||||
columns = [
|
||||
{
|
||||
"label": _("Section Code"),
|
||||
"label": _("Tax Withholding Category"),
|
||||
"options": "Tax Withholding Category",
|
||||
"fieldname": "section_code",
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"width": 90,
|
||||
"width": 180,
|
||||
},
|
||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
|
||||
]
|
||||
|
||||
if additional_table_columns:
|
||||
columns.extend(additional_table_columns)
|
||||
|
||||
if filters.naming_series == "Naming Series":
|
||||
columns.append(
|
||||
{
|
||||
@@ -208,7 +268,12 @@ def get_columns(filters):
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
|
||||
{
|
||||
"label": _(f"{filters.get('party_type', 'Party')} Type"),
|
||||
"fieldname": "party_entity_type",
|
||||
"fieldtype": "Data",
|
||||
"width": 100,
|
||||
},
|
||||
]
|
||||
)
|
||||
if filters.party_type == "Supplier":
|
||||
|
||||
@@ -11,7 +11,11 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
||||
from erpnext.accounts.doctype.tax_withholding_category.test_tax_withholding_category import (
|
||||
create_tax_withholding_category,
|
||||
)
|
||||
<<<<<<< HEAD:erpnext/accounts/report/tds_payable_monthly/test_tds_payable_monthly.py
|
||||
from erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly import execute
|
||||
=======
|
||||
from erpnext.accounts.report.tax_withholding_details.tax_withholding_details import _execute, execute
|
||||
>>>>>>> e22326065d (feat: enhance tax withholding details report with additional columns support (backport #54409) (#54432)):erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
@@ -112,13 +116,49 @@ class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
|
||||
]
|
||||
self.check_expected_values(result, expected_values)
|
||||
|
||||
def test_additional_tax_withholding_category_column(self):
|
||||
tds_category = "TDS - Additional Column"
|
||||
create_tax_category(tds_category, rate=10, account="TDS - _TC", cumulative_threshold=1)
|
||||
inv = make_purchase_invoice(rate=1000, do_not_submit=True)
|
||||
inv.apply_tds = 1
|
||||
inv.tax_withholding_category = tds_category
|
||||
inv.submit()
|
||||
|
||||
field_name = "category_name"
|
||||
expected_value = "Additional Column Display Name"
|
||||
frappe.db.set_value("Tax Withholding Category", tds_category, field_name, expected_value)
|
||||
|
||||
additional_table_columns = [
|
||||
{
|
||||
"label": "Category Name",
|
||||
"fieldname": field_name,
|
||||
"fieldtype": "Data",
|
||||
"width": 140,
|
||||
"_doctype": "Tax Withholding Category",
|
||||
}
|
||||
]
|
||||
|
||||
filters = frappe._dict(
|
||||
company="_Test Company",
|
||||
party_type="Supplier",
|
||||
from_date=today(),
|
||||
to_date=today(),
|
||||
)
|
||||
|
||||
columns, data = _execute(filters, additional_table_columns)
|
||||
|
||||
self.assertTrue(any(col.get("fieldname") == field_name for col in columns))
|
||||
invoice_row = next((row for row in data if row.get("ref_no") == inv.name), None)
|
||||
self.assertIsNotNone(invoice_row)
|
||||
self.assertEqual(invoice_row.get(field_name), expected_value)
|
||||
|
||||
def check_expected_values(self, result, expected_values):
|
||||
for i in range(len(result)):
|
||||
voucher = frappe._dict(result[i])
|
||||
voucher_expected_values = expected_values[i]
|
||||
voucher_actual_values = (
|
||||
voucher.ref_no,
|
||||
voucher.section_code,
|
||||
voucher.tax_withholding_category,
|
||||
voucher.rate,
|
||||
voucher.base_total,
|
||||
voucher.tax_amount,
|
||||
|
||||
@@ -1532,11 +1532,7 @@ def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx, number_of_
|
||||
# if the Depreciation Schedule is being prepared for the first time
|
||||
else:
|
||||
if row.daily_prorata_based:
|
||||
amount = (
|
||||
flt(asset.gross_purchase_amount)
|
||||
- flt(asset.opening_accumulated_depreciation)
|
||||
- flt(row.expected_value_after_useful_life)
|
||||
)
|
||||
amount = flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
|
||||
total_days = (
|
||||
date_diff(
|
||||
get_last_day(
|
||||
@@ -1548,7 +1544,11 @@ def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx, number_of_
|
||||
),
|
||||
add_days(
|
||||
get_last_day(
|
||||
add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
(row.frequency_of_depreciation * (asset.number_of_depreciations_booked + 1))
|
||||
* -1,
|
||||
),
|
||||
),
|
||||
1,
|
||||
),
|
||||
@@ -1571,11 +1571,9 @@ def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx, number_of_
|
||||
|
||||
return daily_depr_amount * (date_diff(to_date, from_date) + 1)
|
||||
else:
|
||||
return (
|
||||
flt(asset.gross_purchase_amount)
|
||||
- flt(asset.opening_accumulated_depreciation)
|
||||
- flt(row.expected_value_after_useful_life)
|
||||
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
|
||||
return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt(
|
||||
row.total_number_of_depreciations
|
||||
)
|
||||
|
||||
|
||||
def get_shift_depr_amount(asset, row, schedule_idx):
|
||||
|
||||
@@ -660,7 +660,7 @@ class TestDepreciationMethods(AssetSetup):
|
||||
available_for_use_date="2030-06-06",
|
||||
is_existing_asset=1,
|
||||
number_of_depreciations_booked=2,
|
||||
opening_accumulated_depreciation=47095.89,
|
||||
opening_accumulated_depreciation=47178.08,
|
||||
expected_value_after_useful_life=10000,
|
||||
depreciation_start_date="2032-12-31",
|
||||
total_number_of_depreciations=3,
|
||||
@@ -668,7 +668,7 @@ class TestDepreciationMethods(AssetSetup):
|
||||
)
|
||||
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
expected_schedules = [["2032-12-31", 42904.11, 90000.0]]
|
||||
expected_schedules = [["2032-12-31", 30000.0, 77178.08], ["2033-06-06", 12821.92, 90000.0]]
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
||||
for d in asset.get("schedules")
|
||||
|
||||
11
erpnext/change_log/v14/v14_92_13.md
Normal file
11
erpnext/change_log/v14/v14_92_13.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# End of Life reached
|
||||
|
||||
With the release of ERPNext version 16, version 14 has reached its end of life.
|
||||
|
||||
This means that version 14 of ERPNext (which is running on this site) will no longer receive critical bug fixes and is no longer covered under Frappe Support.
|
||||
|
||||
We highly recommend that you update to version 16 to get the latest bug fixes, features and other improvements.
|
||||
|
||||
[Click here to know more about version 16](https://frappe.io/erpnext/version-16)
|
||||
|
||||
If you're on [Frappe Cloud](https://frappe.io/cloud), [click here to learn how to update to v16](https://docs.frappe.io/cloud/sites/version-upgrade)
|
||||
@@ -728,7 +728,16 @@ class SellingController(StockController):
|
||||
|
||||
|
||||
def set_default_income_account_for_item(obj):
|
||||
for d in obj.get("items"):
|
||||
if d.item_code:
|
||||
if getattr(d, "income_account", None):
|
||||
set_item_default(d.item_code, obj.company, "income_account", d.income_account)
|
||||
"""Set income account as default for items in the transaction.
|
||||
|
||||
Updates the item default income account for each item in the transaction
|
||||
if it differs from the company's default income account.
|
||||
|
||||
Args:
|
||||
obj: Transaction document containing items table with income_account field
|
||||
"""
|
||||
company_default = frappe.get_cached_value("Company", obj.company, "default_income_account")
|
||||
for d in obj.get("items", default=[]):
|
||||
income_account = getattr(d, "income_account", None)
|
||||
if d.item_code and income_account and income_account != company_default:
|
||||
set_item_default(d.item_code, obj.company, "income_account", income_account)
|
||||
|
||||
@@ -42,17 +42,23 @@ class calculate_taxes_and_totals:
|
||||
items = list(filter(lambda item: not item.get("is_alternative"), self.doc.get("items")))
|
||||
return items
|
||||
|
||||
def calculate(self):
|
||||
def calculate(self, ignore_tax_template_validation=False):
|
||||
if not len(self._items):
|
||||
return
|
||||
|
||||
self.discount_amount_applied = False
|
||||
self.need_recomputation = False
|
||||
self.ignore_tax_template_validation = ignore_tax_template_validation
|
||||
|
||||
self._calculate()
|
||||
|
||||
if self.doc.meta.get_field("discount_amount"):
|
||||
self.set_discount_amount()
|
||||
self.apply_discount_amount()
|
||||
|
||||
if not ignore_tax_template_validation and self.need_recomputation:
|
||||
return self.calculate(ignore_tax_template_validation=True)
|
||||
|
||||
# Update grand total as per cash and non trade discount
|
||||
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
||||
self.doc.grand_total -= self.doc.discount_amount
|
||||
@@ -96,6 +102,9 @@ class calculate_taxes_and_totals:
|
||||
self.doc.base_tax_withholding_net_total = sum_base_net_amount
|
||||
|
||||
def validate_item_tax_template(self):
|
||||
if self.ignore_tax_template_validation:
|
||||
return
|
||||
|
||||
if self.doc.get("is_return") and self.doc.get("return_against"):
|
||||
return
|
||||
|
||||
@@ -136,6 +145,10 @@ class calculate_taxes_and_totals:
|
||||
)
|
||||
)
|
||||
|
||||
# For correct tax_amount calculation re-computation is required
|
||||
if self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total":
|
||||
self.need_recomputation = True
|
||||
|
||||
def update_item_tax_map(self):
|
||||
for item in self.doc.items:
|
||||
item.item_tax_rate = get_item_tax_map(
|
||||
@@ -613,18 +626,17 @@ class calculate_taxes_and_totals:
|
||||
if self.doc.meta.get_field("rounded_total"):
|
||||
if self.doc.is_rounded_total_disabled():
|
||||
self.doc.rounded_total = 0
|
||||
self.doc.base_rounded_total = 0
|
||||
self.doc.rounding_adjustment = 0
|
||||
return
|
||||
|
||||
self.doc.rounded_total = round_based_on_smallest_currency_fraction(
|
||||
self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
|
||||
)
|
||||
else:
|
||||
self.doc.rounded_total = round_based_on_smallest_currency_fraction(
|
||||
self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
|
||||
)
|
||||
|
||||
# rounding adjustment should always be the difference vetween grand and rounded total
|
||||
self.doc.rounding_adjustment = flt(
|
||||
self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
|
||||
)
|
||||
# rounding adjustment should always be the difference between grand and rounded total
|
||||
self.doc.rounding_adjustment = flt(
|
||||
self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
|
||||
)
|
||||
|
||||
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
|
||||
|
||||
|
||||
37
erpnext/controllers/tests/test_taxes_and_totals.py
Normal file
37
erpnext/controllers/tests/test_taxes_and_totals.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
|
||||
|
||||
class TestTaxesAndTotals(FrappeTestCase):
|
||||
def test_disabling_rounded_total_resets_base_fields(self):
|
||||
"""Disabling rounded total should also clear base rounded values."""
|
||||
so = make_sales_order(do_not_save=True)
|
||||
so.items[0].qty = 1
|
||||
so.items[0].rate = 1000.25
|
||||
so.items[0].price_list_rate = 1000.25
|
||||
so.items[0].discount_percentage = 0
|
||||
so.items[0].discount_amount = 0
|
||||
so.set("taxes", [])
|
||||
|
||||
so.disable_rounded_total = 0
|
||||
calculate_taxes_and_totals(so)
|
||||
|
||||
self.assertEqual(so.grand_total, 1000.25)
|
||||
self.assertEqual(so.rounded_total, 1000.0)
|
||||
self.assertEqual(so.rounding_adjustment, -0.25)
|
||||
self.assertEqual(so.base_grand_total, 1000.25)
|
||||
self.assertEqual(so.base_rounded_total, 1000.0)
|
||||
self.assertEqual(so.base_rounding_adjustment, -0.25)
|
||||
|
||||
# User toggles disable_rounded_total after values are already set.
|
||||
so.disable_rounded_total = 1
|
||||
|
||||
calculate_taxes_and_totals(so)
|
||||
|
||||
self.assertEqual(so.rounded_total, 0)
|
||||
self.assertEqual(so.rounding_adjustment, 0)
|
||||
self.assertEqual(so.base_rounded_total, 0)
|
||||
self.assertEqual(so.base_rounding_adjustment, 0)
|
||||
@@ -443,7 +443,7 @@ class JobCard(Document):
|
||||
op_row.employee.append(time_log.employee)
|
||||
if time_log.time_in_mins:
|
||||
op_row.completed_time += time_log.time_in_mins
|
||||
op_row.completed_qty += time_log.completed_qty
|
||||
op_row.completed_qty += flt(time_log.completed_qty)
|
||||
|
||||
for row in self.sub_operations:
|
||||
operation_deatils = operation_wise_completed_time.get(row.sub_operation)
|
||||
|
||||
@@ -377,3 +377,4 @@ execute:frappe.db.set_single_value("Accounts Settings", "receivable_payable_fetc
|
||||
erpnext.patches.v14_0.set_update_price_list_based_on
|
||||
erpnext.patches.v14_0.rename_group_by_to_categorize_by_in_custom_reports
|
||||
erpnext.patches.v14_0.update_full_name_in_contract
|
||||
erpnext.patches.v16_0.update_currency_exchange_settings_for_frankfurter #2025-12-11
|
||||
|
||||
@@ -2,6 +2,15 @@ import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
try:
|
||||
from erpnext.patches.v16_0.update_currency_exchange_settings_for_frankfurter import execute
|
||||
|
||||
execute()
|
||||
except ImportError:
|
||||
update_frankfurter_app_parameter_and_result()
|
||||
|
||||
|
||||
def update_frankfurter_app_parameter_and_result():
|
||||
settings = frappe.get_doc("Currency Exchange Settings")
|
||||
if settings.service_provider != "frankfurter.app":
|
||||
return
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
settings_meta = frappe.get_meta("Currency Exchange Settings")
|
||||
settings = frappe.get_doc("Currency Exchange Settings")
|
||||
|
||||
if (
|
||||
"frankfurter.dev" not in settings_meta.get_options("service_provider").split("\n")
|
||||
or settings.service_provider != "frankfurter.app"
|
||||
):
|
||||
return
|
||||
|
||||
settings.service_provider = "frankfurter.dev"
|
||||
settings.set_parameters_and_result()
|
||||
settings.flags.ignore_validate = True
|
||||
settings.save()
|
||||
@@ -609,18 +609,21 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
disable_rounded_total = frappe.sys_defaults.disable_rounded_total;
|
||||
}
|
||||
|
||||
if (cint(disable_rounded_total)) {
|
||||
this.frm.doc.rounded_total = 0;
|
||||
this.frm.doc.base_rounded_total = 0;
|
||||
this.frm.doc.rounding_adjustment = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) {
|
||||
this.frm.doc.rounded_total = round_based_on_smallest_currency_fraction(this.frm.doc.grand_total,
|
||||
this.frm.doc.currency, precision("rounded_total"));
|
||||
this.frm.doc.rounding_adjustment = flt(this.frm.doc.rounded_total - this.frm.doc.grand_total,
|
||||
precision("rounding_adjustment"));
|
||||
if (frappe.meta.get_docfield(this.frm.doc.doctype, "rounded_total", this.frm.doc.name)) {
|
||||
if (cint(disable_rounded_total)) {
|
||||
this.frm.doc.rounded_total = 0;
|
||||
this.frm.doc.rounding_adjustment = 0;
|
||||
} else {
|
||||
this.frm.doc.rounded_total = round_based_on_smallest_currency_fraction(
|
||||
this.frm.doc.grand_total,
|
||||
this.frm.doc.currency,
|
||||
precision("rounded_total")
|
||||
);
|
||||
this.frm.doc.rounding_adjustment = flt(
|
||||
this.frm.doc.rounded_total - this.frm.doc.grand_total,
|
||||
precision("rounding_adjustment")
|
||||
);
|
||||
}
|
||||
|
||||
this.set_in_company_currency(this.frm.doc, ["rounding_adjustment", "rounded_total"]);
|
||||
}
|
||||
|
||||
@@ -438,6 +438,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
item.weight_per_unit = 0;
|
||||
item.weight_uom = '';
|
||||
item.uom = null // make UOM blank to update the existing UOM when item changes
|
||||
item.conversion_factor = 0;
|
||||
|
||||
if(['Sales Invoice'].includes(this.frm.doc.doctype)) {
|
||||
@@ -1088,9 +1089,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
plc_conversion_rate() {
|
||||
if(this.frm.doc.price_list_currency === this.get_company_currency()) {
|
||||
this.frm.set_value("plc_conversion_rate", 1.0);
|
||||
} else if(this.frm.doc.price_list_currency === this.frm.doc.currency
|
||||
&& this.frm.doc.plc_conversion_rate && cint(this.frm.doc.plc_conversion_rate) != 1 &&
|
||||
cint(this.frm.doc.plc_conversion_rate) != cint(this.frm.doc.conversion_rate)) {
|
||||
} else if (
|
||||
this.frm.doc.price_list_currency === this.frm.doc.currency &&
|
||||
this.frm.doc.plc_conversion_rate &&
|
||||
flt(this.frm.doc.plc_conversion_rate) != 1 &&
|
||||
flt(this.frm.doc.plc_conversion_rate) != flt(this.frm.doc.conversion_rate)
|
||||
) {
|
||||
this.frm.set_value("conversion_rate", this.frm.doc.plc_conversion_rate);
|
||||
}
|
||||
|
||||
|
||||
@@ -381,7 +381,7 @@ erpnext.SerialNoBatchSelector = class SerialNoBatchSelector {
|
||||
query: "erpnext.controllers.queries.get_batch_no",
|
||||
};
|
||||
},
|
||||
onchange: function () {
|
||||
change: function () {
|
||||
const batch_no = this.get_value();
|
||||
if (!batch_no) {
|
||||
this.grid_row.on_grid_fields_dict.available_qty.set_value(0);
|
||||
|
||||
@@ -11,7 +11,7 @@ from frappe.cache_manager import clear_defaults_cache
|
||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.desk.page.setup_wizard.setup_wizard import make_records
|
||||
from frappe.utils import cint, formatdate, get_link_to_form, get_timestamp, today
|
||||
from frappe.utils import add_months, cint, formatdate, get_first_day, get_link_to_form, get_timestamp, today
|
||||
from frappe.utils.nestedset import NestedSet, rebuild_tree
|
||||
|
||||
from erpnext.accounts.doctype.account.account import get_account_currency
|
||||
@@ -614,27 +614,29 @@ def install_country_fixtures(company, country):
|
||||
|
||||
|
||||
def update_company_current_month_sales(company):
|
||||
current_month_year = formatdate(today(), "MM-yyyy")
|
||||
from_date = get_first_day(today())
|
||||
to_date = get_first_day(add_months(from_date, 1))
|
||||
|
||||
results = frappe.db.sql(
|
||||
f"""
|
||||
"""
|
||||
SELECT
|
||||
SUM(base_grand_total) AS total,
|
||||
DATE_FORMAT(`posting_date`, '%m-%Y') AS month_year
|
||||
DATE_FORMAT(posting_date, '%%m-%%Y') AS month_year
|
||||
FROM
|
||||
`tabSales Invoice`
|
||||
WHERE
|
||||
DATE_FORMAT(`posting_date`, '%m-%Y') = '{current_month_year}'
|
||||
posting_date >= %s
|
||||
AND posting_date < %s
|
||||
AND docstatus = 1
|
||||
AND company = {frappe.db.escape(company)}
|
||||
AND company = %s
|
||||
GROUP BY
|
||||
month_year
|
||||
""",
|
||||
""",
|
||||
(from_date, to_date, company),
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
monthly_total = results[0]["total"] if len(results) > 0 else 0
|
||||
|
||||
frappe.db.set_value("Company", company, "total_monthly_sales", monthly_total)
|
||||
|
||||
|
||||
|
||||
@@ -68,9 +68,9 @@ def patched_requests_get(*args, **kwargs):
|
||||
if kwargs["params"].get("date") and kwargs["params"].get("from") and kwargs["params"].get("to"):
|
||||
if test_exchange_values.get(kwargs["params"]["date"]):
|
||||
return PatchResponse({"result": test_exchange_values[kwargs["params"]["date"]]}, 200)
|
||||
elif args[0].startswith("https://api.frankfurter.app") and kwargs.get("params"):
|
||||
elif args[0].startswith("https://api.frankfurter.dev") and kwargs.get("params"):
|
||||
if kwargs["params"].get("base") and kwargs["params"].get("symbols"):
|
||||
date = args[0].replace("https://api.frankfurter.app/", "")
|
||||
date = args[0].replace("https://api.frankfurter.dev/v1/", "")
|
||||
if test_exchange_values.get(date):
|
||||
return PatchResponse(
|
||||
{"rates": {kwargs["params"].get("symbols"): test_exchange_values.get(date)}}, 200
|
||||
@@ -149,7 +149,7 @@ class TestCurrencyExchange(unittest.TestCase):
|
||||
self.assertEqual(flt(exchange_rate, 3), 65.1)
|
||||
|
||||
settings = frappe.get_single("Currency Exchange Settings")
|
||||
settings.service_provider = "frankfurter.app"
|
||||
settings.service_provider = "frankfurter.dev"
|
||||
settings.save()
|
||||
|
||||
def test_exchange_rate_strict(self, mock_get):
|
||||
|
||||
@@ -309,8 +309,9 @@ class TransactionDeletionRecord(Document):
|
||||
self.db_set("error_log", None)
|
||||
|
||||
def get_doctypes_to_be_ignored_list(self):
|
||||
singles = frappe.get_all("DocType", filters={"issingle": 1}, pluck="name")
|
||||
doctypes_to_be_ignored_list = singles
|
||||
doctypes_to_be_ignored_list = frappe.get_all(
|
||||
"DocType", or_filters=[["issingle", "=", 1], ["is_virtual", "=", 1]], pluck="name"
|
||||
)
|
||||
for doctype in self.doctypes_to_be_ignored:
|
||||
doctypes_to_be_ignored_list.append(doctype.doctype_name)
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ def setup_currency_exchange():
|
||||
ces.set("result_key", [])
|
||||
ces.set("req_params", [])
|
||||
|
||||
ces.api_endpoint = "https://api.frankfurter.app/{transaction_date}"
|
||||
ces.api_endpoint = "https://api.frankfurter.dev/v1/{transaction_date}"
|
||||
ces.append("result_key", {"key": "rates"})
|
||||
ces.append("result_key", {"key": "{to_currency}"})
|
||||
ces.append("req_params", {"key": "base", "value": "{from_currency}"})
|
||||
|
||||
@@ -1507,6 +1507,23 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
|
||||
self.assertEqual(stock_value_difference, 100.0 * 5)
|
||||
|
||||
def test_negative_stock_with_higher_precision(self):
|
||||
original_flt_precision = frappe.db.get_default("float_precision")
|
||||
frappe.db.set_single_value("System Settings", "float_precision", 7)
|
||||
|
||||
item_code = make_item(
|
||||
"Test Negative Stock High Precision Item", properties={"is_stock_item": 1, "valuation_rate": 1}
|
||||
).name
|
||||
dn = create_delivery_note(
|
||||
item_code=item_code,
|
||||
qty=0.0000010,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
self.assertRaises(frappe.ValidationError, dn.submit)
|
||||
|
||||
frappe.db.set_single_value("System Settings", "float_precision", original_flt_precision)
|
||||
|
||||
|
||||
def create_delivery_note(**args):
|
||||
dn = frappe.new_doc("Delivery Note")
|
||||
|
||||
@@ -686,18 +686,14 @@ def get_available_item_locations(
|
||||
locations = get_available_item_locations_for_batched_item(
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty,
|
||||
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||
)
|
||||
else:
|
||||
locations = get_available_item_locations_for_other_item(
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty,
|
||||
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||
)
|
||||
|
||||
@@ -790,9 +786,7 @@ def get_available_item_locations_for_serialized_item(
|
||||
def get_available_item_locations_for_batched_item(
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty=0,
|
||||
consider_rejected_warehouses=False,
|
||||
):
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
@@ -813,7 +807,6 @@ def get_available_item_locations_for_batched_item(
|
||||
.groupby(sle.warehouse, sle.batch_no, sle.item_code)
|
||||
.having(Sum(sle.actual_qty) > 0)
|
||||
.orderby(IfNull(batch.expiry_date, "2200-01-01"), batch.creation, sle.batch_no, sle.warehouse)
|
||||
.limit(ceil(required_qty + total_picked_qty))
|
||||
)
|
||||
|
||||
if from_warehouses:
|
||||
@@ -838,7 +831,6 @@ def get_available_item_locations_for_serial_and_batched_item(
|
||||
locations = get_available_item_locations_for_batched_item(
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||
)
|
||||
@@ -872,9 +864,7 @@ def get_available_item_locations_for_serial_and_batched_item(
|
||||
def get_available_item_locations_for_other_item(
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty=0,
|
||||
consider_rejected_warehouses=False,
|
||||
):
|
||||
bin = frappe.qb.DocType("Bin")
|
||||
@@ -883,7 +873,6 @@ def get_available_item_locations_for_other_item(
|
||||
.select(bin.warehouse, bin.actual_qty.as_("qty"))
|
||||
.where((bin.item_code == item_code) & (bin.actual_qty > 0))
|
||||
.orderby(bin.creation)
|
||||
.limit(ceil(required_qty + total_picked_qty))
|
||||
)
|
||||
|
||||
if from_warehouses:
|
||||
|
||||
@@ -193,7 +193,7 @@ class SerialNo(StockController):
|
||||
entries["last_sle"] = last_sle
|
||||
|
||||
if sle_dict.get("incoming", []):
|
||||
entries["purchase_sle"] = sle_dict["incoming"][-1]
|
||||
entries["purchase_sle"] = sle_dict["incoming"][0]
|
||||
|
||||
if last_sle.get("actual_qty") < 0 and sle_dict.get("outgoing", []):
|
||||
entries["delivery_sle"] = sle_dict["outgoing"][0]
|
||||
|
||||
@@ -41,9 +41,37 @@ def get_data(report_filters):
|
||||
gl_data = voucher_wise_gl_data.get(key) or {}
|
||||
d.account_value = gl_data.get("account_value", 0)
|
||||
d.difference_value = d.stock_value - d.account_value
|
||||
d.ledger_type = "Stock Ledger Entry"
|
||||
if abs(d.difference_value) > 0.1:
|
||||
data.append(d)
|
||||
|
||||
if key in voucher_wise_gl_data:
|
||||
del voucher_wise_gl_data[key]
|
||||
|
||||
if voucher_wise_gl_data:
|
||||
data += get_gl_ledgers_with_no_stock_ledger_entries(voucher_wise_gl_data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_gl_ledgers_with_no_stock_ledger_entries(voucher_wise_gl_data):
|
||||
data = []
|
||||
|
||||
for key in voucher_wise_gl_data:
|
||||
gl_data = voucher_wise_gl_data.get(key) or {}
|
||||
data.append(
|
||||
{
|
||||
"name": gl_data.get("name"),
|
||||
"ledger_type": "GL Entry",
|
||||
"voucher_type": gl_data.get("voucher_type"),
|
||||
"voucher_no": gl_data.get("voucher_no"),
|
||||
"posting_date": gl_data.get("posting_date"),
|
||||
"stock_value": 0,
|
||||
"account_value": gl_data.get("account_value", 0),
|
||||
"difference_value": gl_data.get("account_value", 0) * -1,
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -88,6 +116,7 @@ def get_gl_data(report_filters, filters):
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"sum(debit_in_account_currency) - sum(credit_in_account_currency) as account_value",
|
||||
"posting_date",
|
||||
],
|
||||
group_by="voucher_type, voucher_no",
|
||||
)
|
||||
@@ -105,10 +134,15 @@ def get_columns(filters):
|
||||
{
|
||||
"label": _("Stock Ledger ID"),
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"options": "Stock Ledger Entry",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "ledger_type",
|
||||
"width": "80",
|
||||
},
|
||||
{
|
||||
"label": _("Ledger Type"),
|
||||
"fieldname": "ledger_type",
|
||||
"fieldtype": "Data",
|
||||
},
|
||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date"},
|
||||
{"label": _("Posting Time"), "fieldname": "posting_time", "fieldtype": "Time"},
|
||||
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": "110"},
|
||||
|
||||
@@ -715,7 +715,11 @@ class update_entries_after:
|
||||
diff = self.wh_data.qty_after_transaction + flt(sle.actual_qty)
|
||||
diff = flt(diff, self.flt_precision) # respect system precision
|
||||
|
||||
if diff < 0 and abs(diff) > 0.0001:
|
||||
diff_threshold = 0.0001
|
||||
if self.flt_precision > 4:
|
||||
diff_threshold = 10 ** (-1 * self.flt_precision)
|
||||
|
||||
if diff < 0 and abs(diff) > diff_threshold:
|
||||
# negative stock!
|
||||
exc = sle.copy().update({"diff": diff})
|
||||
self.exceptions.setdefault(sle.warehouse, []).append(exc)
|
||||
@@ -987,7 +991,7 @@ class update_entries_after:
|
||||
# else it remains the same as that of previous entry
|
||||
self.wh_data.valuation_rate = new_stock_value / new_stock_qty
|
||||
|
||||
if not self.wh_data.valuation_rate and sle.voucher_detail_no:
|
||||
if self.wh_data.valuation_rate is None and sle.voucher_detail_no:
|
||||
allow_zero_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
|
||||
if not allow_zero_rate:
|
||||
self.wh_data.valuation_rate = self.get_fallback_rate(sle)
|
||||
|
||||
@@ -158,7 +158,7 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord
|
||||
customer = contact_doc.get_link_for("Customer")
|
||||
|
||||
ignore_permissions = False
|
||||
if is_website_user():
|
||||
if is_website_user() and user != "Guest":
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
|
||||
@@ -449,10 +449,16 @@ def get_documents_with_active_service_level_agreement():
|
||||
|
||||
|
||||
def set_documents_with_active_service_level_agreement():
|
||||
active = [
|
||||
sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])
|
||||
]
|
||||
frappe.cache().hset("service_level_agreement", "active", active)
|
||||
try:
|
||||
active = frozenset(
|
||||
sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])
|
||||
)
|
||||
frappe.cache().hset("service_level_agreement", "active", active)
|
||||
except (frappe.DoesNotExistError, frappe.db.TableMissingError):
|
||||
# This happens during install / uninstall when wildcard hook for SLA intercepts some doc action.
|
||||
# In both cases, the error can be safely ignored.
|
||||
active = frozenset()
|
||||
|
||||
return active
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<form action="/search_help" style="display: flex;">
|
||||
<input name='q' class='form-control' type='text'
|
||||
style='max-width: 400px; display: inline-block; margin-right: 10px;'
|
||||
value='{{ frappe.form_dict.q or ''}}'
|
||||
value='{{ (frappe.form_dict.q or '') | e }}'
|
||||
{% if not frappe.form_dict.q%}placeholder="{{ _("What do you need help with?") }}"{% endif %}>
|
||||
<input type='submit'
|
||||
class='btn btn-sm btn-light btn-search' value="{{ _("Search") }}">
|
||||
|
||||
Reference in New Issue
Block a user