@xiaoyuanhao/yunzhi-cli 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/arguments.cjs +7 -4
- package/lib/attendance-clock-entry.cjs +31 -0
- package/lib/attendance-configuration-entry.cjs +35 -0
- package/lib/attendance-correction-applications.cjs +64 -0
- package/lib/attendance-correction-approval.cjs +135 -0
- package/lib/attendance-correction-start.cjs +47 -0
- package/lib/attendance-leave-enums.cjs +193 -0
- package/lib/attendance-leave-ids.cjs +15 -0
- package/lib/attendance-leave-list-filters.cjs +21 -0
- package/lib/attendance-leave-route.cjs +20 -0
- package/lib/attendance-operation-result.cjs +97 -0
- package/lib/attendance-report-inbox.cjs +42 -0
- package/lib/attendance-report-remarks.cjs +62 -0
- package/lib/attendance-statistics.cjs +49 -0
- package/lib/attendance.cjs +404 -0
- package/lib/auth.cjs +1 -1
- package/lib/business-schemas.cjs +3 -1
- package/lib/cli.cjs +294 -1
- package/lib/command-guidance.cjs +1162 -1
- package/lib/command-reference.cjs +54 -0
- package/lib/contact-help.cjs +43 -0
- package/lib/contact-mobile.cjs +14 -0
- package/lib/contact.cjs +85 -0
- package/lib/correction-help.cjs +57 -0
- package/lib/dormitory-replenishment.cjs +63 -0
- package/lib/dormitory-report-detail.cjs +76 -0
- package/lib/dormitory-report-id.cjs +8 -0
- package/lib/dormitory-report-summary.cjs +66 -0
- package/lib/dormitory-report-view.cjs +36 -0
- package/lib/dormitory-rooms.cjs +41 -0
- package/lib/dormitory-scope.cjs +18 -0
- package/lib/dormitory-statistics.cjs +30 -0
- package/lib/download.cjs +17 -8
- package/lib/gateway.cjs +9 -4
- package/lib/leave-approval-receipt.cjs +126 -0
- package/lib/leave-attachment-upload.cjs +110 -0
- package/lib/leave-form-users.cjs +48 -0
- package/lib/leave-start-receipt.cjs +64 -0
- package/lib/leave.cjs +457 -0
- package/lib/replenishment-records.cjs +39 -0
- package/lib/result.cjs +1 -0
- package/lib/saved-report-summary.cjs +16 -0
- package/lib/school-contacts.cjs +24 -0
- package/lib/school-help.cjs +53 -0
- package/lib/school-schema.cjs +38 -0
- package/lib/student-application-help.cjs +76 -0
- package/lib/student-leave-form.cjs +108 -0
- package/lib/student-leave-operations.cjs +24 -0
- package/lib/student-leave-query.cjs +74 -0
- package/lib/student-leave-resumption.cjs +2 -0
- package/lib/student-leave-start.cjs +34 -0
- package/lib/student-leave.cjs +51 -0
- package/lib/student-query-help.cjs +57 -0
- package/lib/student-replenishment.cjs +64 -0
- package/lib/student-report-detail.cjs +39 -0
- package/lib/student-report-summary.cjs +30 -0
- package/lib/teacher-approval-help.cjs +132 -0
- package/lib/teacher-leave-files.cjs +112 -0
- package/lib/teacher-leave-form-check.cjs +185 -0
- package/lib/teacher-leave-query.cjs +99 -0
- package/lib/teacher-own-leave-help.cjs +134 -0
- package/lib/workflow-entry.cjs +42 -0
- package/package.json +1 -1
package/lib/arguments.cjs
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
const { unknownOption } = require('./suggestions.cjs');
|
|
2
2
|
const { Failure, invalid } = require('./result.cjs');
|
|
3
3
|
|
|
4
|
+
const publicNames = { program_code: 'workflow-code', manage_id: 'group-id', bedroom_id: 'room-id', time_bucket: 'time-window' };
|
|
5
|
+
const flagName = name => '--' + (publicNames[name] || name.replaceAll('_', '-'));
|
|
6
|
+
|
|
4
7
|
// The flags remain command-specific; no unrecognized option reaches the Gateway.
|
|
5
8
|
function options(args, values, booleans = [], raw = [], strict = false) {
|
|
6
9
|
const payload = {};
|
|
7
10
|
const seen = new Set();
|
|
8
11
|
for (let i = 0; i < args.length; i++) {
|
|
9
12
|
const flag = args[i];
|
|
10
|
-
const name =
|
|
13
|
+
const name = values.concat(booleans).find(key => flagName(key) === flag);
|
|
11
14
|
if (flag.startsWith('--') && (values.includes(name) || booleans.includes(name)) && name !== 'label_id') {
|
|
12
15
|
if (seen.has(name)) throw invalid(flag + ' 不能重复传入。');
|
|
13
16
|
seen.add(name);
|
|
14
17
|
}
|
|
15
18
|
if (booleans.includes(name) && flag.startsWith('--')) { payload[name] = true; continue; }
|
|
16
|
-
if (!flag.startsWith('--') || !values.includes(name)) throw unknownOption(flag, values.concat(booleans).map(
|
|
19
|
+
if (!flag.startsWith('--') || !values.includes(name)) throw unknownOption(flag, values.concat(booleans).map(flagName));
|
|
17
20
|
const value = args[++i];
|
|
18
21
|
if (!value?.trim() || ['--help', '-h'].includes(value)
|
|
19
|
-
|| (value.startsWith('--') && values.concat(booleans).
|
|
22
|
+
|| (value.startsWith('--') && values.concat(booleans).some(key => flagName(key) === value))
|
|
20
23
|
|| (strict && value.startsWith('--'))) throw invalid(flag + ' 需要非空参数。');
|
|
21
24
|
if (name === 'label_id') (payload.label_ids ||= []).push(raw.includes(name) ? value : value.trim());
|
|
22
25
|
else payload[name] = raw.includes(name) ? value : value.trim();
|
|
@@ -24,7 +27,7 @@ function options(args, values, booleans = [], raw = [], strict = false) {
|
|
|
24
27
|
return payload;
|
|
25
28
|
}
|
|
26
29
|
function required(payload, ...names) {
|
|
27
|
-
for (const name of names) if (!payload[name]) throw invalid(
|
|
30
|
+
for (const name of names) if (!payload[name]) throw invalid(flagName(name) + ' 为必填参数。');
|
|
28
31
|
}
|
|
29
32
|
function confirm(payload) {
|
|
30
33
|
if (!payload.yes) throw new Failure(10, 'confirmation', 'confirmation_required', '执行前必须明确传入 --yes。');
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { options } = require('./arguments.cjs');
|
|
2
|
+
const { required, Failure } = require('./result.cjs');
|
|
3
|
+
const gateway = require('./gateway.cjs');
|
|
4
|
+
async function execute(args) {
|
|
5
|
+
const payload = options(args, [], [], [], true);
|
|
6
|
+
let requestId;
|
|
7
|
+
try {
|
|
8
|
+
const result = await gateway.execute('attendance-leave:read', 'attendance/clock-entry', payload, {
|
|
9
|
+
tenant_id: required('string'), user_id: required('string'), domain: required('string'), kind: required('string'),
|
|
10
|
+
title: required('string'), url: required('string'), message: required('string'),
|
|
11
|
+
});
|
|
12
|
+
requestId = result.value.request_id;
|
|
13
|
+
const data = result.value.data;
|
|
14
|
+
try {
|
|
15
|
+
const url = new URL(data.url);
|
|
16
|
+
if (data.domain !== 'attendance' || data.kind !== 'PHYSICAL_CLOCK_IN' || !data.tenant_id.trim() || !data.user_id.trim()
|
|
17
|
+
|| !data.title.trim() || !data.message.trim() || url.protocol !== 'https:' || !url.hostname
|
|
18
|
+
|| url.username || url.password || url.hash || url.pathname !== '/micro/app/teacherAttendance/'
|
|
19
|
+
|| [...url.searchParams.keys()].join(',') !== 'corpid' || !url.searchParams.get('corpid').trim()
|
|
20
|
+
|| /[\x00-\x1f\x7f-\x9f]/.test(url.searchParams.get('corpid'))) throw new Error();
|
|
21
|
+
} catch { throw new Failure(1, 'api', 'invalid_response', '现场打卡入口的身份、业务范围或原客户端地址无效,未提供可用入口。'); }
|
|
22
|
+
return result;
|
|
23
|
+
} catch (failure) {
|
|
24
|
+
if (failure instanceof Failure) {
|
|
25
|
+
if (requestId) failure.result.value.request_id = requestId;
|
|
26
|
+
failure.result.value.error.details = { ...failure.result.value.error.details, domain: 'attendance' };
|
|
27
|
+
}
|
|
28
|
+
throw failure;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
module.exports = { execute };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const { options } = require('./arguments.cjs');
|
|
2
|
+
const { required, Failure, invalid } = require('./result.cjs');
|
|
3
|
+
const gateway = require('./gateway.cjs');
|
|
4
|
+
const paths = { attendance: '/safety-campus/attendance-v2/list', 'student-attendance': '/safety-campus/attendance-student-v2/list', 'dormitory-attendance': '/safety-campus/dorm/list' };
|
|
5
|
+
async function execute(args, domain) {
|
|
6
|
+
const payload = options(args, ['target'], [], [], true);
|
|
7
|
+
const target = payload.target ?? 'rules';
|
|
8
|
+
if (target !== 'rules' && target !== 'devices' && !(domain !== 'dormitory-attendance' && ['resumption-method', 'workflows'].includes(target)))
|
|
9
|
+
throw invalid(domain === 'dormitory-attendance' ? '--target 仅支持 rules(考勤规则)或 devices(设备管理)。' : '--target 仅支持 rules(考勤规则)、devices(设备管理)、workflows(审批流程)或 resumption-method(销假方式)。');
|
|
10
|
+
const path = target === 'workflows' ? '/workFlow/home' : target === 'devices' ? '/accessCenter/device/device/listPage' : target === 'rules' ? paths[domain] : domain === 'attendance'
|
|
11
|
+
? '/safety-campus/attendance-v2/service-configuration/teacher-leave-method'
|
|
12
|
+
: '/safety-campus/attendance-student-v2/service-configuration/student-leave-method';
|
|
13
|
+
let requestId;
|
|
14
|
+
try {
|
|
15
|
+
const result = await gateway.execute('attendance-leave:read', domain + '/configuration-entry', payload, {
|
|
16
|
+
tenant_id: required('string'), user_id: required('string'), domain: required('string'), kind: required('string'),
|
|
17
|
+
title: required('string'), url: required('string'), message: required('string'),
|
|
18
|
+
});
|
|
19
|
+
requestId = result.value.request_id;
|
|
20
|
+
const data = result.value.data;
|
|
21
|
+
try {
|
|
22
|
+
const url = new URL(data.url);
|
|
23
|
+
if (data.domain !== domain || data.kind !== 'CONFIGURATION' || !data.tenant_id.trim() || !data.user_id.trim() || !data.title.trim()
|
|
24
|
+
|| url.protocol !== 'https:' || !url.hostname || url.username || url.password || url.hash || url.search || url.pathname !== path) throw new Error();
|
|
25
|
+
} catch { throw new Failure(1, 'api', 'invalid_response', '考勤配置入口返回的业务范围、身份或网页地址无效。'); }
|
|
26
|
+
return result;
|
|
27
|
+
} catch (failure) {
|
|
28
|
+
if (failure instanceof Failure) {
|
|
29
|
+
if (requestId) failure.result.value.request_id = requestId;
|
|
30
|
+
failure.result.value.error.details = { ...failure.result.value.error.details, domain, ...payload };
|
|
31
|
+
}
|
|
32
|
+
throw failure;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
module.exports = { execute };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const { options, required: requireArguments } = require('./arguments.cjs');
|
|
2
|
+
const { required, nullable, invalid, Failure } = require('./result.cjs');
|
|
3
|
+
const { positiveId } = require('./attendance-leave-ids.cjs');
|
|
4
|
+
const { formField } = require('./leave.cjs');
|
|
5
|
+
const gateway = require('./gateway.cjs');
|
|
6
|
+
|
|
7
|
+
async function execute(action, args) {
|
|
8
|
+
const list = action === 'correction-list';
|
|
9
|
+
const payload = options(args, list ? ['status', 'page', 'page_size', ...require('./attendance-leave-list-filters.cjs').dates] : ['id'], [], [], true);
|
|
10
|
+
if (list) {
|
|
11
|
+
require('./attendance-leave-list-filters.cjs').validate(payload);
|
|
12
|
+
for (const [key, fallback, max] of [['page', 1, 2147483647], ['page_size', 20, 50]]) {
|
|
13
|
+
const value = payload[key] ?? fallback;
|
|
14
|
+
if (!/^[0-9]+$/.test(String(value)) || +value < 1 || +value > max) throw invalid('--' + key.replaceAll('_', '-') + ' 必须为1到' + max + '的整数。');
|
|
15
|
+
payload[key] = +value;
|
|
16
|
+
}
|
|
17
|
+
if (payload.status !== undefined) {
|
|
18
|
+
const enums = require('./attendance-leave-enums.cjs');
|
|
19
|
+
payload.status = enums.encode(enums.workflowStatus, payload.status, '--status');
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
requireArguments(payload, 'id');
|
|
23
|
+
if (!require('./attendance-leave-ids.cjs').applicationId(payload.id)) throw invalid('--id 必须为补卡工作流返回的有效申请 ID。');
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const result = await gateway.execute('attendance-leave:read', 'attendance/' + action, payload, list ? {
|
|
27
|
+
tenant_id: required('string'), user_id: required('string'), page: required('integer'), page_size: required('integer'),
|
|
28
|
+
total: required('integer'), total_pages: required('integer'), items: required([{
|
|
29
|
+
id: required('string'), program_code: required('string'), title: nullable('string'),
|
|
30
|
+
status: required('integer'), status_description: nullable('string'), created_at: nullable('string'),
|
|
31
|
+
}]),
|
|
32
|
+
} : {
|
|
33
|
+
tenant_id: required('string'), user_id: required('string'), id: required('string'),
|
|
34
|
+
program_code: required('string'), form_id: required('string'), title: nullable('string'),
|
|
35
|
+
status: required('integer'), status_description: nullable('string'), fields: required([formField]),
|
|
36
|
+
attachments: required([require('./teacher-leave-files.cjs').attachment]),
|
|
37
|
+
clock_reference: nullable({ date: required('string'), record_id: required('string'), clock_record_found: required('boolean') }),
|
|
38
|
+
progress: required([{ task_id: nullable('string'), node_name: nullable('string'), node_type: nullable('integer'),
|
|
39
|
+
handler_name: nullable('string'), handler_time: nullable('string'), status: required('integer'), status_description: nullable('string'), opinion: nullable('string') }]),
|
|
40
|
+
});
|
|
41
|
+
const data = result.value.data;
|
|
42
|
+
const reference = data.clock_reference;
|
|
43
|
+
const validReference = reference == null || positiveId(reference.record_id)
|
|
44
|
+
&& /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(reference.date) && +reference.date.slice(0, 4) > 0
|
|
45
|
+
&& Number.isFinite(Date.parse(reference.date)) && new Date(reference.date).toISOString().slice(0, 10) === reference.date;
|
|
46
|
+
const readonly = fields => fields.every(field => field.editable === false && (!field.groups || field.groups.every(group => readonly(group.fields))));
|
|
47
|
+
if (list ? data.page !== payload.page || data.page_size !== payload.page_size || data.total < 0
|
|
48
|
+
|| data.total_pages !== Math.ceil(data.total / data.page_size)
|
|
49
|
+
|| data.items.length > Math.min(data.page_size, Math.max(0, data.total - (data.page - 1) * data.page_size))
|
|
50
|
+
|| new Set(data.items.map(row => row.id)).size !== data.items.length
|
|
51
|
+
|| data.items.some(row => !require('./attendance-leave-ids.cjs').applicationId(row.id) || payload.status != null && row.status !== payload.status)
|
|
52
|
+
: !validReference || data.id !== payload.id || !positiveId(data.form_id) || !readonly(data.fields)
|
|
53
|
+
|| data.progress.some(step => step.task_id != null && !positiveId(step.task_id))) {
|
|
54
|
+
const failure = new Failure(1, 'api', 'invalid_response', !validReference ? '本人补卡申请的关联日期或打卡标识无效。' : '本人补卡申请的标识、字段权限、状态或分页信息不一致。');
|
|
55
|
+
if (result.value.request_id) failure.result.value.request_id = result.value.request_id;
|
|
56
|
+
throw failure;
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
} catch (failure) {
|
|
60
|
+
if (failure instanceof Failure) failure.result.value.error.details = { ...failure.result.value.error.details, ...payload };
|
|
61
|
+
throw failure;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
module.exports = { execute };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const { options, required: requireArguments } = require('./arguments.cjs');
|
|
2
|
+
const { required, nullable, invalid, Failure } = require('./result.cjs');
|
|
3
|
+
const gateway = require('./gateway.cjs');
|
|
4
|
+
const positive = value => typeof value === 'string' && /^[1-9][0-9]*$/.test(value) && BigInt(value) <= 9223372036854775807n;
|
|
5
|
+
const task = {
|
|
6
|
+
task_id: required('string'), application_id: required('string'), applicant_id: required('string'), applicant_tenant_id: required('string'),
|
|
7
|
+
applicant_name: nullable('string'), title: nullable('string'), submitted_at: nullable('string'), task_status: required('integer'),
|
|
8
|
+
handler_id: nullable('string'), handler_tenant_id: nullable('string'), handled_at: nullable('string'), notice_id: nullable('string'), notice_read: nullable('boolean'),
|
|
9
|
+
};
|
|
10
|
+
const scope = { category: required('string'), tenant_id: required('string'), reviewer_id: required('string') };
|
|
11
|
+
const page = { ...scope, items: required([task]), page: required('integer'), page_size: required('integer'), total: required('integer'), total_pages: required('integer') };
|
|
12
|
+
const detail = { attachments: required([require('./teacher-leave-files.cjs').attachment]), ...scope, task: required(task), form_id: required('string'), status: required('integer'), status_description: nullable('string'),
|
|
13
|
+
fields: required([require('./leave.cjs').formField]), progress: required([{ task_id: nullable('string'), node_name: nullable('string'),
|
|
14
|
+
node_type: nullable('integer'), handler_name: nullable('string'), handler_time: nullable('string'), status: required('integer'),
|
|
15
|
+
status_description: nullable('string'), opinion: nullable('string') }]),
|
|
16
|
+
can_approve: required('boolean'), can_reject: required('boolean'), opinion_visible: required('boolean'), opinion_required: required('boolean'),
|
|
17
|
+
reject_return_types: required(['integer']),
|
|
18
|
+
};
|
|
19
|
+
function validDetail(data) {
|
|
20
|
+
const fields = rows => rows.every(field => positive(field.widget_id) && typeof field.editable === 'boolean'
|
|
21
|
+
&& (data.can_approve || field.editable === false) && (!field.groups || field.groups.every(group => fields(group.fields))));
|
|
22
|
+
return positive(data.form_id) && validTask(data.task, data)
|
|
23
|
+
&& (data.category === 'pending' || !data.can_approve && !data.can_reject && !data.opinion_visible && !data.opinion_required)
|
|
24
|
+
&& data.reject_return_types.every(value => [0, 1].includes(value)) && (data.can_reject || !data.reject_return_types.length)
|
|
25
|
+
&& (data.opinion_visible || !data.opinion_required) && fields(data.fields)
|
|
26
|
+
&& data.progress.every(step => step.task_id == null || positive(step.task_id));
|
|
27
|
+
}
|
|
28
|
+
function validTask(item, data) {
|
|
29
|
+
return positive(item.task_id) && require('./attendance-leave-ids.cjs').applicationId(item.application_id) && item.applicant_tenant_id === data.tenant_id
|
|
30
|
+
&& (data.category !== 'pending' || item.task_status === 0)
|
|
31
|
+
&& (data.category !== 'handled' || item.handler_id === data.reviewer_id && item.handler_tenant_id === data.tenant_id && [-1, 1, 2].includes(item.task_status))
|
|
32
|
+
&& (data.category === 'copied' ? positive(item.notice_id) && typeof item.notice_read === 'boolean' : item.notice_id == null);
|
|
33
|
+
}
|
|
34
|
+
async function execute(command, args) {
|
|
35
|
+
if (['correction-approve', 'correction-reject'].includes(command)) return decide(command, args);
|
|
36
|
+
const isDetail = command === 'correction-approval';
|
|
37
|
+
const payload = isDetail ? options(args, ['category', 'task_id', 'notice_id'], [], [], true)
|
|
38
|
+
: { page: 1, page_size: 20, ...options(args, ['category', 'page', 'page_size', ...require('./attendance-leave-list-filters.cjs').dates, ...require('./attendance-leave-list-filters.cjs').applicants], [], [], true) };
|
|
39
|
+
if (!isDetail) require('./attendance-leave-list-filters.cjs').validate(payload);
|
|
40
|
+
requireArguments(payload, 'category');
|
|
41
|
+
if (!['pending', 'handled', 'copied'].includes(payload.category)) throw invalid('--category 必须为 pending、handled 或 copied。');
|
|
42
|
+
if (isDetail) {
|
|
43
|
+
requireArguments(payload, 'task_id');
|
|
44
|
+
if (!positive(payload.task_id)) throw invalid('--task-id 必须为业务返回的有效审批任务ID。');
|
|
45
|
+
if (payload.category === 'copied') {
|
|
46
|
+
requireArguments(payload, 'notice_id'); if (!positive(payload.notice_id)) throw invalid('--notice-id 必须为实际抄送通知ID。');
|
|
47
|
+
} else if (payload.notice_id !== undefined) throw invalid('仅抄送详情接受 --notice-id。');
|
|
48
|
+
} else for (const [key, maximum] of [['page', 2147483647], ['page_size', 50]]) {
|
|
49
|
+
if (!/^\d+$/.test(String(payload[key])) || +payload[key] < 1 || +payload[key] > maximum) throw invalid('--' + key.replaceAll('_', '-') + ' 超出允许范围。');
|
|
50
|
+
payload[key] = +payload[key];
|
|
51
|
+
}
|
|
52
|
+
let envelope;
|
|
53
|
+
try {
|
|
54
|
+
const { config, token } = await require('./session.cjs').session('attendance-leave:read');
|
|
55
|
+
envelope = await gateway.request(config, 'attendance/' + command, token, payload);
|
|
56
|
+
const result = gateway.publicResult(envelope, isDetail ? detail : page); const data = result.value.data;
|
|
57
|
+
if (data.category !== payload.category || (isDetail ? !validDetail(data) || data.task.task_id !== payload.task_id
|
|
58
|
+
|| data.task.notice_id !== (payload.notice_id ?? null) : data.page !== payload.page || data.page_size !== payload.page_size
|
|
59
|
+
|| data.total < 0 || data.total_pages !== Math.ceil(data.total / data.page_size)
|
|
60
|
+
|| data.items.length > Math.min(data.page_size, Math.max(0, data.total - (data.page - 1) * data.page_size))
|
|
61
|
+
|| !data.items.every(item => validTask(item, data))
|
|
62
|
+
|| new Set(data.items.map(item => data.category === 'copied' ? item.notice_id : item.task_id)).size !== data.items.length))
|
|
63
|
+
throw new Failure(1, 'api', 'invalid_response', '补卡审批查询的范围、标识、字段或分页不一致。');
|
|
64
|
+
if (isDetail && payload.category === 'pending') data.form_check = require('./teacher-leave-form-check.cjs')
|
|
65
|
+
.evaluate(data, [], { approval: true });
|
|
66
|
+
return result;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error instanceof Failure) {
|
|
69
|
+
error.result.value.error.details = { ...error.result.value.error.details, ...payload };
|
|
70
|
+
if (envelope?.request_id) error.result.value.request_id = envelope.request_id;
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function decide(command, args) {
|
|
76
|
+
const approve = command === 'correction-approve';
|
|
77
|
+
const payload = options(args, ['task_id', 'opinion', ...(approve ? ['field_values'] : ['return_to'])], [], [], true);
|
|
78
|
+
requireArguments(payload, 'task_id');
|
|
79
|
+
if (!positive(payload.task_id)) throw invalid('--task-id 必须为业务返回的有效审批任务ID。');
|
|
80
|
+
if (payload.opinion != null && payload.opinion.length > 200) throw invalid('--opinion 最多200个字符。');
|
|
81
|
+
if (!approve) {
|
|
82
|
+
requireArguments(payload, 'return_to');
|
|
83
|
+
const enums = require('./attendance-leave-enums.cjs');
|
|
84
|
+
payload.return_type = enums.encode(enums.returnTo, payload.return_to, '--return-to');
|
|
85
|
+
delete payload.return_to;
|
|
86
|
+
}
|
|
87
|
+
require('./leave.cjs').parseFieldValues(payload);
|
|
88
|
+
if (approve) {
|
|
89
|
+
let current;
|
|
90
|
+
try {
|
|
91
|
+
current = await execute('correction-approval', ['--category', 'pending', '--task-id', payload.task_id]);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
const failure = error.result?.value?.error;
|
|
94
|
+
if (failure) {
|
|
95
|
+
failure.execution_status = 'NOT_EXECUTED';
|
|
96
|
+
if (['auth_required', 'missing_scope'].includes(failure.subtype))
|
|
97
|
+
failure.required_scopes = ['attendance-leave:read', 'attendance-leave:approve'];
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
const check = require('./teacher-leave-form-check.cjs').evaluate(current.value.data, payload.field_values, { approval: true });
|
|
102
|
+
if (current.value.data.opinion_required && !payload.opinion?.trim()) {
|
|
103
|
+
check.issues.push({ code: 'missing_required', parameter: 'opinion', message: '请补充审批意见。' });
|
|
104
|
+
if (check.status === 'complete') check.status = 'needs_input';
|
|
105
|
+
}
|
|
106
|
+
if (check.status !== 'complete') throw new Failure(2, 'validation', 'form_input_required',
|
|
107
|
+
'审批表单还有需要处理的信息,尚未上传附件或执行审批。', { execution_status: 'NOT_EXECUTED', form_check: check });
|
|
108
|
+
}
|
|
109
|
+
const send = async () => {
|
|
110
|
+
let envelope;
|
|
111
|
+
try {
|
|
112
|
+
const { config, token } = await require('./session.cjs').session('attendance-leave:approve');
|
|
113
|
+
envelope = await gateway.request(config, 'attendance/' + command, token, payload);
|
|
114
|
+
const result = gateway.publicResult(envelope, { task_id: required('string'), request_id: required('string'), action: required('string'),
|
|
115
|
+
application_id: nullable('string'), execution_status: required('string'), post_processing_status: required('string'),
|
|
116
|
+
application_status: nullable('integer'), message: required('string') });
|
|
117
|
+
const data = result.value.data;
|
|
118
|
+
if (data.task_id !== payload.task_id || data.action !== (approve ? 'APPROVE' : 'RETURN')
|
|
119
|
+
|| envelope.request_id != null && data.request_id !== envelope.request_id
|
|
120
|
+
|| data.application_id != null && !require('./attendance-leave-ids.cjs').applicationId(data.application_id)
|
|
121
|
+
|| !require('./command-guidance.cjs').validResult('attendance ' + command, data))
|
|
122
|
+
throw new Failure(1, 'api', 'invalid_response', '补卡审批回执与任务、请求、动作或执行阶段不一致,结果待核实,不要自动重试。');
|
|
123
|
+
require('./leave-start-receipt.cjs').project(envelope, result, '补卡审批');
|
|
124
|
+
return result;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error instanceof Failure) {
|
|
127
|
+
error.result.value.error.details = { ...error.result.value.error.details, task_id: payload.task_id, action: approve ? 'APPROVE' : 'RETURN' };
|
|
128
|
+
if (envelope?.request_id) error.result.value.request_id = envelope.request_id;
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
return approve ? require('./teacher-leave-files.cjs').submit(command, payload, send, 'attendance') : send();
|
|
134
|
+
}
|
|
135
|
+
module.exports = { execute };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const { options, required: requireArguments } = require('./arguments.cjs');
|
|
2
|
+
const { required, nullable, invalid, Failure } = require('./result.cjs');
|
|
3
|
+
const gateway = require('./gateway.cjs');
|
|
4
|
+
|
|
5
|
+
async function execute(args, action) {
|
|
6
|
+
const payload = options(args, ['date', 'result_id', 'correction_time', 'field_values'], [], [], true);
|
|
7
|
+
requireArguments(payload, 'date', 'result_id', 'correction_time');
|
|
8
|
+
const time = payload.correction_time;
|
|
9
|
+
if (!validTime(time) || time.slice(0, 10) !== payload.date)
|
|
10
|
+
throw invalid('--correction-time 必须为有效的北京时间 YYYY-MM-DD HH:mm:ss,且属于所选考勤日期。');
|
|
11
|
+
require('./leave.cjs').parseFieldValues(payload);
|
|
12
|
+
await require('./attendance.cjs').checkCorrectionBeforeSubmit(payload);
|
|
13
|
+
return require('./teacher-leave-files.cjs').submit(action, payload, async () => {
|
|
14
|
+
let envelope;
|
|
15
|
+
try {
|
|
16
|
+
const { config, token } = await require('./session.cjs').session('attendance-leave:write');
|
|
17
|
+
envelope = await gateway.request(config, 'attendance/' + action, token, payload);
|
|
18
|
+
const result = gateway.publicResult(envelope, {
|
|
19
|
+
request_id: required('string'), date: required('string'), result_id: required('string'), correction_time: required('string'),
|
|
20
|
+
program_code: nullable('string'), id: nullable('string'), next_task_id: nullable('string'),
|
|
21
|
+
application_status: nullable('integer'), execution_status: required('string'), post_processing_status: required('string'), message: required('string'),
|
|
22
|
+
});
|
|
23
|
+
const data = result.value.data;
|
|
24
|
+
const guidance = require('./command-guidance.cjs');
|
|
25
|
+
if (!validTime(data.correction_time) || data.date !== data.correction_time.slice(0, 10)
|
|
26
|
+
|| data.date !== payload.date || data.result_id !== payload.result_id
|
|
27
|
+
|| time != null && data.correction_time !== time
|
|
28
|
+
|| envelope.request_id != null && envelope.request_id !== data.request_id
|
|
29
|
+
|| !guidance.validResult('attendance ' + action, data))
|
|
30
|
+
throw new Failure(1, 'api', 'invalid_response', '补卡申请回执与目标、请求或执行阶段不一致,结果待核实,不要自动重试。');
|
|
31
|
+
require('./leave-start-receipt.cjs').project(envelope, result, '补卡申请');
|
|
32
|
+
return result;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error instanceof Failure) {
|
|
35
|
+
error.result.value.error.details = { ...error.result.value.error.details, date: payload.date, result_id: payload.result_id, correction_time: time };
|
|
36
|
+
if (envelope?.request_id) error.result.value.request_id = envelope.request_id;
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}, 'attendance');
|
|
41
|
+
}
|
|
42
|
+
function validTime(time) {
|
|
43
|
+
if (typeof time !== 'string' || !/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/.test(time) || +time.slice(0, 4) < 1) return false;
|
|
44
|
+
const instant = Date.parse(time.replace(' ', 'T') + '+08:00');
|
|
45
|
+
return Number.isFinite(instant) && new Date(instant + 8 * 3600 * 1000).toISOString().slice(0, 19).replace('T', ' ') === time;
|
|
46
|
+
}
|
|
47
|
+
module.exports = { execute };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const { invalid, Failure } = require('./result.cjs');
|
|
2
|
+
|
|
3
|
+
const direction = { in: '1', out: '2' };
|
|
4
|
+
const clockDirection = { none: '0', ...direction };
|
|
5
|
+
const returnTo = { start: 0, previous: 1 };
|
|
6
|
+
const gateStatus = { unknown: 0, 'normal-in': 1, 'normal-out': 2, late: -1, 'early-leave': -2,
|
|
7
|
+
'missing-clock': -3, 'on-leave': -6, 'stay-school': -7, 'school-dismissed': -8 };
|
|
8
|
+
const dormitoryStatus = { 'normal-out': 11, 'normal-in': 12, 'late-in': 13, 'late-out': 14,
|
|
9
|
+
'missing-out': 15, 'missing-in': 16, 'on-leave': -6 };
|
|
10
|
+
const workflowStatus = { withdrawn: -2, returned: -1, draft: 0, processing: 1, completed: 2 };
|
|
11
|
+
const studentBusinessStatus = { 'not-started': -2, 'handled-by-peer': -1, pending: 0, approved: 1, rejected: 2, withdrawn: 3 };
|
|
12
|
+
const taskStatus = { withdrawn: -2, returned: -1, pending: 0, transferred: 1, completed: 2 };
|
|
13
|
+
const nodeType = { start: 0, normal: 1, end: 10 };
|
|
14
|
+
const attendanceType = { 'student-gate': '1', dormitory: '2', teacher: '4' };
|
|
15
|
+
const clockSource = { device: 1, location: 2, correction: 3, 'off-site': 4 };
|
|
16
|
+
const resumptionStatus = { 'not-resumed': '0', resumed: '1' };
|
|
17
|
+
const resumptionSource = { workflow: 1, 'automatic-clock': 2 };
|
|
18
|
+
// Saved reports use KqConstant, not the daily dormitory status codes.
|
|
19
|
+
const reportStatus = { unknown: '0', 'normal-in': '1', 'normal-out': '2', 'in-dormitory': '4',
|
|
20
|
+
late: '-1', 'early-leave': '-2', 'missing-clock': '-3', 'no-record': '-4', 'manual-entry': '-5', 'on-leave': '-6' };
|
|
21
|
+
const reportGroup = { total: '3', ...reportStatus };
|
|
22
|
+
const reportScope = { class: '1', school: '2', grade: '3', tag: '6' };
|
|
23
|
+
const dormitoryReportScope = { 'dormitory-group': '4', school: '5' };
|
|
24
|
+
const reportDirection = { unknown: '0', ...direction };
|
|
25
|
+
const reportDetailType = { class: '1', total: '2' };
|
|
26
|
+
const retireState = { 'all-in': '1', 'not-all-in': '2' };
|
|
27
|
+
|
|
28
|
+
function encode(values, value, flag) {
|
|
29
|
+
if (!Object.hasOwn(values, value)) throw invalid(flag + ' 必须为 ' + Object.keys(values).join('、') + '。');
|
|
30
|
+
return values[value];
|
|
31
|
+
}
|
|
32
|
+
function decode(values, value, field) {
|
|
33
|
+
if (value == null) return value;
|
|
34
|
+
const match = Object.entries(values).find(([, code]) => String(code) === String(value));
|
|
35
|
+
if (!match) throw new Failure(1, 'api', 'invalid_response', '返回的 ' + field + ' 包含无法识别的业务编码。', { details: { field, value } });
|
|
36
|
+
return match[0];
|
|
37
|
+
}
|
|
38
|
+
function field(row, key, values) {
|
|
39
|
+
if (!row || !Object.hasOwn(row, key) || row[key] == null) return;
|
|
40
|
+
const match = Object.entries(values).find(([, code]) => String(code) === String(row[key]));
|
|
41
|
+
if (match) row[key] = match[0];
|
|
42
|
+
else {
|
|
43
|
+
row[key + '_code'] = row[key];
|
|
44
|
+
row[key] = 'unknown';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function studentRecord(row) {
|
|
48
|
+
field(row, 'status', studentBusinessStatus);
|
|
49
|
+
field(row, 'workflow_status', workflowStatus);
|
|
50
|
+
leaveFacts(row);
|
|
51
|
+
if (row.original_leave) studentRecord(row.original_leave);
|
|
52
|
+
for (const resumption of row.resumptions || []) studentRecord(resumption);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function leaveFacts(row) {
|
|
56
|
+
field(row, 'resumption_status', resumptionStatus);
|
|
57
|
+
field(row, 'resumption_source', resumptionSource);
|
|
58
|
+
for (const step of row.progress || []) {
|
|
59
|
+
field(step, 'status', taskStatus);
|
|
60
|
+
field(step, 'node_type', nodeType);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function attendanceDirection(row, dormitory) {
|
|
65
|
+
if (!row || !Object.hasOwn(row, 'in_out_type')) return;
|
|
66
|
+
row.direction = row.in_out_type;
|
|
67
|
+
delete row.in_out_type;
|
|
68
|
+
field(row, 'direction', clockDirection);
|
|
69
|
+
row.direction_name = row.direction == null ? null : row.direction === 'in' ? dormitory ? '归寝' : '进校'
|
|
70
|
+
: row.direction === 'out' ? dormitory ? '出寝' : '出校' : row.direction === 'none' ? '不分进出' : '未知方向';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function savedReports(data, dormitory) {
|
|
74
|
+
for (const report of data.reports || (data.report ? [data.report] : [])) {
|
|
75
|
+
if (dormitory && require('./dormitory-report-id.cjs').currentReport(report.report_id)) continue;
|
|
76
|
+
field(report, 'scope_type', dormitory ? dormitoryReportScope : reportScope);
|
|
77
|
+
field(report, 'direction', reportDirection);
|
|
78
|
+
}
|
|
79
|
+
const rows = row => {
|
|
80
|
+
field(row, 'status', reportStatus);
|
|
81
|
+
field(row, 'direction', reportDirection);
|
|
82
|
+
};
|
|
83
|
+
for (const group of data.groups || []) {
|
|
84
|
+
field(group, 'type', reportGroup);
|
|
85
|
+
for (const row of group.rows) rows(row);
|
|
86
|
+
}
|
|
87
|
+
for (const row of data.rows || []) rows(row);
|
|
88
|
+
if (dormitory) for (const row of [data.total, ...(data.bedrooms || [])]) field(row, 'retire_state', retireState);
|
|
89
|
+
else for (const row of [data.total, ...(data.classes || [])]) field(row, 'detail_type', reportDetailType);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Applied only after native response validation; dynamic form values and numeric quantities are untouched.
|
|
93
|
+
function project(command, data) {
|
|
94
|
+
const [domain, action] = command.split(' ');
|
|
95
|
+
const dormitory = domain === 'dormitory-attendance';
|
|
96
|
+
if (['student-attendance', 'dormitory-attendance'].includes(domain)
|
|
97
|
+
&& ['reports', 'report-summary', 'report-class', 'report-tag', 'report-group', 'report-room'].includes(action))
|
|
98
|
+
savedReports(data, dormitory);
|
|
99
|
+
if (['attendance', 'student-attendance', 'dormitory-attendance'].includes(domain)) {
|
|
100
|
+
field(data, 'attendance_type', attendanceType);
|
|
101
|
+
const statuses = dormitory ? dormitoryStatus : gateStatus;
|
|
102
|
+
const result = row => { field(row, 'status', statuses); attendanceDirection(row, dormitory); };
|
|
103
|
+
if (domain === 'attendance' && action === 'correction-form') result(data.result);
|
|
104
|
+
if (action === 'list') for (const row of data.items) {
|
|
105
|
+
result(row.result);
|
|
106
|
+
field(row, 'clock_method', clockSource);
|
|
107
|
+
}
|
|
108
|
+
if (dormitory && action === 'report-room' && data.saved_room) for (const row of data.saved_room.records) {
|
|
109
|
+
result(row.result);
|
|
110
|
+
field(row, 'clock_method', clockSource);
|
|
111
|
+
}
|
|
112
|
+
if (dormitory) require('./dormitory-report-view.cjs').project(data, action);
|
|
113
|
+
if (action === 'show') {
|
|
114
|
+
const day = data.day || data;
|
|
115
|
+
for (const row of day.attendance_results) result(row);
|
|
116
|
+
for (const row of day.clock_records) {
|
|
117
|
+
attendanceDirection(row, dormitory);
|
|
118
|
+
field(row, 'source', clockSource);
|
|
119
|
+
}
|
|
120
|
+
for (const rule of day.rules) for (const shift of rule.shifts) attendanceDirection(shift, dormitory);
|
|
121
|
+
}
|
|
122
|
+
if (action === 'applicable-rule') for (const shift of data.rule?.shifts || []) attendanceDirection(shift, dormitory);
|
|
123
|
+
if (domain === 'attendance' && action === 'statistics') for (const row of data.items) {
|
|
124
|
+
row.has_leave = row.leave_flag == null ? null : row.leave_flag === 1;
|
|
125
|
+
delete row.leave_flag;
|
|
126
|
+
}
|
|
127
|
+
if (dormitory && ['statistics', 'export'].includes(action)) field(data, 'status', statuses);
|
|
128
|
+
}
|
|
129
|
+
if (['student-attendance', 'dormitory-attendance'].includes(domain)
|
|
130
|
+
&& ['manual-entry', 'manual-entries'].includes(action)) {
|
|
131
|
+
const directions = dormitory ? { none: '0' } : direction;
|
|
132
|
+
field(data, 'direction', directions);
|
|
133
|
+
for (const option of data.options || []) field(option, 'direction', directions);
|
|
134
|
+
for (const row of data.records || []) field(row, 'direction', directions);
|
|
135
|
+
}
|
|
136
|
+
if (['leave', 'student-leave', 'attendance'].includes(domain)
|
|
137
|
+
&& ['approval', 'resumption-approval', 'correction-approval'].includes(action)) {
|
|
138
|
+
field(data, 'status', workflowStatus);
|
|
139
|
+
field(data.task, 'task_status', taskStatus);
|
|
140
|
+
leaveFacts(data);
|
|
141
|
+
data.reject_return_targets = data.reject_return_types.map(value => decode(returnTo, value, 'reject_return_targets'));
|
|
142
|
+
delete data.reject_return_types;
|
|
143
|
+
}
|
|
144
|
+
if (['leave', 'student-leave', 'attendance'].includes(domain)) {
|
|
145
|
+
field(data, 'application_status', workflowStatus);
|
|
146
|
+
if (['approvals', 'resumption-approvals', 'correction-approvals'].includes(action))
|
|
147
|
+
for (const row of data.items) field(row, 'task_status', taskStatus);
|
|
148
|
+
}
|
|
149
|
+
if (domain === 'attendance' && ['correction-list', 'correction-show'].includes(action)) {
|
|
150
|
+
for (const row of action === 'correction-list' ? data.items : [data]) {
|
|
151
|
+
field(row, 'status', workflowStatus);
|
|
152
|
+
leaveFacts(row);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (domain === 'leave' && ['list', 'show', 'resumption-list', 'resumption-show'].includes(action)) {
|
|
156
|
+
for (const row of action.endsWith('list') ? data.items : [data]) {
|
|
157
|
+
field(row, 'workflow_status', workflowStatus);
|
|
158
|
+
field(row, 'business_status', studentBusinessStatus);
|
|
159
|
+
leaveFacts(row);
|
|
160
|
+
for (const resumption of row.resumption_applications || []) field(resumption, 'status', studentBusinessStatus);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (domain === 'student-leave' && ['list', 'show', 'resumption-list', 'resumption-show'].includes(action)) {
|
|
164
|
+
for (const row of action.endsWith('list') ? data.items : [data]) {
|
|
165
|
+
field(row, 'workflow_status', workflowStatus);
|
|
166
|
+
leaveFacts(row);
|
|
167
|
+
for (const record of row.records || []) studentRecord(record);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function projectError(command, error) {
|
|
172
|
+
const details = error.details;
|
|
173
|
+
if (!details || typeof details !== 'object') return;
|
|
174
|
+
const [domain, action] = command.split(' ');
|
|
175
|
+
const statuses = domain === 'attendance' && action === 'correction-list' ? workflowStatus
|
|
176
|
+
: domain === 'student-leave' && ['list', 'resumption-list'].includes(action)
|
|
177
|
+
? details.scope === 'mine' ? workflowStatus : details.scope === 'managed' ? studentBusinessStatus : null
|
|
178
|
+
: ['attendance', 'student-attendance', 'dormitory-attendance'].includes(domain) && ['list', 'statistics', 'export'].includes(action)
|
|
179
|
+
? domain === 'dormitory-attendance' ? dormitoryStatus : gateStatus : null;
|
|
180
|
+
if (statuses && Object.values(statuses).includes(details.status)) field(details, 'status', statuses);
|
|
181
|
+
// Keep unknown response facts as diagnostic evidence; only known request selectors are translated.
|
|
182
|
+
if (/^(student|dormitory)-attendance manual-entry$/.test(command) && details.direction != null) {
|
|
183
|
+
const values = command.startsWith('student-') ? direction : { none: '0' };
|
|
184
|
+
const match = Object.entries(values).find(([, code]) => code === details.direction);
|
|
185
|
+
if (match) details.direction = match[0];
|
|
186
|
+
}
|
|
187
|
+
if (/^(leave|student-leave|attendance) (reject|resumption-reject|correction-reject)$/.test(command)
|
|
188
|
+
&& Object.values(returnTo).includes(details.return_type)) {
|
|
189
|
+
details.return_to = decode(returnTo, details.return_type, 'return_to');
|
|
190
|
+
delete details.return_type;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
module.exports = { direction, returnTo, gateStatus, dormitoryStatus, workflowStatus, studentBusinessStatus, encode, project, projectError };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function applicationId(value, purpose) {
|
|
2
|
+
const prefix = purpose === 'TEACHER_LEAVE' ? 'leave_a_' : purpose === 'TEACHER_RESUMPTION' ? 'resumption_a_' : purpose === 'STUDENT_LEAVE' ? 'student_leave_a_' : purpose === 'STUDENT_RESUMPTION' ? 'student_resumption_a_' : 'application_';
|
|
3
|
+
return typeof value === 'string' && new RegExp('^' + prefix + '[1-9][0-9]{0,18}$').test(value)
|
|
4
|
+
&& BigInt(value.slice(prefix.length)) <= 9223372036854775807n;
|
|
5
|
+
}
|
|
6
|
+
const positiveId = value => typeof value === 'string' && /^[1-9][0-9]*$/.test(value) && BigInt(value) <= 9223372036854775807n;
|
|
7
|
+
function replenishmentId(value, type, date) {
|
|
8
|
+
if (typeof value !== 'string') return false;
|
|
9
|
+
const match = /^replenishment_([12])_([0-9]{8})_([1-9][0-9]{0,9})$/.exec(value);
|
|
10
|
+
if (!match || match[1] !== type || Number(match[3]) > 2147483647) return false;
|
|
11
|
+
const day = match[2].slice(0, 4) + '-' + match[2].slice(4, 6) + '-' + match[2].slice(6);
|
|
12
|
+
return +match[2].slice(0, 4) > 0 && Number.isFinite(Date.parse(day)) && new Date(day).toISOString().slice(0, 10) === day
|
|
13
|
+
&& (date == null || date === day);
|
|
14
|
+
}
|
|
15
|
+
module.exports = { applicationId, positiveId, replenishmentId };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { invalid } = require('./result.cjs');
|
|
2
|
+
const dates = ['start_date', 'end_date', 'submitted_start_date', 'submitted_end_date'];
|
|
3
|
+
const applicants = ['applicant_id', 'applicant_name'];
|
|
4
|
+
const subjects = ['subject_id', 'subject_name'];
|
|
5
|
+
function validate(payload) {
|
|
6
|
+
for (const [start, end] of [['start_date', 'end_date'], ['submitted_start_date', 'submitted_end_date']]) {
|
|
7
|
+
if (payload[start] === undefined && payload[end] === undefined) continue;
|
|
8
|
+
for (const key of [start, end]) {
|
|
9
|
+
const value = payload[key];
|
|
10
|
+
if (typeof value !== 'string' || !/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(value)
|
|
11
|
+
|| +value.slice(0, 4) < 1 || !Number.isFinite(Date.parse(value)) || new Date(value).toISOString().slice(0, 10) !== value)
|
|
12
|
+
throw invalid('--' + start.replaceAll('_', '-') + ' 与 --' + end.replaceAll('_', '-') + ' 须成对提供有效的 YYYY-MM-DD 日期。');
|
|
13
|
+
}
|
|
14
|
+
if (payload[start] > payload[end]) throw invalid('开始日期不能晚于结束日期。');
|
|
15
|
+
}
|
|
16
|
+
for (const key of [...applicants, ...subjects]) {
|
|
17
|
+
if (payload[key] !== undefined && (!payload[key].trim() || payload[key].length > 100))
|
|
18
|
+
throw invalid('--' + key.replaceAll('_', '-') + ' 须为 1 到 100 个字符。');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
module.exports = { dates, applicants, subjects, validate };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Public CLI names map to the existing Gateway contract; no old CLI aliases are registered.
|
|
2
|
+
const leaveActions = {
|
|
3
|
+
submit: 'start', remind: 'urge',
|
|
4
|
+
'resumption-submit': 'resumption-start', 'resumption-remind': 'resumption-urge',
|
|
5
|
+
'form-people-search': 'form-users', 'resumption-form-people-search': 'resumption-form-users',
|
|
6
|
+
'approval-form-people-search': 'approval-form-users',
|
|
7
|
+
'resumption-approval-form-people-search': 'resumption-approval-form-users',
|
|
8
|
+
};
|
|
9
|
+
const correctionActions = {
|
|
10
|
+
'correction-submit': 'correction-start',
|
|
11
|
+
'correction-form-people-search': 'correction-form-users',
|
|
12
|
+
'correction-approval-form-people-search': 'correction-approval-form-users',
|
|
13
|
+
};
|
|
14
|
+
function endpoint(path) {
|
|
15
|
+
const [space, action, ...rest] = path.split('/');
|
|
16
|
+
const names = ['leave', 'student-leave'].includes(space) ? leaveActions
|
|
17
|
+
: space === 'attendance' ? correctionActions : {};
|
|
18
|
+
return [space, names[action] || action, ...rest].join('/');
|
|
19
|
+
}
|
|
20
|
+
module.exports = { endpoint };
|