mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-09 23:39:28 +00:00
Merge pull request #58902 from frappe/mergify/bp/version-16-hotfix/pr-58792
fix: calculate holiday list totals for half days (backport #58792)
This commit is contained in:
@@ -506,3 +506,4 @@ erpnext.patches.v16_0.rename_secondary_item_type_field
|
||||
erpnext.patches.v16_0.append_fieldname_to_pos_search_fields
|
||||
erpnext.patches.v16_0.set_secondary_item_valuation_type
|
||||
erpnext.patches.v16_0.add_transaction_roles_to_sms_settings
|
||||
erpnext.patches.v16_0.recalculate_holiday_list_totals
|
||||
|
||||
18
erpnext/patches/v16_0/recalculate_holiday_list_totals.py
Normal file
18
erpnext/patches/v16_0/recalculate_holiday_list_totals.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import frappe
|
||||
from frappe.query_builder import Case
|
||||
from frappe.query_builder.functions import Coalesce, Sum
|
||||
|
||||
|
||||
def execute():
|
||||
holiday_list = frappe.qb.DocType("Holiday List")
|
||||
holiday = frappe.qb.DocType("Holiday")
|
||||
total_holidays = (
|
||||
frappe.qb.from_(holiday)
|
||||
.select(Sum(Case().when(holiday.is_half_day == 1, 0.5).else_(1)))
|
||||
.where(
|
||||
(holiday.parent == holiday_list.name)
|
||||
& (holiday.parenttype == "Holiday List")
|
||||
& (holiday.parentfield == "holidays")
|
||||
)
|
||||
)
|
||||
frappe.qb.update(holiday_list).set(holiday_list.total_holidays, Coalesce(total_holidays, 0)).run()
|
||||
@@ -1,11 +1,18 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
function update_total_holidays(frm) {
|
||||
let total_holidays = 0;
|
||||
for (const holiday of frm.doc.holidays || []) {
|
||||
total_holidays += holiday.is_half_day ? 0.5 : 1;
|
||||
}
|
||||
frm.doc.total_holidays = total_holidays;
|
||||
frm.refresh_field("total_holidays");
|
||||
}
|
||||
|
||||
frappe.ui.form.on("Holiday List", {
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.holidays) {
|
||||
frm.set_value("total_holidays", frm.doc.holidays.length);
|
||||
}
|
||||
update_total_holidays(frm);
|
||||
|
||||
frm.call("get_supported_countries").then((r) => {
|
||||
frm.subdivisions_by_country = r.message.subdivisions_by_country;
|
||||
@@ -43,6 +50,18 @@ frappe.ui.form.on("Holiday List", {
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Holiday", {
|
||||
holidays_add: function (frm) {
|
||||
update_total_holidays(frm);
|
||||
},
|
||||
holidays_remove: function (frm) {
|
||||
update_total_holidays(frm);
|
||||
},
|
||||
is_half_day: function (frm) {
|
||||
update_total_holidays(frm);
|
||||
},
|
||||
});
|
||||
|
||||
frappe.tour["Holiday List"] = [
|
||||
{
|
||||
fieldname: "holiday_list_name",
|
||||
|
||||
@@ -58,9 +58,10 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "total_holidays",
|
||||
"fieldtype": "Int",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Total Holidays",
|
||||
"precision": "1",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ from datetime import date
|
||||
import frappe
|
||||
from frappe import _, throw
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import formatdate, getdate, today
|
||||
from frappe.utils import cint, formatdate, getdate, today
|
||||
|
||||
|
||||
class OverlapError(frappe.ValidationError):
|
||||
@@ -34,7 +34,7 @@ class HolidayList(Document):
|
||||
is_half_day: DF.Check
|
||||
subdivision: DF.Autocomplete | None
|
||||
to_date: DF.Date
|
||||
total_holidays: DF.Int
|
||||
total_holidays: DF.Float
|
||||
weekly_off: DF.Literal[
|
||||
"", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
|
||||
]
|
||||
@@ -42,10 +42,13 @@ class HolidayList(Document):
|
||||
|
||||
def validate(self):
|
||||
self.validate_days()
|
||||
self.total_holidays = len(self.holidays)
|
||||
self.update_total_holidays()
|
||||
self.validate_duplicate_date()
|
||||
self.sort_holidays()
|
||||
|
||||
def update_total_holidays(self):
|
||||
self.total_holidays = sum(0.5 if cint(holiday.is_half_day) else 1 for holiday in self.holidays)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_weekly_off_dates(self):
|
||||
if not self.weekly_off:
|
||||
@@ -67,6 +70,8 @@ class HolidayList(Document):
|
||||
},
|
||||
)
|
||||
|
||||
self.update_total_holidays()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_supported_countries(self):
|
||||
from holidays.utils import list_supported_countries
|
||||
@@ -108,6 +113,8 @@ class HolidayList(Document):
|
||||
"holidays", {"description": holiday_name, "holiday_date": holiday_date, "weekly_off": 0}
|
||||
)
|
||||
|
||||
self.update_total_holidays()
|
||||
|
||||
def sort_holidays(self):
|
||||
self.holidays.sort(key=lambda x: getdate(x.holiday_date))
|
||||
for i in range(len(self.holidays)):
|
||||
@@ -153,6 +160,7 @@ class HolidayList(Document):
|
||||
@frappe.whitelist()
|
||||
def clear_table(self):
|
||||
self.set("holidays", [])
|
||||
self.update_total_holidays()
|
||||
|
||||
def validate_duplicate_date(self):
|
||||
unique_dates = []
|
||||
|
||||
@@ -5,7 +5,7 @@ from contextlib import contextmanager
|
||||
from datetime import date, timedelta
|
||||
|
||||
import frappe
|
||||
from frappe.utils import getdate
|
||||
from frappe.utils import get_datetime, getdate
|
||||
|
||||
from erpnext.setup.doctype.holiday_list.holiday_list import local_country_name
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
@@ -46,6 +46,94 @@ class TestHolidayList(ERPNextTestSuite):
|
||||
self.assertIn(date(2023, 2, 26), holidays)
|
||||
self.assertNotIn(date(2023, 3, 5), holidays)
|
||||
|
||||
def test_total_holidays_includes_half_days(self):
|
||||
holiday_list = make_holiday_list(
|
||||
"test_half_day_holiday_list",
|
||||
from_date="2023-01-01",
|
||||
to_date="2023-01-03",
|
||||
holiday_dates=[
|
||||
{"holiday_date": "2023-01-01", "description": "Full-day holiday"},
|
||||
{
|
||||
"holiday_date": "2023-01-02",
|
||||
"description": "Half-day holiday",
|
||||
"is_half_day": 1,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
self.assertEqual(holiday_list.total_holidays, 1.5)
|
||||
self.assertEqual(frappe.db.get_value("Holiday List", holiday_list.name, "total_holidays"), 1.5)
|
||||
|
||||
def test_weekly_off_updates_total_without_saving(self):
|
||||
holiday_list = frappe.new_doc("Holiday List")
|
||||
holiday_list.from_date = "2023-01-01"
|
||||
holiday_list.to_date = "2023-01-14"
|
||||
holiday_list.weekly_off = "Saturday"
|
||||
holiday_list.is_half_day = 1
|
||||
holiday_list.append("holidays", {"holiday_date": "2023-01-01", "description": "Full day"})
|
||||
|
||||
holiday_list.get_weekly_off_dates()
|
||||
self.assertEqual(len(holiday_list.holidays), 3)
|
||||
self.assertEqual(holiday_list.total_holidays, 2)
|
||||
|
||||
holiday_list.get_weekly_off_dates()
|
||||
self.assertEqual(len(holiday_list.holidays), 3)
|
||||
self.assertEqual(holiday_list.total_holidays, 2)
|
||||
|
||||
holiday_list.clear_table()
|
||||
self.assertEqual(holiday_list.holidays, [])
|
||||
self.assertEqual(holiday_list.total_holidays, 0)
|
||||
|
||||
def test_local_holidays_updates_total_without_saving(self):
|
||||
holiday_list = frappe.new_doc("Holiday List")
|
||||
holiday_list.from_date = "2023-01-01"
|
||||
holiday_list.to_date = "2023-01-02"
|
||||
holiday_list.country = "DE"
|
||||
holiday_list.append(
|
||||
"holidays", {"holiday_date": "2023-01-02", "description": "Half day", "is_half_day": 1}
|
||||
)
|
||||
|
||||
holiday_list.get_local_holidays()
|
||||
self.assertEqual(len(holiday_list.holidays), 2)
|
||||
self.assertEqual(holiday_list.total_holidays, 1.5)
|
||||
|
||||
holiday_list.get_local_holidays()
|
||||
self.assertEqual(len(holiday_list.holidays), 2)
|
||||
self.assertEqual(holiday_list.total_holidays, 1.5)
|
||||
|
||||
def test_recalculate_existing_holiday_list_totals(self):
|
||||
from erpnext.patches.v16_0.recalculate_holiday_list_totals import execute
|
||||
|
||||
cases = (("mixed", [0, 1], 1.5), ("half", [1, 1, 1], 1.5), ("full", [0, 0], 2), ("empty", [], 0))
|
||||
holiday_lists = []
|
||||
for name, half_days, expected in cases:
|
||||
holiday_list = make_holiday_list(
|
||||
f"test_backfill_holidays_{name}",
|
||||
from_date="2023-01-01",
|
||||
to_date="2023-01-03",
|
||||
holiday_dates=[
|
||||
{
|
||||
"holiday_date": date(2023, 1, idx),
|
||||
"description": "Test holiday",
|
||||
"is_half_day": is_half_day,
|
||||
}
|
||||
for idx, is_half_day in enumerate(half_days, start=1)
|
||||
],
|
||||
)
|
||||
# Simulate totals persisted by the old controller, including a stale empty list.
|
||||
holiday_list.db_set("total_holidays", len(half_days) or 1, update_modified=False)
|
||||
holiday_lists.append((holiday_list, expected))
|
||||
|
||||
for _ in range(2):
|
||||
execute()
|
||||
for holiday_list, expected in holiday_lists:
|
||||
with self.subTest(holiday_list=holiday_list.name):
|
||||
total, modified = frappe.db.get_value(
|
||||
"Holiday List", holiday_list.name, ["total_holidays", "modified"]
|
||||
)
|
||||
self.assertEqual(total, expected)
|
||||
self.assertEqual(modified, get_datetime(holiday_list.modified))
|
||||
|
||||
def test_local_holidays(self):
|
||||
holiday_list = frappe.new_doc("Holiday List")
|
||||
holiday_list.from_date = "2022-01-01"
|
||||
|
||||
Reference in New Issue
Block a user