276 lines
8.6 KiB
TypeScript
276 lines
8.6 KiB
TypeScript
/** 加班套件运行时:时长可按起止自动计算,支持手改;加班人默认当前登录人 */
|
|
|
|
import {
|
|
buildCurrentOwnerPerson,
|
|
type OwnerUserSnapshot
|
|
} from '@/utils/oa-owner-field-defaults'
|
|
import {
|
|
calcOvertimeHours,
|
|
DEFAULT_OVERTIME_WINDOWS,
|
|
type OvertimeWindows,
|
|
type RestDayResolver
|
|
} from '@/utils/oa-overtime-hours'
|
|
|
|
export const OVERTIME_MANUAL_PREFIX = '__manual_'
|
|
|
|
function asRules(schema: unknown): Record<string, unknown>[] {
|
|
if (Array.isArray(schema)) return schema.filter((r) => r && typeof r === 'object') as any
|
|
if (schema && typeof schema === 'object') {
|
|
const s = schema as any
|
|
const rules = s.rule || s.rules || s.fields
|
|
if (Array.isArray(rules)) return rules.filter((r: any) => r && typeof r === 'object')
|
|
if (s.type || s.field) return [s]
|
|
}
|
|
return []
|
|
}
|
|
|
|
function walk(rules: Record<string, unknown>[], out: Record<string, unknown>[]) {
|
|
for (const r of rules) {
|
|
out.push(r)
|
|
if (Array.isArray(r.children)) {
|
|
walk(r.children as any, out)
|
|
}
|
|
const props = r.props as any
|
|
if (props && Array.isArray(props.rule)) walk(props.rule, out)
|
|
}
|
|
}
|
|
|
|
export type OvertimeKitBinding = {
|
|
personField?: string
|
|
startField: string
|
|
endField: string
|
|
durationField: string
|
|
windows: OvertimeWindows
|
|
}
|
|
|
|
function resolveBinding(kit: Record<string, unknown>): OvertimeKitBinding | null {
|
|
const props = (kit.props && typeof kit.props === 'object' ? kit.props : {}) as Record<
|
|
string,
|
|
any
|
|
>
|
|
let personField = props.personField ? String(props.personField) : undefined
|
|
let startField = props.startField ? String(props.startField) : undefined
|
|
let endField = props.endField ? String(props.endField) : undefined
|
|
let durationField = props.durationField ? String(props.durationField) : undefined
|
|
|
|
const children = (
|
|
Array.isArray(kit.children)
|
|
? kit.children
|
|
: Array.isArray(props.rule)
|
|
? props.rule
|
|
: []
|
|
) as Record<string, any>[]
|
|
|
|
for (const c of children) {
|
|
if (!c?.field) continue
|
|
const sem = String(c.props?._fc_kit_field || '')
|
|
if (sem === 'overtimePerson' && !personField) personField = String(c.field)
|
|
if (sem === 'overtimeStart' && !startField) startField = String(c.field)
|
|
if (sem === 'overtimeEnd' && !endField) endField = String(c.field)
|
|
if (sem === 'overtimeDuration' && !durationField) durationField = String(c.field)
|
|
}
|
|
|
|
if (!startField || !endField || !durationField) {
|
|
const dateFields: string[] = []
|
|
let dur: string | undefined
|
|
for (const c of children) {
|
|
if (!c?.field) continue
|
|
const t = String(c.type || '')
|
|
if (t === 'datePicker' || t === 'date' || t === 'timePicker' || t === 'time') {
|
|
dateFields.push(String(c.field))
|
|
} else if (
|
|
(t === 'inputNumber' || t === 'number') &&
|
|
(String(c.title || '').includes('时长') || String(c.props?._fc_kit_field || '') === 'overtimeDuration')
|
|
) {
|
|
dur = String(c.field)
|
|
}
|
|
}
|
|
startField = startField || dateFields[0]
|
|
endField = endField || dateFields[1]
|
|
durationField = durationField || dur
|
|
}
|
|
|
|
if (!startField || !endField || !durationField) return null
|
|
|
|
return {
|
|
personField,
|
|
startField,
|
|
endField,
|
|
durationField,
|
|
windows: {
|
|
workdayOvertimeStart: String(
|
|
props.workdayOvertimeStart || DEFAULT_OVERTIME_WINDOWS.workdayOvertimeStart
|
|
),
|
|
workdayOvertimeEnd: String(
|
|
props.workdayOvertimeEnd || DEFAULT_OVERTIME_WINDOWS.workdayOvertimeEnd
|
|
),
|
|
restBreakStart: String(props.restBreakStart || DEFAULT_OVERTIME_WINDOWS.restBreakStart),
|
|
restBreakEnd: String(props.restBreakEnd || DEFAULT_OVERTIME_WINDOWS.restBreakEnd)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function listOvertimeKitBindings(schema: unknown): OvertimeKitBinding[] {
|
|
const flat: Record<string, unknown>[] = []
|
|
walk(asRules(schema), flat)
|
|
const out: OvertimeKitBinding[] = []
|
|
for (const r of flat) {
|
|
if (String(r.type || '') !== 'overtimeKit') continue
|
|
const b = resolveBinding(r)
|
|
if (b) out.push(b)
|
|
}
|
|
return out
|
|
}
|
|
|
|
function isEmptyPersonValue(raw: unknown): boolean {
|
|
if (raw == null || raw === '') return true
|
|
if (Array.isArray(raw)) return raw.length === 0
|
|
if (typeof raw === 'object') {
|
|
const o = raw as Record<string, unknown>
|
|
return !String(o.id || '').trim()
|
|
}
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* 发起页:加班人字段为空时写入当前登录人(人员组件为多选,值为数组)。
|
|
*/
|
|
export function applyOvertimeKitDefaults(
|
|
schema: unknown,
|
|
formData: Record<string, unknown>,
|
|
user: OwnerUserSnapshot | null | undefined
|
|
): Record<string, unknown> {
|
|
const data = { ...formData }
|
|
const person = buildCurrentOwnerPerson(user)
|
|
if (!person) return data
|
|
for (const kit of listOvertimeKitBindings(schema)) {
|
|
const f = kit.personField
|
|
if (f && isEmptyPersonValue(data[f])) {
|
|
data[f] = [person]
|
|
}
|
|
}
|
|
return data
|
|
}
|
|
|
|
/** 未手改时写回时长;返回是否有变更 */
|
|
export function applyOvertimeKitDurationsToFormData(
|
|
schema: unknown,
|
|
formData: Record<string, unknown>,
|
|
isRestDay: RestDayResolver
|
|
): { data: Record<string, unknown>; changed: boolean } {
|
|
const data = { ...formData }
|
|
let changed = false
|
|
for (const kit of listOvertimeKitBindings(schema)) {
|
|
const manualKey = OVERTIME_MANUAL_PREFIX + kit.durationField
|
|
if (data[manualKey]) continue
|
|
const hours = calcOvertimeHours(
|
|
String(data[kit.startField] || ''),
|
|
String(data[kit.endField] || ''),
|
|
kit.windows,
|
|
isRestDay
|
|
)
|
|
if (hours == null) continue
|
|
if (data[kit.durationField] !== hours) {
|
|
data[kit.durationField] = hours
|
|
changed = true
|
|
}
|
|
}
|
|
return { data, changed }
|
|
}
|
|
|
|
export function markOvertimeDurationManual(
|
|
formData: Record<string, unknown>,
|
|
durationField: string,
|
|
manual = true
|
|
) {
|
|
const data = { ...formData }
|
|
const key = OVERTIME_MANUAL_PREFIX + durationField
|
|
if (manual) data[key] = true
|
|
else delete data[key]
|
|
return data
|
|
}
|
|
|
|
export function isOvertimeDurationField(schema: unknown, field: string): boolean {
|
|
return listOvertimeKitBindings(schema).some((k) => k.durationField === field)
|
|
}
|
|
|
|
export function findOvertimeKitByStartOrEnd(
|
|
schema: unknown,
|
|
field: string
|
|
): OvertimeKitBinding | null {
|
|
return (
|
|
listOvertimeKitBindings(schema).find(
|
|
(k) => k.startField === field || k.endField === field
|
|
) || null
|
|
)
|
|
}
|
|
|
|
/**
|
|
* 运行时补齐加班套件子规则:时长数字框默认左对齐。
|
|
*/
|
|
export function ensureOvertimeKitRuleProps(rules: unknown): any[] {
|
|
if (!Array.isArray(rules)) return []
|
|
return rules.map((raw) => {
|
|
if (!raw || typeof raw !== 'object') return raw
|
|
const rule: any = { ...(raw as object) }
|
|
if (String(rule.type || '') === 'overtimeKit') {
|
|
const binding = resolveBinding(rule)
|
|
const patchChildren = (list: any[]): any[] =>
|
|
list.map((c) => {
|
|
if (!c || typeof c !== 'object') return c
|
|
const child: any = { ...c }
|
|
const t = String(child.type || '')
|
|
const isDuration =
|
|
(binding && child.field === binding.durationField) ||
|
|
((t === 'inputNumber' || t === 'number') &&
|
|
String(child.title || '').includes('时长'))
|
|
if (isDuration) {
|
|
child.props = { ...(child.props || {}), align: 'left' }
|
|
const cls = String(child.class || child.className || '')
|
|
if (!cls.includes('oa-overtime-duration')) {
|
|
child.class = (cls ? cls + ' ' : '') + 'oa-overtime-duration'
|
|
}
|
|
}
|
|
if (Array.isArray(child.children)) {
|
|
child.children = patchChildren(child.children)
|
|
}
|
|
return child
|
|
})
|
|
if (Array.isArray(rule.children)) {
|
|
rule.children = patchChildren(rule.children)
|
|
}
|
|
if (Array.isArray(rule.props?.rule)) {
|
|
rule.props = {
|
|
...rule.props,
|
|
rule: patchChildren(rule.props.rule)
|
|
}
|
|
}
|
|
}
|
|
if (Array.isArray(rule.children)) {
|
|
rule.children = ensureOvertimeKitRuleProps(rule.children)
|
|
}
|
|
if (Array.isArray(rule.props?.rule)) {
|
|
rule.props = {
|
|
...rule.props,
|
|
rule: ensureOvertimeKitRuleProps(rule.props.rule)
|
|
}
|
|
}
|
|
return rule
|
|
})
|
|
}
|
|
|
|
/** 供类型检查 / 后续扩展:当前 schema 是否含加班套件 */
|
|
export function hasOvertimeKit(schema: unknown): boolean {
|
|
const flat: Record<string, unknown>[] = []
|
|
const walk = (rules: Record<string, unknown>[]) => {
|
|
for (const r of rules) {
|
|
flat.push(r)
|
|
if (Array.isArray(r.children)) walk(r.children as any)
|
|
const props = r.props as any
|
|
if (props && Array.isArray(props.rule)) walk(props.rule)
|
|
}
|
|
}
|
|
walk(asRules(schema))
|
|
return flat.some((r) => String(r.type || '') === 'overtimeKit')
|
|
}
|