diff --git a/packages/core/database/src/relation-repository/belongs-to-many-repository.ts b/packages/core/database/src/relation-repository/belongs-to-many-repository.ts index eca56eafa3..1d75c03723 100644 --- a/packages/core/database/src/relation-repository/belongs-to-many-repository.ts +++ b/packages/core/database/src/relation-repository/belongs-to-many-repository.ts @@ -22,7 +22,7 @@ export class BelongsToManyRepository extends MultipleRelationRepository { async aggregate(options: AggregateOptions) { const targetRepository = this.targetCollection.repository; - const sourceModel = await this.getSourceModel(); + const sourceModel = await this.getSourceModel(await this.getTransaction(options)); const association = this.association as any; diff --git a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/client/AggregateInstruction.tsx b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/client/AggregateInstruction.tsx index d4dd846fea..bc6096aeb4 100644 --- a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/client/AggregateInstruction.tsx +++ b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/client/AggregateInstruction.tsx @@ -387,6 +387,21 @@ export default class extends Instruction { }, }, }, + precision: { + type: 'number', + title: `{{t("Result precision", { ns: "${NAMESPACE}" })}}`, + description: `{{t("Number of decimal places for query result.", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'FormItem', + 'x-component': 'InputNumber', + 'x-component-props': { + min: 0, + max: 14, + step: 1, + precision: 0, + className: 'auto-width', + }, + default: 2, + }, }; scope = { useCollectionDataSource, diff --git a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/locale/zh-CN.json index f3b9df4362..12cdb3181f 100644 --- a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/locale/zh-CN.json @@ -8,5 +8,7 @@ "Data of associated collection": "关联数据表数据", "Field to aggregate": "聚合字段", "Distinct": "去重", - "Query result": "查询结果" + "Query result": "查询结果", + "Result precision": "结果精度", + "Number of decimal places for query result.": "查询结果小数位数" } diff --git a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/AggregateInstruction.ts b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/AggregateInstruction.ts index 7517611809..e81da5f374 100644 --- a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/AggregateInstruction.ts +++ b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/AggregateInstruction.ts @@ -23,7 +23,7 @@ const aggregators = { export default class extends Instruction { async run(node: FlowNodeModel, input, processor: Processor) { - const { aggregator, associated, collection, association = {}, params = {} } = node.config; + const { aggregator, associated, collection, association = {}, params = {}, precision = 2 } = node.config; const options = processor.getParsedValue(params, node.id); const [dataSourceName, collectionName] = parseCollectionName(collection); const { collectionManager } = this.workflow.app.dataSourceManager.dataSources.get(dataSourceName); @@ -49,7 +49,10 @@ export default class extends Instruction { }); return { - result: round((options.dataType === DataTypes.DOUBLE ? Number(result) : result) || 0, 14), + result: round( + (options.dataType === DataTypes.DOUBLE ? Number(result) : result) || 0, + Math.max(0, Math.min(precision, 14)), + ), status: JOB_STATUS.RESOLVED, }; } diff --git a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/__tests__/instruction.test.ts b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/__tests__/instruction.test.ts index 4a3102cd1d..a75d3ba606 100644 --- a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/__tests__/instruction.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/__tests__/instruction.test.ts @@ -36,6 +36,7 @@ describe('workflow > instructions > aggregate', () => { TagRepo = db.getCollection('tags').repository; workflow = await WorkflowModel.create({ + sync: true, enabled: true, type: 'collection', config: { @@ -62,8 +63,6 @@ describe('workflow > instructions > aggregate', () => { const post = await PostRepo.create({ values: { title: 't1' } }); - await sleep(500); - const [execution] = await workflow.getExecutions(); const [job] = await execution.getJobs(); expect(job.result).toBe(1); @@ -86,8 +85,6 @@ describe('workflow > instructions > aggregate', () => { const post = await PostRepo.create({ values: { title: 't1' } }); - await sleep(500); - const [execution] = await workflow.getExecutions(); const [job] = await execution.getJobs(); expect(job.result).toBe(0); @@ -107,16 +104,12 @@ describe('workflow > instructions > aggregate', () => { const p1 = await PostRepo.create({ values: { title: 't1', read: 1 } }); - await sleep(500); - const [e1] = await workflow.getExecutions(); const [j1] = await e1.getJobs(); expect(j1.result).toBe(1); const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } }); - await sleep(500); - const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); const [j2] = await e2.getJobs(); expect(j2.result).toBe(3); @@ -136,22 +129,18 @@ describe('workflow > instructions > aggregate', () => { const p1 = await PostRepo.create({ values: { title: 't1', score: 0.1 } }); - await sleep(500); - const [e1] = await workflow.getExecutions(); const [j1] = await e1.getJobs(); expect(j1.result).toBe(0.1); const p2 = await PostRepo.create({ values: { title: 't2', score: 0.2 } }); - await sleep(500); - const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); const [j2] = await e2.getJobs(); expect(j2.result).toBe(0.3); }); - it('sum number will be rounded', async () => { + it('sum number will be rounded to 2 decimal places by default', async () => { const n1 = await workflow.createNode({ type: 'aggregate', config: { @@ -163,9 +152,59 @@ describe('workflow > instructions > aggregate', () => { }, }); - const p1 = await PostRepo.create({ values: { title: 't1', score: 0.100000000000001 } }); + const p1 = await PostRepo.create({ values: { title: 't1', score: 0.123 } }); - await sleep(500); + const [e1] = await workflow.getExecutions(); + const [j1] = await e1.getJobs(); + expect(j1.result).toBe(0.12); + + const p2 = await PostRepo.create({ values: { title: 't2', score: 0.456 } }); + + const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); + const [j2] = await e2.getJobs(); + expect(j2.result).toBe(0.58); + }); + + it('sum precision configured -1 as 0', async () => { + const n1 = await workflow.createNode({ + type: 'aggregate', + config: { + aggregator: 'sum', + collection: 'posts', + params: { + field: 'score', + }, + precision: -1, + }, + }); + + const p1 = await PostRepo.create({ values: { title: 't1', score: 0.123 } }); + + const [e1] = await workflow.getExecutions(); + const [j1] = await e1.getJobs(); + expect(j1.result).toBe(0); + + const p2 = await PostRepo.create({ values: { title: 't2', score: 0.456 } }); + + const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); + const [j2] = await e2.getJobs(); + expect(j2.result).toBe(1); + }); + + it('sum precision configured over 14 as 14', async () => { + const n1 = await workflow.createNode({ + type: 'aggregate', + config: { + aggregator: 'sum', + collection: 'posts', + params: { + field: 'score', + }, + precision: 15, + }, + }); + + const p1 = await PostRepo.create({ values: { title: 't1', score: 0.100000000000001 } }); const [e1] = await workflow.getExecutions(); const [j1] = await e1.getJobs(); @@ -173,8 +212,6 @@ describe('workflow > instructions > aggregate', () => { const p2 = await PostRepo.create({ values: { title: 't2', score: 0.200000000000001 } }); - await sleep(500); - const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); const [j2] = await e2.getJobs(); expect(j2.result).toBe(0.3); @@ -194,8 +231,6 @@ describe('workflow > instructions > aggregate', () => { const p2 = await PostRepo.create({ values: { title: 't2' } }); - await sleep(500); - const [e1] = await workflow.getExecutions(); const [j1] = await e1.getJobs(); expect(j1.result).toBe(0); @@ -215,16 +250,12 @@ describe('workflow > instructions > aggregate', () => { const p1 = await PostRepo.create({ values: { title: 't1', read: 1 } }); - await sleep(500); - const [e1] = await workflow.getExecutions(); const [j1] = await e1.getJobs(); expect(j1.result).toBe(1); const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } }); - await sleep(500); - const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); const [j2] = await e2.getJobs(); expect(j2.result).toBe(1.5); @@ -244,16 +275,12 @@ describe('workflow > instructions > aggregate', () => { const p1 = await PostRepo.create({ values: { title: 't1', read: 1 } }); - await sleep(500); - const [e1] = await workflow.getExecutions(); const [j1] = await e1.getJobs(); expect(j1.result).toBe(1); const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } }); - await sleep(500); - const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); const [j2] = await e2.getJobs(); expect(j2.result).toBe(1); @@ -273,16 +300,12 @@ describe('workflow > instructions > aggregate', () => { const p1 = await PostRepo.create({ values: { title: 't1', read: 1 } }); - await sleep(500); - const [e1] = await workflow.getExecutions(); const [j1] = await e1.getJobs(); expect(j1.result).toBe(1); const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } }); - await sleep(500); - const [e2] = await workflow.getExecutions({ order: [['id', 'desc']] }); const [j2] = await e2.getJobs(); expect(j2.result).toBe(2); @@ -333,8 +356,6 @@ describe('workflow > instructions > aggregate', () => { const p1 = await PostRepo.create({ values: { title: 't1', comments: [{}, { status: 1 }] } }); - await sleep(500); - const [e1] = await workflow.getExecutions(); const [j1, j2] = await e1.getJobs({ order: [['id', 'ASC']] }); expect(j1.result).toBe(2); @@ -343,7 +364,7 @@ describe('workflow > instructions > aggregate', () => { it('sum', async () => { const PostModel = db.getCollection('posts').model; - const p1 = await PostModel.create({ title: 't1', read: 1 }); + const p1 = await PostModel.create({ title: 't1', read: 1 }, { hooks: false }); const n1 = await workflow.createNode({ type: 'create', @@ -400,8 +421,6 @@ describe('workflow > instructions > aggregate', () => { const p2 = await PostRepo.create({ values: { title: 't2', read: 2 } }); - await sleep(500); - const [e1] = await workflow.getExecutions(); const [j1, j2, j3] = await e1.getJobs({ order: [['id', 'ASC']] }); expect(j2.result).toBe(3); @@ -429,8 +448,6 @@ describe('workflow > instructions > aggregate', () => { await PostRepo.create({ values: { title: 't1' } }); - await sleep(500); - const [execution] = await workflow.getExecutions(); expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED); const [job] = await execution.getJobs();