@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.
Files changed (63) hide show
  1. package/lib/arguments.cjs +7 -4
  2. package/lib/attendance-clock-entry.cjs +31 -0
  3. package/lib/attendance-configuration-entry.cjs +35 -0
  4. package/lib/attendance-correction-applications.cjs +64 -0
  5. package/lib/attendance-correction-approval.cjs +135 -0
  6. package/lib/attendance-correction-start.cjs +47 -0
  7. package/lib/attendance-leave-enums.cjs +193 -0
  8. package/lib/attendance-leave-ids.cjs +15 -0
  9. package/lib/attendance-leave-list-filters.cjs +21 -0
  10. package/lib/attendance-leave-route.cjs +20 -0
  11. package/lib/attendance-operation-result.cjs +97 -0
  12. package/lib/attendance-report-inbox.cjs +42 -0
  13. package/lib/attendance-report-remarks.cjs +62 -0
  14. package/lib/attendance-statistics.cjs +49 -0
  15. package/lib/attendance.cjs +404 -0
  16. package/lib/auth.cjs +1 -1
  17. package/lib/business-schemas.cjs +3 -1
  18. package/lib/cli.cjs +294 -1
  19. package/lib/command-guidance.cjs +1162 -1
  20. package/lib/command-reference.cjs +54 -0
  21. package/lib/contact-help.cjs +43 -0
  22. package/lib/contact-mobile.cjs +14 -0
  23. package/lib/contact.cjs +85 -0
  24. package/lib/correction-help.cjs +57 -0
  25. package/lib/dormitory-replenishment.cjs +63 -0
  26. package/lib/dormitory-report-detail.cjs +76 -0
  27. package/lib/dormitory-report-id.cjs +8 -0
  28. package/lib/dormitory-report-summary.cjs +66 -0
  29. package/lib/dormitory-report-view.cjs +36 -0
  30. package/lib/dormitory-rooms.cjs +41 -0
  31. package/lib/dormitory-scope.cjs +18 -0
  32. package/lib/dormitory-statistics.cjs +30 -0
  33. package/lib/download.cjs +17 -8
  34. package/lib/gateway.cjs +9 -4
  35. package/lib/leave-approval-receipt.cjs +126 -0
  36. package/lib/leave-attachment-upload.cjs +110 -0
  37. package/lib/leave-form-users.cjs +48 -0
  38. package/lib/leave-start-receipt.cjs +64 -0
  39. package/lib/leave.cjs +457 -0
  40. package/lib/replenishment-records.cjs +39 -0
  41. package/lib/result.cjs +1 -0
  42. package/lib/saved-report-summary.cjs +16 -0
  43. package/lib/school-contacts.cjs +24 -0
  44. package/lib/school-help.cjs +53 -0
  45. package/lib/school-schema.cjs +38 -0
  46. package/lib/student-application-help.cjs +76 -0
  47. package/lib/student-leave-form.cjs +108 -0
  48. package/lib/student-leave-operations.cjs +24 -0
  49. package/lib/student-leave-query.cjs +74 -0
  50. package/lib/student-leave-resumption.cjs +2 -0
  51. package/lib/student-leave-start.cjs +34 -0
  52. package/lib/student-leave.cjs +51 -0
  53. package/lib/student-query-help.cjs +57 -0
  54. package/lib/student-replenishment.cjs +64 -0
  55. package/lib/student-report-detail.cjs +39 -0
  56. package/lib/student-report-summary.cjs +30 -0
  57. package/lib/teacher-approval-help.cjs +132 -0
  58. package/lib/teacher-leave-files.cjs +112 -0
  59. package/lib/teacher-leave-form-check.cjs +185 -0
  60. package/lib/teacher-leave-query.cjs +99 -0
  61. package/lib/teacher-own-leave-help.cjs +134 -0
  62. package/lib/workflow-entry.cjs +42 -0
  63. package/package.json +1 -1
@@ -0,0 +1,97 @@
1
+ // Describe only confirmed receipt stages; application progress is a separate fact.
2
+ function authorizationDescription(scopes) {
3
+ const names = { 'attendance-leave:read': '查看', 'attendance-leave:write': '办理', 'attendance-leave:approve': '审批' };
4
+ const labels = [...new Set((scopes || []).map(scope => names[scope]).filter(Boolean))];
5
+ return labels.length ? `本次考勤请假所需授权:${labels.join('、')}。` : undefined;
6
+ }
7
+
8
+ function describe(command, value) {
9
+ const action = command.split(' ')[1].replace(/^(resumption-|correction-)/, '');
10
+ const completed = {
11
+ submit: '申请已提交,不代表审批已经通过。',
12
+ approve: '当前审批节点已同意,不代表整张申请已经通过。',
13
+ reject: '当前审批节点已退回。',
14
+ withdraw: '申请已撤回。',
15
+ remind: '催办已完成,不代表审批已经通过。',
16
+ remark: '考勤备注已保存。',
17
+ 'report-remark': '报表备注已保存。',
18
+ 'manual-entry': '补签到记录已保存。',
19
+ 'manual-entry-delete': '补签到记录已删除。',
20
+ }[action];
21
+ const execution = {
22
+ COMPLETED: completed || '本次操作已执行。',
23
+ NOT_EXECUTED: '本次操作未执行。',
24
+ UNCONFIRMED: '本次操作结果未知,不能认定成功或未执行;先查询结果,不要重复提交。',
25
+ }[value.execution_status];
26
+ if (!execution) return;
27
+ const processing = {
28
+ SUBMITTED: '后续处理任务已提交,尚不能据此确认考勤结果已更新。',
29
+ COMPLETED: '回执确认后续处理已完成;具体考勤判定以考勤查询结果为准。',
30
+ FAILED: '后续处理失败,不能据此否定已确认的操作结果。',
31
+ NOT_STARTED: '回执未确认后续处理已启动。',
32
+ }[value.post_processing_status];
33
+ return execution + (processing || '')
34
+ + (value.attachment_uploads?.length ? '附件上传结果单独保留,文件上传成功不等于申请或审批成功。' : '');
35
+ }
36
+
37
+ function project(command, envelope) {
38
+ const value = envelope.ok ? envelope.data : envelope.error;
39
+ if (!value) return;
40
+ const authorization = authorizationDescription(envelope.ok && /^auth (login|connect)$/.test(command)
41
+ ? value.requested_scopes : !envelope.ok ? value.required_scopes : undefined);
42
+ if (authorization) value.authorization_description = authorization;
43
+ if (!/^(leave|student-leave|attendance|student-attendance|dormitory-attendance) /.test(command)) return;
44
+ let executionStatus = value.execution_status || value.details?.execution_status;
45
+ if (!executionStatus && envelope.ok && /^(leave|student-leave) (resumption-)?(remind|withdraw)$/.test(command)) {
46
+ const prefix = command.includes('resumption-') ? 'resumption' : 'leave';
47
+ const remind = command.endsWith('remind');
48
+ if (value.event === prefix + (remind ? '_urged' : '_withdrawn')
49
+ && (remind ? Number.isInteger(value.notified_count) && value.notified_count >= 0 : value.withdrawn === true))
50
+ executionStatus = 'COMPLETED';
51
+ }
52
+ const explanation = describe(command, { ...value, execution_status: executionStatus });
53
+ if (explanation) value.operation_result_description = explanation;
54
+ if (envelope.ok && /^(student|dormitory)-attendance manual-entr(y|ies)$/.test(command)) {
55
+ value.time_description = '补签到确认的是所选考勤时段已到;时段起止不是实际到达时刻,创建时间是办理时间。本记录不提供真实到达时间。';
56
+ }
57
+ if (envelope.ok && /^(attendance|student-attendance|dormitory-attendance) show$/.test(command)) {
58
+ const day = value.day || value;
59
+ value.result_basis = { source: 'saved_attendance_results', description: '考勤结论来自已保存的考勤判定。更新时间表示记录保存时间;本响应未提供重算执行记录,不能仅凭更新时间判断是否重算或请假、销假是否同步。' };
60
+ if (day.attendance_results?.length === 0)
61
+ value.result_absence = { reason: null, description: '本次查询未返回已保存考勤判定,未查明原因;不能据此认定没有规则、正常出勤或缺勤。' };
62
+ }
63
+ if (envelope.ok && command === 'attendance correction-form' && value.status === 'ready' && value.form_check) {
64
+ const visible = new Set(value.form_check.visible_fields.map(field => field.field_key));
65
+ const textFields = [];
66
+ const collect = fields => {
67
+ for (const field of fields || []) {
68
+ if (!field.editable || field.computed || !visible.has(field.field_key)) continue;
69
+ if (['text', 'multiline_text'].includes(field.type)) textFields.push(field.label);
70
+ for (const group of field.groups || []) collect(group.fields);
71
+ collect(field.row_template);
72
+ }
73
+ };
74
+ collect(value.fields);
75
+ value.text_input = { available: textFields.length > 0, fields: [...new Set(textFields)],
76
+ description: textFields.length ? '这些是当前可填写的文字字段,填写用途以字段说明为准。'
77
+ : '当前显示的表单没有可填写的文字字段,不能随本次申请保存文字原因或补充说明。' };
78
+ }
79
+ if (envelope.ok && /^student-attendance report-(summary|class|tag)$/.test(command)) {
80
+ value.basis = { attendance: command.endsWith('summary') ? '保存的报表汇总' : '查询时按该报表范围读取的明细',
81
+ date: value.report.content_date, time_bucket: value.report.time_bucket,
82
+ description: '接收日期不是考勤日期;保存汇总与查询时明细可能不同,差异原因需另行核实。' };
83
+ }
84
+ }
85
+
86
+ function transportFailure(endpoint, failure) {
87
+ // Only the actual write request reaches this boundary, not its form/permission reads.
88
+ if (!/^(leave|student-leave)\/(submit|start|approve|reject|remind|urge|withdraw|resumption-(submit|start|approve|reject|remind|urge|withdraw))$/.test(endpoint)
89
+ && !/^attendance\/correction-(submit|start|approve|reject)$/.test(endpoint)) return;
90
+ const error = failure.result?.value?.error;
91
+ if (!error || error.execution_status) return;
92
+ if (error.type === 'network')
93
+ error.execution_status = error.subtype === 'connection_failed' ? 'NOT_EXECUTED' : 'UNCONFIRMED';
94
+ else if (error.type === 'api' && error.subtype === 'invalid_response') error.execution_status = 'UNCONFIRMED';
95
+ }
96
+
97
+ module.exports = { project, transportFailure };
@@ -0,0 +1,42 @@
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 { currentReport } = require('./dormitory-report-id.cjs');
5
+ function validDate(value) {
6
+ return /^\d{4}-\d{2}-\d{2}$/.test(value) && +value.slice(0, 4) > 0 && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;
7
+ }
8
+ async function execute(args, domain) {
9
+ const payload = options(args, ['delivery_date', 'start_date', 'end_date'], [], [], true);
10
+ const single = payload.delivery_date !== undefined;
11
+ if (single && (payload.start_date !== undefined || payload.end_date !== undefined)) throw invalid('--delivery-date 与 --start-date/--end-date 不能混用。');
12
+ if (!single) requireArguments(payload, 'start_date', 'end_date');
13
+ const start = single ? payload.delivery_date : payload.start_date, end = single ? payload.delivery_date : payload.end_date;
14
+ if (!validDate(start) || !validDate(end) || start > end) throw invalid('接收日期必须为有效的 YYYY-MM-DD,开始日期不能晚于结束日期。');
15
+ try {
16
+ const result = await gateway.execute('attendance-leave:read', domain + '/reports', payload, {
17
+ tenant_id: required('string'), user_id: required('string'), delivery_date: nullable('string'), start_date: required('string'), end_date: required('string'), attendance_type: required('string'), total: required('integer'),
18
+ reports: required([{ report_id: required('string'), content_date: nullable('string'), delivery_date: required('string'), time_bucket: nullable('string'),
19
+ scope_type: nullable('string'), scope_id: nullable('string'), scope_name: nullable('string'), snapshot_missing_count: nullable('integer'),
20
+ configuration_name: nullable('string'), calculation_time: nullable('string'), direction: nullable('string') }]),
21
+ });
22
+ const data = result.value.data;
23
+ if (data.delivery_date !== (single ? start : null) || data.start_date !== start || data.end_date !== end || data.attendance_type !== (domain === 'dormitory-attendance' ? '2' : '1')
24
+ || data.total !== data.reports.length || new Set(data.reports.map(row => row.report_id)).size !== data.reports.length
25
+ || data.reports.some(row => !(domain === 'dormitory-attendance' && currentReport(row.report_id)
26
+ ? row.content_date === row.report_id.slice(17, 25).replace(/^(\d{4})(\d{2})(\d{2})$/, '$1-$2-$3')
27
+ && ['school', 'class', 'rooms'].includes(row.scope_type) && ['in', 'out'].includes(row.direction)
28
+ && typeof row.time_bucket === 'string' && row.time_bucket.trim().length > 0 && row.snapshot_missing_count === null
29
+ && (row.scope_type === 'class' ? typeof row.scope_id === 'string' && row.scope_id.trim().length > 0 : row.scope_id === null)
30
+ : /^[1-9][0-9]*$/.test(row.report_id) && BigInt(row.report_id) <= 2147483647n && row.snapshot_missing_count !== null)
31
+ || !validDate(row.delivery_date) || row.delivery_date < start || row.delivery_date > end || row.content_date != null && !validDate(row.content_date)
32
+ || row.snapshot_missing_count < 0)) {
33
+ const failure = new Failure(1, 'api', 'invalid_response', '考勤报告返回的日期、类型、数量或报告记录缺失或不一致。');
34
+ if (result.value.request_id) failure.result.value.request_id = result.value.request_id; throw failure;
35
+ }
36
+ return result;
37
+ } catch (failure) {
38
+ if (failure instanceof Failure) failure.result.value.error.details = { ...failure.result.value.error.details, ...payload };
39
+ throw failure;
40
+ }
41
+ }
42
+ module.exports = { execute };
@@ -0,0 +1,62 @@
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
+ function parse(args, write) {
6
+ const payload = options(args, ['target_user_id', 'date', ...(write ? ['time_bucket', 'remark'] : [])], write ? ['clear'] : [], ['time_bucket', 'remark'], true);
7
+ requireArguments(payload, 'target_user_id', 'date', ...(write ? ['time_bucket'] : []));
8
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(payload.date) || !Number.isFinite(Date.parse(payload.date))
9
+ || new Date(payload.date).toISOString().slice(0, 10) !== payload.date) throw invalid('--date 必须为有效的 YYYY-MM-DD 内容日期。');
10
+ if (write) {
11
+ if ((payload.remark != null) === (payload.clear === true)) throw invalid('--remark <text> 与 --clear 必须且只能选择一项。');
12
+ if (payload.clear) payload.remark = '';
13
+ delete payload.clear;
14
+ }
15
+ return payload;
16
+ }
17
+ function preserve(failure, payload, write) {
18
+ if (failure instanceof Failure) {
19
+ const error = failure.result.value.error;
20
+ const unknown = write && (error.type === 'network' && error.subtype !== 'connection_failed'
21
+ || error.type === 'api' && error.subtype === 'invalid_response');
22
+ error.details = { ...error.details, target_user_id: payload.target_user_id, date: payload.date,
23
+ ...(write ? { time_bucket: payload.time_bucket } : {}), ...(unknown ? { execution_status: 'UNCONFIRMED' } : {}) };
24
+ if (unknown && !error.message.includes('不要自动重试')) error.message += '请查询该学生当日旧报告备注核实,不要自动重试。';
25
+ }
26
+ throw failure;
27
+ }
28
+ function invalidReply(result) {
29
+ const failure = new Failure(1, 'api', 'invalid_response', '旧报告备注返回的目标、日期、窗口、备注记录或执行信息缺失或不一致。');
30
+ if (result.value.request_id) failure.result.value.request_id = result.value.request_id;
31
+ return failure;
32
+ }
33
+ async function save(args, domain = 'student-attendance') {
34
+ const payload = parse(args, true);
35
+ try {
36
+ const result = await gateway.execute('attendance-leave:write', domain + '/report-remark', payload, {
37
+ tenant_id: required('string'), operator_id: required('string'), subject_id: required('string'), date: required('string'),
38
+ time_bucket: required('string'), request_id: required('string'), execution_status: required('string'), message: required('string'),
39
+ });
40
+ const data = result.value.data;
41
+ if (data.subject_id !== payload.target_user_id || data.date !== payload.date || data.time_bucket !== payload.time_bucket
42
+ || !['COMPLETED', 'UNCONFIRMED'].includes(data.execution_status)
43
+ || result.value.request_id && result.value.request_id !== data.request_id) throw invalidReply(result);
44
+ return result;
45
+ } catch (failure) { preserve(failure, payload, true); }
46
+ }
47
+ async function read(args, domain = 'student-attendance') {
48
+ const payload = parse(args, false);
49
+ try {
50
+ const result = await gateway.execute('attendance-leave:read', domain + '/report-remarks', payload, {
51
+ tenant_id: required('string'), operator_id: required('string'), subject_id: required('string'), date: required('string'),
52
+ remarks: required([{ remark_id: required('string'), time_bucket: nullable('string'), remark: nullable('string'),
53
+ created_by: nullable('string'), created_at: nullable('integer'), modified_by: nullable('string'), modified_at: nullable('integer') }]),
54
+ });
55
+ const data = result.value.data;
56
+ if (data.subject_id !== payload.target_user_id || data.date !== payload.date
57
+ || new Set(data.remarks.map(note => note.remark_id)).size !== data.remarks.length
58
+ || data.remarks.some(note => !/^[1-9][0-9]*$/.test(note.remark_id) || BigInt(note.remark_id) > 2147483647n)) throw invalidReply(result);
59
+ return result;
60
+ } catch (failure) { preserve(failure, payload, false); }
61
+ }
62
+ module.exports = { read, save };
@@ -0,0 +1,49 @@
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 validDate = value => typeof value === 'string' && /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(value)
5
+ && +value.slice(0, 4) > 0 && Number.isFinite(Date.parse(value)) && new Date(value).toISOString().slice(0, 10) === value;
6
+ const validTime = value => value == null || typeof value === 'string' && /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/.test(value)
7
+ && Number.isFinite(Date.parse(value.replace(' ', 'T') + 'Z'))
8
+ && new Date(value.replace(' ', 'T') + 'Z').toISOString().slice(0, 19).replace('T', ' ') === value;
9
+ const countKeys = ['normal_count', 'late_count', 'early_count', 'missing_count'];
10
+ const counts = Object.fromEntries(countKeys.map(key => [key, nullable('integer')]));
11
+ const shape = {
12
+ tenant_id: required('string'), user_id: required('string'), month: required('string'), queried_at: required('string'),
13
+ recorded_date_count: required('integer'), leave_date_count: nullable('integer'), ...counts,
14
+ items: required([{ record_id: required('string'), date: required('string'), rule_id: nullable('string'), ...counts,
15
+ leave_flag: nullable('integer'), created_at: nullable('string'), updated_at: nullable('string') }]),
16
+ };
17
+ function validateMonth(payload) {
18
+ requireArguments(payload, 'month');
19
+ if (!validDate(payload.month + '-01')) throw invalid('--month 必须为有效的 YYYY-MM 月份,无默认月份。');
20
+ }
21
+ async function execute(args) {
22
+ const payload = options(args, ['month'], [], [], true); validateMonth(payload);
23
+ return validate(await gateway.execute('attendance-leave:read', 'attendance/statistics', payload, shape), payload);
24
+ }
25
+ function validate(result, payload) {
26
+ const data = result.value.data;
27
+ const dates = new Set(data.items.map(row => row.date));
28
+ const leaveDates = new Set(data.items.filter(row => row.leave_flag === 1).map(row => row.date));
29
+ const valid = data.month === payload.month && validTime(data.queried_at)
30
+ && data.recorded_date_count === dates.size
31
+ && data.leave_date_count === (data.items.some(row => row.leave_flag == null) ? null : leaveDates.size)
32
+ && new Set(data.items.map(row => row.record_id)).size === data.items.length
33
+ && new Set(data.items.map(row => row.date + ':' + row.rule_id)).size === data.items.length
34
+ && data.items.every(row => validDate(row.date) && row.date.startsWith(payload.month + '-')
35
+ && row.record_id.trim() !== '' && (row.rule_id == null || /^[1-9][0-9]*$/.test(row.rule_id) && BigInt(row.rule_id) <= 9223372036854775807n)
36
+ && [null, 0, 1].includes(row.leave_flag) && validTime(row.created_at) && validTime(row.updated_at)
37
+ && countKeys.every(key => row[key] == null || Number.isSafeInteger(row[key]) && row[key] >= 0 && row[key] <= 2147483647))
38
+ && countKeys.every(key => {
39
+ if (data.items.some(row => row[key] == null)) return data[key] === null;
40
+ return Number.isSafeInteger(data[key]) && data[key] === data.items.reduce((sum, row) => sum + row[key], 0);
41
+ });
42
+ if (!valid) {
43
+ const failure = new Failure(1, 'api', 'invalid_response', '本人月统计返回的月份、逐日记录、计数或数据时间不完整或不一致。');
44
+ if (result.value.request_id) failure.result.value.request_id = result.value.request_id;
45
+ throw failure;
46
+ }
47
+ return result;
48
+ }
49
+ module.exports = { execute };