You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
661 lines
25 KiB
661 lines
25 KiB
'use strict';
|
|
|
|
const { TEMP_POSITION_BASE } = require('./constants');
|
|
const { hasOwn, toInt, parsePagination, ensureCreatedBy, normalizeParentId, normalizeNullableInt, ensureObjectPayload } = require('./helpers');
|
|
const {
|
|
ensureInstance,
|
|
ensureTemplate,
|
|
ensureTemplateChapter,
|
|
ensureTemplateBlock,
|
|
ensureDataSource,
|
|
resolveSchemaVersion,
|
|
getTemplateChapterTree,
|
|
getInstanceChapterTree,
|
|
cloneInstanceToTemplate,
|
|
getTemplateBlockBatchBinding,
|
|
normalizeTemplateBlockConfigOverrides,
|
|
cloneTemplateToInstance,
|
|
} = require('./repository');
|
|
const {
|
|
ensureNoTemplateChapterCycle,
|
|
reorderTemplateChapterSiblingsWithInsert,
|
|
reorderTemplateChapterSiblings,
|
|
reorderTemplateBlockSiblingsWithInsert,
|
|
reorderTemplateBlockSiblings,
|
|
} = require('./ordering');
|
|
const {
|
|
getDataSourceTableColumns,
|
|
normalizeTemplateBlockConfigForStorage,
|
|
} = require('./dataSourceQuery');
|
|
const {
|
|
sanitizeQueryBlockConfig,
|
|
normalizeChartContentSnapshot,
|
|
} = require('./chartSnapshot');
|
|
const {
|
|
resolveBlockPayload,
|
|
finalizeResolvedBlockPayload,
|
|
} = require('./blockPayloadResolver');
|
|
|
|
const extractBlockQueryConfig = config => {
|
|
const safeConfig = ensureObjectPayload(config || {}, 'config');
|
|
if (safeConfig.query && typeof safeConfig.query === 'object' && !Array.isArray(safeConfig.query)) {
|
|
return safeConfig.query;
|
|
}
|
|
return safeConfig;
|
|
};
|
|
|
|
const extractBlockQueryTable = config => {
|
|
const safeConfig = ensureObjectPayload(config || {}, 'config');
|
|
const queryConfig = extractBlockQueryConfig(safeConfig);
|
|
return String(
|
|
queryConfig.table
|
|
|| queryConfig.tableId
|
|
|| queryConfig.tableName
|
|
|| safeConfig.table
|
|
|| safeConfig.tableId
|
|
|| safeConfig.tableName
|
|
|| safeConfig.previewQuery?.table
|
|
|| ''
|
|
).trim();
|
|
};
|
|
|
|
const getFieldSetForTable = async (ctx, dataSourceId, tableId, transaction = null) => {
|
|
const payload = await getDataSourceTableColumns(ctx, dataSourceId, tableId, transaction);
|
|
return new Set((payload.rows || []).map(item => String(item.name || '').trim()).filter(Boolean));
|
|
};
|
|
|
|
const validateTemplateBlockBindingConsistency = async (
|
|
ctx,
|
|
template,
|
|
templateBlockId,
|
|
config,
|
|
transaction = null
|
|
) => {
|
|
const binding = await getTemplateBlockBatchBinding(ctx, template.id, templateBlockId, transaction);
|
|
if (!binding) return;
|
|
const currentTable = extractBlockQueryTable(config);
|
|
if (!currentTable) {
|
|
throw '当前模板块已配置批量绑定,请先补齐查询表/数据集或删除绑定';
|
|
}
|
|
if (binding.targetTable && binding.targetTable !== currentTable) {
|
|
throw '当前模板块已配置批量绑定,查询表/数据集与绑定规则不一致,请先修改或删除绑定';
|
|
}
|
|
const fieldSet = await getFieldSetForTable(ctx, template.dataSourceId, currentTable, transaction);
|
|
if (!fieldSet.has(String(binding.targetField || '').trim())) {
|
|
throw '当前模板块已配置批量绑定,目标字段与块配置不一致,请先修改或删除绑定';
|
|
}
|
|
};
|
|
|
|
module.exports.getTemplates = 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 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 (departmentId) where.departmentId = departmentId;
|
|
if (hasOwn(ctx.request.query, 'isActive')) {
|
|
where.isActive = String(ctx.request.query.isActive) === 'true';
|
|
}
|
|
const list = await models.ReportTemplate.findAndCountAll({
|
|
where,
|
|
include: [{ model: models.ReportSchemaVersion, required: false }],
|
|
order: [['id', 'DESC']],
|
|
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.getTemplateDetail = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const templateId = toInt(ctx.params.templateId);
|
|
if (!templateId) throw '缺少参数: templateId';
|
|
const template = await models.ReportTemplate.findByPk(templateId, {
|
|
include: [{ model: models.ReportSchemaVersion, required: false }],
|
|
});
|
|
if (!template) throw '模板不存在';
|
|
const chapters = await getTemplateChapterTree(ctx, templateId);
|
|
ctx.body = {
|
|
...template.toJSON(),
|
|
chapters,
|
|
};
|
|
ctx.status = 200;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = { message: typeof error === 'string' ? error : '获取模板详情失败' };
|
|
}
|
|
};
|
|
|
|
module.exports.getTemplateInstances = async (ctx, next) => {
|
|
try {
|
|
const { models, ORM: { Op } } = ctx.app.fs.dc;
|
|
const templateId = toInt(ctx.params.templateId);
|
|
if (!templateId) throw '缺少参数: templateId';
|
|
await ensureTemplate(ctx, templateId);
|
|
const { offset, limit } = parsePagination(ctx.request.query);
|
|
const where = { templateId };
|
|
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 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 (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.updateTemplate = async (ctx, next) => {
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const templateId = toInt(ctx.params.templateId);
|
|
if (!templateId) throw '缺少参数: templateId';
|
|
await ensureTemplate(ctx, templateId);
|
|
const body = ctx.request.body || {};
|
|
const updateData = {};
|
|
if (hasOwn(body, 'name')) updateData.name = body.name ? String(body.name).trim() : '';
|
|
if (hasOwn(body, 'description')) updateData.description = body.description || null;
|
|
if (hasOwn(body, 'version')) updateData.version = body.version ? String(body.version).trim() : '1.0.0';
|
|
if (hasOwn(body, 'type')) updateData.type = body.type ? String(body.type).trim() : 'general';
|
|
if (hasOwn(body, 'isActive')) updateData.isActive = Boolean(body.isActive);
|
|
if (hasOwn(body, 'departmentId')) updateData.departmentId = normalizeNullableInt(body.departmentId, 'departmentId');
|
|
if (hasOwn(body, 'schemaVersionId')) {
|
|
const schema = await resolveSchemaVersion(ctx, body.schemaVersionId);
|
|
updateData.schemaVersionId = schema.id;
|
|
}
|
|
await models.ReportTemplate.update(updateData, { where: { id: templateId } });
|
|
ctx.status = 204;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = { message: typeof error === 'string' ? error : '更新模板失败' };
|
|
}
|
|
};
|
|
|
|
module.exports.getTemplateChapters = async (ctx, next) => {
|
|
try {
|
|
const templateId = toInt(ctx.params.templateId);
|
|
if (!templateId) throw '缺少参数: templateId';
|
|
await ensureTemplate(ctx, templateId);
|
|
ctx.body = await getTemplateChapterTree(ctx, templateId);
|
|
ctx.status = 200;
|
|
} catch (error) {
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = { message: typeof error === 'string' ? error : '获取模板章节列表失败' };
|
|
}
|
|
};
|
|
|
|
module.exports.createTemplateChapter = async (ctx, next) => {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const templateId = toInt(ctx.params.templateId);
|
|
if (!templateId) throw '缺少参数: templateId';
|
|
await ensureTemplate(ctx, templateId, transaction);
|
|
const { title, position } = ctx.request.body || {};
|
|
if (!String(title || '').trim()) throw '缺少参数: title';
|
|
const parentId = normalizeParentId(ctx.request.body?.parentId);
|
|
await ensureNoTemplateChapterCycle(ctx, templateId, null, parentId, transaction);
|
|
const created = await models.ReportTemplateChapter.create({
|
|
templateId,
|
|
parentId,
|
|
title: String(title).trim(),
|
|
position: TEMP_POSITION_BASE - 1,
|
|
}, {
|
|
transaction,
|
|
returning: true,
|
|
});
|
|
await reorderTemplateChapterSiblingsWithInsert(
|
|
ctx,
|
|
templateId,
|
|
parentId,
|
|
created.id,
|
|
position,
|
|
transaction
|
|
);
|
|
const fresh = await models.ReportTemplateChapter.findByPk(created.id, { transaction });
|
|
await transaction.commit();
|
|
ctx.body = fresh;
|
|
ctx.status = 200;
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = { message: typeof error === 'string' ? error : '创建模板章节失败' };
|
|
}
|
|
};
|
|
|
|
module.exports.updateTemplateChapter = async (ctx, next) => {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const templateId = toInt(ctx.params.templateId);
|
|
const templateChapterId = toInt(ctx.params.templateChapterId);
|
|
if (!templateId || !templateChapterId) throw '缺少参数';
|
|
const chapter = await ensureTemplateChapter(ctx, templateId, templateChapterId, transaction);
|
|
const body = ctx.request.body || {};
|
|
const updateData = {};
|
|
if (hasOwn(body, 'title')) updateData.title = body.title ? String(body.title).trim() : '';
|
|
|
|
const oldParentId = chapter.parentId;
|
|
const nextParentId = hasOwn(body, 'parentId') ? normalizeParentId(body.parentId) : chapter.parentId;
|
|
await ensureNoTemplateChapterCycle(ctx, templateId, templateChapterId, nextParentId, transaction);
|
|
|
|
const needMove = hasOwn(body, 'parentId') || hasOwn(body, 'position');
|
|
if (!needMove) {
|
|
await models.ReportTemplateChapter.update(updateData, {
|
|
where: { id: templateChapterId },
|
|
transaction,
|
|
});
|
|
await transaction.commit();
|
|
ctx.status = 204;
|
|
return;
|
|
}
|
|
|
|
updateData.parentId = nextParentId;
|
|
updateData.position = TEMP_POSITION_BASE - 1;
|
|
await models.ReportTemplateChapter.update(updateData, {
|
|
where: { id: templateChapterId },
|
|
transaction,
|
|
});
|
|
|
|
if (oldParentId !== nextParentId) {
|
|
await reorderTemplateChapterSiblings(ctx, templateId, oldParentId, transaction);
|
|
}
|
|
|
|
const targetPosition = hasOwn(body, 'position')
|
|
? body.position
|
|
: (oldParentId === nextParentId ? chapter.position : undefined);
|
|
await reorderTemplateChapterSiblingsWithInsert(
|
|
ctx,
|
|
templateId,
|
|
nextParentId,
|
|
templateChapterId,
|
|
targetPosition,
|
|
transaction
|
|
);
|
|
|
|
await transaction.commit();
|
|
ctx.status = 204;
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = { message: typeof error === 'string' ? error : '更新模板章节失败' };
|
|
}
|
|
};
|
|
|
|
module.exports.deleteTemplateChapter = async (ctx, next) => {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const templateId = toInt(ctx.params.templateId);
|
|
const templateChapterId = toInt(ctx.params.templateChapterId);
|
|
if (!templateId || !templateChapterId) throw '缺少参数';
|
|
const chapter = await ensureTemplateChapter(ctx, templateId, templateChapterId, transaction);
|
|
await models.ReportTemplateChapter.destroy({
|
|
where: { id: templateChapterId, templateId },
|
|
transaction,
|
|
});
|
|
await reorderTemplateChapterSiblings(ctx, templateId, chapter.parentId, transaction);
|
|
await transaction.commit();
|
|
ctx.status = 204;
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = { message: typeof error === 'string' ? error : '删除模板章节失败' };
|
|
}
|
|
};
|
|
|
|
module.exports.createTemplateBlock = async (ctx, next) => {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const templateId = toInt(ctx.params.templateId);
|
|
const templateChapterId = toInt(ctx.params.templateChapterId);
|
|
if (!templateId || !templateChapterId) throw '缺少参数';
|
|
await ensureTemplate(ctx, templateId, transaction);
|
|
await ensureTemplateChapter(ctx, templateId, templateChapterId, transaction);
|
|
|
|
const { blockType, position, config, contentSnapshot } = ctx.request.body || {};
|
|
const normalizedBlockType = String(blockType || 'text');
|
|
const normalizedConfig = await normalizeTemplateBlockConfigForStorage(
|
|
ctx,
|
|
templateId,
|
|
normalizedBlockType,
|
|
config || {},
|
|
transaction
|
|
);
|
|
const created = await models.ReportTemplateBlock.create({
|
|
templateId,
|
|
chapterId: templateChapterId,
|
|
blockType: normalizedBlockType,
|
|
position: TEMP_POSITION_BASE - 1,
|
|
defaultConfig: sanitizeQueryBlockConfig(normalizedConfig),
|
|
contentSnapshot: String(normalizedBlockType).trim().toLowerCase() === 'chart'
|
|
? (normalizeChartContentSnapshot(contentSnapshot)
|
|
|| normalizeChartContentSnapshot(normalizedConfig))
|
|
: (contentSnapshot || null),
|
|
}, {
|
|
transaction,
|
|
returning: true,
|
|
});
|
|
await reorderTemplateBlockSiblingsWithInsert(
|
|
ctx,
|
|
templateChapterId,
|
|
created.id,
|
|
position,
|
|
transaction
|
|
);
|
|
const fresh = await models.ReportTemplateBlock.findByPk(created.id, { transaction });
|
|
await transaction.commit();
|
|
ctx.body = fresh;
|
|
ctx.status = 200;
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = { message: typeof error === 'string' ? error : '创建模板内容块失败' };
|
|
}
|
|
};
|
|
|
|
module.exports.updateTemplateBlock = async (ctx, next) => {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const templateId = toInt(ctx.params.templateId);
|
|
const templateBlockId = toInt(ctx.params.templateBlockId);
|
|
if (!templateId || !templateBlockId) throw '缺少参数';
|
|
const template = await ensureTemplate(ctx, templateId, transaction);
|
|
const block = await ensureTemplateBlock(ctx, templateId, templateBlockId, transaction);
|
|
const body = ctx.request.body || {};
|
|
const updateData = {};
|
|
|
|
const hasConfigMutation = hasOwn(body, 'config') || hasOwn(body, 'blockType');
|
|
if (hasOwn(body, 'blockType')) updateData.blockType = String(body.blockType || 'text');
|
|
if (hasConfigMutation) {
|
|
const finalBlockType = hasOwn(body, 'blockType') ? updateData.blockType : block.blockType;
|
|
const finalConfig = hasOwn(body, 'config') ? (body.config || {}) : (block.defaultConfig || {});
|
|
updateData.defaultConfig = await normalizeTemplateBlockConfigForStorage(
|
|
ctx,
|
|
templateId,
|
|
finalBlockType,
|
|
finalConfig,
|
|
transaction
|
|
);
|
|
updateData.defaultConfig = sanitizeQueryBlockConfig(updateData.defaultConfig);
|
|
await validateTemplateBlockBindingConsistency(
|
|
ctx,
|
|
template,
|
|
templateBlockId,
|
|
updateData.defaultConfig,
|
|
transaction
|
|
);
|
|
const finalContentSnapshot = hasOwn(body, 'contentSnapshot')
|
|
? body.contentSnapshot
|
|
: block.contentSnapshot;
|
|
updateData.contentSnapshot = String(finalBlockType || '').trim().toLowerCase() === 'chart'
|
|
? (normalizeChartContentSnapshot(finalContentSnapshot)
|
|
|| normalizeChartContentSnapshot(finalConfig))
|
|
: (finalContentSnapshot || null);
|
|
} else if (hasOwn(body, 'contentSnapshot')) {
|
|
updateData.contentSnapshot = body.contentSnapshot || null;
|
|
}
|
|
|
|
const oldChapterId = block.chapterId;
|
|
const nextChapterId = hasOwn(body, 'chapterId') ? toInt(body.chapterId) : block.chapterId;
|
|
if (!nextChapterId) throw '参数错误: chapterId';
|
|
await ensureTemplateChapter(ctx, templateId, nextChapterId, transaction);
|
|
const needMove = hasOwn(body, 'chapterId') || hasOwn(body, 'position');
|
|
|
|
if (!needMove) {
|
|
await models.ReportTemplateBlock.update(updateData, {
|
|
where: { id: templateBlockId },
|
|
transaction,
|
|
});
|
|
await transaction.commit();
|
|
ctx.status = 204;
|
|
return;
|
|
}
|
|
|
|
updateData.chapterId = nextChapterId;
|
|
updateData.position = TEMP_POSITION_BASE - 1;
|
|
await models.ReportTemplateBlock.update(updateData, {
|
|
where: { id: templateBlockId },
|
|
transaction,
|
|
});
|
|
|
|
if (oldChapterId !== nextChapterId) {
|
|
await reorderTemplateBlockSiblings(ctx, oldChapterId, transaction);
|
|
}
|
|
|
|
const targetPosition = hasOwn(body, 'position')
|
|
? body.position
|
|
: (oldChapterId === nextChapterId ? block.position : undefined);
|
|
await reorderTemplateBlockSiblingsWithInsert(
|
|
ctx,
|
|
nextChapterId,
|
|
templateBlockId,
|
|
targetPosition,
|
|
transaction
|
|
);
|
|
|
|
await transaction.commit();
|
|
ctx.status = 204;
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = { message: typeof error === 'string' ? error : '更新模板内容块失败' };
|
|
}
|
|
};
|
|
|
|
module.exports.deleteTemplateBlock = async (ctx, next) => {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const templateId = toInt(ctx.params.templateId);
|
|
const templateBlockId = toInt(ctx.params.templateBlockId);
|
|
if (!templateId || !templateBlockId) throw '缺少参数';
|
|
const block = await ensureTemplateBlock(ctx, templateId, templateBlockId, transaction);
|
|
await models.ReportTemplateBlock.destroy({
|
|
where: { id: templateBlockId, templateId },
|
|
transaction,
|
|
});
|
|
await reorderTemplateBlockSiblings(ctx, block.chapterId, transaction);
|
|
await transaction.commit();
|
|
ctx.status = 204;
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = { message: typeof error === 'string' ? error : '删除模板内容块失败' };
|
|
}
|
|
};
|
|
|
|
module.exports.deleteTemplate = async (ctx, next) => {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const templateId = toInt(ctx.params.templateId);
|
|
if (!templateId) throw '缺少参数: templateId';
|
|
await ensureTemplate(ctx, templateId, transaction);
|
|
await models.ReportTemplateBlock.destroy({
|
|
where: { templateId },
|
|
transaction,
|
|
});
|
|
await models.ReportTemplateChapter.destroy({
|
|
where: { templateId },
|
|
transaction,
|
|
});
|
|
await models.ReportTemplate.destroy({
|
|
where: { id: templateId },
|
|
transaction,
|
|
});
|
|
await transaction.commit();
|
|
ctx.status = 204;
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = { message: typeof error === 'string' ? error : '删除模板失败' };
|
|
}
|
|
};
|
|
|
|
module.exports.saveInstanceAsTemplate = async (ctx, next) => {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const instanceId = toInt(ctx.params.instanceId);
|
|
if (!instanceId) throw '缺少参数: instanceId';
|
|
const instance = await ensureInstance(ctx, instanceId, transaction);
|
|
const body = ctx.request.body || {};
|
|
const createdBy = ensureCreatedBy(body);
|
|
const name = String(body.name || '').trim();
|
|
const departmentId = hasOwn(body, 'departmentId')
|
|
? normalizeNullableInt(body.departmentId, 'departmentId')
|
|
: normalizeNullableInt(instance.departmentId, 'departmentId');
|
|
if (!name) throw '缺少参数: name';
|
|
const schemaVersion = await resolveSchemaVersion(ctx, instance.schemaVersionId, transaction);
|
|
const createdTemplate = await models.ReportTemplate.create({
|
|
name,
|
|
description: body.description || null,
|
|
version: String(body.version || '1.0.0'),
|
|
schemaVersionId: schemaVersion.id,
|
|
dataSourceId: toInt(instance.dataSourceId),
|
|
type: String(body.type || instance.type || 'general'),
|
|
createdBy,
|
|
departmentId,
|
|
isActive: hasOwn(body, 'isActive') ? Boolean(body.isActive) : true,
|
|
}, {
|
|
transaction,
|
|
returning: true,
|
|
});
|
|
await cloneInstanceToTemplate(ctx, instanceId, createdTemplate.id, transaction);
|
|
const chapters = await getTemplateChapterTree(ctx, createdTemplate.id, transaction);
|
|
await transaction.commit();
|
|
ctx.body = {
|
|
...createdTemplate.toJSON(),
|
|
chapters,
|
|
};
|
|
ctx.status = 200;
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = { message: typeof error === 'string' ? error : '报表另存模板失败' };
|
|
}
|
|
};
|
|
|
|
module.exports.createInstanceFromTemplate = async (ctx, next) => {
|
|
const transaction = await ctx.app.fs.dc.orm.transaction();
|
|
try {
|
|
const { models } = ctx.app.fs.dc;
|
|
const templateId = toInt(ctx.params.templateId);
|
|
if (!templateId) throw '缺少参数: templateId';
|
|
const template = await ensureTemplate(ctx, templateId, transaction);
|
|
const body = ctx.request.body || {};
|
|
const createdBy = ensureCreatedBy(body);
|
|
const name = String(body.name || '').trim();
|
|
const departmentId = hasOwn(body, 'departmentId')
|
|
? normalizeNullableInt(body.departmentId, 'departmentId')
|
|
: normalizeNullableInt(template.departmentId, 'departmentId');
|
|
if (!name) throw '缺少参数: name';
|
|
const templateDataSourceId = toInt(template.dataSourceId);
|
|
if (!templateDataSourceId) throw '模板未绑定数据源';
|
|
await ensureDataSource(ctx, templateDataSourceId, transaction);
|
|
const blockConfigOverrideMap = hasOwn(body, 'blockConfigOverrides')
|
|
? await normalizeTemplateBlockConfigOverrides(
|
|
ctx,
|
|
template.id,
|
|
body.blockConfigOverrides,
|
|
transaction
|
|
)
|
|
: new Map();
|
|
const schemaVersion = await resolveSchemaVersion(ctx, body.schemaVersionId || template.schemaVersionId, transaction);
|
|
const createdInstance = await models.ReportInstance.create({
|
|
templateId: template.id,
|
|
schemaVersionId: schemaVersion.id,
|
|
name,
|
|
type: String(body.type || template.type || 'general'),
|
|
dataSourceId: templateDataSourceId,
|
|
createdBy,
|
|
departmentId,
|
|
}, {
|
|
transaction,
|
|
returning: true,
|
|
});
|
|
await cloneTemplateToInstance(
|
|
ctx,
|
|
template.id,
|
|
createdInstance.id,
|
|
blockConfigOverrideMap,
|
|
transaction,
|
|
{
|
|
resolveBlockPayload: async (templateBlock, payload) => {
|
|
return resolveBlockPayload(ctx, {
|
|
blockType: templateBlock?.blockType,
|
|
dataSourceId: templateDataSourceId,
|
|
config: payload?.config || {},
|
|
contentSnapshot: payload?.contentSnapshot || null,
|
|
transaction,
|
|
});
|
|
},
|
|
finalizeCreatedBlock: async (templateBlock, createdPayload, helpers) => {
|
|
return finalizeResolvedBlockPayload(ctx, {
|
|
blockType: templateBlock?.blockType,
|
|
config: createdPayload?.config || {},
|
|
contentSnapshot: createdPayload?.contentSnapshot || null,
|
|
blockIdMap: helpers?.blockIdMap,
|
|
getCreatedBlockStateByInstanceId: helpers?.getCreatedStateByInstanceId,
|
|
});
|
|
},
|
|
}
|
|
);
|
|
const chapters = await getInstanceChapterTree(ctx, createdInstance.id, transaction);
|
|
await transaction.commit();
|
|
ctx.body = {
|
|
...createdInstance.toJSON(),
|
|
chapters,
|
|
};
|
|
ctx.status = 200;
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
ctx.logger.log(error);
|
|
ctx.status = 400;
|
|
ctx.body = { message: typeof error === 'string' ? error : '模板创建实例失败' };
|
|
}
|
|
};
|
|
|