HR moves some leave values into LeavePolicySchedule, which is better than hiding every number inside methods. The schedule still expresses too few dimensions for local eligibility, source version, employee category, and complete effective history.
1. LeavePolicySchedule fields
| Field | Current meaning |
|---|---|
| AnnualLeaveTiers | (MinWorkYears, Days) list |
| MarriageDays | Marriage leave days |
| MaternityDays | National base plus local extension total |
| PaternityDays | Paternity/care leave days |
| BereavementDays | Bereavement leave days |
| Region | Free-form string |
| EffectiveFrom | Defaults to DateTime.Today |
There is no EffectiveTo, PolicyId/Version, source URI/hash, PublishedAt, ApprovedBy, employee category, birth order, difficult-labor increment, miscarriage rules, or supersession history.
2. Creation validation
Create rejects empty tiers, non-strictly ascending MinWorkYears, and negative top-level leave-day values. It copies the tier array, and ValueObject equality includes every field and tier.
// Supply the date so the snapshot does not inherit a process-date default.var schedule = LeavePolicySchedule.Create( annualLeaveTiers: [(1, 5), (10, 10), (20, 15)], marriageDays: 3, maternityDays: 158, paternityDays: 15, bereavementDays: 3, region: "ExampleProvince", effectiveFrom: new DateOnly(2026, 1, 1));
// Structural success does not prove that an approved authority supports the values.if (schedule.IsFailure) return schedule.Error;return schedule.Value;Tier MinWorkYears and Days can themselves be negative. Region is not trimmed unless entirely blank. There is no maximum tier count or leave-day bound.
3. Four presets
LeavePolicies exposes National, Guangdong, Shanghai, and Beijing, with Default=National. Static initialization supplies no EffectiveFrom, so every process creates snapshots dated on its startup day.
The same binary therefore yields unequal policy value objects on different days, and the date is not a legal effective date. Comments describe the presets as 2024 reference values; they must not silently be treated as current in a 2026 product.
4. Statutory annual-leave tiers
National tiers are 5 days at one year, 10 at ten, and 15 at twenty, matching the official regulation. The algorithm walks ascending tiers and returns zero below one year.
Method XML instead says below one year gets five days, one-to-ten gets ten, and over ten gets fifteen. That contradicts both executable data and the authority. Code and authority are the documentation truth; the XML needs correction.
5. Month approximation is not the official calendar formula
CalcAnnualLeaveProrated accepts 0–12 worked months and floors fullDays × months / 12. Official measures use remaining calendar days for a new hire and elapsed calendar days minus leave already arranged for termination.
var approximate = LeaveCalculator.CalcAnnualLeaveProrated( totalWorkYears: 12m, workedMonths: 7, schedule: LeavePolicies.National);
// Current value is floor(10×7/12)=5; hire date and leave already taken are absent.if (approximate.IsFailure) return approximate.Error;
// Formal settlement needs a calendar-day engine and source dates.return new AdvisoryLeaveEstimate(approximate.Value);The source comment itself calls this a simplification, so it is not suitable for exact wage compensation or dispute resolution.
6. Marriage, paternity, and bereavement
The getters return schedule values directly. They do not model eligibility, registration date, relationship, travel leave, local conditions, employer-better policy, or policy selection by event date.
The wrapper must resolve employment jurisdiction and date, then combine statutory floor and employer policy. Passing no schedule silently uses National and can understate a local entitlement.
7. Maternity calculation
The engine starts with policy.MaternityDays, always adds 15 for difficult labor, and adds 15 per additional baby. The national special rules support these baseline increments.
Local implementations can define different increments or conditions. The schedule stores only total MaternityDays, so it cannot express a regional difficult-labor rule. A recent Guangdong implementation rule is an example of why this needs structured local policy.
8. Multiple-birth bounds
Counts below one fail. There is no upper bound, and (count-1)*15 can overflow int. Public wrappers need a reasonable maximum and checked arithmetic.
The policy also omits miscarriage leave by gestational age, nursing time, whether rewards stack, and spouse-care eligibility.
9. Medical-period matrix
CalcMedicalPeriodMonths returns 3/6/9/12/18/24 based on total and current-company service. Negative values fail. It does not require current-company years to be no greater than total years.
It returns only the maximum-month category, not accumulated sick leave, computation period, local policy, pay treatment, or occupational-injury classification.
10. Missing policy resolution
Callers manually pass a schedule; omission uses National. There is no Region + event-date resolution or rejection of expired/future policy. A Shanghai event can silently use National if the caller forgets one argument.
A fail-closed resolver requires a jurisdiction and selects exactly one Published policy covering the event date. Missing or overlapping policy is an Error rather than a reassuring but wrong default.
11. Target policy model
var policy = await policies.ResolveAsync(new LeavePolicyQuery( JurisdictionCode: employment.JurisdictionCode, LeaveType: LeaveType.Maternity, OccurredOn: birthDate, EmployeeCategory: employment.Category), cancellationToken);
// Published policy carries version, source hash, effective interval, and structured rules.if (policy.IsFailure) return policy.Error;
// The result keeps each addition and policy-selection reason, not just a day count.return leaveEngine.Calculate(facts, policy.Value) .WithEvidence(policy.Value.Version, policy.Value.SourceHash);This is a GA target, not the current API.
12. Regulatory-change workflow
HR/legal discovers a change → retain official text and hash → structure a draft → two-person review → golden cases → assign EffectiveFrom/To → publish → supersede but retain old policy → report affected calculations.
Never scrape legislation at startup and activate it automatically. External text needs trusted-source validation, legal interpretation, and approval.
13. Test matrix
| Scenario | Must prove |
|---|---|
| Tier | 1/10/20 boundaries, negative tier, disorder, duplicates |
| Proration | Leap year, hire/termination date, leave taken, approximation delta |
| Maternity | National/local, difficult labor, multiple birth, overflow, stacking |
| Region | Missing, unknown, expired, overlapping, future policy |
| Medical | Every matrix boundary and company service > total service |
| Value object | Same published version equals across process and date |
| Source | Hash change creates review, not automatic activation |
| History | Old events always resolve the historical policy |
No automated test currently exists.
14. Inspection commands
# Schedule fields, dynamic default date, presets, and fixed increments.rg -n "DateTime.Today|LeavePolicies|MaternityDays|days \+= 15|workedMonths / 12m" \ src/Platform/IndustryExtensions/BitzOrcas.Platform.IndustryExtensions.Hr -g '*.cs'
# Version, end date, source evidence, and tests should currently have no matches.rg -n "PolicyVersion|EffectiveTo|SourceHash|ApprovedBy|\[Fact\]" \ src/Platform/IndustryExtensions/BitzOrcas.Platform.IndustryExtensions.Hr tests -g '*.cs'Industry Extensions overview · Finance calculators · Auction and GA