mirror of
https://github.com/docmost/docmost.git
synced 2026-06-10 01:52:43 +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,
|
||||
BaseRowsDeletedEvent,
|
||||
} from '../events/base-events';
|
||||
import { FormulaService } from '../formula/formula.service';
|
||||
|
||||
@Injectable()
|
||||
export class BaseRowService {
|
||||
@@ -50,6 +51,7 @@ export class BaseRowService {
|
||||
private readonly basePropertyRepo: BasePropertyRepo,
|
||||
private readonly baseViewRepo: BaseViewRepo,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly formulaService: FormulaService,
|
||||
) {}
|
||||
|
||||
async create(userId: string, workspaceId: string, dto: CreateRowDto) {
|
||||
@@ -70,15 +72,29 @@ export class BaseRowService {
|
||||
position = generateJitteredKeyBetween(lastPosition, null);
|
||||
}
|
||||
|
||||
const properties = await this.basePropertyRepo.findByBaseId(dto.baseId);
|
||||
|
||||
let validatedCells: Record<string, unknown> = {};
|
||||
if (dto.cells && Object.keys(dto.cells).length > 0) {
|
||||
const properties = await this.basePropertyRepo.findByBaseId(dto.baseId);
|
||||
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({
|
||||
baseId: dto.baseId,
|
||||
cells: validatedCells as any,
|
||||
cells: finalCells as any,
|
||||
position,
|
||||
creatorId: userId,
|
||||
workspaceId,
|
||||
@@ -108,9 +124,21 @@ export class BaseRowService {
|
||||
const properties = await this.basePropertyRepo.findByBaseId(dto.baseId);
|
||||
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(
|
||||
dto.rowId,
|
||||
validatedCells,
|
||||
finalCells,
|
||||
{
|
||||
baseId: dto.baseId,
|
||||
workspaceId,
|
||||
@@ -129,7 +157,7 @@ export class BaseRowService {
|
||||
requestId: dto.requestId ?? null,
|
||||
rowId: dto.rowId,
|
||||
patch: dto.cells,
|
||||
updatedCells: validatedCells,
|
||||
updatedCells: finalCells,
|
||||
};
|
||||
this.eventEmitter.emit(EventName.BASE_ROW_UPDATED, event);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user