@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,100 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join, resolve } from 'node:path';
5
+
6
+ import { CliError } from './errors.mjs';
7
+
8
+ const CONFIG_FILE_ENVIRONMENT_NAME = 'GUANWEN_PROJECT_WRITING_CONFIG_FILE';
9
+
10
+ function configuredValue(value) {
11
+ if (value === undefined || value === null) return '';
12
+ return String(value).trim();
13
+ }
14
+
15
+ function validateGatewayUrl(value) {
16
+ const gatewayUrl = configuredValue(value);
17
+ let parsed;
18
+ try {
19
+ parsed = new URL(gatewayUrl);
20
+ } catch {
21
+ throw new CliError('CONFIG_INVALID', 'gateway URL 无效');
22
+ }
23
+ if (!['http:', 'https:'].includes(parsed.protocol)
24
+ || parsed.username
25
+ || parsed.password
26
+ || parsed.hash) {
27
+ throw new CliError('CONFIG_INVALID', 'gateway URL 无效');
28
+ }
29
+ return gatewayUrl;
30
+ }
31
+
32
+ export function resolveUserConfigPath({
33
+ environment = process.env,
34
+ platform = process.platform,
35
+ homeDirectory = homedir(),
36
+ } = {}) {
37
+ const configuredPath = configuredValue(environment[CONFIG_FILE_ENVIRONMENT_NAME]);
38
+ if (configuredPath) return resolve(configuredPath);
39
+
40
+ if (platform === 'win32') {
41
+ const appData = configuredValue(environment.APPDATA)
42
+ || join(homeDirectory, 'AppData', 'Roaming');
43
+ return join(appData, 'Guanwen', 'high-tech-project', 'config.json');
44
+ }
45
+ const configHome = configuredValue(environment.XDG_CONFIG_HOME)
46
+ || join(homeDirectory, '.config');
47
+ return join(configHome, 'guanwen', 'high-tech-project', 'config.json');
48
+ }
49
+
50
+ export async function loadUserConfiguration(options = {}) {
51
+ const configPath = resolveUserConfigPath(options);
52
+ let source;
53
+ try {
54
+ source = await readFile(configPath, 'utf8');
55
+ } catch (error) {
56
+ if (error?.code === 'ENOENT') return {};
57
+ throw new CliError('CONFIG_INVALID', '无法读取项目申报 CLI 配置', { configPath });
58
+ }
59
+
60
+ let configuration;
61
+ try {
62
+ configuration = JSON.parse(source);
63
+ } catch {
64
+ throw new CliError('CONFIG_INVALID', '项目申报 CLI 配置文件不是合法 JSON', { configPath });
65
+ }
66
+ if (configuration === null || Array.isArray(configuration) || typeof configuration !== 'object') {
67
+ throw new CliError('CONFIG_INVALID', '项目申报 CLI 配置文件必须是 JSON 对象', { configPath });
68
+ }
69
+ if (configuration.gatewayUrl === undefined || configuration.gatewayUrl === null
70
+ || configuredValue(configuration.gatewayUrl) === '') {
71
+ return {};
72
+ }
73
+ try {
74
+ return { gatewayUrl: validateGatewayUrl(configuration.gatewayUrl) };
75
+ } catch (error) {
76
+ if (error instanceof CliError) error.details = { configPath };
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ export async function persistGatewayUrl(gatewayUrl, options = {}) {
82
+ const value = validateGatewayUrl(gatewayUrl);
83
+ const configPath = resolveUserConfigPath(options);
84
+ const temporaryPath = `${configPath}.${process.pid}.${randomUUID()}.tmp`;
85
+ await mkdir(dirname(configPath), { recursive: true });
86
+ try {
87
+ await writeFile(
88
+ temporaryPath,
89
+ `${JSON.stringify({ gatewayUrl: value }, null, 2)}\n`,
90
+ { encoding: 'utf8', flag: 'wx', mode: 0o600 },
91
+ );
92
+ await rename(temporaryPath, configPath);
93
+ } catch (error) {
94
+ if (error instanceof CliError) throw error;
95
+ throw new CliError('FILE_WRITE_FAILED', '无法写入项目申报 CLI 配置', { configPath });
96
+ } finally {
97
+ await rm(temporaryPath, { force: true }).catch(() => undefined);
98
+ }
99
+ return configPath;
100
+ }
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { randomUUID } from 'node:crypto';
4
+ import { resolve } from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
6
+
7
+ import { parseArguments } from './cli/arguments.mjs';
8
+ import { CommandRegistry } from './cli/commandRegistry.mjs';
9
+ import { failureEnvelope, serializeEnvelope, successEnvelope } from './cli/output.mjs';
10
+ import { resolveProjectContext } from './core/context.mjs';
11
+ import { CliError, normalizeCliError } from './core/errors.mjs';
12
+ import { readJsonInput } from './core/files.mjs';
13
+ import { GatewayClient } from './core/gatewayClient.mjs';
14
+ import { loadUserConfiguration } from './core/userConfig.mjs';
15
+ import { commands as achievementCommands } from './services/achievement.mjs';
16
+ import { commands as financeCommands } from './services/finance.mjs';
17
+ import { commands as iprCommands } from './services/ipr.mjs';
18
+ import { commands as knowledgeCommands } from './services/knowledge.mjs';
19
+ import { commands as peopleCommands } from './services/people.mjs';
20
+ import { commands as materialCommands } from './services/material.mjs';
21
+ import { commands as policyCommands } from './services/policy.mjs';
22
+ import { commands as productCommands } from './services/product.mjs';
23
+ import { commands as projectCommands } from './services/project.mjs';
24
+ import { commands as rdCommands } from './services/rd.mjs';
25
+ import { commands as reportCommands } from './services/report.mjs';
26
+ import { commands as sopCommands } from './services/sop.mjs';
27
+ import { commands as workflowCommands } from './services/workflow.mjs';
28
+ import { JAVA_PROJECT_WRITING_METHODS } from './methodMappings.mjs';
29
+ import { withJavaMethod } from './services/shared.mjs';
30
+
31
+ function validateObject(input) {
32
+ if (input === null || Array.isArray(input) || typeof input !== 'object') {
33
+ throw new CliError('ARGUMENT_INVALID', '命令输入必须是对象');
34
+ }
35
+ return input;
36
+ }
37
+
38
+ function commandSummary(definition) {
39
+ return {
40
+ name: definition.name,
41
+ javaMethod: definition.javaMethod ?? null,
42
+ method: definition.method,
43
+ path: definition.path,
44
+ idempotent: definition.idempotent,
45
+ requiredContext: definition.requiredContext,
46
+ };
47
+ }
48
+
49
+ function metaDescribeInput(input) {
50
+ const value = validateObject(input);
51
+ if (typeof value.method !== 'string' || !value.method.trim()) {
52
+ throw new CliError('ARGUMENT_INVALID', 'method 不能为空');
53
+ }
54
+ return value;
55
+ }
56
+
57
+ export function createCommandRegistry() {
58
+ const registry = new CommandRegistry();
59
+ registry.register({
60
+ name: 'help',
61
+ method: 'LOCAL',
62
+ path: null,
63
+ idempotent: false,
64
+ requiredContext: [],
65
+ validate: validateObject,
66
+ handler: async () => ({
67
+ usage: 'high-tech-project <domain> <operation> [options]',
68
+ discovery: '运行 commands 查看可用命令',
69
+ }),
70
+ });
71
+ registry.register({
72
+ name: 'commands',
73
+ method: 'LOCAL',
74
+ path: null,
75
+ idempotent: false,
76
+ requiredContext: [],
77
+ validate: validateObject,
78
+ handler: async () => ({ commands: registry.list().map(commandSummary) }),
79
+ });
80
+ registry.register({
81
+ name: 'meta.list',
82
+ method: 'LOCAL',
83
+ path: null,
84
+ idempotent: false,
85
+ requiredContext: [],
86
+ validate: validateObject,
87
+ handler: async ({ registry: currentRegistry }) => ({
88
+ methods: JAVA_PROJECT_WRITING_METHODS.map((method) => ({
89
+ method,
90
+ command: currentRegistry.list().find((item) => item.javaMethod === method)?.name ?? null,
91
+ })),
92
+ }),
93
+ });
94
+ registry.register({
95
+ name: 'meta.describe',
96
+ method: 'LOCAL',
97
+ path: null,
98
+ idempotent: false,
99
+ requiredContext: [],
100
+ validate: metaDescribeInput,
101
+ handler: async ({ registry: currentRegistry, input }) => {
102
+ const definition = currentRegistry.list().find((item) => item.javaMethod === input.method);
103
+ if (!definition) {
104
+ throw new CliError('ARGUMENT_INVALID', `未知项目撰写方法: ${input.method}`);
105
+ }
106
+ return { method: input.method, command: commandSummary(definition) };
107
+ },
108
+ });
109
+ registry.register({
110
+ name: 'account.check',
111
+ method: 'LOCAL',
112
+ path: null,
113
+ idempotent: false,
114
+ requiredContext: [],
115
+ validate: validateObject,
116
+ handler: async ({ context }) => ({
117
+ gatewayUrl: context.gatewayUrl,
118
+ contextId: context.contextId,
119
+ tenantCode: context.tenantCode,
120
+ userId: context.userId,
121
+ userName: context.userName,
122
+ realname: context.realname,
123
+ credentialConfigured: Boolean(context.kfcloudAuth),
124
+ }),
125
+ });
126
+ registry.register({
127
+ name: 'account.test',
128
+ method: 'POST',
129
+ path: '/api/project-writing/bind/list',
130
+ idempotent: true,
131
+ requiredContext: ['gatewayUrl'],
132
+ validate: validateObject,
133
+ handler: async ({ client, context }) => {
134
+ await client.request({
135
+ method: 'POST',
136
+ path: '/api/project-writing/bind/list',
137
+ idempotent: true,
138
+ body: context.tenantCode ? { tenantCode: context.tenantCode } : {},
139
+ });
140
+ return { connected: true };
141
+ },
142
+ });
143
+ for (const command of [
144
+ ...projectCommands,
145
+ ...peopleCommands,
146
+ ...iprCommands,
147
+ ...rdCommands,
148
+ ...productCommands,
149
+ ...financeCommands,
150
+ ...achievementCommands,
151
+ ...policyCommands,
152
+ ...materialCommands,
153
+ ...reportCommands,
154
+ ...sopCommands,
155
+ ...knowledgeCommands,
156
+ ...workflowCommands,
157
+ ]) {
158
+ registry.register(command.javaMethod ? withJavaMethod(command, command.javaMethod) : command);
159
+ }
160
+ const mappedMethods = new Set(registry.list().map((definition) => definition.javaMethod).filter(Boolean));
161
+ const missingMethods = JAVA_PROJECT_WRITING_METHODS.filter((method) => !mappedMethods.has(method));
162
+ if (missingMethods.length > 0) {
163
+ throw new CliError('CLI_INTERNAL_ERROR', '项目撰写方法未完成 CLI 映射', { missingMethods });
164
+ }
165
+ return registry;
166
+ }
167
+
168
+ function requestedCommand(argv) {
169
+ return argv.filter((token) => !token.startsWith('--')).slice(0, 2).join('.') || 'unknown';
170
+ }
171
+
172
+ function hasInvocationGateway(options, input, environment) {
173
+ return [
174
+ options.gatewayUrl,
175
+ input.gatewayUrl,
176
+ environment.GUANWEN_PROJECT_WRITING_GATEWAY_URL,
177
+ ].some((value) => value !== undefined && value !== null && String(value).trim() !== '');
178
+ }
179
+
180
+ function assertRequiredContext(definition, context) {
181
+ const missing = definition.requiredContext.filter((field) => !context[field]);
182
+ if (missing.length > 0) {
183
+ throw new CliError('CONFIG_MISSING', '项目申报账户配置不完整', { missing });
184
+ }
185
+ }
186
+
187
+ function diagnosticLogger(stream) {
188
+ return {
189
+ warn(entry) {
190
+ stream.write(`${JSON.stringify(entry)}\n`);
191
+ },
192
+ };
193
+ }
194
+
195
+ export async function executeCli(
196
+ argv,
197
+ {
198
+ environment = process.env,
199
+ cwd = process.cwd(),
200
+ registry = createCommandRegistry(),
201
+ requestId = randomUUID(),
202
+ clock = () => performance.now(),
203
+ stderr = process.stderr,
204
+ } = {},
205
+ ) {
206
+ const startedAt = clock();
207
+ let command = requestedCommand(argv);
208
+ let pretty = argv.includes('--pretty');
209
+ try {
210
+ const parsed = parseArguments(argv);
211
+ command = parsed.command;
212
+ pretty = parsed.options.pretty === true;
213
+ const definition = registry.get(command);
214
+ if (!definition) {
215
+ throw new CliError('ARGUMENT_INVALID', `未知命令: ${command}`);
216
+ }
217
+ const input = parsed.options.input
218
+ ? await readJsonInput(parsed.options.input, parsed.options.baseDir || cwd)
219
+ : {};
220
+ const configuration = hasInvocationGateway(parsed.options, input, environment)
221
+ ? {}
222
+ : await loadUserConfiguration({ environment });
223
+ const context = resolveProjectContext({
224
+ options: parsed.options,
225
+ input,
226
+ environment,
227
+ configuration,
228
+ });
229
+ assertRequiredContext(definition, context);
230
+ const validatedInput = definition.validate(input);
231
+ const client = definition.method === 'LOCAL'
232
+ ? null
233
+ : new GatewayClient({ context, logger: diagnosticLogger(stderr) });
234
+ const data = await definition.handler({
235
+ client,
236
+ command: definition,
237
+ context,
238
+ input: validatedInput,
239
+ registry,
240
+ baseDir: parsed.options.baseDir || cwd,
241
+ });
242
+ return {
243
+ envelope: successEnvelope(command, data, {
244
+ requestId,
245
+ durationMs: Math.max(0, Math.round(clock() - startedAt)),
246
+ }),
247
+ exitCode: 0,
248
+ pretty,
249
+ };
250
+ } catch (error) {
251
+ const normalized = normalizeCliError(error);
252
+ return {
253
+ envelope: failureEnvelope(command, normalized, {
254
+ requestId,
255
+ durationMs: Math.max(0, Math.round(clock() - startedAt)),
256
+ }),
257
+ exitCode: normalized.exitCode,
258
+ pretty,
259
+ };
260
+ }
261
+ }
262
+
263
+ export async function main(argv = process.argv.slice(2)) {
264
+ const result = await executeCli(argv);
265
+ process.stdout.write(`${serializeEnvelope(result.envelope, result.pretty)}\n`);
266
+ process.exitCode = result.exitCode;
267
+ }
268
+
269
+ const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : '';
270
+ if (invokedPath === import.meta.url) {
271
+ await main();
272
+ }
@@ -0,0 +1,39 @@
1
+ import { resolve } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+
4
+ import { persistGatewayUrl } from '../core/userConfig.mjs';
5
+
6
+ function installGateway(environment) {
7
+ return String(
8
+ environment.npm_config_guanwen_gateway_url
9
+ || environment.GUANWEN_PROJECT_WRITING_INSTALL_GATEWAY_URL
10
+ || '',
11
+ ).trim();
12
+ }
13
+
14
+ export async function configureGatewayFromInstall({
15
+ environment = process.env,
16
+ stdout = process.stdout,
17
+ } = {}) {
18
+ const gatewayUrl = installGateway(environment);
19
+ if (!gatewayUrl) return { configured: false };
20
+
21
+ const configPath = await persistGatewayUrl(gatewayUrl, { environment });
22
+ stdout.write(`[high-tech-project] gateway configured in ${configPath}\n`);
23
+ return { configured: true, configPath };
24
+ }
25
+
26
+ export async function main() {
27
+ try {
28
+ await configureGatewayFromInstall();
29
+ } catch (error) {
30
+ const message = error instanceof Error ? error.message : 'gateway 配置失败';
31
+ process.stderr.write(`[high-tech-project] ${message}\n`);
32
+ process.exitCode = 1;
33
+ }
34
+ }
35
+
36
+ const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : '';
37
+ if (invokedPath === import.meta.url) {
38
+ await main();
39
+ }
@@ -0,0 +1,29 @@
1
+ export const JAVA_PROJECT_WRITING_METHODS = Object.freeze([
2
+ 'project_writing_customer_detail',
3
+ 'project_writing_person_list', 'project_writing_person_batch_save_all', 'project_writing_person_batch_save',
4
+ 'project_writing_person_chart_stats', 'project_writing_person_situation_get', 'project_writing_person_situation_batch_save',
5
+ 'project_writing_person_file_create', 'project_writing_person_file_smart_import', 'project_writing_person_batch_delete_by_uuid',
6
+ 'project_writing_ipr_list', 'project_writing_ipr_batch_save', 'project_writing_ipr_batch_delete_by_uuid',
7
+ 'project_writing_ipr_file_create', 'project_writing_ipr_file_smart_import',
8
+ 'project_writing_system_manage_file_smart_import', 'project_writing_system_manage_file_create',
9
+ 'project_writing_system_manage_file_md_to_docx', 'project_writing_supplement_file_list',
10
+ 'project_writing_supplement_file_update', 'project_writing_ipr_situation_get', 'project_writing_ipr_situation_batch_save',
11
+ 'project_writing_rd_project_list', 'project_writing_rd_project_batch_save', 'project_writing_rd_project_bind_iprs',
12
+ 'project_writing_rd_project_bind_person', 'project_writing_rd_project_bind_fee', 'project_writing_rd_project_batch_delete',
13
+ 'project_writing_rd_project_file_list', 'project_writing_rd_project_file_create', 'project_writing_rd_project_file_delete_batch',
14
+ 'project_writing_ps_product_list', 'project_writing_ps_product_batch_save', 'project_writing_ps_product_bind_iprs',
15
+ 'project_writing_ps_product_bind_rd_projects', 'project_writing_ps_product_batch_delete',
16
+ 'project_writing_ps_product_file_list', 'project_writing_ps_product_file_create', 'project_writing_ps_product_file_delete_batch',
17
+ 'project_writing_tech_achievement_situation_get', 'project_writing_tech_achievement_situation_save_or_update',
18
+ 'project_writing_system_manage_situation_get', 'project_writing_system_manage_situation_save_or_update',
19
+ 'project_writing_tech_achievement_create', 'project_writing_tech_achievement_update',
20
+ 'project_writing_tech_achievement_list', 'project_writing_tech_achievement_file_create',
21
+ 'project_writing_rd_project_brief_create', 'project_writing_rd_project_brief_update', 'project_writing_rd_project_brief_detail',
22
+ 'project_writing_ps_product_brief_create', 'project_writing_ps_product_brief_update', 'project_writing_ps_product_brief_detail',
23
+ 'project_writing_fee_detail_create', 'project_writing_fee_detail_update', 'project_writing_fee_detail_list',
24
+ 'project_writing_fee_detail_batch_save', 'project_writing_operation_status_create',
25
+ 'project_writing_operation_status_update', 'project_writing_operation_status_list', 'project_writing_operation_status_batch_save',
26
+ 'project_writing_product_income_create', 'project_writing_product_income_update',
27
+ 'project_writing_product_income_list', 'project_writing_product_income_batch_save',
28
+ 'project_writing_rd_fund_situation_list', 'project_writing_rd_fund_situation_batch_save',
29
+ ]);
@@ -0,0 +1,147 @@
1
+ import { CliError } from '../core/errors.mjs';
2
+ import {
3
+ contentInput,
4
+ createFileCommand,
5
+ objectInput as sharedObjectInput,
6
+ requiredString,
7
+ valueOf,
8
+ withJavaMethod,
9
+ } from './shared.mjs';
10
+
11
+ const REQUIRED_CONTEXT = ['gatewayUrl', 'uuid'];
12
+
13
+ function objectInput(input) {
14
+ return sharedObjectInput(input);
15
+ }
16
+
17
+ function positiveId(value, field) {
18
+ if (!Number.isSafeInteger(value) || value <= 0) {
19
+ throw new CliError('ARGUMENT_INVALID', `${field} 必须是正整数`);
20
+ }
21
+ }
22
+
23
+ function pageInput(input) {
24
+ const value = objectInput(input);
25
+ for (const field of ['pageNumber', 'pageSize']) {
26
+ if (value[field] !== undefined) positiveId(value[field], field);
27
+ }
28
+ return value;
29
+ }
30
+
31
+ function idsInput(input) {
32
+ const value = objectInput(input);
33
+ if (!Array.isArray(value.ids) || value.ids.length === 0) {
34
+ throw new CliError('ARGUMENT_INVALID', 'ids 必须是非空 ID 数组');
35
+ }
36
+ value.ids.forEach((id) => positiveId(id, 'ids'));
37
+ return value;
38
+ }
39
+
40
+ function achievementInput(input) {
41
+ const value = objectInput(input);
42
+ positiveId(value.techAchievementId, 'techAchievementId');
43
+ return value;
44
+ }
45
+
46
+ function definition(name, path, idempotent, validate, handler, javaMethod) {
47
+ return withJavaMethod({ name, method: 'POST', path, idempotent, multipart: false, requiredContext: REQUIRED_CONTEXT, validate, handler }, javaMethod);
48
+ }
49
+
50
+ function fileCreateInput(input) {
51
+ const value = objectInput(input);
52
+ if (!Number.isSafeInteger(value.techAchievementId ?? value.tech_achievement_id)
53
+ || (value.techAchievementId ?? value.tech_achievement_id) <= 0) {
54
+ throw new CliError('ARGUMENT_INVALID', 'tech_achievement_id 必须是正整数');
55
+ }
56
+ requiredString(value.fileName ?? value.file_name, 'file_name');
57
+ requiredString(value.filePath ?? value.file_path, 'file_path');
58
+ return value;
59
+ }
60
+
61
+ export const commands = [
62
+ definition(
63
+ 'achievement.list', '/api/project-writing/tech-achievement/page', true, pageInput,
64
+ async ({ client, context, input }) => await client.request({
65
+ method: 'POST', path: '/api/project-writing/tech-achievement/page', idempotent: true,
66
+ body: { ...input, pageNumber: input.pageNumber ?? 1, pageSize: input.pageSize ?? 20, uuid: context.uuid },
67
+ }),
68
+ ),
69
+ definition(
70
+ 'achievement.save', '/api/project-writing/tech-achievement/create', false, objectInput,
71
+ async ({ client, context, input }) => await client.request({
72
+ method: 'POST', idempotent: false,
73
+ path: input.id
74
+ ? '/api/project-writing/tech-achievement/update'
75
+ : '/api/project-writing/tech-achievement/create',
76
+ body: { ...input, uuid: context.uuid },
77
+ }),
78
+ ),
79
+ definition(
80
+ 'achievement.situation-get', '/api/project-writing/tech-achievement-situation/detail', true, objectInput,
81
+ async ({ client, context }) => await client.request({
82
+ method: 'POST', path: '/api/project-writing/tech-achievement-situation/detail', idempotent: true,
83
+ body: { uuid: context.uuid },
84
+ }), 'project_writing_tech_achievement_situation_get',
85
+ ),
86
+ definition(
87
+ 'achievement.situation-save', '/api/project-writing/tech-achievement-situation/save-or-update', false, contentInput,
88
+ async ({ client, context, input }) => await client.request({
89
+ method: 'POST', path: '/api/project-writing/tech-achievement-situation/save-or-update', idempotent: false,
90
+ body: { uuid: context.uuid, content: valueOf(input, 'content') },
91
+ }), 'project_writing_tech_achievement_situation_save_or_update',
92
+ ),
93
+ definition(
94
+ 'achievement.create', '/api/project-writing/tech-achievement/create', false, objectInput,
95
+ async ({ client, context, input }) => await client.request({
96
+ method: 'POST', path: '/api/project-writing/tech-achievement/create', idempotent: false,
97
+ body: { ...input, uuid: context.uuid },
98
+ }), 'project_writing_tech_achievement_create',
99
+ ),
100
+ definition(
101
+ 'achievement.update', '/api/project-writing/tech-achievement/update', false, (input) => {
102
+ const value = objectInput(input);
103
+ positiveId(value.id, 'id');
104
+ return value;
105
+ },
106
+ async ({ client, context, input }) => await client.request({
107
+ method: 'POST', path: '/api/project-writing/tech-achievement/update', idempotent: false,
108
+ body: { ...input, uuid: context.uuid },
109
+ }), 'project_writing_tech_achievement_update',
110
+ ),
111
+ definition(
112
+ 'achievement.list-all', '/api/project-writing/tech-achievement/list', true, objectInput,
113
+ async ({ client, context, input }) => await client.request({
114
+ method: 'POST', path: '/api/project-writing/tech-achievement/list', idempotent: true,
115
+ body: { ...input, uuid: context.uuid },
116
+ }), 'project_writing_tech_achievement_list',
117
+ ),
118
+ withJavaMethod(createFileCommand({
119
+ name: 'achievement.file-create', path: '/api/project-writing/tech-achievement-file/create',
120
+ idField: 'techAchievementId', validate: fileCreateInput,
121
+ extraFields: [
122
+ { snake: 'file_type', camel: 'fileType' },
123
+ { snake: 'file_code', camel: 'fileCode' },
124
+ { snake: 'file_format', camel: 'fileFormat' },
125
+ ],
126
+ }), 'project_writing_tech_achievement_file_create'),
127
+ definition(
128
+ 'achievement.delete-batch', '/api/project-writing/tech-achievement/delete-batch', false, idsInput,
129
+ async ({ client, input }) => await client.request({
130
+ method: 'POST', path: '/api/project-writing/tech-achievement/delete-batch', body: input.ids, idempotent: false,
131
+ }),
132
+ ),
133
+ definition(
134
+ 'achievement.files', '/api/project-writing/tech-achievement-file/list', true, achievementInput,
135
+ async ({ client, context, input }) => await client.request({
136
+ method: 'POST', path: '/api/project-writing/tech-achievement-file/list', idempotent: true,
137
+ body: { uuid: context.uuid, techAchievementId: input.techAchievementId },
138
+ }),
139
+ ),
140
+ definition(
141
+ 'achievement.submissions', '/api/project-writing/tech-achievement-submit/list', true, achievementInput,
142
+ async ({ client, context, input }) => await client.request({
143
+ method: 'POST', path: '/api/project-writing/tech-achievement-submit/list', idempotent: true,
144
+ body: { uuid: context.uuid, techAchievementId: input.techAchievementId },
145
+ }),
146
+ ),
147
+ ];