Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-32144

This commit is contained in:
Deepesh Garg
2022-09-13 09:39:37 +05:30
committed by GitHub
18 changed files with 776 additions and 559 deletions

View File

@@ -65,6 +65,8 @@ GNU/General Public License (see [license.txt](license.txt))
The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors.
By contributing to ERPNext, you agree that your contributions will be licensed under its GNU General Public License (v3).
---
## Contributing

View File

@@ -19,7 +19,7 @@ from erpnext.hr.doctype.attendance.attendance import (
mark_attendance,
)
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday
from erpnext.hr.tests.test_utils import get_first_sunday
test_records = frappe.get_test_records("Attendance")

View File

@@ -33,6 +33,7 @@ from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
create_assignment_for_multiple_employees,
)
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.hr.tests.test_utils import get_first_sunday
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
make_holiday_list,
make_leave_application,
@@ -1105,23 +1106,6 @@ def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, el
allocate_leave.submit()
def get_first_sunday(holiday_list, for_date=None):
date = for_date or getdate()
month_start_date = get_first_day(date)
month_end_date = get_last_day(date)
first_sunday = frappe.db.sql(
"""
select holiday_date from `tabHoliday`
where parent = %s
and holiday_date between %s and %s
order by holiday_date
""",
(holiday_list, month_start_date, month_end_date),
)[0][0]
return first_sunday
def make_policy_assignment(employee, leave_type, leave_period):
frappe.delete_doc_if_exists("Leave Type", leave_type, force=1)
frappe.get_doc(

View File

@@ -9,13 +9,11 @@ from frappe.utils import add_days, add_months, flt, get_year_ending, get_year_st
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
from erpnext.hr.doctype.leave_application.test_leave_application import (
get_first_sunday,
make_allocation_record,
)
from erpnext.hr.doctype.leave_application.test_leave_application import make_allocation_record
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.hr.report.employee_leave_balance.employee_leave_balance import execute
from erpnext.hr.tests.test_utils import get_first_sunday
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
make_holiday_list,
make_leave_application,

View File

@@ -9,12 +9,10 @@ from frappe.utils import add_days, flt, get_year_ending, get_year_start, getdate
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
from erpnext.hr.doctype.leave_application.test_leave_application import (
get_first_sunday,
make_allocation_record,
)
from erpnext.hr.doctype.leave_application.test_leave_application import make_allocation_record
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation
from erpnext.hr.report.employee_leave_balance_summary.employee_leave_balance_summary import execute
from erpnext.hr.tests.test_utils import get_first_sunday
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
make_holiday_list,
make_leave_application,

View File

@@ -0,0 +1,19 @@
import frappe
from frappe.utils import get_first_day, get_last_day, getdate
def get_first_sunday(holiday_list="Salary Slip Test Holiday List", for_date=None):
date = for_date or getdate()
month_start_date = get_first_day(date)
month_end_date = get_last_day(date)
first_sunday = frappe.db.sql(
"""
select holiday_date from `tabHoliday`
where parent = %s
and holiday_date between %s and %s
order by holiday_date
""",
(holiday_list, month_start_date, month_end_date),
)[0][0]
return first_sunday

View File

@@ -17,6 +17,7 @@
"posting_date",
"status",
"repay_from_salary",
"manually_update_paid_amount_in_salary_slip",
"section_break_8",
"loan_type",
"loan_amount",
@@ -410,16 +411,23 @@
"fieldname": "is_npa",
"fieldtype": "Check",
"label": "Is NPA"
},
{
"allow_on_submit": 1,
"default": "0",
"depends_on": "repay_from_salary",
"fieldname": "manually_update_paid_amount_in_salary_slip",
"fieldtype": "Check",
"label": "Manually Update Paid Amount in Salary Slip"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-06-30 12:04:13.728880",
"modified": "2022-09-12 03:36:49.145014",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -445,6 +453,5 @@
"search_fields": "posting_date",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -11,17 +11,24 @@ frappe.query_reports["BOM Stock Calculated"] = {
"options": "BOM",
"reqd": 1
},
{
"fieldname": "qty_to_make",
"label": __("Quantity to Make"),
"fieldtype": "Int",
"default": "1"
},
{
{
"fieldname": "warehouse",
"label": __("Warehouse"),
"fieldtype": "Link",
"options": "Warehouse",
},
{
"fieldname": "qty_to_make",
"label": __("Quantity to Make"),
"fieldtype": "Float",
"default": "1.0",
"reqd": 1
},
{
"fieldname": "show_exploded_view",
"label": __("Show exploded view"),
"fieldtype": "Check"
"fieldtype": "Check",
"default": false,
}
]
}

View File

@@ -4,29 +4,31 @@
import frappe
from frappe import _
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils.data import comma_and
from pypika.terms import ExistsCriterion
def execute(filters=None):
# if not filters: filters = {}
columns = get_columns()
summ_data = []
data = []
data = get_bom_stock(filters)
bom_data = get_bom_data(filters)
qty_to_make = filters.get("qty_to_make")
manufacture_details = get_manufacturer_records()
for row in data:
reqd_qty = qty_to_make * row.actual_qty
last_pur_price = frappe.db.get_value("Item", row.item_code, "last_purchase_rate")
summ_data.append(get_report_data(last_pur_price, reqd_qty, row, manufacture_details))
return columns, summ_data
for row in bom_data:
required_qty = qty_to_make * row.qty_per_unit
last_purchase_rate = frappe.db.get_value("Item", row.item_code, "last_purchase_rate")
data.append(get_report_data(last_purchase_rate, required_qty, row, manufacture_details))
return columns, data
def get_report_data(last_pur_price, reqd_qty, row, manufacture_details):
to_build = row.to_build if row.to_build > 0 else 0
diff_qty = to_build - reqd_qty
def get_report_data(last_purchase_rate, required_qty, row, manufacture_details):
qty_per_unit = row.qty_per_unit if row.qty_per_unit > 0 else 0
difference_qty = row.actual_qty - required_qty
return [
row.item_code,
row.description,
@@ -34,85 +36,126 @@ def get_report_data(last_pur_price, reqd_qty, row, manufacture_details):
comma_and(
manufacture_details.get(row.item_code, {}).get("manufacturer_part", []), add_quotes=False
),
qty_per_unit,
row.actual_qty,
str(to_build),
reqd_qty,
diff_qty,
last_pur_price,
required_qty,
difference_qty,
last_purchase_rate,
]
def get_columns():
"""return columns"""
columns = [
_("Item") + ":Link/Item:100",
_("Description") + "::150",
_("Manufacturer") + "::250",
_("Manufacturer Part Number") + "::250",
_("Qty") + ":Float:50",
_("Stock Qty") + ":Float:100",
_("Reqd Qty") + ":Float:100",
_("Diff Qty") + ":Float:100",
_("Last Purchase Price") + ":Float:100",
return [
{
"fieldname": "item",
"label": _("Item"),
"fieldtype": "Link",
"options": "Item",
"width": 120,
},
{
"fieldname": "description",
"label": _("Description"),
"fieldtype": "Data",
"width": 150,
},
{
"fieldname": "manufacturer",
"label": _("Manufacturer"),
"fieldtype": "Data",
"width": 120,
},
{
"fieldname": "manufacturer_part_number",
"label": _("Manufacturer Part Number"),
"fieldtype": "Data",
"width": 150,
},
{
"fieldname": "qty_per_unit",
"label": _("Qty Per Unit"),
"fieldtype": "Float",
"width": 110,
},
{
"fieldname": "available_qty",
"label": _("Available Qty"),
"fieldtype": "Float",
"width": 120,
},
{
"fieldname": "required_qty",
"label": _("Required Qty"),
"fieldtype": "Float",
"width": 120,
},
{
"fieldname": "difference_qty",
"label": _("Difference Qty"),
"fieldtype": "Float",
"width": 130,
},
{
"fieldname": "last_purchase_rate",
"label": _("Last Purchase Rate"),
"fieldtype": "Float",
"width": 160,
},
]
return columns
def get_bom_stock(filters):
conditions = ""
bom = filters.get("bom")
table = "`tabBOM Item`"
qty_field = "qty"
def get_bom_data(filters):
if filters.get("show_exploded_view"):
table = "`tabBOM Explosion Item`"
qty_field = "stock_qty"
bom_item_table = "BOM Explosion Item"
else:
bom_item_table = "BOM Item"
bom_item = frappe.qb.DocType(bom_item_table)
bin = frappe.qb.DocType("Bin")
query = (
frappe.qb.from_(bom_item)
.left_join(bin)
.on(bom_item.item_code == bin.item_code)
.select(
bom_item.item_code,
bom_item.description,
bom_item.qty_consumed_per_unit.as_("qty_per_unit"),
IfNull(Sum(bin.actual_qty), 0).as_("actual_qty"),
)
.where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM"))
.groupby(bom_item.item_code)
)
if filters.get("warehouse"):
warehouse_details = frappe.db.get_value(
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
)
if warehouse_details:
conditions += (
" and exists (select name from `tabWarehouse` wh \
where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)"
% (warehouse_details.lft, warehouse_details.rgt)
wh = frappe.qb.DocType("Warehouse")
query = query.where(
ExistsCriterion(
frappe.qb.from_(wh)
.select(wh.name)
.where(
(wh.lft >= warehouse_details.lft)
& (wh.rgt <= warehouse_details.rgt)
& (bin.warehouse == wh.name)
)
)
)
else:
conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse"))
query = query.where(bin.warehouse == frappe.db.escape(filters.get("warehouse")))
else:
conditions += ""
return frappe.db.sql(
"""
SELECT
bom_item.item_code,
bom_item.description,
bom_item.{qty_field},
ifnull(sum(ledger.actual_qty), 0) as actual_qty,
ifnull(sum(FLOOR(ledger.actual_qty / bom_item.{qty_field})), 0) as to_build
FROM
{table} AS bom_item
LEFT JOIN `tabBin` AS ledger
ON bom_item.item_code = ledger.item_code
{conditions}
WHERE
bom_item.parent = '{bom}' and bom_item.parenttype='BOM'
GROUP BY bom_item.item_code""".format(
qty_field=qty_field, table=table, conditions=conditions, bom=bom
),
as_dict=1,
)
return query.run(as_dict=True)
def get_manufacturer_records():
details = frappe.get_all(
"Item Manufacturer", fields=["manufacturer", "manufacturer_part_no", "item_code"]
)
manufacture_details = frappe._dict()
for detail in details:
dic = manufacture_details.setdefault(detail.get("item_code"), {})

View File

@@ -0,0 +1,115 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from frappe.tests.utils import FrappeTestCase
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.manufacturing.report.bom_stock_calculated.bom_stock_calculated import (
execute as bom_stock_calculated_report,
)
from erpnext.stock.doctype.item.test_item import make_item
class TestBOMStockCalculated(FrappeTestCase):
def setUp(self):
self.fg_item, self.rm_items = create_items()
self.boms = create_boms(self.fg_item, self.rm_items)
def test_bom_stock_calculated(self):
qty_to_make = 10
# Case 1: When Item(s) Qty and Stock Qty are equal.
data = bom_stock_calculated_report(
filters={
"qty_to_make": qty_to_make,
"bom": self.boms[0].name,
}
)[1]
expected_data = get_expected_data(self.boms[0], qty_to_make)
self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
# Case 2: When Item(s) Qty and Stock Qty are different and BOM Qty is 1.
data = bom_stock_calculated_report(
filters={
"qty_to_make": qty_to_make,
"bom": self.boms[1].name,
}
)[1]
expected_data = get_expected_data(self.boms[1], qty_to_make)
self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
# Case 3: When Item(s) Qty and Stock Qty are different and BOM Qty is greater than 1.
data = bom_stock_calculated_report(
filters={
"qty_to_make": qty_to_make,
"bom": self.boms[2].name,
}
)[1]
expected_data = get_expected_data(self.boms[2], qty_to_make)
self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
def create_items():
fg_item = make_item(properties={"is_stock_item": 1}).name
rm_item1 = make_item(
properties={
"is_stock_item": 1,
"standard_rate": 100,
"opening_stock": 100,
"last_purchase_rate": 100,
}
).name
rm_item2 = make_item(
properties={
"is_stock_item": 1,
"standard_rate": 200,
"opening_stock": 200,
"last_purchase_rate": 200,
}
).name
return fg_item, [rm_item1, rm_item2]
def create_boms(fg_item, rm_items):
def update_bom_items(bom, uom, conversion_factor):
for item in bom.items:
item.uom = uom
item.conversion_factor = conversion_factor
return bom
bom1 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10)
bom2 = make_bom(item=fg_item, quantity=1, raw_materials=rm_items, rm_qty=10, do_not_submit=True)
bom2 = update_bom_items(bom2, "Box", 10)
bom2.save()
bom2.submit()
bom3 = make_bom(item=fg_item, quantity=2, raw_materials=rm_items, rm_qty=10, do_not_submit=True)
bom3 = update_bom_items(bom3, "Box", 10)
bom3.save()
bom3.submit()
return [bom1, bom2, bom3]
def get_expected_data(bom, qty_to_make):
expected_data = []
for idx in range(len(bom.items)):
expected_data.append(
[
bom.items[idx].item_code,
bom.items[idx].item_code,
"",
"",
float(bom.items[idx].stock_qty / bom.quantity),
float(100 * (idx + 1)),
float(qty_to_make * (bom.items[idx].stock_qty / bom.quantity)),
float((100 * (idx + 1)) - (qty_to_make * (bom.items[idx].stock_qty / bom.quantity))),
float(100 * (idx + 1)),
]
)
return expected_data

View File

@@ -9,7 +9,7 @@ from frappe.utils import add_days, date_diff, get_year_ending, get_year_start, g
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday
from erpnext.hr.tests.test_utils import get_first_sunday
from erpnext.hr.utils import get_holiday_dates_for_employee
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import (
calculate_lwp,

View File

@@ -587,7 +587,7 @@ class SalarySlip(TransactionBase):
if self.salary_structure:
self.calculate_component_amounts("deductions")
self.set_loan_repayment()
self.add_applicable_loans()
self.set_precision_for_component_amounts()
self.set_net_pay()
@@ -632,9 +632,19 @@ class SalarySlip(TransactionBase):
continue
amount = self.eval_condition_and_formula(struct_row, data)
if (
amount or (struct_row.amount_based_on_formula and amount is not None)
) and struct_row.statistical_component == 0:
if struct_row.statistical_component:
# update statitical component amount in reference data based on payment days
# since row for statistical component is not added to salary slip
if struct_row.depends_on_payment_days:
joining_date, relieving_date = self.get_joining_and_relieving_dates()
default_data[struct_row.abbr] = amount
data[struct_row.abbr] = flt(
(flt(amount) * flt(self.payment_days) / cint(self.total_working_days)),
struct_row.precision("amount"),
)
elif amount or struct_row.amount_based_on_formula and amount is not None:
default_amount = self.eval_condition_and_formula(struct_row, default_data)
self.update_component_row(
struct_row, amount, component_type, data=data, default_amount=default_amount
@@ -687,8 +697,8 @@ class SalarySlip(TransactionBase):
for key in ("earnings", "deductions"):
for d in self.get(key):
default_data[d.abbr] = d.default_amount
data[d.abbr] = d.amount
default_data[d.abbr] = d.default_amount or 0
data[d.abbr] = d.amount or 0
return data, default_data
@@ -1370,44 +1380,33 @@ class SalarySlip(TransactionBase):
return joining_date, relieving_date
def set_loan_repayment(self):
def add_applicable_loans(self):
self.total_loan_repayment = 0
self.total_interest_amount = 0
self.total_principal_amount = 0
self.set("loans", [])
loans = [d.loan for d in self.get("loans")]
for loan in self.get_loan_details():
amounts = calculate_amounts(loan.name, self.posting_date, "Regular Payment")
if (amounts["interest_amount"] or amounts["payable_principal_amount"]) and (
amounts["payable_principal_amount"] + amounts["interest_amount"]
> amounts["written_off_amount"]
):
if amounts["interest_amount"] > amounts["written_off_amount"]:
amounts["interest_amount"] -= amounts["written_off_amount"]
amounts["written_off_amount"] = 0
else:
amounts["written_off_amount"] -= amounts["interest_amount"]
amounts["interest_amount"] = 0
if amounts["payable_principal_amount"] > amounts["written_off_amount"]:
amounts["payable_principal_amount"] -= amounts["written_off_amount"]
amounts["written_off_amount"] = 0
else:
amounts["written_off_amount"] -= amounts["payable_principal_amount"]
amounts["payable_principal_amount"] = 0
self.append(
"loans",
{
"loan": loan.name,
"interest_amount": amounts["interest_amount"],
"principal_amount": amounts["payable_principal_amount"],
"loan_account": loan.loan_account,
"interest_income_account": loan.interest_income_account,
},
)
if loan.name not in loans:
amounts = calculate_amounts(loan.name, self.posting_date, "Regular Payment")
if (
amounts["interest_amount"] + amounts["payable_principal_amount"]
> amounts["written_off_amount"]
):
self.append(
"loans",
{
"loan": loan.name,
"interest_amount": amounts["interest_amount"],
"principal_amount": amounts["payable_principal_amount"],
"total_payment": amounts["interest_amount"] + amounts["payable_principal_amount"]
if not loan.manually_update_paid_amount_in_salary_slip
else 0,
"loan_account": loan.loan_account,
"interest_income_account": loan.interest_income_account,
},
)
for payment in self.get("loans"):
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
@@ -1432,7 +1431,14 @@ class SalarySlip(TransactionBase):
def get_loan_details(self):
loan_details = frappe.get_all(
"Loan",
fields=["name", "interest_income_account", "loan_account", "loan_type", "is_term_loan"],
fields=[
"name",
"interest_income_account",
"loan_account",
"loan_type",
"is_term_loan",
"manually_update_paid_amount_in_salary_slip",
],
filters={
"applicant": self.employee,
"docstatus": 1,

View File

@@ -27,6 +27,7 @@ from erpnext.hr.doctype.attendance.attendance import mark_attendance
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
from erpnext.hr.tests.test_utils import get_first_sunday
from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import (
create_exemption_category,
create_payroll_period,
@@ -55,18 +56,7 @@ class TestSalarySlip(FrappeTestCase):
frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0)
month_start_date = get_first_day(nowdate())
month_end_date = get_last_day(nowdate())
first_sunday = frappe.db.sql(
"""
select holiday_date from `tabHoliday`
where parent = 'Salary Slip Test Holiday List'
and holiday_date between %s and %s
order by holiday_date
""",
(month_start_date, month_end_date),
)[0][0]
first_sunday = get_first_sunday()
mark_attendance(emp_id, first_sunday, "Absent", ignore_validate=True) # invalid lwp
mark_attendance(
@@ -273,19 +263,7 @@ class TestSalarySlip(FrappeTestCase):
frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0)
month_start_date = get_first_day(nowdate())
month_end_date = get_last_day(nowdate())
first_sunday = frappe.db.sql(
"""
select holiday_date from `tabHoliday`
where parent = 'Salary Slip Test Holiday List'
and holiday_date between %s and %s
order by holiday_date
""",
(month_start_date, month_end_date),
)[0][0]
first_sunday = get_first_sunday()
make_leave_application(emp_id, first_sunday, add_days(first_sunday, 3), "Leave Without Pay")
leave_type_ppl = create_leave_type(leave_type_name="Test Partially Paid Leave", is_ppl=1)
@@ -338,19 +316,7 @@ class TestSalarySlip(FrappeTestCase):
frappe.db.set_value("Employee", emp, {"relieving_date": None, "status": "Active"})
# mark attendance
month_start_date = get_first_day(nowdate())
month_end_date = get_last_day(nowdate())
first_sunday = frappe.db.sql(
"""
select holiday_date from `tabHoliday`
where parent = 'Salary Slip Test Holiday List'
and holiday_date between %s and %s
order by holiday_date
""",
(month_start_date, month_end_date),
)[0][0]
first_sunday = get_first_sunday()
mark_attendance(
emp, add_days(first_sunday, 1), "Absent", ignore_validate=True
) # counted as absent
@@ -359,8 +325,8 @@ class TestSalarySlip(FrappeTestCase):
make_salary_structure_for_timesheet(emp)
timesheet = make_timesheet(emp, simulate=True, is_billable=1)
salary_slip = make_salary_slip_for_timesheet(timesheet.name)
salary_slip.start_date = month_start_date
salary_slip.end_date = month_end_date
salary_slip.start_date = get_first_day(nowdate())
salary_slip.end_date = get_last_day(nowdate())
salary_slip.save()
salary_slip.submit()
salary_slip.reload()
@@ -402,18 +368,7 @@ class TestSalarySlip(FrappeTestCase):
)
# mark employee absent for a day since this case works fine if payment days are equal to working days
month_start_date = get_first_day(nowdate())
month_end_date = get_last_day(nowdate())
first_sunday = frappe.db.sql(
"""
select holiday_date from `tabHoliday`
where parent = 'Salary Slip Test Holiday List'
and holiday_date between %s and %s
order by holiday_date
""",
(month_start_date, month_end_date),
)[0][0]
first_sunday = get_first_sunday()
mark_attendance(
employee, add_days(first_sunday, 1), "Absent", ignore_validate=True
@@ -1032,6 +987,42 @@ class TestSalarySlip(FrappeTestCase):
components = [row.salary_component for row in new_ss.get("earnings")]
self.assertNotIn("Statistical Component", components)
@change_settings(
"Payroll Settings",
{"payroll_based_on": "Attendance", "consider_unmarked_attendance_as": "Present"},
)
def test_statistical_component_based_on_payment_days(self):
"""
Tests whether component using statistical component in the formula
gets the updated value based on payment days
"""
from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
create_salary_structure_assignment,
)
emp = make_employee("test_statistical_component@salary.com")
first_sunday = get_first_sunday()
mark_attendance(emp, add_days(first_sunday, 1), "Absent", ignore_validate=True)
salary_structure = make_salary_structure_for_payment_days_based_component_dependency(
test_statistical_comp=True
)
create_salary_structure_assignment(
emp, salary_structure.name, company="_Test Company", currency="INR"
)
# make salary slip and assert payment days
ss = make_salary_slip_for_payment_days_dependency_test(
"test_statistical_component@salary.com", salary_structure.name
)
amount = precision = None
for entry in ss.earnings:
if entry.salary_component == "Dependency Component":
amount = entry.amount
precision = entry.precision("amount")
break
self.assertEqual(amount, flt((1000 * ss.payment_days / ss.total_working_days) * 0.5, precision))
def make_activity_for_employee(self):
activity_type = frappe.get_doc("Activity Type", "_Test Activity Type")
activity_type.billing_rate = 50
@@ -1151,7 +1142,11 @@ def create_account(account_name, company, parent_account, account_type=None):
def make_earning_salary_component(
setup=False, test_tax=False, company_list=None, include_flexi_benefits=False
setup=False,
test_tax=False,
company_list=None,
include_flexi_benefits=False,
test_statistical_comp=False,
):
data = [
{
@@ -1517,7 +1512,7 @@ def make_holiday_list(list_name=None, from_date=None, to_date=None):
return holiday_list
def make_salary_structure_for_payment_days_based_component_dependency():
def make_salary_structure_for_payment_days_based_component_dependency(test_statistical_comp=False):
earnings = [
{
"salary_component": "Basic Salary - Payment Days",
@@ -1535,6 +1530,27 @@ def make_salary_structure_for_payment_days_based_component_dependency():
"formula": "base * 0.20",
},
]
if test_statistical_comp:
earnings.extend(
[
{
"salary_component": "Statistical Component",
"abbr": "SC",
"type": "Earning",
"statistical_component": 1,
"amount": 1000,
"depends_on_payment_days": 1,
},
{
"salary_component": "Dependency Component",
"abbr": "DC",
"type": "Earning",
"amount_based_on_formula": 1,
"formula": "SC * 0.5",
"depends_on_payment_days": 0,
},
]
)
make_salary_component(earnings, False, company_list=["_Test Company"])

View File

@@ -962,6 +962,7 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
target.additional_discount_percentage = 0.0
target.discount_amount = 0.0
target.inter_company_order_reference = ""
target.shipping_rule = ""
default_price_list = frappe.get_value("Supplier", supplier, "default_price_list")
if default_price_list:
@@ -1080,6 +1081,7 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
target.additional_discount_percentage = 0.0
target.discount_amount = 0.0
target.inter_company_order_reference = ""
target.shipping_rule = ""
target.customer = ""
target.customer_name = ""
target.run_method("set_missing_values")

View File

@@ -174,7 +174,7 @@ class PickList(Document):
frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx))
item_code = item.item_code
reference = item.sales_order_item or item.material_request_item
key = (item_code, item.uom, reference)
key = (item_code, item.uom, item.warehouse, reference)
item.idx = None
item.name = None

View File

@@ -58,6 +58,21 @@ frappe.ui.form.on('Repost Item Valuation', {
}
frm.trigger('show_reposting_progress');
if (frm.doc.status === 'Queued' && frm.doc.docstatus === 1) {
frm.trigger('execute_reposting');
}
},
execute_reposting(frm) {
frm.add_custom_button(__("Start Reposting"), () => {
frappe.call({
method: 'erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.execute_repost_item_valuation',
callback: function() {
frappe.msgprint(__('Reposting has been started in the background.'));
}
});
});
},
show_reposting_progress: function(frm) {

View File

@@ -302,3 +302,9 @@ def in_configured_timeslot(repost_settings=None, current_time=None):
return end_time >= now_time >= start_time
else:
return now_time >= start_time or now_time <= end_time
@frappe.whitelist()
def execute_repost_item_valuation():
"""Execute repost item valuation via scheduler."""
frappe.get_doc("Scheduled Job Type", "repost_item_valuation.repost_entries").enqueue(force=True)

File diff suppressed because it is too large Load Diff