// Package worktime computes monthly working-time roll-ups under the Korean Labor // Standards Act model: a 주 소정근로시간 (default 40h) baseline, business-day // expansion for the month, plus recognized leave/overtime. package worktime import "time" // MonthBusinessDays returns the count of weekdays (Mon–Fri) in the given month. // Public holidays are out of scope here; admins can adjust via leave/공가. func MonthBusinessDays(year int, month time.Month) int { n := 0 d := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC) for d.Month() == month { if wd := d.Weekday(); wd != time.Saturday && wd != time.Sunday { n++ } d = d.AddDate(0, 0, 1) } return n } // StandardMinutes is the expected worked minutes for the month: business days × // the policy's daily standard minutes. func StandardMinutes(year int, month time.Month, dailyStandardMin int) int { if dailyStandardMin <= 0 { dailyStandardMin = 480 } return MonthBusinessDays(year, month) * dailyStandardMin } // NetWorkMinutes computes worked minutes between clock-in and clock-out minus an // unpaid break. Returns 0 if either bound is missing or the span is negative. func NetWorkMinutes(in, out *time.Time, lunchMinutes int) int { if in == nil || out == nil { return 0 } mins := int(out.Sub(*in).Minutes()) - lunchMinutes if mins < 0 { return 0 } return mins } // Summary is the monthly timesheet roll-up returned to the UI. type Summary struct { Year int `json:"year"` Month int `json:"month"` BusinessDays int `json:"businessDays"` StandardMinutes int `json:"standardMinutes"` WorkedMinutes int `json:"workedMinutes"` LeaveMinutes int `json:"leaveMinutes"` // recognized (연차/공가 등) as worked-equivalent OvertimeMinutes int `json:"overtimeMinutes"` // approved overtime RecognizedTotal int `json:"recognizedTotal"` // worked + leave FulfillmentPct float64 `json:"fulfillmentPct"` // recognizedTotal / standard DaysPresent int `json:"daysPresent"` } // Compute assembles the summary from raw minute inputs. func Compute(year, month, dailyStandardMin, worked, leave, overtime, daysPresent int) Summary { std := StandardMinutes(year, time.Month(month), dailyStandardMin) recognized := worked + leave pct := 0.0 if std > 0 { pct = float64(recognized) / float64(std) * 100 } return Summary{ Year: year, Month: month, BusinessDays: MonthBusinessDays(year, time.Month(month)), StandardMinutes: std, WorkedMinutes: worked, LeaveMinutes: leave, OvertimeMinutes: overtime, RecognizedTotal: recognized, FulfillmentPct: pct, DaysPresent: daysPresent, } }