117 lines
3.0 KiB
TypeScript
117 lines
3.0 KiB
TypeScript
import { DataTypes } from 'sequelize';
|
|
|
|
import { BaseFieldOptions, Field } from '@nocobase/database';
|
|
import evaluators from '@nocobase/evaluators';
|
|
|
|
import { toDbType } from '../utils';
|
|
|
|
export interface FormulaFieldOptions extends BaseFieldOptions {
|
|
type: 'formula';
|
|
engine: string;
|
|
expression: string;
|
|
}
|
|
|
|
const DataTypeMap = {
|
|
boolean: DataTypes.BOOLEAN,
|
|
integer: DataTypes.INTEGER,
|
|
bigInt: DataTypes.BIGINT,
|
|
double: DataTypes.DOUBLE,
|
|
decimal: DataTypes.DECIMAL,
|
|
string: DataTypes.STRING,
|
|
date: DataTypes.DATE(3),
|
|
};
|
|
|
|
export class FormulaField extends Field {
|
|
get dataType() {
|
|
const { dataType } = this.options;
|
|
return DataTypeMap[dataType] ?? DataTypes.DOUBLE;
|
|
}
|
|
|
|
calculate(scope) {
|
|
const { expression, engine = 'math.js', dataType = 'double' } = this.options;
|
|
const evaluate = evaluators.get(engine);
|
|
try {
|
|
const result = evaluate(expression, scope);
|
|
return toDbType(result, dataType);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
initFieldData = async ({ transaction }) => {
|
|
const { name } = this.options;
|
|
|
|
const records = await this.collection.repository.find({
|
|
order: [this.collection.model.primaryKeyAttribute],
|
|
transaction,
|
|
});
|
|
|
|
for (const record of records) {
|
|
const scope = record.toJSON();
|
|
const result = this.calculate(scope);
|
|
if (result != null) {
|
|
await record.update(
|
|
{
|
|
[name]: result,
|
|
},
|
|
{
|
|
transaction,
|
|
silent: true,
|
|
hooks: false,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
calculateField = async (instance) => {
|
|
const { name } = this.options;
|
|
const result = this.calculate(instance.toJSON());
|
|
instance.set(name, result);
|
|
};
|
|
|
|
updateFieldData = async (instance, { transaction }) => {
|
|
if (this.collection.name === instance.collectionName && instance.name === this.options.name) {
|
|
this.options = Object.assign(this.options, instance.options);
|
|
const { name } = this.options;
|
|
|
|
const records = await this.collection.repository.find({
|
|
order: [this.collection.model.primaryKeyAttribute],
|
|
transaction,
|
|
});
|
|
|
|
for (const record of records) {
|
|
const scope = record.toJSON();
|
|
const result = this.calculate(scope);
|
|
await record.update(
|
|
{
|
|
[name]: result,
|
|
},
|
|
{
|
|
transaction,
|
|
silent: true,
|
|
hooks: false,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
bind() {
|
|
super.bind();
|
|
// this.on('afterSync', this.initFieldData);
|
|
// TODO: should not depends on fields table (which is defined by other plugin)
|
|
this.database.on('fields.afterUpdate', this.updateFieldData);
|
|
this.on('beforeSave', this.calculateField);
|
|
}
|
|
|
|
unbind() {
|
|
super.unbind();
|
|
this.off('beforeSave', this.calculateField);
|
|
// TODO: should not depends on fields table
|
|
this.database.off('fields.afterUpdate', this.updateFieldData);
|
|
// this.off('afterSync', this.initFieldData);
|
|
}
|
|
}
|