Update staffing management to enhance payroll and hiring functionalities
ci / server (push) Failing after 3m39s
ci / client (push) Successful in 14s

- Increased the `MonthlyPayrollCap` from 10,000 to 40,000, allowing for greater flexibility in hiring and subject assignments.
- Revised payroll calculation logic to ensure that staff members are compensated based on their actual weekly hours, with a minimum payment reflecting one full rate.
- Updated documentation to clarify the new payroll structure and its implications for hiring and subject assignments.
- Enhanced tests to validate the new payroll cap and ensure proper functionality in staffing scenarios, including the handling of uncovered subjects.
- Improved localization strings to reflect changes in staffing and payroll terminology.
This commit is contained in:
Leonid Pershin
2026-08-19 12:51:38 +03:00
parent d61240d5c1
commit 25ce26064d
20 changed files with 376 additions and 112 deletions
+7 -3
View File
@@ -211,6 +211,7 @@ internal sealed record StaffMemberResponse(
string Position,
string PositionLabel,
float HourlyWageAsk,
float WeeklyHours,
float MonthlyPay,
IReadOnlyList<DefLabelResponse> Subjects);
@@ -227,7 +228,7 @@ internal static class StaffingMapper
roster ??= new Roster([], [], []);
pool ??= ApplicantPool.Empty;
var rules = catalog?.StaffingRules;
var payroll = rules is null ? 0f : Staffing.Payroll(rules, roster.People);
var payroll = rules is null || catalog is null ? 0f : Staffing.Payroll(catalog, rules, roster);
var remaining = MathF.Round(MathF.Max(0f, allocated - payroll), 2);
var uncovered = catalog is null
@@ -257,7 +258,7 @@ internal static class StaffingMapper
.Where(person => person.IsStaff)
.OrderBy(person => person.Name.Surname, StringComparer.Ordinal)
.ThenBy(person => person.Id, StringComparer.Ordinal)
.Select(person => Member(person, catalog, rules, locale, asOf))
.Select(person => Member(person, roster, catalog, rules, locale, asOf))
.ToArray();
var positions = catalog is null
@@ -300,13 +301,15 @@ internal static class StaffingMapper
private static StaffMemberResponse Member(
Person person,
Roster roster,
DefCatalog? catalog,
StaffingDef? rules,
string locale,
DateTime asOf)
{
var ask = person.HourlyWageAsk ?? 0f;
var pay = rules is null ? 0f : Staffing.MonthlyPay(rules, ask, person.Subjects.Count);
var hours = catalog is null ? 0f : Staffing.WeeklyHours(catalog, roster, person);
var pay = rules is null ? 0f : Staffing.MonthlyPay(rules, ask, hours);
var subjects = person.Subjects
.Select(name => new DefLabelResponse(
name,
@@ -324,6 +327,7 @@ internal static class StaffingMapper
person.Position ?? string.Empty,
PeopleListMapper.PositionLabel(catalog, locale, person.Position) ?? person.Position ?? string.Empty,
ask,
MathF.Round(hours, 1),
pay,
subjects);
}
+20 -20
View File
@@ -1,20 +1,20 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Simulation": {
"TickRate": 20,
"MaxSchools": 6,
"GameMinutesPerRealSecond": 5,
"DefaultStartDate": "2012-03-31T06:00:00",
"SavesDirectory": "saves",
"ModsDirectory": "mods",
"SaveIntervalSeconds": 30,
"MonthlyPayrollCap": 10000,
"SchoolWeekDays": 5
}
}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Simulation": {
"TickRate": 20,
"MaxSchools": 6,
"GameMinutesPerRealSecond": 5,
"DefaultStartDate": "2012-03-31T06:00:00",
"SavesDirectory": "saves",
"ModsDirectory": "mods",
"SaveIntervalSeconds": 30,
"MonthlyPayrollCap": 40000,
"SchoolWeekDays": 5
}
}
@@ -5,7 +5,10 @@
"parentChance": 0.35,
"hourlyWageBase": 30,
"hourlyWagePerSkill": 0.6,
// One ставка. A hired person costs this much even with no subjects, and hours beyond it are
// paid on top — that is what makes loading one teacher instead of hiring two cost money.
"baseWeeklyHours": 20,
// Nobody can carry more than this. Past it the subject needs a second teacher.
"maxWeeklyHours": 36,
"weeksPerMonth": 4,
"extraSubjectSurcharge": 0.25,
}