mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 19:29:10 +00:00
Merge pull request #48461 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -6,7 +6,11 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import now_datetime, nowdate
|
from frappe.utils import now_datetime, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.budget.budget import BudgetError, get_actual_expense
|
from erpnext.accounts.doctype.budget.budget import (
|
||||||
|
BudgetError,
|
||||||
|
get_accumulated_monthly_budget,
|
||||||
|
get_actual_expense,
|
||||||
|
)
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
@@ -96,6 +100,10 @@ class TestBudget(unittest.TestCase):
|
|||||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||||
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
|
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
|
||||||
|
|
||||||
|
accumulated_limit = get_accumulated_monthly_budget(
|
||||||
|
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
|
||||||
|
)
|
||||||
|
|
||||||
mr = frappe.get_doc(
|
mr = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Material Request",
|
"doctype": "Material Request",
|
||||||
@@ -109,7 +117,7 @@ class TestBudget(unittest.TestCase):
|
|||||||
"uom": "_Test UOM",
|
"uom": "_Test UOM",
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
"schedule_date": nowdate(),
|
"schedule_date": nowdate(),
|
||||||
"rate": 100000,
|
"rate": accumulated_limit + 1,
|
||||||
"expense_account": "_Test Account Cost for Goods Sold - _TC",
|
"expense_account": "_Test Account Cost for Goods Sold - _TC",
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1126,9 +1126,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
elif self.is_return and self.update_stock and (self.is_internal_supplier or not self.return_against):
|
elif self.is_return and self.update_stock and (self.is_internal_supplier or not self.return_against):
|
||||||
net_rate = item.base_net_amount
|
net_rate = item.base_net_amount
|
||||||
if item.sales_incoming_rate: # for internal transfer
|
|
||||||
net_rate = item.qty * item.sales_incoming_rate
|
|
||||||
|
|
||||||
stock_amount = net_rate + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
|
stock_amount = net_rate + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
|
||||||
|
|
||||||
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
|
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
|
|||||||
)
|
)
|
||||||
out.append(row)
|
out.append(row)
|
||||||
|
|
||||||
out.sort(key=lambda x: x["section_code"])
|
out.sort(key=lambda x: (x["section_code"], x["transaction_date"]))
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|||||||
@@ -67,11 +67,12 @@ class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
|
|||||||
mid_year = add_to_date(fiscal_year[1], months=6)
|
mid_year = add_to_date(fiscal_year[1], months=6)
|
||||||
tds_doc = frappe.get_doc("Tax Withholding Category", "TDS - 3")
|
tds_doc = frappe.get_doc("Tax Withholding Category", "TDS - 3")
|
||||||
tds_doc.rates[0].to_date = mid_year
|
tds_doc.rates[0].to_date = mid_year
|
||||||
|
from_date = add_to_date(mid_year, days=1)
|
||||||
tds_doc.append(
|
tds_doc.append(
|
||||||
"rates",
|
"rates",
|
||||||
{
|
{
|
||||||
"tax_withholding_rate": 20,
|
"tax_withholding_rate": 20,
|
||||||
"from_date": add_to_date(mid_year, days=1),
|
"from_date": from_date,
|
||||||
"to_date": fiscal_year[2],
|
"to_date": fiscal_year[2],
|
||||||
"single_threshold": 1,
|
"single_threshold": 1,
|
||||||
"cumulative_threshold": 1,
|
"cumulative_threshold": 1,
|
||||||
@@ -80,18 +81,19 @@ class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
|
|||||||
|
|
||||||
tds_doc.save()
|
tds_doc.save()
|
||||||
|
|
||||||
inv_1 = make_purchase_invoice(rate=1000, do_not_submit=True)
|
inv_1 = make_purchase_invoice(
|
||||||
|
rate=1000, posting_date=add_to_date(fiscal_year[1], days=1), do_not_save=True, do_not_submit=True
|
||||||
|
)
|
||||||
|
inv_1.set_posting_time = 1
|
||||||
inv_1.apply_tds = 1
|
inv_1.apply_tds = 1
|
||||||
inv_1.tax_withholding_category = "TDS - 3"
|
inv_1.tax_withholding_category = tds_doc.name
|
||||||
|
inv_1.save()
|
||||||
inv_1.submit()
|
inv_1.submit()
|
||||||
|
|
||||||
inv_2 = make_purchase_invoice(
|
inv_2 = make_purchase_invoice(rate=1000, posting_date=from_date, do_not_save=True, do_not_submit=True)
|
||||||
rate=1000, do_not_submit=True, posting_date=add_to_date(mid_year, days=1), do_not_save=True
|
|
||||||
)
|
|
||||||
inv_2.set_posting_time = 1
|
inv_2.set_posting_time = 1
|
||||||
|
inv_2.apply_tds = 1
|
||||||
inv_1.apply_tds = 1
|
inv_2.tax_withholding_category = tds_doc.name
|
||||||
inv_2.tax_withholding_category = "TDS - 3"
|
|
||||||
inv_2.save()
|
inv_2.save()
|
||||||
inv_2.submit()
|
inv_2.submit()
|
||||||
|
|
||||||
|
|||||||
@@ -79,8 +79,14 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
|
|||||||
options: erpnext.get_presentation_currency_list(),
|
options: erpnext.get_presentation_currency_list(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "with_period_closing_entry",
|
fieldname: "with_period_closing_entry_for_opening",
|
||||||
label: __("Period Closing Entry"),
|
label: __("With Period Closing Entry For Opening Balances"),
|
||||||
|
fieldtype: "Check",
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "with_period_closing_entry_for_current_period",
|
||||||
|
label: __("Period Closing Entry For Current Period"),
|
||||||
fieldtype: "Check",
|
fieldtype: "Check",
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ def get_data(filters):
|
|||||||
max_rgt,
|
max_rgt,
|
||||||
filters,
|
filters,
|
||||||
gl_entries_by_account,
|
gl_entries_by_account,
|
||||||
ignore_closing_entries=not flt(filters.with_period_closing_entry),
|
ignore_closing_entries=not flt(filters.with_period_closing_entry_for_current_period),
|
||||||
ignore_opening_entries=True,
|
ignore_opening_entries=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -274,7 +274,7 @@ def get_opening_balance(
|
|||||||
):
|
):
|
||||||
opening_balance = opening_balance.where(closing_balance.posting_date >= filters.year_start_date)
|
opening_balance = opening_balance.where(closing_balance.posting_date >= filters.year_start_date)
|
||||||
|
|
||||||
if not flt(filters.with_period_closing_entry):
|
if not flt(filters.with_period_closing_entry_for_opening):
|
||||||
if doctype == "Account Closing Balance":
|
if doctype == "Account Closing Balance":
|
||||||
opening_balance = opening_balance.where(closing_balance.is_period_closing_voucher_entry == 0)
|
opening_balance = opening_balance.where(closing_balance.is_period_closing_voucher_entry == 0)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -107,11 +107,7 @@ def convert_to_presentation_currency(gl_entries, currency_info):
|
|||||||
credit_in_account_currency = flt(entry["credit_in_account_currency"])
|
credit_in_account_currency = flt(entry["credit_in_account_currency"])
|
||||||
account_currency = entry["account_currency"]
|
account_currency = entry["account_currency"]
|
||||||
|
|
||||||
if (
|
if len(account_currencies) == 1 and account_currency == presentation_currency:
|
||||||
len(account_currencies) == 1
|
|
||||||
and account_currency == presentation_currency
|
|
||||||
and (debit_in_account_currency or credit_in_account_currency)
|
|
||||||
):
|
|
||||||
entry["debit"] = debit_in_account_currency
|
entry["debit"] = debit_in_account_currency
|
||||||
entry["credit"] = credit_in_account_currency
|
entry["credit"] = credit_in_account_currency
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1027,12 +1027,7 @@ def is_reposting_pending():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def future_sle_exists(args, sl_entries=None, allow_force_reposting=True):
|
def future_sle_exists(args, sl_entries=None):
|
||||||
if allow_force_reposting and frappe.db.get_single_value(
|
|
||||||
"Stock Reposting Settings", "do_reposting_for_each_stock_transaction"
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
|
|
||||||
key = (args.voucher_type, args.voucher_no)
|
key = (args.voucher_type, args.voucher_no)
|
||||||
if not hasattr(frappe.local, "future_sle"):
|
if not hasattr(frappe.local, "future_sle"):
|
||||||
frappe.local.future_sle = {}
|
frappe.local.future_sle = {}
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ def update_qty(bin_name, args):
|
|||||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
|
||||||
# actual qty is not up to date in case of backdated transaction
|
# actual qty is not up to date in case of backdated transaction
|
||||||
if future_sle_exists(args, allow_force_reposting=False):
|
if future_sle_exists(args):
|
||||||
last_sle_qty = (
|
last_sle_qty = (
|
||||||
frappe.qb.from_(sle)
|
frappe.qb.from_(sle)
|
||||||
.select(sle.qty_after_transaction)
|
.select(sle.qty_after_transaction)
|
||||||
|
|||||||
@@ -75,7 +75,9 @@ frappe.ui.form.on("Inventory Dimension", {
|
|||||||
|
|
||||||
set_parent_fields(frm) {
|
set_parent_fields(frm) {
|
||||||
if (frm.doc.apply_to_all_doctypes) {
|
if (frm.doc.apply_to_all_doctypes) {
|
||||||
frm.set_df_property("fetch_from_parent", "options", frm.doc.reference_document);
|
let options = ["\n", frm.doc.reference_document];
|
||||||
|
|
||||||
|
frm.set_df_property("fetch_from_parent", "options", options);
|
||||||
} else if (frm.doc.document_type && frm.doc.istable) {
|
} else if (frm.doc.document_type && frm.doc.istable) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_parent_fields",
|
method: "erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_parent_fields",
|
||||||
@@ -85,7 +87,7 @@ frappe.ui.form.on("Inventory Dimension", {
|
|||||||
},
|
},
|
||||||
callback: (r) => {
|
callback: (r) => {
|
||||||
if (r.message && r.message.length) {
|
if (r.message && r.message.length) {
|
||||||
frm.set_df_property("fetch_from_parent", "options", [""].concat(r.message));
|
frm.set_df_property("fetch_from_parent", "options", ["\n"].concat(r.message));
|
||||||
} else {
|
} else {
|
||||||
frm.set_df_property("fetch_from_parent", "hidden", 1);
|
frm.set_df_property("fetch_from_parent", "hidden", 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,6 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.apply_to_all_doctypes",
|
|
||||||
"description": "Set fieldname from which you want to fetch the data from the parent form.",
|
"description": "Set fieldname from which you want to fetch the data from the parent form.",
|
||||||
"fieldname": "fetch_from_parent",
|
"fieldname": "fetch_from_parent",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
@@ -189,7 +188,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-10-05 12:52:18.705431",
|
"modified": "2025-07-07 15:51:29.329064",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Inventory Dimension",
|
"name": "Inventory Dimension",
|
||||||
@@ -236,4 +235,4 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,16 +40,11 @@ class InventoryDimension(Document):
|
|||||||
self.reset_value()
|
self.reset_value()
|
||||||
self.set_source_and_target_fieldname()
|
self.set_source_and_target_fieldname()
|
||||||
self.set_type_of_transaction()
|
self.set_type_of_transaction()
|
||||||
self.set_fetch_value_from()
|
|
||||||
|
|
||||||
def set_type_of_transaction(self):
|
def set_type_of_transaction(self):
|
||||||
if self.apply_to_all_doctypes:
|
if self.apply_to_all_doctypes:
|
||||||
self.type_of_transaction = "Both"
|
self.type_of_transaction = "Both"
|
||||||
|
|
||||||
def set_fetch_value_from(self):
|
|
||||||
if self.apply_to_all_doctypes:
|
|
||||||
self.fetch_from_parent = self.reference_document
|
|
||||||
|
|
||||||
def do_not_update_document(self):
|
def do_not_update_document(self):
|
||||||
if self.is_new() or not self.has_stock_ledger():
|
if self.is_new() or not self.has_stock_ledger():
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -154,6 +154,8 @@ class TestInventoryDimension(FrappeTestCase):
|
|||||||
reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1
|
reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
inv_dimension.db_set("fetch_from_parent", "Rack")
|
||||||
|
|
||||||
self.assertEqual(inv_dimension.type_of_transaction, "Both")
|
self.assertEqual(inv_dimension.type_of_transaction, "Both")
|
||||||
self.assertEqual(inv_dimension.fetch_from_parent, "Rack")
|
self.assertEqual(inv_dimension.fetch_from_parent, "Rack")
|
||||||
|
|
||||||
|
|||||||
@@ -489,10 +489,6 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
self.update_inventory_dimensions(row, data)
|
self.update_inventory_dimensions(row, data)
|
||||||
|
|
||||||
if self.docstatus == 1 and has_dimensions and not row.batch_no:
|
|
||||||
data.qty_after_transaction = data.actual_qty
|
|
||||||
data.actual_qty = 0.0
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def make_sle_on_cancel(self):
|
def make_sle_on_cancel(self):
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
"end_time",
|
"end_time",
|
||||||
"limits_dont_apply_on",
|
"limits_dont_apply_on",
|
||||||
"item_based_reposting",
|
"item_based_reposting",
|
||||||
"do_reposting_for_each_stock_transaction",
|
|
||||||
"errors_notification_section",
|
"errors_notification_section",
|
||||||
"notify_reposting_error_to_role"
|
"notify_reposting_error_to_role"
|
||||||
],
|
],
|
||||||
@@ -66,18 +65,12 @@
|
|||||||
"fieldname": "errors_notification_section",
|
"fieldname": "errors_notification_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Errors Notification"
|
"label": "Errors Notification"
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "do_reposting_for_each_stock_transaction",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Do reposting for each Stock Transaction"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-04-24 12:19:40.204888",
|
"modified": "2025-07-08 11:27:46.659056",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Reposting Settings",
|
"name": "Stock Reposting Settings",
|
||||||
|
|||||||
@@ -11,10 +11,6 @@ class StockRepostingSettings(Document):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
self.set_minimum_reposting_time_slot()
|
self.set_minimum_reposting_time_slot()
|
||||||
|
|
||||||
def before_save(self):
|
|
||||||
if self.do_reposting_for_each_stock_transaction:
|
|
||||||
self.item_based_reposting = 1
|
|
||||||
|
|
||||||
def set_minimum_reposting_time_slot(self):
|
def set_minimum_reposting_time_slot(self):
|
||||||
"""Ensure that timeslot for reposting is at least 12 hours."""
|
"""Ensure that timeslot for reposting is at least 12 hours."""
|
||||||
if not self.limit_reposting_timeslot:
|
if not self.limit_reposting_timeslot:
|
||||||
|
|||||||
@@ -38,51 +38,3 @@ class TestStockRepostingSettings(unittest.TestCase):
|
|||||||
|
|
||||||
users = get_recipients()
|
users = get_recipients()
|
||||||
self.assertTrue(user in users)
|
self.assertTrue(user in users)
|
||||||
|
|
||||||
def test_do_reposting_for_each_stock_transaction(self):
|
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
|
||||||
|
|
||||||
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 1)
|
|
||||||
if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"):
|
|
||||||
frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0)
|
|
||||||
|
|
||||||
item = make_item(
|
|
||||||
"_Test item for reposting check for each transaction", properties={"is_stock_item": 1}
|
|
||||||
).name
|
|
||||||
|
|
||||||
stock_entry = make_stock_entry(
|
|
||||||
item_code=item,
|
|
||||||
qty=1,
|
|
||||||
rate=100,
|
|
||||||
stock_entry_type="Material Receipt",
|
|
||||||
target="_Test Warehouse - _TC",
|
|
||||||
)
|
|
||||||
|
|
||||||
riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name")
|
|
||||||
self.assertTrue(riv)
|
|
||||||
|
|
||||||
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0)
|
|
||||||
|
|
||||||
def test_do_not_reposting_for_each_stock_transaction(self):
|
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
|
||||||
|
|
||||||
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0)
|
|
||||||
if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"):
|
|
||||||
frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0)
|
|
||||||
|
|
||||||
item = make_item(
|
|
||||||
"_Test item for do not reposting check for each transaction", properties={"is_stock_item": 1}
|
|
||||||
).name
|
|
||||||
|
|
||||||
stock_entry = make_stock_entry(
|
|
||||||
item_code=item,
|
|
||||||
qty=1,
|
|
||||||
rate=100,
|
|
||||||
stock_entry_type="Material Receipt",
|
|
||||||
target="_Test Warehouse - _TC",
|
|
||||||
)
|
|
||||||
|
|
||||||
riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name")
|
|
||||||
self.assertFalse(riv)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user