<script lang="ts" setup>
|
import type { HrmOvertimeApi } from '#/api/hrm/overtime';
|
import type { FormType } from '../data';
|
|
import { computed, ref } from 'vue';
|
|
import { message } from 'ant-design-vue';
|
|
import { useVbenForm } from '#/adapter/form';
|
import {
|
createOvertime,
|
getCurrentOvertimeApplicant,
|
getOvertime,
|
updateOvertime,
|
} from '#/api/hrm/overtime';
|
import { $t } from '#/locales';
|
import { useVbenModal } from '#/packages/effects/common-ui/src';
|
|
import { useFormSchema } from '../data';
|
|
const emit = defineEmits(['success']);
|
const formType = ref<FormType>('create');
|
|
const isDetail = computed(() => formType.value === 'detail');
|
const getTitle = computed(() => {
|
if (isDetail.value) return '查看加班申请';
|
return formType.value === 'update' ? '修改加班申请' : '新增加班申请';
|
});
|
|
/** 根据时段、跨天标识和用餐扣除计算加班时长,并按 0.5 小时取整 */
|
function calculateDuration(values: Record<string, any>) {
|
const { startTime, endTime } = values;
|
if (!startTime || !endTime) return { crossDay: false, duration: 0 };
|
|
const toMinutes = (time: string) => {
|
const [hour = 0, minute = 0] = time.split(':').map(Number);
|
return hour * 60 + minute;
|
};
|
const startMinutes = toMinutes(startTime);
|
let endMinutes = toMinutes(endTime);
|
const crossDay = endMinutes <= startMinutes;
|
if (crossDay) endMinutes += 24 * 60;
|
|
const deductionMinutes = values.mealDeduction
|
? Number(values.mealDeductionHours || 0) * 60
|
: 0;
|
const actualHours = Math.max(0, (endMinutes - startMinutes - deductionMinutes) / 60);
|
return {
|
crossDay,
|
duration: Math.round(actualHours * 2) / 2,
|
};
|
}
|
|
const [Form, formApi] = useVbenForm({
|
commonConfig: {
|
componentProps: { class: 'w-full' },
|
formItemClass: 'col-span-1',
|
labelWidth: 120,
|
},
|
wrapperClass: 'grid-cols-2',
|
layout: 'horizontal',
|
schema: [],
|
scrollToFirstError: true,
|
showDefaultActions: false,
|
handleValuesChange: (values, changedFields) => {
|
const durationFields = [
|
'startTime',
|
'endTime',
|
'mealDeduction',
|
'mealDeductionHours',
|
];
|
if (changedFields.some((field) => durationFields.includes(field))) {
|
const result = calculateDuration(values);
|
void formApi.setValues(result);
|
}
|
if (changedFields.includes('relatedType') && !values.relatedType) {
|
void formApi.setFieldValue('relatedCode', undefined);
|
}
|
},
|
});
|
|
async function setCurrentEmployee() {
|
const employee = await getCurrentOvertimeApplicant();
|
await formApi.setValues({
|
employeeId: employee.employeeId,
|
employeeName: employee.employeeName,
|
employeeNo: employee.employeeNo,
|
deptName: employee.deptName,
|
postName: employee.postName,
|
mealDeduction: false,
|
mealDeductionHours: 1,
|
crossDay: false,
|
duration: 0,
|
});
|
}
|
|
const [Modal, modalApi] = useVbenModal({
|
async onConfirm() {
|
if (isDetail.value) {
|
await modalApi.close();
|
return;
|
}
|
const { valid } = await formApi.validate();
|
if (!valid) return;
|
|
const data = (await formApi.getValues()) as HrmOvertimeApi.Overtime;
|
if (!data.employeeId) {
|
message.warning('当前账号未关联员工档案,暂时无法提交加班申请');
|
return;
|
}
|
if (!data.duration || data.duration <= 0) {
|
message.warning('加班时长必须大于 0 小时,请检查时段或用餐扣除');
|
return;
|
}
|
if (data.relatedType && !data.relatedCode?.trim()) {
|
message.warning('请选择关联类型后填写对应的项目或工单编号');
|
return;
|
}
|
|
modalApi.lock();
|
try {
|
if (data.id) await updateOvertime(data);
|
else await createOvertime(data);
|
await modalApi.close();
|
emit('success');
|
message.success($t('ui.actionMessage.operationSuccess'));
|
} finally {
|
modalApi.unlock();
|
}
|
},
|
async onOpenChange(isOpen: boolean) {
|
if (!isOpen) return;
|
|
const data = modalApi.getData<{ formType: FormType; id?: number }>();
|
formType.value = data.formType;
|
formApi.setState({ schema: useFormSchema() });
|
formApi.setDisabled(isDetail.value);
|
modalApi.setState({ showConfirmButton: !isDetail.value });
|
|
if (!data.id) {
|
await setCurrentEmployee();
|
return;
|
}
|
|
modalApi.lock();
|
try {
|
await formApi.setValues(await getOvertime(data.id));
|
} finally {
|
modalApi.unlock();
|
}
|
},
|
});
|
</script>
|
|
<template>
|
<Modal :title="getTitle" class="w-[900px]">
|
<Form class="mx-4" />
|
</Modal>
|
</template>
|