'use strict'; const { hasOwn, toInt, parsePagination, ensureCreatedBy, normalizeNullableInt } = require('./helpers'); const { ensureDataSource, ensureInstance, ensureTemplate, resolveSchemaVersion, getInstanceChapterTree, createChapterTreeFromPayload, } = require('./repository'); const { ensureHttpUrl, downloadDocxFile, convertDocxToHtml, parseDocxHtmlToChapters, } = require('./docxImport'); const { buildDocxImportChapterPayload } = require('./tree'); module.exports.getInstances = async (ctx, next) => { try { const { models, ORM: { Op } } = ctx.app.fs.dc; const { offset, limit } = parsePagination(ctx.request.query); const where = {}; const name = String(ctx.request.query.name || '').trim(); const type = String(ctx.request.query.type || '').trim(); const createdBy = String(ctx.request.query.createdBy || '').trim(); const templateId = toInt(ctx.request.query.templateId); const departmentId = normalizeNullableInt(ctx.request.query.departmentId, 'departmentId'); if (name) where.name = { [Op.like]: `%${name}%` }; if (type) where.type = type; if (createdBy) where.createdBy = createdBy; if (templateId) where.templateId = templateId; if (departmentId) where.departmentId = departmentId; const list = await models.ReportInstance.findAndCountAll({ where, order: [['id', 'DESC']], include: [ { model: models.ReportTemplate, required: false }, { model: models.ReportDataSource, required: false }, { model: models.ReportSchemaVersion, required: false }, ], offset, limit, }); ctx.body = { count: list.count, rows: list.rows }; ctx.status = 200; } catch (error) { ctx.logger.log(error); ctx.status = 400; ctx.body = { message: typeof error === 'string' ? error : '获取报表实例列表失败' }; } }; module.exports.createInstance = async (ctx, next) => { const transaction = await ctx.app.fs.dc.orm.transaction(); try { const { models } = ctx.app.fs.dc; const body = ctx.request.body || {}; const { name, dataSourceId, schemaVersionId, type, templateId, chapters } = body; const createdBy = ensureCreatedBy(body); const departmentId = normalizeNullableInt(body.departmentId, 'departmentId'); if (!String(name || '').trim()) throw '缺少参数: name'; await ensureDataSource(ctx, dataSourceId, transaction); const schemaVersion = await resolveSchemaVersion(ctx, schemaVersionId, transaction); let parsedTemplateId = null; if (templateId !== null && templateId !== undefined && templateId !== '') { const template = await ensureTemplate(ctx, templateId, transaction); parsedTemplateId = template.id; } const created = await models.ReportInstance.create({ templateId: parsedTemplateId, schemaVersionId: schemaVersion.id, name: String(name).trim(), type: String(type || 'general'), dataSourceId: toInt(dataSourceId), createdBy, departmentId, }, { transaction, returning: true, }); if (Array.isArray(chapters) && chapters.length) { await createChapterTreeFromPayload(ctx, created.id, null, chapters, transaction); } const chapterTree = await getInstanceChapterTree(ctx, created.id, transaction); await transaction.commit(); ctx.body = { ...created.toJSON(), chapters: chapterTree, }; ctx.status = 200; } catch (error) { await transaction.rollback(); ctx.logger.log(error); ctx.status = 400; ctx.body = { message: typeof error === 'string' ? error : '创建报表实例失败' }; } }; module.exports.importInstanceFromDocx = async (ctx, next) => { const transaction = await ctx.app.fs.dc.orm.transaction(); try { const { models } = ctx.app.fs.dc; const body = ctx.request.body || {}; const { name, dataSourceId, schemaVersionId, type, sourceFileUrl } = body; const createdBy = ensureCreatedBy(body); const departmentId = normalizeNullableInt(body.departmentId, 'departmentId'); if (!String(name || '').trim()) throw 'missing param: name'; const normalizedSourceFileUrl = ensureHttpUrl(sourceFileUrl, 'sourceFileUrl'); await ensureDataSource(ctx, dataSourceId, transaction); const schemaVersion = await resolveSchemaVersion(ctx, schemaVersionId, transaction); const downloadedFile = await downloadDocxFile(normalizedSourceFileUrl); const sourceFileName = String(downloadedFile.sourceFileName || '').trim() || 'import.docx'; const html = await convertDocxToHtml(ctx, downloadedFile.fileBuffer); const chapterNodes = parseDocxHtmlToChapters(html); const chapterPayload = buildDocxImportChapterPayload( chapterNodes, normalizedSourceFileUrl, sourceFileName ); const created = await models.ReportInstance.create({ templateId: null, schemaVersionId: schemaVersion.id, name: String(name).trim(), type: String(type || 'general'), dataSourceId: toInt(dataSourceId), createdBy, departmentId, sourceFileUrl: normalizedSourceFileUrl, sourceFileName, }, { transaction, returning: true, }); await createChapterTreeFromPayload(ctx, created.id, null, chapterPayload, transaction); const chapters = await getInstanceChapterTree(ctx, created.id, transaction); await transaction.commit(); ctx.body = { ...created.toJSON(), chapters, }; ctx.status = 200; } catch (error) { await transaction.rollback(); ctx.logger.log(error); ctx.status = 400; ctx.body = { message: typeof error === 'string' ? error : (error?.message || 'failed to import report instance from docx'), }; } }; module.exports.getInstanceDetail = async (ctx, next) => { try { const { models } = ctx.app.fs.dc; const instanceId = toInt(ctx.params.instanceId); if (!instanceId) throw '缺少参数: instanceId'; const instance = await models.ReportInstance.findByPk(instanceId, { include: [ { model: models.ReportTemplate, required: false }, { model: models.ReportDataSource, required: false }, { model: models.ReportSchemaVersion, required: false }, ], }); if (!instance) throw '报表实例不存在'; const chapters = await getInstanceChapterTree(ctx, instanceId); ctx.body = { ...instance.toJSON(), chapters, }; ctx.status = 200; } catch (error) { ctx.logger.log(error); ctx.status = 400; ctx.body = { message: typeof error === 'string' ? error : '获取报表实例详情失败' }; } }; module.exports.updateInstance = async (ctx, next) => { try { const { models } = ctx.app.fs.dc; const instanceId = toInt(ctx.params.instanceId); if (!instanceId) throw '缺少参数: instanceId'; await ensureInstance(ctx, instanceId); const body = ctx.request.body || {}; const updateData = {}; if (hasOwn(body, 'name')) updateData.name = body.name ? String(body.name).trim() : ''; if (hasOwn(body, 'type')) updateData.type = body.type ? String(body.type).trim() : 'general'; if (hasOwn(body, 'templateId')) updateData.templateId = body.templateId ? toInt(body.templateId) : null; if (hasOwn(body, 'exportedAt')) updateData.exportedAt = body.exportedAt ? new Date(body.exportedAt) : null; if (hasOwn(body, 'dataSourceId')) { await ensureDataSource(ctx, body.dataSourceId); updateData.dataSourceId = toInt(body.dataSourceId); } if (hasOwn(body, 'departmentId')) updateData.departmentId = normalizeNullableInt(body.departmentId, 'departmentId'); if (hasOwn(body, 'schemaVersionId')) { const schemaVersion = await resolveSchemaVersion(ctx, body.schemaVersionId); updateData.schemaVersionId = schemaVersion.id; } await models.ReportInstance.update(updateData, { where: { id: instanceId } }); ctx.status = 204; } catch (error) { ctx.logger.log(error); ctx.status = 400; ctx.body = { message: typeof error === 'string' ? error : '更新报表实例失败' }; } }; module.exports.deleteInstance = async (ctx, next) => { try { const { models } = ctx.app.fs.dc; const instanceId = toInt(ctx.params.instanceId); if (!instanceId) throw '缺少参数: instanceId'; await ensureInstance(ctx, instanceId); await models.ReportInstance.destroy({ where: { id: instanceId } }); ctx.status = 204; } catch (error) { ctx.logger.log(error); ctx.status = 400; ctx.body = { message: typeof error === 'string' ? error : '删除报表实例失败' }; } };