mirror of
https://github.com/docmost/docmost.git
synced 2026-08-28 17:27:06 +08:00
feat(base): wire inline formula evaluation into row service
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
import { FormulaService } from "../formula.service";
|
||||||
|
import type { BaseProperty } from "@docmost/db/types/entity.types";
|
||||||
|
|
||||||
|
const mkProp = (
|
||||||
|
id: string, type: string, typeOptions: any = {},
|
||||||
|
name = id,
|
||||||
|
): BaseProperty => ({
|
||||||
|
id, baseId: "base_1", name, type: type as any, position: "a",
|
||||||
|
typeOptions, isPrimary: false, workspaceId: "ws_1",
|
||||||
|
createdAt: new Date(), updatedAt: new Date(),
|
||||||
|
schemaVersion: 0, pendingType: null, pendingTypeOptions: null,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
describe("FormulaService.evaluateInline", () => {
|
||||||
|
const svc = new FormulaService({ add: jest.fn() } as any);
|
||||||
|
|
||||||
|
it("computes a formula on create", () => {
|
||||||
|
const price = mkProp("prop_price", "number", {}, "Price");
|
||||||
|
const qty = mkProp("prop_qty", "number", {}, "Qty");
|
||||||
|
const total = mkProp("prop_total", "formula", {
|
||||||
|
source: 'prop("Price") * prop("Qty")',
|
||||||
|
ast: { t: "op", op: "*", args: [
|
||||||
|
{ t: "prop", id: "prop_price" },
|
||||||
|
{ t: "prop", id: "prop_qty" },
|
||||||
|
]},
|
||||||
|
resultType: "number",
|
||||||
|
dependencies: ["prop_price", "prop_qty"],
|
||||||
|
astVersion: 1,
|
||||||
|
}, "Total");
|
||||||
|
|
||||||
|
const patch = svc.evaluateInline({
|
||||||
|
properties: [price, qty, total],
|
||||||
|
row: { prop_price: 10, prop_qty: 3 },
|
||||||
|
dirtyProps: ["prop_price", "prop_qty", "prop_total"],
|
||||||
|
});
|
||||||
|
expect(patch).toEqual({ prop_total: 30 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns empty patch when no formula is affected", () => {
|
||||||
|
const price = mkProp("prop_price", "number", {}, "Price");
|
||||||
|
expect(
|
||||||
|
svc.evaluateInline({ properties: [price], row: { prop_price: 10 }, dirtyProps: ["prop_price"] }),
|
||||||
|
).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -41,6 +41,7 @@ import {
|
|||||||
BaseRowUpdatedEvent,
|
BaseRowUpdatedEvent,
|
||||||
BaseRowsDeletedEvent,
|
BaseRowsDeletedEvent,
|
||||||
} from '../events/base-events';
|
} from '../events/base-events';
|
||||||
|
import { FormulaService } from '../formula/formula.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BaseRowService {
|
export class BaseRowService {
|
||||||
@@ -50,6 +51,7 @@ export class BaseRowService {
|
|||||||
private readonly basePropertyRepo: BasePropertyRepo,
|
private readonly basePropertyRepo: BasePropertyRepo,
|
||||||
private readonly baseViewRepo: BaseViewRepo,
|
private readonly baseViewRepo: BaseViewRepo,
|
||||||
private readonly eventEmitter: EventEmitter2,
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
private readonly formulaService: FormulaService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(userId: string, workspaceId: string, dto: CreateRowDto) {
|
async create(userId: string, workspaceId: string, dto: CreateRowDto) {
|
||||||
@@ -70,15 +72,29 @@ export class BaseRowService {
|
|||||||
position = generateJitteredKeyBetween(lastPosition, null);
|
position = generateJitteredKeyBetween(lastPosition, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const properties = await this.basePropertyRepo.findByBaseId(dto.baseId);
|
||||||
|
|
||||||
let validatedCells: Record<string, unknown> = {};
|
let validatedCells: Record<string, unknown> = {};
|
||||||
if (dto.cells && Object.keys(dto.cells).length > 0) {
|
if (dto.cells && Object.keys(dto.cells).length > 0) {
|
||||||
const properties = await this.basePropertyRepo.findByBaseId(dto.baseId);
|
|
||||||
validatedCells = this.validateCells(dto.cells, properties);
|
validatedCells = this.validateCells(dto.cells, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On create, treat every user-provided cell plus every formula property
|
||||||
|
// as dirty. The formula patch is merged into the cells we persist.
|
||||||
|
const dirtyProps = Object.keys(validatedCells);
|
||||||
|
const formulaPatch = this.formulaService.evaluateInline({
|
||||||
|
properties,
|
||||||
|
row: validatedCells,
|
||||||
|
dirtyProps: [
|
||||||
|
...dirtyProps,
|
||||||
|
...properties.filter((p) => p.type === 'formula').map((p) => p.id),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const finalCells = { ...validatedCells, ...formulaPatch };
|
||||||
|
|
||||||
const created = await this.baseRowRepo.insertRow({
|
const created = await this.baseRowRepo.insertRow({
|
||||||
baseId: dto.baseId,
|
baseId: dto.baseId,
|
||||||
cells: validatedCells as any,
|
cells: finalCells as any,
|
||||||
position,
|
position,
|
||||||
creatorId: userId,
|
creatorId: userId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -108,9 +124,21 @@ export class BaseRowService {
|
|||||||
const properties = await this.basePropertyRepo.findByBaseId(dto.baseId);
|
const properties = await this.basePropertyRepo.findByBaseId(dto.baseId);
|
||||||
const validatedCells = this.validateCells(dto.cells, properties);
|
const validatedCells = this.validateCells(dto.cells, properties);
|
||||||
|
|
||||||
|
const existing = await this.baseRowRepo.findById(dto.rowId, { workspaceId });
|
||||||
|
const mergedRow = {
|
||||||
|
...((existing?.cells as Record<string, unknown>) ?? {}),
|
||||||
|
...validatedCells,
|
||||||
|
};
|
||||||
|
const formulaPatch = this.formulaService.evaluateInline({
|
||||||
|
properties,
|
||||||
|
row: mergedRow,
|
||||||
|
dirtyProps: Object.keys(validatedCells),
|
||||||
|
});
|
||||||
|
const finalCells = { ...validatedCells, ...formulaPatch };
|
||||||
|
|
||||||
const updated = await this.baseRowRepo.updateCells(
|
const updated = await this.baseRowRepo.updateCells(
|
||||||
dto.rowId,
|
dto.rowId,
|
||||||
validatedCells,
|
finalCells,
|
||||||
{
|
{
|
||||||
baseId: dto.baseId,
|
baseId: dto.baseId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -129,7 +157,7 @@ export class BaseRowService {
|
|||||||
requestId: dto.requestId ?? null,
|
requestId: dto.requestId ?? null,
|
||||||
rowId: dto.rowId,
|
rowId: dto.rowId,
|
||||||
patch: dto.cells,
|
patch: dto.cells,
|
||||||
updatedCells: validatedCells,
|
updatedCells: finalCells,
|
||||||
};
|
};
|
||||||
this.eventEmitter.emit(EventName.BASE_ROW_UPDATED, event);
|
this.eventEmitter.emit(EventName.BASE_ROW_UPDATED, event);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user