All checks were successful
build-and-push / build (push) Successful in 39s
- config/db/storage/auth/router/perms: eQMS 규약 미러링, 권한 2-tier (관리자 전체 / 구성원 본인·신청만), oauth2-proxy 헤더 인증 + DEV_AUTH mock - 모델: 구성원/부서, 근무(출퇴근·휴가·공가·초과), 프로젝트(회사/제품/버전· 작업자portion·담당자·태스크·계약·첨부·분할입금), 인센티브(설정·단계· 유저배분·분기정산), 회계(거래·세금) - internal/worktime: 근로기준법 월 집계 엔진 - internal/incentive: BE/non-BE × 계약금/중도금/잔금 3단계 계산 + 시뮬레이션 - 시드 데이터, Go 멀티스테이지 Dockerfile - ADMIN_GROUPS 기본값 'admin' (전 내부 앱 공통 그룹) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79 lines
2.7 KiB
Go
79 lines
2.7 KiB
Go
// 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,
|
||
}
|
||
}
|