Changes
3 changed files (+1028/-10)
-
-
@@ -3,11 +3,13 @@ // SPDX-License-Identifier: AGPL-3.0-onlyimport * as proto from "@bufbuild/protobuf"; import { DateSchema } from "@yamori/proto/yamori/type/v1/date_pb.js"; import { RecordKindWriteInputSchema } from "@yamori/proto/yamori/work_record/v1/record_kind_write_input_pb.js"; import { WorkRecordSchema } from "@yamori/proto/yamori/work_record/v1/work_record_pb.js"; import { WorkRecordBatchWriteInputSchema } from "@yamori/proto/yamori/work_record/v1/work_record_batch_write_input_pb.js"; import { WorkRecordBatchWriteMaskSchema } from "@yamori/proto/yamori/work_record/v1/work_record_batch_write_mask_pb.js"; import { WorkRecordReadMaskSchema } from "@yamori/proto/yamori/work_record/v1/work_record_read_mask_pb.js"; import { WorkRecordFilterSchema } from "@yamori/proto/yamori/work_record/v1/work_record_filter_pb.js"; import { RecordKindSchema } from "@yamori/proto/yamori/work_record/v1/record_kind_pb.js"; import { type IDBPObjectStore, type StoreNames } from "idb"; import { packDate, unpackDate } from "../../../helpers";
-
@@ -40,7 +42,130 @@ );} } export class NonexistentLeaveIDError extends Error { constructor( public readonly leaveID: string | undefined, public readonly workspaceID: string, ) { super( `Leave with id="${leaveID}" does not exist in the workspace (workspaceID=${workspaceID})`, ); } } async function createKindWritePayload( targetDatePacked: number, isHalved: boolean, input: proto.MessageShape<typeof RecordKindWriteInputSchema> | undefined, { workspace, getPaidLeaveProvisions, }: Pick<WriteInput, "workspace" | "getPaidLeaveProvisions">, ): Promise< | Extract< NonNullable<YamoriDB["workRecords"]["value"]["kind"]>, { type: "day_whole" } >["data"] | undefined > { const getProvidedAtPacked = async ( targetDatePacked: number, providedAt: proto.MessageShape<typeof DateSchema> | undefined, amount: number = 1, ): Promise<number> => { const paidLeaveProvisions = await getPaidLeaveProvisions(); const packed = providedAt && packDate(providedAt); const provision = packed ? paidLeaveProvisions?.find((provision) => provision.providedAtPacked === packed) : paidLeaveProvisions ?.filter( (provision) => provision.providedAtPacked <= targetDatePacked && provision.expiresAtPacked > targetDatePacked && provision.remainingDays >= amount, ) .sort((a, b) => a.expiresAtPacked - b.expiresAtPacked)[0]; if (!provision) { if (providedAt) { throw new NoPaidLeaveProvidedAtError(providedAt); } throw new NoPaidLeaveAvailableError(unpackDate(targetDatePacked)); } return provision.providedAtPacked; }; switch (input?.kind.case) { case "dayOff": { return { type: "day_off", }; } case "skipped": case "worked": { return { type: input.kind.case === "skipped" ? "skipped" : "worked", hourlyPaidLeave: input.kind.value.hourlyPaidLeave && { hours: input.kind.value.hourlyPaidLeave.hours, // TODO: 時間単位年休に対応 providedAtPacked: await getProvidedAtPacked( targetDatePacked, input.kind.value.hourlyPaidLeave.providedAt, ), }, hourlyWorkspaceDefinedLeaves: input.kind.value.hourlyWorkspaceDefinedLeave.map( (leave) => { const found = workspace.leaveDefinitions.find( (def) => def.id === leave.leaveId?.value, ); if (!found) { throw new NonexistentLeaveIDError(leave.leaveId?.value, workspace.id); } return { hours: leave.hours, leaveId: found.id, }; }, ), }; } case "paidLeave": { return { type: "paid_leave", providedAtPacked: await getProvidedAtPacked( targetDatePacked, input.kind.value.providedAt, isHalved ? 0.5 : 1, ), }; } case "workspaceDefinedLeaveId": { const leaveID = input.kind.value.value; const found = workspace.leaveDefinitions.find((def) => def.id === leaveID); if ( !found || !found.revisions.some((rev) => rev.startAtPackedDate <= targetDatePacked) ) { throw new NonexistentLeaveIDError(leaveID, workspace.id); } return { type: "workspace_defined_leave", leaveId: leaveID, }; } default: return undefined; } } export interface WriteInput { workspace: YamoriDB["workspaces"]["value"]; getPaidLeaveProvisions(): Promise<YamoriDB["paidLeaveProvision"]["value"][]>; mask?: proto.MessageInitShape<typeof WorkRecordBatchWriteMaskSchema>;
-
@@ -50,6 +175,7 @@ export async function write(base: YamoriDB["workRecords"]["value"], message: proto.MessageShape<typeof WorkRecordBatchWriteInputSchema>, { workspace, getPaidLeaveProvisions, mask: { fields = WorkRecordBatchWriteInputSchema.fields.map((field) => field.number),
-
@@ -228,6 +354,84 @@ ) {value.kind = undefined; } break; case WorkRecordBatchWriteInputSchema.field.dayWhole.number: if (message.kind.case === "dayWhole") { const data = await createKindWritePayload( value.datePacked, false, message.kind.value, { workspace, async getPaidLeaveProvisions() { if (!paidLeaveProvisions) { paidLeaveProvisions = await getPaidLeaveProvisions(); } return paidLeaveProvisions; }, }, ); value.kind = data && { type: "day_whole", data, }; } else if ( !message.record.case && message.kind.case === undefined && value.kind?.type === "day_whole" ) { value.kind = undefined; } break; case WorkRecordBatchWriteInputSchema.field.dayHalved.number: if (message.kind.case === "dayHalved") { const am = await createKindWritePayload( value.datePacked, true, message.kind.value.am, { workspace, async getPaidLeaveProvisions() { if (!paidLeaveProvisions) { paidLeaveProvisions = await getPaidLeaveProvisions(); } return paidLeaveProvisions; }, }, ); const pm = await createKindWritePayload( value.datePacked, true, message.kind.value.pm, { workspace, async getPaidLeaveProvisions() { if (!paidLeaveProvisions) { paidLeaveProvisions = await getPaidLeaveProvisions(); } return paidLeaveProvisions; }, }, ); value.kind = am && pm && { type: "day_halved", am, pm, }; } else if ( !message.record.case && message.kind.case === undefined && value.kind?.type === "day_halved" ) { value.kind = undefined; } break; } }
-
@@ -267,6 +471,87 @@ IDBKeyRange.bound([workspaceID, workerID, since], [workspaceID, workerID, until]),); } function recordEntryToMessage( entry: Extract< NonNullable<YamoriDB["workRecords"]["value"]["kind"]>, { type: "day_whole" } >["data"], datePacked: number, workspace: YamoriDB["workspaces"]["value"], ): proto.MessageShape<typeof RecordKindSchema> { switch (entry.type) { case "day_off": return proto.create(RecordKindSchema, { kind: { case: "dayOff", value: {}, }, }); case "skipped": case "worked": { return proto.create(RecordKindSchema, { kind: { case: entry.type === "skipped" ? "skipped" : "worked", value: { hourlyPaidLeave: entry.hourlyPaidLeave && { hours: entry.hourlyPaidLeave.hours, providedAt: typeof entry.hourlyPaidLeave.providedAtPacked === "number" ? unpackDate(entry.hourlyPaidLeave.providedAtPacked) : undefined, }, hourlyWorkspaceDefinedLeaves: entry.hourlyWorkspaceDefinedLeaves .map((l) => { const found = workspace.leaveDefinitions.find( (def) => def.id === l.leaveId, ); if (!found) { return null; } return { hours: l.hours, leave: leave.build(found, { date: unpackDate(datePacked), }), }; }) .filter((l): l is NonNullable<typeof l> => !!l), }, }, }); } case "paid_leave": { return proto.create(RecordKindSchema, { kind: { case: "paidLeave", value: { providedAt: typeof entry.providedAtPacked === "number" ? unpackDate(entry.providedAtPacked) : undefined, }, }, }); } case "workspace_defined_leave": { const found = workspace.leaveDefinitions.find((def) => def.id === entry.leaveId); if (!found) { return proto.create(RecordKindSchema); } return proto.create(RecordKindSchema, { kind: { case: "workspaceDefinedLeave", value: leave.build(found, { date: unpackDate(datePacked), }), }, }); } } } export interface BuildInput { workspace: YamoriDB["workspaces"]["value"];
-
@@ -290,6 +575,25 @@ init.date = unpackDate(entry.datePacked);break; case WorkRecordSchema.field.note.number: init.note = entry.note; break; case WorkRecordSchema.field.dayWhole.number: if (entry.kind?.type === "day_whole") { init.kind = { case: "dayWhole", value: recordEntryToMessage(entry.kind.data, entry.datePacked, workspace), }; } break; case WorkRecordSchema.field.dayHalved.number: if (entry.kind?.type === "day_halved") { init.kind = { case: "dayHalved", value: { am: recordEntryToMessage(entry.kind.am, entry.datePacked, workspace), pm: recordEntryToMessage(entry.kind.pm, entry.datePacked, workspace), }, }; } break; case WorkRecordSchema.field.dayOff.number: if (entry.kind?.type === "day_whole" && entry.kind.data.type === "day_off") {
-
-
-
@@ -15,6 +15,8 @@ } from "@bufbuild/protobuf";import { WorkRecordBatchWriteInputSchema } from "@yamori/proto/yamori/work_record/v1/work_record_batch_write_input_pb.js"; import { WriteWorkRecordRequestSchema } from "@yamori/proto/yamori/worker/v1/write_work_record_request_pb.js"; import { WriteWorkRecordResponseSchema } from "@yamori/proto/yamori/worker/v1/write_work_record_response_pb.js"; import { RecordKindWriteInputSchema } from "@yamori/proto/yamori/work_record/v1/record_kind_write_input_pb.js"; import { RecordKindSchema } from "@yamori/proto/yamori/work_record/v1/record_kind_pb.js"; import { WorkerSchema } from "@yamori/proto/yamori/worker/v1/worker_pb.js"; import { idbBackend } from "../../../../lib";
-
@@ -166,7 +168,399 @@ WriteWorkRecordRequestSchema.field.writeWorkRecordKey.name,); }); test("Should reject nonexistent leave_id", async () => { for (const target of ["whole day", "am", "pm"] as const) { const kind = ( payload: MessageInitShape<typeof RecordKindWriteInputSchema>["kind"], ): MessageInitShape<typeof WorkRecordBatchWriteInputSchema>["kind"] => { return target === "whole day" ? { case: "dayWhole", value: { kind: payload } } : { case: "dayHalved", value: { am: { kind: target === "am" ? payload : { case: "dayOff", value: {} } }, pm: { kind: target === "pm" ? payload : { case: "dayOff", value: {} } }, }, }; }; const expectKind = ( actual: unknown, expected: MessageInitShape<typeof RecordKindSchema>["kind"], ) => { switch (target) { case "whole day": expect(actual).toMatchObject({ case: "dayWhole", value: { kind: expected, }, }); return; case "am": expect(actual).toMatchObject({ case: "dayHalved", value: { am: { kind: expected }, pm: { kind: { case: "dayOff" } }, }, }); return; case "pm": expect(actual).toMatchObject({ case: "dayHalved", value: { am: { kind: { case: "dayOff" } }, pm: { kind: expected }, }, }); return; } }; test(`Should reject nonexistent leave_id set as ${target}`, async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx); const leave = await createLeaveDefinition(ctx, { workspaceId: workspace.id, leaveDefinition: { displayName: "Foo", revisions: [ { startAt: { year: 2024, month: 1, day: 1 }, snapshot: { isWorkerDeemedToBeWorked: true, }, }, ], }, createLeaveDefinitionKey: workspace.createLeaveDefinitionKey, }); const worker = await createWorker(ctx, workspace); const resp = await writeWorkRecord(ctx, { workspaceId: workspace.id, workerId: worker.id, writeWorkRecordKey: worker.writeWorkRecordKey, workRecord: { dates: [{ year: 2024, month: 1, day: 1 }], kind: kind({ case: "workspaceDefinedLeaveId", value: { value: leave.id?.value + "-000", }, }), }, }); if (resp.result.case !== "notFound") { console.dir(resp.result.value); expect.unreachable(`Expected "not_found", got ${JSON.stringify(resp.result.case)}`); } expect(resp.result.value.typeName).toBe("yamori.work_record.v1.Leave"); }); test(`Should reject reference to leave not started yet set to ${target}`, async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx); const leave = await createLeaveDefinition(ctx, { workspaceId: workspace.id, leaveDefinition: { displayName: "Foo", revisions: [ { startAt: { year: 2024, month: 1, day: 2 }, snapshot: { isWorkerDeemedToBeWorked: true, }, }, ], }, createLeaveDefinitionKey: workspace.createLeaveDefinitionKey, }); const worker = await createWorker(ctx, workspace); const resp = await writeWorkRecord(ctx, { workspaceId: workspace.id, workerId: worker.id, writeWorkRecordKey: worker.writeWorkRecordKey, workRecord: { dates: [ { year: 2024, month: 1, day: 1 }, { year: 2024, month: 1, day: 2 }, ], kind: kind({ case: "workspaceDefinedLeaveId", value: leave.id!, }), }, }); if (resp.result.case !== "notFound") { console.dir(resp.result.value); expect.unreachable(`Expected "not_found", got ${JSON.stringify(resp.result.case)}`); } expect(resp.result.value.typeName).toBe("yamori.work_record.v1.Leave"); }); test(`Should prevent setting a paid leave to ${target} if none is available`, async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx); const worker = await createWorker(ctx, workspace); await providePaidProvision(ctx, { workspaceId: workspace.id, workerId: worker.id, providePaidLeaveKey: worker.providePaidLeaveKey, paidLeave: { providedAt: { year: 2022, month: 1, day: 1, }, amountDays: 1, }, }); const resp = await writeWorkRecord(ctx, { workspaceId: workspace.id, workerId: worker.id, writeWorkRecordKey: worker.writeWorkRecordKey, workRecord: { dates: [{ year: 2024, month: 1, day: 1 }], kind: kind({ case: "paidLeave", value: {}, }), }, }); if (resp.result.case !== "notFound") { expect.unreachable(`Expected "not_found", got ${JSON.stringify(resp.result.case)}`); } expect(resp.result.value.typeName).toBe("yamori.worker.v1.PaidLeaveProvision"); }); test(`Should prevent setting a paid leave to ${target} if none has corresponding provided_at`, async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx); const worker = await createWorker(ctx, workspace); await providePaidProvision(ctx, { workspaceId: workspace.id, workerId: worker.id, providePaidLeaveKey: worker.providePaidLeaveKey, paidLeave: { providedAt: { year: 2023, month: 1, day: 2, }, amountDays: 1, }, }); const resp = await writeWorkRecord(ctx, { workspaceId: workspace.id, workerId: worker.id, writeWorkRecordKey: worker.writeWorkRecordKey, workRecord: { dates: [{ year: 2024, month: 1, day: 1 }], kind: kind({ case: "paidLeave", value: { providedAt: { year: 2023, month: 1, day: 1, }, }, }), }, }); if (resp.result.case !== "notFound") { expect.unreachable(`Expected "not_found", got ${JSON.stringify(resp.result.case)}`); } expect(resp.result.value.typeName).toBe("yamori.worker.v1.PaidLeaveProvision"); }); test(`Should prevent setting paid leaves to ${target} result in exceeding amount`, async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx); const worker = await createWorker(ctx, workspace); await providePaidProvision(ctx, { workspaceId: workspace.id, workerId: worker.id, providePaidLeaveKey: worker.providePaidLeaveKey, paidLeave: { providedAt: { year: 2024, month: 1, day: 1, }, amountDays: 1, }, }); const resp = await writeWorkRecord(ctx, { workspaceId: workspace.id, workerId: worker.id, writeWorkRecordKey: worker.writeWorkRecordKey, workRecord: { dates: [ { year: 2024, month: 1, day: 1 }, { year: 2025, month: 1, day: 1 }, { year: 2025, month: 1, day: 2 }, ], kind: kind({ case: "paidLeave", value: {}, }), }, }); if (resp.result.case !== "notFound") { expect.unreachable(`Expected "not_found", got ${JSON.stringify(resp.result.case)}`); } expect(resp.result.value.typeName).toBe("yamori.worker.v1.PaidLeaveProvision"); }); test(`Should allow paid leaves on ${target}`, async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx); const worker = await createWorker(ctx, workspace); await providePaidProvision(ctx, { workspaceId: workspace.id, workerId: worker.id, providePaidLeaveKey: worker.providePaidLeaveKey, paidLeave: { providedAt: { year: 2024, month: 1, day: 1, }, amountDays: 10, }, }); const resp = await writeWorkRecord(ctx, { workspaceId: workspace.id, workerId: worker.id, writeWorkRecordKey: worker.writeWorkRecordKey, workRecord: { dates: [ { year: 2024, month: 1, day: 1 }, { year: 2025, month: 1, day: 1 }, ], kind: kind({ case: "paidLeave", value: {}, }), }, }); if (resp.result.case !== "ok") { expect.unreachable(`Expected "ok", got ${JSON.stringify(resp.result.case)}`); } const after = await getWorker(ctx, { workspaceId: workspace.id, workerId: worker.id, workRecordFilter: { since: { year: 2024, month: 1, day: 1, }, until: { year: 2026, month: 1, day: 1, }, }, readMask: { fields: [ WorkerSchema.field.workRecords.number, WorkerSchema.field.paidLeaveProvisions.number, ], }, }); expect(after).toEqual( expect.objectContaining({ workRecords: [ expect.objectContaining({ date: expect.objectContaining({ year: 2024, month: 1, day: 1, }), }), expect.objectContaining({ date: expect.objectContaining({ year: 2025, month: 1, day: 1, }), }), ], paidLeaveProvisions: [ expect.objectContaining({ providedAt: expect.objectContaining({ year: 2024, month: 1, day: 1, }), expiresAt: expect.objectContaining({ year: 2026, month: 1, day: 1, }), amountDays: 10, remainingDays: target === "whole day" ? 8 : 9, }), ], }), ); expectKind(after.workRecords[0]?.kind, { case: "paidLeave", value: { providedAt: { year: 2024, month: 1, day: 1, }, }, }); expectKind(after.workRecords[1]?.kind, { case: "paidLeave", value: { providedAt: { year: 2024, month: 1, day: 1, }, }, }); }); } test("Should reject nonexistent leave_id (deprecated field)", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx);
-
@@ -207,7 +601,7 @@expect(resp.result.value.typeName).toBe("yamori.work_record.v1.Leave"); }); test("Should reject reference to leave not started yet", async () => { test("Should reject reference to leave not started yet (deprecated field)", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx);
-
@@ -251,7 +645,7 @@expect(resp.result.value.typeName).toBe("yamori.work_record.v1.Leave"); }); test("Should prevent setting a paid leave if none is available", async () => { test("Should prevent setting a paid leave if none is available (deprecated field)", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx);
-
@@ -291,7 +685,7 @@expect(resp.result.value.typeName).toBe("yamori.worker.v1.PaidLeaveProvision"); }); test("Should prevent setting a paid leave if none has corresponding provided_at", async () => { test("Should prevent setting a paid leave if none has corresponding provided_at (deprecated)", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx);
-
@@ -337,7 +731,7 @@expect(resp.result.value.typeName).toBe("yamori.worker.v1.PaidLeaveProvision"); }); test("Should prevent setting paid leaves result in exceeding amount", async () => { test("Should prevent setting paid leaves result in exceeding amount (deprecated field)", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx);
-
@@ -380,7 +774,7 @@expect(resp.result.value.typeName).toBe("yamori.worker.v1.PaidLeaveProvision"); }); test("Should allow paid leaves", async () => { test("Should allow paid leaves (deprecated field)", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx);
-
@@ -541,6 +935,208 @@ workRecord: {dates: [ { year: 2024, month: 1, day: 1 }, { year: 2024, month: 1, day: 2 }, { year: 2024, month: 1, day: 3 }, ], kind: { case: "dayHalved", value: { am: { kind: { case: "dayOff", value: {}, }, }, pm: { kind: { case: "paidLeave", value: {}, }, }, }, }, }, }); if (resp.result.case !== "ok") { expect.unreachable(`Expected "ok", got ${JSON.stringify(resp.result.case)}`); } const after = await getWorker(ctx, { workspaceId: workspace.id, workerId: worker.id, workRecordFilter: { since: { year: 2024, month: 1, day: 1, }, until: { year: 2026, month: 1, day: 1, }, }, readMask: { fields: [ WorkerSchema.field.workRecords.number, WorkerSchema.field.paidLeaveProvisions.number, ], }, }); expect(after).toMatchObject({ workRecords: [ { date: expect.objectContaining({ year: 2024, month: 1, day: 1, }), kind: { case: "dayHalved", value: { am: expect.anything(), pm: { kind: { case: "paidLeave", value: { providedAt: { year: 2023, month: 1, day: 1, }, }, }, }, }, }, }, { date: expect.objectContaining({ year: 2024, month: 1, day: 2, }), kind: { case: "dayHalved", value: { am: expect.anything(), pm: { kind: { case: "paidLeave", value: { providedAt: { year: 2023, month: 1, day: 1, }, }, }, }, }, }, }, { date: expect.objectContaining({ year: 2024, month: 1, day: 3, }), kind: { case: "dayHalved", value: { am: expect.anything(), pm: { kind: { case: "paidLeave", value: { providedAt: { year: 2024, month: 1, day: 1, }, }, }, }, }, }, }, ], paidLeaveProvisions: [ expect.objectContaining({ providedAt: expect.objectContaining({ year: 2023, month: 1, day: 1, }), expiresAt: expect.objectContaining({ year: 2025, month: 1, day: 1, }), amountDays: 1, remainingDays: 0, }), expect.objectContaining({ providedAt: expect.objectContaining({ year: 2024, month: 1, day: 1, }), expiresAt: expect.objectContaining({ year: 2026, month: 1, day: 1, }), amountDays: 1, remainingDays: 0, isHalvedDayRemaining: true, }), ], }); }); test("Should use next paid leaves if current one is empty (deprecated field)", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx); const worker = await createWorker(ctx, workspace); await providePaidProvision(ctx, { workspaceId: workspace.id, workerId: worker.id, providePaidLeaveKey: worker.providePaidLeaveKey, paidLeave: { providedAt: { year: 2023, month: 1, day: 1, }, amountDays: 1, }, }); await providePaidProvision(ctx, { workspaceId: workspace.id, workerId: worker.id, providePaidLeaveKey: worker.providePaidLeaveKey, paidLeave: { providedAt: { year: 2024, month: 1, day: 1, }, amountDays: 1, }, }); const resp = await writeWorkRecord(ctx, { workspaceId: workspace.id, workerId: worker.id, writeWorkRecordKey: worker.writeWorkRecordKey, workRecord: { dates: [ { year: 2024, month: 1, day: 1 }, { year: 2024, month: 1, day: 2 }, ], record: { case: "paidLeave",
-
@@ -711,7 +1307,115 @@ ],}); }); test("Should write a record", async () => { test("Should write a whole-day record", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx); const leave = await createLeaveDefinition(ctx, { workspaceId: workspace.id, leaveDefinition: { displayName: "Foo", revisions: [ { startAt: { year: 2024, month: 1, day: 1 }, snapshot: { isWorkerDeemedToBeWorked: true, }, }, ], }, createLeaveDefinitionKey: workspace.createLeaveDefinitionKey, }); const worker = await createWorker(ctx, workspace); const resp = await writeWorkRecord(ctx, { workspaceId: workspace.id, workerId: worker.id, writeWorkRecordKey: worker.writeWorkRecordKey, workRecord: { dates: [{ year: 2024, month: 1, day: 1 }], kind: { case: "dayWhole", value: { kind: { case: "workspaceDefinedLeaveId", value: leave.id!, }, }, }, }, }); if (resp.result.case !== "ok") { expect.unreachable(`Expected "ok", got ${JSON.stringify(resp.result.case)}`); } expect(resp.result.value.workRecords).toEqual([ expect.objectContaining({ date: expect.objectContaining({ year: 2024, month: 1, day: 1, }), kind: expect.objectContaining({ case: "dayWhole", value: expect.objectContaining({ kind: expect.objectContaining({ case: "workspaceDefinedLeave", value: expect.objectContaining({ id: leave.id, }), }), }), }), }), ]); const after = await getWorker(ctx, { workspaceId: workspace.id, workerId: worker.id, workRecordFilter: { since: { year: 2024, month: 1, day: 1, }, until: { year: 2024, month: 1, day: 1, }, }, readMask: { fields: [WorkerSchema.field.workRecords.number], }, }); expect(after).toMatchObject({ workRecords: [ expect.objectContaining({ date: expect.objectContaining({ year: 2024, month: 1, day: 1, }), kind: expect.objectContaining({ case: "dayWhole", value: expect.objectContaining({ kind: expect.objectContaining({ case: "workspaceDefinedLeave", value: expect.objectContaining({ id: leave.id, }), }), }), }), }), ], }); }); test("Should write a record (deprecated)", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx);
-
@@ -804,7 +1508,7 @@ ],}); }); test("Should write to multiple dates", async () => { test("Should write to multiple dates (deprecated field)", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx);
-
@@ -919,7 +1623,7 @@ ],}); }); test("Should write to specified user", async () => { test("Should write to specified user (deprecated field)", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx);
-
@@ -1021,7 +1725,7 @@ ],}); }); test("Should overwrite respecting mask", async () => { test("Should overwrite respecting mask (deprecated field)", async () => { const { [CONTEXT]: ctx } = await idbBackend(); const workspace = await createWorkspace(ctx);
-
-
-
@@ -212,6 +212,7 @@ datePacked: packed,}, req.workRecord, { workspace, getPaidLeaveProvisions() { return paidLeaveProvision.getAllForWorker( {
-
@@ -253,6 +254,15 @@ },}); } catch (error) { tx?.abort(); if (error instanceof workRecord.NonexistentLeaveIDError) { return respond({ case: "notFound", value: { typeName: "yamori.work_record.v1.Leave", }, }); } if ( error instanceof workRecord.NoPaidLeaveAvailableError ||
-