@guanwenai/high-tech-project-cli 0.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.
@@ -0,0 +1,220 @@
1
+ import { CliError } from '../core/errors.mjs';
2
+ import {
3
+ createFileCommand,
4
+ idsInput as sharedIdsInput,
5
+ requiredString,
6
+ valueOf,
7
+ withJavaMethod,
8
+ } from './shared.mjs';
9
+
10
+ const REQUIRED_CONTEXT = ['gatewayUrl', 'uuid'];
11
+
12
+ function objectInput(input) {
13
+ if (input === null || Array.isArray(input) || typeof input !== 'object') {
14
+ throw new CliError('ARGUMENT_INVALID', '命令输入必须是对象');
15
+ }
16
+ return input;
17
+ }
18
+
19
+ function positiveId(value, field) {
20
+ if (!Number.isSafeInteger(value) || value <= 0) {
21
+ throw new CliError('ARGUMENT_INVALID', `${field} 必须是正整数`);
22
+ }
23
+ return value;
24
+ }
25
+
26
+ function pageInput(input) {
27
+ const value = objectInput(input);
28
+ for (const field of ['pageNumber', 'pageSize']) {
29
+ if (value[field] !== undefined) positiveId(value[field], field);
30
+ }
31
+ return value;
32
+ }
33
+
34
+ function itemsInput(input) {
35
+ const value = objectInput(input);
36
+ if (!Array.isArray(value.items) || value.items.length === 0
37
+ || value.items.some((item) => item === null || Array.isArray(item) || typeof item !== 'object')) {
38
+ throw new CliError('ARGUMENT_INVALID', 'items 必须是非空对象数组');
39
+ }
40
+ return value;
41
+ }
42
+
43
+ function productInput(input) {
44
+ const value = objectInput(input);
45
+ value.psProductId = value.psProductId ?? value.ps_product_id;
46
+ positiveId(value.psProductId, 'ps_product_id');
47
+ return value;
48
+ }
49
+
50
+ function relationInput(field, exactLength) {
51
+ return (input) => {
52
+ const value = objectInput(input);
53
+ positiveId(value.id, 'id');
54
+ value[field] = value[field] ?? value[field.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`)];
55
+ if (!Array.isArray(value[field])
56
+ || (exactLength && value[field].length !== exactLength)) {
57
+ throw new CliError('ARGUMENT_INVALID', `${field} 必须是有效的 ID 数组`);
58
+ }
59
+ value[field].forEach((id) => positiveId(id, field));
60
+ return value;
61
+ };
62
+ }
63
+
64
+ function command({ name, path, idempotent, validate = objectInput, handler, body, javaMethod }) {
65
+ return withJavaMethod({
66
+ name, method: 'POST', path, idempotent, requiredContext: REQUIRED_CONTEXT, validate,
67
+ handler: handler ?? (async ({ client, context, input }) => await client.request({
68
+ method: 'POST', path, body: body ? body(input, context) : { ...input, uuid: context.uuid }, idempotent,
69
+ })),
70
+ }, javaMethod);
71
+ }
72
+
73
+ function fileCreateInput(input) {
74
+ const value = objectInput(input);
75
+ value.psProductId = value.psProductId ?? value.ps_product_id;
76
+ positiveId(value.psProductId, 'ps_product_id');
77
+ requiredString(value.fileName ?? value.file_name, 'file_name');
78
+ requiredString(value.filePath ?? value.file_path, 'file_path');
79
+ return value;
80
+ }
81
+
82
+ function briefInput(input, idRequired = false) {
83
+ const value = objectInput(input);
84
+ value.psProductId = value.psProductId ?? value.ps_product_id;
85
+ if (idRequired) positiveId(value.id, 'id');
86
+ if (value.psProductId !== undefined) positiveId(value.psProductId, 'ps_product_id');
87
+ return value;
88
+ }
89
+
90
+ export const commands = [
91
+ command({
92
+ name: 'product.list', path: '/api/project-writing/ps-product/page', idempotent: true,
93
+ validate: pageInput,
94
+ handler: async ({ client, context, input }) => await client.request({
95
+ method: 'POST', path: '/api/project-writing/ps-product/page', idempotent: true,
96
+ body: { ...input, pageNumber: input.pageNumber ?? 1, pageSize: input.pageSize ?? 20, uuid: context.uuid },
97
+ }),
98
+ }),
99
+ command({
100
+ name: 'product.detail', path: '/api/project-writing/ps-product-brief/detail', idempotent: true,
101
+ validate: productInput,
102
+ handler: async ({ client, context, input }) => await client.request({
103
+ method: 'POST', path: '/api/project-writing/ps-product-brief/detail', idempotent: true,
104
+ body: {
105
+ psProductId: input.psProductId,
106
+ ...(context.tenantCode ? { tenantCode: context.tenantCode } : {}),
107
+ },
108
+ }),
109
+ }),
110
+ command({
111
+ name: 'product.save-batch', path: '/api/project-writing/ps-product/batch-save', idempotent: false,
112
+ validate: itemsInput, javaMethod: 'project_writing_ps_product_batch_save',
113
+ handler: async ({ client, context, input }) => await client.request({
114
+ method: 'POST', path: '/api/project-writing/ps-product/batch-save', idempotent: false,
115
+ body: input.items.map((item) => ({ ...item, uuid: item.uuid ?? context.uuid })),
116
+ }),
117
+ }),
118
+ command({
119
+ name: 'product.bind-rd-projects', path: '/api/project-writing/ps-product/bind-rd-projects', idempotent: false,
120
+ validate: relationInput('rdProjectIds'), javaMethod: 'project_writing_ps_product_bind_rd_projects',
121
+ handler: async ({ client, context, input }) => await client.request({
122
+ method: 'POST', path: '/api/project-writing/ps-product/bind-rd-projects', idempotent: false,
123
+ body: { id: input.id, rdProjectIds: input.rdProjectIds, uuid: context.uuid },
124
+ }),
125
+ }),
126
+ command({
127
+ name: 'product.bind-iprs', path: '/api/project-writing/ps-product/bind-iprs', idempotent: false,
128
+ validate: relationInput('iprIds'), javaMethod: 'project_writing_ps_product_bind_iprs',
129
+ handler: async ({ client, context, input }) => await client.request({
130
+ method: 'POST', path: '/api/project-writing/ps-product/bind-iprs', idempotent: false,
131
+ body: { id: input.id, iprIds: input.iprIds, uuid: context.uuid },
132
+ }),
133
+ }),
134
+ command({
135
+ name: 'product.bind-income', path: '/api/project-writing/ps-product/bind-income', idempotent: false,
136
+ validate: relationInput('incomeIds', 1),
137
+ handler: async ({ client, context, input }) => await client.request({
138
+ method: 'POST', path: '/api/project-writing/ps-product/bind-income', idempotent: false,
139
+ body: { id: input.id, operationStatusId: input.incomeIds[0], uuid: context.uuid },
140
+ }),
141
+ }),
142
+ command({
143
+ name: 'product.files', path: '/api/project-writing/ps-product-file/page', idempotent: true,
144
+ validate: productInput,
145
+ handler: async ({ client, context, input }) => await client.request({
146
+ method: 'POST', path: '/api/project-writing/ps-product-file/page', idempotent: true,
147
+ body: {
148
+ uuid: context.uuid, psProductId: input.psProductId,
149
+ pageNumber: input.pageNumber ?? 1, pageSize: input.pageSize ?? 20,
150
+ },
151
+ }),
152
+ }),
153
+ command({
154
+ name: 'product.submissions', path: '/api/project-writing/ps-product-submit/list', idempotent: true,
155
+ validate: productInput,
156
+ handler: async ({ client, context, input }) => await client.request({
157
+ method: 'POST', path: '/api/project-writing/ps-product-submit/list', idempotent: true,
158
+ body: { uuid: context.uuid, psProductId: input.psProductId },
159
+ }),
160
+ }),
161
+ command({
162
+ name: 'product.list-all', path: '/api/project-writing/ps-product/list', idempotent: true,
163
+ javaMethod: 'project_writing_ps_product_list',
164
+ body: (input, context) => ({ ...input, uuid: context.uuid }),
165
+ }),
166
+ command({
167
+ name: 'product.delete-batch', path: '/api/project-writing/ps-product/batch-delete', idempotent: false,
168
+ javaMethod: 'project_writing_ps_product_batch_delete', validate: sharedIdsInput,
169
+ body: (input) => input.ids,
170
+ }),
171
+ withJavaMethod(createFileCommand({
172
+ name: 'product.file-create', path: '/api/project-writing/ps-product-file/create',
173
+ idField: 'psProductId', validate: fileCreateInput,
174
+ extraFields: [
175
+ { snake: 'file_type', camel: 'fileType' },
176
+ { snake: 'file_code', camel: 'fileCode' },
177
+ { snake: 'file_format', camel: 'fileFormat' },
178
+ ],
179
+ }), 'project_writing_ps_product_file_create'),
180
+ command({
181
+ name: 'product.file-list', path: '/api/project-writing/ps-product-file/list', idempotent: true,
182
+ javaMethod: 'project_writing_ps_product_file_list',
183
+ body: (input, context) => ({
184
+ uuid: context.uuid,
185
+ ...(input.ids ? { ids: input.ids } : {}),
186
+ ...(input.psProductId !== undefined || input.ps_product_id !== undefined
187
+ ? { psProductId: input.psProductId ?? input.ps_product_id } : {}),
188
+ ...(input.fileType !== undefined || input.file_type !== undefined
189
+ ? { fileType: input.fileType ?? input.file_type } : {}),
190
+ ...(input.fileCode !== undefined || input.file_code !== undefined
191
+ ? { fileCode: input.fileCode ?? input.file_code } : {}),
192
+ ...(input.fileFormat !== undefined || input.file_format !== undefined
193
+ ? { fileFormat: input.fileFormat ?? input.file_format } : {}),
194
+ }),
195
+ }),
196
+ command({
197
+ name: 'product.file-delete-batch', path: '/api/project-writing/ps-product-file/delete-batch', idempotent: false,
198
+ javaMethod: 'project_writing_ps_product_file_delete_batch', validate: sharedIdsInput,
199
+ body: (input) => input.ids,
200
+ }),
201
+ command({
202
+ name: 'product.brief-create', path: '/api/project-writing/ps-product-brief/create', idempotent: false,
203
+ javaMethod: 'project_writing_ps_product_brief_create', validate: (input) => briefInput(input),
204
+ body: (input, context) => ({ ...input, uuid: context.uuid }),
205
+ }),
206
+ command({
207
+ name: 'product.brief-update', path: '/api/project-writing/ps-product-brief/update', idempotent: false,
208
+ javaMethod: 'project_writing_ps_product_brief_update', validate: (input) => briefInput(input, true),
209
+ body: (input, context) => ({ ...input, uuid: context.uuid }),
210
+ }),
211
+ command({
212
+ name: 'product.brief-detail', path: '/api/project-writing/ps-product-brief/detail', idempotent: true,
213
+ javaMethod: 'project_writing_ps_product_brief_detail', validate: objectInput,
214
+ body: (input, context) => ({
215
+ uuid: context.uuid,
216
+ ...(input.psProductId !== undefined || input.ps_product_id !== undefined
217
+ ? { psProductId: input.psProductId ?? input.ps_product_id } : {}),
218
+ }),
219
+ }),
220
+ ];
@@ -0,0 +1,99 @@
1
+ import { CliError } from '../core/errors.mjs';
2
+ import { withJavaMethod } from './shared.mjs';
3
+
4
+ const BASE_CONTEXT = ['gatewayUrl'];
5
+ const PROJECT_CONTEXT = [...BASE_CONTEXT, 'uuid'];
6
+
7
+ function objectInput(input) {
8
+ if (input === null || Array.isArray(input) || typeof input !== 'object') {
9
+ throw new CliError('ARGUMENT_INVALID', '命令输入必须是对象');
10
+ }
11
+ return input;
12
+ }
13
+
14
+ function bindInput(input) {
15
+ const value = objectInput(input);
16
+ const projectId = Number(value.workId);
17
+ if (!Number.isSafeInteger(projectId) || projectId <= 0
18
+ || typeof value.workName !== 'string' || !value.workName.trim()
19
+ || typeof value.customerName !== 'string' || !value.customerName.trim()) {
20
+ throw new CliError('ARGUMENT_INVALID', '项目绑定需要有效的 workId、workName 和 customerName');
21
+ }
22
+ return value;
23
+ }
24
+
25
+ function requestCommand({ name, path, idempotent, requiredContext, validate = objectInput, body, javaMethod }) {
26
+ return withJavaMethod({
27
+ name,
28
+ method: 'POST',
29
+ path,
30
+ idempotent,
31
+ requiredContext,
32
+ validate,
33
+ handler: async ({ client, context, input }) => await client.request({
34
+ method: 'POST',
35
+ path,
36
+ body: body(input, context),
37
+ idempotent,
38
+ }),
39
+ }, javaMethod);
40
+ }
41
+
42
+ export const commands = [
43
+ requestCommand({
44
+ name: 'project.list',
45
+ path: '/api/project-writing/bind/list',
46
+ idempotent: true,
47
+ requiredContext: BASE_CONTEXT,
48
+ body: (input, context) => ({
49
+ ...input,
50
+ ...(context.tenantCode ? { tenantCode: context.tenantCode } : {}),
51
+ }),
52
+ }),
53
+ requestCommand({
54
+ name: 'project.detail',
55
+ path: '/api/project-writing/customer/detail',
56
+ idempotent: true,
57
+ requiredContext: PROJECT_CONTEXT,
58
+ body: (input, context) => ({
59
+ ...input,
60
+ ...(context.tenantCode ? { tenantCode: context.tenantCode } : {}),
61
+ uuid: context.uuid,
62
+ }),
63
+ javaMethod: 'project_writing_customer_detail',
64
+ }),
65
+ requestCommand({
66
+ name: 'project.save',
67
+ path: '/api/project-writing/customer/save',
68
+ idempotent: false,
69
+ requiredContext: PROJECT_CONTEXT,
70
+ body: (input, context) => ({
71
+ ...input,
72
+ ...(context.tenantCode ? { tenantCode: context.tenantCode } : {}),
73
+ uuid: context.uuid,
74
+ }),
75
+ }),
76
+ requestCommand({
77
+ name: 'project.bind',
78
+ path: '/api/project-writing/bind/create',
79
+ idempotent: false,
80
+ requiredContext: BASE_CONTEXT,
81
+ validate: bindInput,
82
+ body: (input) => ({
83
+ projectId: Number(input.workId),
84
+ projectName: input.workName.trim(),
85
+ customerName: input.customerName.trim(),
86
+ }),
87
+ }),
88
+ requestCommand({
89
+ name: 'project.overview',
90
+ path: '/api/project-writing/customer/list',
91
+ idempotent: true,
92
+ requiredContext: PROJECT_CONTEXT,
93
+ body: (input, context) => ({
94
+ ...input,
95
+ ...(context.tenantCode ? { tenantCode: context.tenantCode } : {}),
96
+ uuid: context.uuid,
97
+ }),
98
+ }),
99
+ ];
@@ -0,0 +1,225 @@
1
+ import { CliError } from '../core/errors.mjs';
2
+ import {
3
+ createFileCommand,
4
+ idsInput as sharedIdsInput,
5
+ requiredString,
6
+ valueOf,
7
+ withJavaMethod,
8
+ } from './shared.mjs';
9
+
10
+ const REQUIRED_CONTEXT = ['gatewayUrl', 'uuid'];
11
+
12
+ function objectInput(input) {
13
+ if (input === null || Array.isArray(input) || typeof input !== 'object') {
14
+ throw new CliError('ARGUMENT_INVALID', '命令输入必须是对象');
15
+ }
16
+ return input;
17
+ }
18
+
19
+ function positiveId(value, field) {
20
+ if (!Number.isSafeInteger(value) || value <= 0) {
21
+ throw new CliError('ARGUMENT_INVALID', `${field} 必须是正整数`);
22
+ }
23
+ return value;
24
+ }
25
+
26
+ function pageInput(input) {
27
+ const value = objectInput(input);
28
+ for (const field of ['pageNumber', 'pageSize']) {
29
+ if (value[field] !== undefined) positiveId(value[field], field);
30
+ }
31
+ return value;
32
+ }
33
+
34
+ function itemsInput(input) {
35
+ const value = objectInput(input);
36
+ if (!Array.isArray(value.items) || value.items.length === 0
37
+ || value.items.some((item) => item === null || Array.isArray(item) || typeof item !== 'object')) {
38
+ throw new CliError('ARGUMENT_INVALID', 'items 必须是非空对象数组');
39
+ }
40
+ return value;
41
+ }
42
+
43
+ function projectInput(input) {
44
+ const value = objectInput(input);
45
+ const rdProjectId = value.rdProjectId ?? value.rd_project_id;
46
+ positiveId(rdProjectId, 'rd_project_id');
47
+ value.rdProjectId = rdProjectId;
48
+ return value;
49
+ }
50
+
51
+ function relationInput(field) {
52
+ return (input) => {
53
+ const value = objectInput(input);
54
+ const snakeField = field.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`);
55
+ value[field] = value[field] ?? value[snakeField];
56
+ positiveId(value.id, 'id');
57
+ if (!Array.isArray(value[field]) || value[field].length === 0) {
58
+ throw new CliError('ARGUMENT_INVALID', `${field} 必须是非空 ID 数组`);
59
+ }
60
+ value[field].forEach((id) => positiveId(id, field));
61
+ return value;
62
+ };
63
+ }
64
+
65
+ function fileCreateInput(input) {
66
+ const value = objectInput(input);
67
+ positiveId(value.rdProjectId ?? value.rd_project_id, 'rd_project_id');
68
+ requiredString(value.fileName ?? value.file_name, 'file_name');
69
+ requiredString(value.filePath ?? value.file_path, 'file_path');
70
+ return value;
71
+ }
72
+
73
+ function briefInput(input, idRequired = false) {
74
+ const value = objectInput(input);
75
+ const id = value.id;
76
+ const rdProjectId = value.rdProjectId ?? value.rd_project_id;
77
+ if (idRequired) positiveId(id, 'id');
78
+ if (rdProjectId !== undefined) positiveId(rdProjectId, 'rd_project_id');
79
+ return value;
80
+ }
81
+
82
+ function command({ name, path, idempotent, validate = objectInput, handler, body, javaMethod }) {
83
+ return withJavaMethod({
84
+ name, method: 'POST', path, idempotent, requiredContext: REQUIRED_CONTEXT, validate,
85
+ handler: handler ?? (async ({ client, context, input }) => await client.request({
86
+ method: 'POST', path, body: body ? body(input, context) : { ...input, uuid: context.uuid }, idempotent,
87
+ })),
88
+ }, javaMethod);
89
+ }
90
+
91
+ export const commands = [
92
+ command({
93
+ name: 'rd.list', path: '/api/project-writing/rd-project/page', idempotent: true,
94
+ validate: pageInput,
95
+ handler: async ({ client, context, input }) => await client.request({
96
+ method: 'POST', path: '/api/project-writing/rd-project/page', idempotent: true,
97
+ body: { ...input, pageNumber: input.pageNumber ?? 1, pageSize: input.pageSize ?? 20, uuid: context.uuid },
98
+ }),
99
+ }),
100
+ command({
101
+ name: 'rd.list-all', path: '/api/project-writing/rd-project/list', idempotent: true,
102
+ javaMethod: 'project_writing_rd_project_list',
103
+ body: (input, context) => ({ ...input, uuid: context.uuid }),
104
+ }),
105
+ command({
106
+ name: 'rd.detail', path: '/api/project-writing/rd-project/detail', idempotent: true,
107
+ validate: projectInput,
108
+ }),
109
+ command({
110
+ name: 'rd.save-batch', path: '/api/project-writing/rd-project/batch-save', idempotent: false,
111
+ validate: itemsInput, javaMethod: 'project_writing_rd_project_batch_save',
112
+ handler: async ({ client, context, input }) => await client.request({
113
+ method: 'POST', path: '/api/project-writing/rd-project/batch-save', idempotent: false,
114
+ body: input.items.map((item) => ({ ...item, uuid: item.uuid ?? context.uuid })),
115
+ }),
116
+ }),
117
+ command({
118
+ name: 'rd.bind-person', path: '/api/project-writing/rd-project/bind-person', idempotent: false,
119
+ validate: relationInput('personIds'), javaMethod: 'project_writing_rd_project_bind_person',
120
+ handler: async ({ client, context, input }) => {
121
+ const results = [];
122
+ for (const personId of input.personIds) {
123
+ results.push(await client.request({
124
+ method: 'POST', path: '/api/project-writing/rd-project/bind-person', idempotent: false,
125
+ body: { id: input.id, personId, uuid: context.uuid },
126
+ }));
127
+ }
128
+ return results;
129
+ },
130
+ }),
131
+ command({
132
+ name: 'rd.bind-iprs', path: '/api/project-writing/rd-project/bind-iprs', idempotent: false,
133
+ validate: relationInput('iprIds'), javaMethod: 'project_writing_rd_project_bind_iprs',
134
+ handler: async ({ client, context, input }) => await client.request({
135
+ method: 'POST', path: '/api/project-writing/rd-project/bind-iprs', idempotent: false,
136
+ body: { id: input.id, iprIds: input.iprIds, uuid: context.uuid },
137
+ }),
138
+ }),
139
+ command({
140
+ name: 'rd.files', path: '/api/project-writing/rd-project-file/page', idempotent: true,
141
+ validate: projectInput,
142
+ handler: async ({ client, context, input }) => await client.request({
143
+ method: 'POST', path: '/api/project-writing/rd-project-file/page', idempotent: true,
144
+ body: {
145
+ uuid: context.uuid, rdProjectId: input.rdProjectId,
146
+ pageNumber: input.pageNumber ?? 1, pageSize: input.pageSize ?? 20,
147
+ },
148
+ }),
149
+ }),
150
+ command({
151
+ name: 'rd.submissions', path: '/api/project-writing/rd-project-submit/list', idempotent: true,
152
+ validate: projectInput,
153
+ handler: async ({ client, context, input }) => await client.request({
154
+ method: 'POST', path: '/api/project-writing/rd-project-submit/list', idempotent: true,
155
+ body: { uuid: context.uuid, rdProjectId: input.rdProjectId },
156
+ }),
157
+ }),
158
+ command({
159
+ name: 'rd.bind-fee', path: '/api/project-writing/rd-project/bind-fee', idempotent: false,
160
+ javaMethod: 'project_writing_rd_project_bind_fee', validate: projectInput,
161
+ body: (input, context) => ({
162
+ id: input.rdProjectId,
163
+ feeDetailId: input.rdFundSituationId ?? input.feeDetailId,
164
+ uuid: context.uuid,
165
+ }),
166
+ }),
167
+ command({
168
+ name: 'rd.delete-batch', path: '/api/project-writing/rd-project/batch-delete', idempotent: false,
169
+ javaMethod: 'project_writing_rd_project_batch_delete', validate: sharedIdsInput,
170
+ body: (input) => input.ids,
171
+ }),
172
+ withJavaMethod(createFileCommand({
173
+ name: 'rd.file-create', path: '/api/project-writing/rd-project-file/create',
174
+ idField: 'rdProjectId', validate: fileCreateInput,
175
+ extraFields: [
176
+ { snake: 'file_type', camel: 'fileType' },
177
+ { snake: 'file_code', camel: 'fileCode' },
178
+ { snake: 'file_format', camel: 'fileFormat' },
179
+ ],
180
+ }), 'project_writing_rd_project_file_create'),
181
+ command({
182
+ name: 'rd.file-list', path: '/api/project-writing/rd-project-file/list', idempotent: true,
183
+ javaMethod: 'project_writing_rd_project_file_list',
184
+ body: (input, context) => ({
185
+ uuid: context.uuid,
186
+ ...(input.ids ? { ids: input.ids } : {}),
187
+ ...(input.rdProjectId !== undefined || input.rd_project_id !== undefined
188
+ ? { rdProjectId: input.rdProjectId ?? input.rd_project_id } : {}),
189
+ ...(input.fileType !== undefined || input.file_type !== undefined
190
+ ? { fileType: input.fileType ?? input.file_type } : {}),
191
+ ...(input.fileCode !== undefined || input.file_code !== undefined
192
+ ? { fileCode: input.fileCode ?? input.file_code } : {}),
193
+ ...(input.fileFormat !== undefined || input.file_format !== undefined
194
+ ? { fileFormat: input.fileFormat ?? input.file_format } : {}),
195
+ }),
196
+ }),
197
+ command({
198
+ name: 'rd.file-delete-batch', path: '/api/project-writing/rd-project-file/delete-batch', idempotent: false,
199
+ javaMethod: 'project_writing_rd_project_file_delete_batch', validate: sharedIdsInput,
200
+ body: (input) => input.ids,
201
+ }),
202
+ command({
203
+ name: 'rd.brief-create', path: '/api/project-writing/rd-project-brief/create', idempotent: false,
204
+ javaMethod: 'project_writing_rd_project_brief_create', validate: (input) => briefInput(input),
205
+ body: (input, context) => ({
206
+ ...input, rdProjectId: input.rdProjectId ?? input.rd_project_id, uuid: context.uuid,
207
+ }),
208
+ }),
209
+ command({
210
+ name: 'rd.brief-update', path: '/api/project-writing/rd-project-brief/update', idempotent: false,
211
+ javaMethod: 'project_writing_rd_project_brief_update', validate: (input) => briefInput(input, true),
212
+ body: (input, context) => ({
213
+ ...input, rdProjectId: input.rdProjectId ?? input.rd_project_id, uuid: context.uuid,
214
+ }),
215
+ }),
216
+ command({
217
+ name: 'rd.brief-detail', path: '/api/project-writing/rd-project-brief/detail', idempotent: true,
218
+ javaMethod: 'project_writing_rd_project_brief_detail', validate: objectInput,
219
+ body: (input, context) => ({
220
+ uuid: context.uuid,
221
+ ...(input.rdProjectId !== undefined || input.rd_project_id !== undefined
222
+ ? { rdProjectId: input.rdProjectId ?? input.rd_project_id } : {}),
223
+ }),
224
+ }),
225
+ ];
@@ -0,0 +1,117 @@
1
+ import { CliError } from '../core/errors.mjs';
2
+ import { downloadAtomically } from './material.mjs';
3
+
4
+ const REQUIRED_CONTEXT = ['gatewayUrl', 'uuid'];
5
+ const COMPLETENESS_QUERIES = [
6
+ ['customer', '/api/project-writing/customer/list', {}],
7
+ ['people', '/api/project-writing/person/page', { pageNumber: 1, pageSize: 200, chooseFlag: true }],
8
+ ['ipr', '/api/project-writing/ipr/page', { pageNumber: 1, pageSize: 200, chooseFlag: true }],
9
+ ['rd', '/api/project-writing/rd-project/page', { pageNumber: 1, pageSize: 200 }],
10
+ ['product', '/api/project-writing/ps-product/page', { pageNumber: 1, pageSize: 200 }],
11
+ ['achievement', '/api/project-writing/tech-achievement/page', { pageNumber: 1, pageSize: 200 }],
12
+ ['policy', '/api/project-writing/system-manage-situation/detail', {}],
13
+ ['materials', '/api/project-writing/supplement-file/list', {}],
14
+ ];
15
+ const SUMMARY_QUERIES = [
16
+ ['project', '/api/project-writing/bind/list', {}],
17
+ ...COMPLETENESS_QUERIES,
18
+ ];
19
+ const GENERATE_ROUTES = {
20
+ people: '/api/project-writing/person-submit/generate',
21
+ ipr: '/api/project-writing/ipr-submit/generate',
22
+ rd: '/api/project-writing/rd-project-submit/generate',
23
+ product: '/api/project-writing/ps-product-submit/generate',
24
+ achievement: '/api/project-writing/tech-achievement-submit/generate',
25
+ policy: '/api/project-writing/system-manage-submit/generate',
26
+ };
27
+
28
+ function objectInput(input) {
29
+ if (input === null || Array.isArray(input) || typeof input !== 'object') {
30
+ throw new CliError('ARGUMENT_INVALID', '命令输入必须是对象');
31
+ }
32
+ return input;
33
+ }
34
+
35
+ function idArray(value) {
36
+ return Array.isArray(value) && value.length > 0
37
+ && value.every((id) => Number.isSafeInteger(id) && id > 0);
38
+ }
39
+
40
+ function generateInput(input) {
41
+ const value = objectInput(input);
42
+ if (!Array.isArray(value.jobs) || value.jobs.length === 0) {
43
+ throw new CliError('ARGUMENT_INVALID', 'jobs 必须是非空数组');
44
+ }
45
+ for (const job of value.jobs) {
46
+ if (job === null || typeof job !== 'object' || !Object.hasOwn(GENERATE_ROUTES, job.type)
47
+ || !idArray(job.fileIds)) {
48
+ throw new CliError('ARGUMENT_INVALID', '报告生成任务类型或 fileIds 无效');
49
+ }
50
+ }
51
+ return value;
52
+ }
53
+
54
+ function downloadInput(input) {
55
+ const value = objectInput(input);
56
+ if (typeof value.fileUrl !== 'string' || !value.fileUrl.trim()
57
+ || typeof value.fileName !== 'string' || !value.fileName.trim()
58
+ || typeof value.target !== 'string' || !value.target.trim()) {
59
+ throw new CliError('ARGUMENT_INVALID', '报告下载需要 fileUrl、fileName 和 target');
60
+ }
61
+ return value;
62
+ }
63
+
64
+ async function aggregate(client, context, queries) {
65
+ const sections = {};
66
+ const failed = [];
67
+ for (let offset = 0; offset < queries.length; offset += 4) {
68
+ const batch = queries.slice(offset, offset + 4);
69
+ await Promise.all(batch.map(async ([section, path, body]) => {
70
+ try {
71
+ sections[section] = await client.request({
72
+ method: 'POST', path, idempotent: true, body: { uuid: context.uuid, ...body },
73
+ });
74
+ } catch (error) {
75
+ failed.push({
76
+ section,
77
+ code: error?.code ?? 'BUSINESS_ERROR',
78
+ message: error instanceof Error ? error.message : '查询失败',
79
+ });
80
+ }
81
+ }));
82
+ }
83
+ return { complete: failed.length === 0, sections, failed };
84
+ }
85
+
86
+ export const commands = [
87
+ {
88
+ name: 'report.completeness', method: 'POST', path: '/api/project-writing/customer/list',
89
+ idempotent: true, multipart: false, requiredContext: REQUIRED_CONTEXT, validate: objectInput,
90
+ handler: async ({ client, context }) => await aggregate(client, context, COMPLETENESS_QUERIES),
91
+ },
92
+ {
93
+ name: 'report.summary', method: 'POST', path: '/api/project-writing/bind/list',
94
+ idempotent: true, multipart: false, requiredContext: REQUIRED_CONTEXT, validate: objectInput,
95
+ handler: async ({ client, context }) => await aggregate(client, context, SUMMARY_QUERIES),
96
+ },
97
+ {
98
+ name: 'report.generate', method: 'POST', path: GENERATE_ROUTES.people,
99
+ idempotent: false, multipart: false, requiredContext: REQUIRED_CONTEXT, validate: generateInput,
100
+ handler: async ({ client, context, input }) => {
101
+ const results = [];
102
+ for (const job of input.jobs) {
103
+ const { type, ...payload } = job;
104
+ results.push(await client.request({
105
+ method: 'POST', path: GENERATE_ROUTES[type], idempotent: false,
106
+ body: { uuid: context.uuid, ...payload },
107
+ }));
108
+ }
109
+ return results;
110
+ },
111
+ },
112
+ {
113
+ name: 'report.download', method: 'POST', path: '/api/project-writing/file/download',
114
+ idempotent: false, multipart: false, requiredContext: REQUIRED_CONTEXT, validate: downloadInput,
115
+ handler: downloadAtomically,
116
+ },
117
+ ];