mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-05 13:24:47 +00:00
Merge pull request #31574 from frappe/version-13-hotfix
chore: weekly version-13 release
This commit is contained in:
@@ -181,12 +181,20 @@ class RequestforQuotation(BuyingController):
|
|||||||
doc_args = self.as_dict()
|
doc_args = self.as_dict()
|
||||||
doc_args.update({"supplier": data.get("supplier"), "supplier_name": data.get("supplier_name")})
|
doc_args.update({"supplier": data.get("supplier"), "supplier_name": data.get("supplier_name")})
|
||||||
|
|
||||||
|
# Get Contact Full Name
|
||||||
|
supplier_name = None
|
||||||
|
if data.get("contact"):
|
||||||
|
contact_name = frappe.db.get_value(
|
||||||
|
"Contact", data.get("contact"), ["first_name", "middle_name", "last_name"]
|
||||||
|
)
|
||||||
|
supplier_name = (" ").join(x for x in contact_name if x) # remove any blank values
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
"update_password_link": update_password_link,
|
"update_password_link": update_password_link,
|
||||||
"message": frappe.render_template(self.message_for_supplier, doc_args),
|
"message": frappe.render_template(self.message_for_supplier, doc_args),
|
||||||
"rfq_link": rfq_link,
|
"rfq_link": rfq_link,
|
||||||
"user_fullname": full_name,
|
"user_fullname": full_name,
|
||||||
"supplier_name": data.get("supplier_name"),
|
"supplier_name": supplier_name or data.get("supplier_name"),
|
||||||
"supplier_salutation": self.salutation or "Dear Mx.",
|
"supplier_salutation": self.salutation or "Dear Mx.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -368,3 +368,4 @@ erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
|
|||||||
erpnext.patches.v13_0.update_employee_advance_status
|
erpnext.patches.v13_0.update_employee_advance_status
|
||||||
erpnext.patches.v13_0.job_card_status_on_hold
|
erpnext.patches.v13_0.job_card_status_on_hold
|
||||||
erpnext.patches.v13_0.add_cost_center_in_loans
|
erpnext.patches.v13_0.add_cost_center_in_loans
|
||||||
|
erpnext.patches.v13_0.show_india_localisation_deprecation_warning
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import click
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if not frappe.db.exists("Company", {"country": "India"}):
|
||||||
|
return
|
||||||
|
|
||||||
|
click.secho(
|
||||||
|
"India-specific regional features have been moved to a separate app"
|
||||||
|
" and will be removed from ERPNext in Version 14."
|
||||||
|
" Please install India Compliance after upgrading to Version 14:\n"
|
||||||
|
"https://github.com/resilient-tech/india-compliance",
|
||||||
|
fg="yellow",
|
||||||
|
)
|
||||||
@@ -626,7 +626,7 @@ class SalarySlip(TransactionBase):
|
|||||||
for struct_row in self._salary_structure_doc.get(component_type):
|
for struct_row in self._salary_structure_doc.get(component_type):
|
||||||
amount = self.eval_condition_and_formula(struct_row, data)
|
amount = self.eval_condition_and_formula(struct_row, data)
|
||||||
if amount is not None and struct_row.statistical_component == 0:
|
if amount is not None and struct_row.statistical_component == 0:
|
||||||
self.update_component_row(struct_row, amount, component_type)
|
self.update_component_row(struct_row, amount, component_type, data=data)
|
||||||
|
|
||||||
def get_data_for_eval(self):
|
def get_data_for_eval(self):
|
||||||
"""Returns data for evaluating formula"""
|
"""Returns data for evaluating formula"""
|
||||||
@@ -780,7 +780,7 @@ class SalarySlip(TransactionBase):
|
|||||||
self.update_component_row(tax_row, tax_amount, "deductions")
|
self.update_component_row(tax_row, tax_amount, "deductions")
|
||||||
|
|
||||||
def update_component_row(
|
def update_component_row(
|
||||||
self, component_data, amount, component_type, additional_salary=None, is_recurring=0
|
self, component_data, amount, component_type, additional_salary=None, is_recurring=0, data=None
|
||||||
):
|
):
|
||||||
component_row = None
|
component_row = None
|
||||||
for d in self.get(component_type):
|
for d in self.get(component_type):
|
||||||
@@ -850,6 +850,8 @@ class SalarySlip(TransactionBase):
|
|||||||
component_row.amount = amount
|
component_row.amount = amount
|
||||||
|
|
||||||
self.update_component_amount_based_on_payment_days(component_row)
|
self.update_component_amount_based_on_payment_days(component_row)
|
||||||
|
if data:
|
||||||
|
data[component_row.abbr] = component_row.amount
|
||||||
|
|
||||||
def update_component_amount_based_on_payment_days(self, component_row):
|
def update_component_amount_based_on_payment_days(self, component_row):
|
||||||
joining_date, relieving_date = self.get_joining_and_relieving_dates()
|
joining_date, relieving_date = self.get_joining_and_relieving_dates()
|
||||||
|
|||||||
@@ -1119,6 +1119,7 @@ def make_earning_salary_component(
|
|||||||
"formula": "BS*.5",
|
"formula": "BS*.5",
|
||||||
"type": "Earning",
|
"type": "Earning",
|
||||||
"amount_based_on_formula": 1,
|
"amount_based_on_formula": 1,
|
||||||
|
"depends_on_payment_days": 0,
|
||||||
},
|
},
|
||||||
{"salary_component": "Leave Encashment", "abbr": "LE", "type": "Earning"},
|
{"salary_component": "Leave Encashment", "abbr": "LE", "type": "Earning"},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
@@ -18,6 +19,7 @@ class SalaryStructure(Document):
|
|||||||
self.strip_condition_and_formula_fields()
|
self.strip_condition_and_formula_fields()
|
||||||
self.validate_max_benefits_with_flexi()
|
self.validate_max_benefits_with_flexi()
|
||||||
self.validate_component_based_on_tax_slab()
|
self.validate_component_based_on_tax_slab()
|
||||||
|
self.validate_payment_days_based_dependent_component()
|
||||||
|
|
||||||
def set_missing_values(self):
|
def set_missing_values(self):
|
||||||
overwritten_fields = [
|
overwritten_fields = [
|
||||||
@@ -58,6 +60,35 @@ class SalaryStructure(Document):
|
|||||||
if flt(self.net_pay) < 0 and self.salary_slip_based_on_timesheet:
|
if flt(self.net_pay) < 0 and self.salary_slip_based_on_timesheet:
|
||||||
frappe.throw(_("Net pay cannot be negative"))
|
frappe.throw(_("Net pay cannot be negative"))
|
||||||
|
|
||||||
|
def validate_payment_days_based_dependent_component(self):
|
||||||
|
abbreviations = self.get_component_abbreviations()
|
||||||
|
for component_type in ("earnings", "deductions"):
|
||||||
|
for row in self.get(component_type):
|
||||||
|
if (
|
||||||
|
row.formula
|
||||||
|
and row.depends_on_payment_days
|
||||||
|
# check if the formula contains any of the payment days components
|
||||||
|
and any(re.search(r"\b" + abbr + r"\b", row.formula) for abbr in abbreviations)
|
||||||
|
):
|
||||||
|
message = _("Row #{0}: The {1} Component has the options {2} and {3} enabled.").format(
|
||||||
|
row.idx,
|
||||||
|
frappe.bold(row.salary_component),
|
||||||
|
frappe.bold("Amount based on formula"),
|
||||||
|
frappe.bold("Depends On Payment Days"),
|
||||||
|
)
|
||||||
|
message += "<br><br>" + _(
|
||||||
|
"Disable {0} for the {1} component, to prevent the amount from being deducted twice, as its formula already uses a payment-days-based component."
|
||||||
|
).format(
|
||||||
|
frappe.bold("Depends On Payment Days"), frappe.bold(row.salary_component)
|
||||||
|
)
|
||||||
|
frappe.throw(message, title=_("Payment Days Dependency"))
|
||||||
|
|
||||||
|
def get_component_abbreviations(self):
|
||||||
|
abbr = [d.abbr for d in self.earnings if d.depends_on_payment_days]
|
||||||
|
abbr += [d.abbr for d in self.deductions if d.depends_on_payment_days]
|
||||||
|
|
||||||
|
return abbr
|
||||||
|
|
||||||
def strip_condition_and_formula_fields(self):
|
def strip_condition_and_formula_fields(self):
|
||||||
# remove whitespaces from condition and formula fields
|
# remove whitespaces from condition and formula fields
|
||||||
for row in self.earnings:
|
for row in self.earnings:
|
||||||
|
|||||||
@@ -468,6 +468,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
and not d.is_fixed_asset
|
and not d.is_fixed_asset
|
||||||
and flt(d.qty)
|
and flt(d.qty)
|
||||||
and provisional_accounting_for_non_stock_items
|
and provisional_accounting_for_non_stock_items
|
||||||
|
and d.get("provisional_expense_account")
|
||||||
):
|
):
|
||||||
self.add_provisional_gl_entry(
|
self.add_provisional_gl_entry(
|
||||||
d, gl_entries, self.posting_date, d.get("provisional_expense_account")
|
d, gl_entries, self.posting_date, d.get("provisional_expense_account")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.permissions import add_user_permission, remove_user_permission
|
from frappe.permissions import add_user_permission, remove_user_permission
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import flt, nowdate, nowtime
|
from frappe.utils import add_days, flt, nowdate, nowtime
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||||
@@ -1414,6 +1414,138 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
self.assertEqual(se.items[0].item_name, item.item_name)
|
self.assertEqual(se.items[0].item_name, item.item_name)
|
||||||
self.assertEqual(se.items[0].stock_uom, item.stock_uom)
|
self.assertEqual(se.items[0].stock_uom, item.stock_uom)
|
||||||
|
|
||||||
|
def test_reposting_for_depedent_warehouse(self):
|
||||||
|
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_sl_entries
|
||||||
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
|
|
||||||
|
# Inward at WH1 warehouse (Component)
|
||||||
|
# 1st Repack (Component (WH1) - Subcomponent (WH2))
|
||||||
|
# 2nd Repack (Subcomponent (WH2) - FG Item (WH3))
|
||||||
|
# Material Transfer of FG Item -> WH 3 -> WH2 -> Wh1 (Two transfer entries)
|
||||||
|
# Backdated transction which should update valuation rate in repack as well trasfer entries
|
||||||
|
|
||||||
|
for item_code in ["FG Item 1", "Sub Component 1", "Component 1"]:
|
||||||
|
create_item(item_code)
|
||||||
|
|
||||||
|
for warehouse in ["WH 1", "WH 2", "WH 3"]:
|
||||||
|
create_warehouse(warehouse)
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code="Component 1",
|
||||||
|
rate=100,
|
||||||
|
purpose="Material Receipt",
|
||||||
|
qty=10,
|
||||||
|
to_warehouse="WH 1 - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -10),
|
||||||
|
)
|
||||||
|
|
||||||
|
repack1 = make_stock_entry(
|
||||||
|
item_code="Component 1",
|
||||||
|
purpose="Repack",
|
||||||
|
do_not_save=True,
|
||||||
|
qty=10,
|
||||||
|
from_warehouse="WH 1 - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -9),
|
||||||
|
)
|
||||||
|
|
||||||
|
repack1.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": "Sub Component 1",
|
||||||
|
"qty": 10,
|
||||||
|
"t_warehouse": "WH 2 - _TC",
|
||||||
|
"transfer_qty": 10,
|
||||||
|
"uom": "Nos",
|
||||||
|
"stock_uom": "Nos",
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
repack1.save()
|
||||||
|
repack1.submit()
|
||||||
|
|
||||||
|
self.assertEqual(repack1.items[1].basic_rate, 100)
|
||||||
|
self.assertEqual(repack1.items[1].amount, 1000)
|
||||||
|
|
||||||
|
repack2 = make_stock_entry(
|
||||||
|
item_code="Sub Component 1",
|
||||||
|
purpose="Repack",
|
||||||
|
do_not_save=True,
|
||||||
|
qty=10,
|
||||||
|
from_warehouse="WH 2 - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -8),
|
||||||
|
)
|
||||||
|
|
||||||
|
repack2.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": "FG Item 1",
|
||||||
|
"qty": 10,
|
||||||
|
"t_warehouse": "WH 3 - _TC",
|
||||||
|
"transfer_qty": 10,
|
||||||
|
"uom": "Nos",
|
||||||
|
"stock_uom": "Nos",
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
repack2.save()
|
||||||
|
repack2.submit()
|
||||||
|
|
||||||
|
self.assertEqual(repack2.items[1].basic_rate, 100)
|
||||||
|
self.assertEqual(repack2.items[1].amount, 1000)
|
||||||
|
|
||||||
|
transfer1 = make_stock_entry(
|
||||||
|
item_code="FG Item 1",
|
||||||
|
purpose="Material Transfer",
|
||||||
|
qty=10,
|
||||||
|
from_warehouse="WH 3 - _TC",
|
||||||
|
to_warehouse="WH 2 - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -7),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(transfer1.items[0].basic_rate, 100)
|
||||||
|
self.assertEqual(transfer1.items[0].amount, 1000)
|
||||||
|
|
||||||
|
transfer2 = make_stock_entry(
|
||||||
|
item_code="FG Item 1",
|
||||||
|
purpose="Material Transfer",
|
||||||
|
qty=10,
|
||||||
|
from_warehouse="WH 2 - _TC",
|
||||||
|
to_warehouse="WH 1 - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -6),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(transfer2.items[0].basic_rate, 100)
|
||||||
|
self.assertEqual(transfer2.items[0].amount, 1000)
|
||||||
|
|
||||||
|
# Backdated transaction
|
||||||
|
receipt2 = make_stock_entry(
|
||||||
|
item_code="Component 1",
|
||||||
|
rate=200,
|
||||||
|
purpose="Material Receipt",
|
||||||
|
qty=10,
|
||||||
|
to_warehouse="WH 1 - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -15),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(receipt2.items[0].basic_rate, 200)
|
||||||
|
self.assertEqual(receipt2.items[0].amount, 2000)
|
||||||
|
|
||||||
|
repost_name = frappe.db.get_value(
|
||||||
|
"Repost Item Valuation", {"voucher_no": receipt2.name, "docstatus": 1}, "name"
|
||||||
|
)
|
||||||
|
|
||||||
|
doc = frappe.get_doc("Repost Item Valuation", repost_name)
|
||||||
|
repost_sl_entries(doc)
|
||||||
|
|
||||||
|
for obj in [repack1, repack2, transfer1, transfer2]:
|
||||||
|
obj.load_from_db()
|
||||||
|
|
||||||
|
index = 1 if obj.purpose == "Repack" else 0
|
||||||
|
self.assertEqual(obj.items[index].basic_rate, 200)
|
||||||
|
self.assertEqual(obj.items[index].basic_amount, 2000)
|
||||||
|
|
||||||
|
|
||||||
def make_serialized_item(**args):
|
def make_serialized_item(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -247,16 +247,11 @@ def repost_future_sle(
|
|||||||
data.sle_changed = False
|
data.sle_changed = False
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if doc and i % 2 == 0:
|
if doc:
|
||||||
update_args_in_repost_item_valuation(
|
update_args_in_repost_item_valuation(
|
||||||
doc, i, args, distinct_item_warehouses, affected_transactions
|
doc, i, args, distinct_item_warehouses, affected_transactions
|
||||||
)
|
)
|
||||||
|
|
||||||
if doc and args:
|
|
||||||
update_args_in_repost_item_valuation(
|
|
||||||
doc, i, args, distinct_item_warehouses, affected_transactions
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def update_args_in_repost_item_valuation(
|
def update_args_in_repost_item_valuation(
|
||||||
doc, index, args, distinct_item_warehouses, affected_transactions
|
doc, index, args, distinct_item_warehouses, affected_transactions
|
||||||
@@ -491,7 +486,8 @@ class update_entries_after(object):
|
|||||||
elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data:
|
elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data:
|
||||||
return entries_to_fix
|
return entries_to_fix
|
||||||
else:
|
else:
|
||||||
return self.append_future_sle_for_dependant(dependant_sle, entries_to_fix)
|
self.append_future_sle_for_dependant(dependant_sle, entries_to_fix)
|
||||||
|
return entries_to_fix
|
||||||
|
|
||||||
def update_distinct_item_warehouses(self, dependant_sle):
|
def update_distinct_item_warehouses(self, dependant_sle):
|
||||||
key = (dependant_sle.item_code, dependant_sle.warehouse)
|
key = (dependant_sle.item_code, dependant_sle.warehouse)
|
||||||
@@ -510,14 +506,11 @@ class update_entries_after(object):
|
|||||||
|
|
||||||
def append_future_sle_for_dependant(self, dependant_sle, entries_to_fix):
|
def append_future_sle_for_dependant(self, dependant_sle, entries_to_fix):
|
||||||
self.initialize_previous_data(dependant_sle)
|
self.initialize_previous_data(dependant_sle)
|
||||||
|
self.distinct_item_warehouses[(self.item_code, dependant_sle.warehouse)] = frappe._dict(
|
||||||
args = self.data[dependant_sle.warehouse].previous_sle or frappe._dict(
|
{"sle": dependant_sle}
|
||||||
{"item_code": self.item_code, "warehouse": dependant_sle.warehouse}
|
|
||||||
)
|
)
|
||||||
future_sle_for_dependant = list(self.get_sle_after_datetime(args))
|
|
||||||
|
|
||||||
entries_to_fix.extend(future_sle_for_dependant)
|
self.new_items_found = True
|
||||||
return sorted(entries_to_fix, key=lambda k: k["timestamp"])
|
|
||||||
|
|
||||||
def process_sle(self, sle):
|
def process_sle(self, sle):
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|||||||
Reference in New Issue
Block a user