- 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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ce4023ca59
commit
f83724b995
@@ -0,0 +1,78 @@
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user