@open-mercato/core 0.6.7-develop.6694.1.a06be5605e → 0.6.7-develop.6696.1.4236709f7e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,6 +18,7 @@ const MANAGE_FEATURE = "staff.leave_requests.manage";
18
18
  const SEND_FEATURE = "staff.leave_requests.send";
19
19
  const MY_VIEW_FEATURE = "staff.my_leave_requests.view";
20
20
  const MY_SEND_FEATURE = "staff.my_leave_requests.send";
21
+ const NIL_UUID = "00000000-0000-0000-0000-000000000000";
21
22
  const routeMetadata = {
22
23
  GET: { requireAuth: true },
23
24
  POST: { requireAuth: true },
@@ -91,6 +92,48 @@ async function resolveLeaveRequestAccess(ctx) {
91
92
  );
92
93
  return { canManage, canSend, canView, memberId: member?.id ?? null };
93
94
  }
95
+ async function buildLeaveRequestFilters(query, ctx) {
96
+ const filters = {};
97
+ if (typeof query.ids === "string" && query.ids.trim().length > 0) {
98
+ const ids = query.ids.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
99
+ if (ids.length > 0) {
100
+ filters[F.id] = { $in: ids };
101
+ }
102
+ }
103
+ if (query.status) {
104
+ filters[F.status] = query.status;
105
+ }
106
+ if (query.memberId) {
107
+ filters[F.member_id] = query.memberId;
108
+ }
109
+ const term = sanitizeSearchTerm(query.search);
110
+ if (term) {
111
+ const like = `%${escapeLikePattern(term)}%`;
112
+ const em = ctx.container.resolve("em").fork();
113
+ const members = await findWithDecryption(
114
+ em,
115
+ StaffTeamMember,
116
+ { displayName: { $ilike: like }, deletedAt: null },
117
+ { fields: ["id"] },
118
+ { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null }
119
+ );
120
+ const memberIds = members.map((member) => member.id);
121
+ if (!memberIds.length) {
122
+ filters[F.id] = NIL_UUID;
123
+ } else {
124
+ filters[F.member_id] = { $in: memberIds };
125
+ }
126
+ }
127
+ const access = await resolveLeaveRequestAccess(ctx);
128
+ if (!access.canManage) {
129
+ if (!access.canView || !access.memberId) {
130
+ filters[F.id] = NIL_UUID;
131
+ } else {
132
+ filters[F.member_id] = access.memberId;
133
+ }
134
+ }
135
+ return filters;
136
+ }
94
137
  const crud = makeCrudRoute({
95
138
  metadata: routeMetadata,
96
139
  orm: {
@@ -129,48 +172,7 @@ const crud = makeCrudRoute({
129
172
  createdAt: F.created_at,
130
173
  updatedAt: F.updated_at
131
174
  },
132
- buildFilters: async (query, ctx) => {
133
- const filters = {};
134
- if (typeof query.ids === "string" && query.ids.trim().length > 0) {
135
- const ids = query.ids.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
136
- if (ids.length > 0) {
137
- filters[F.id] = { $in: ids };
138
- }
139
- }
140
- if (query.status) {
141
- filters[F.status] = query.status;
142
- }
143
- if (query.memberId) {
144
- filters[F.member_id] = query.memberId;
145
- }
146
- const term = sanitizeSearchTerm(query.search);
147
- if (term) {
148
- const like = `%${escapeLikePattern(term)}%`;
149
- const em = ctx.container.resolve("em").fork();
150
- const members = await findWithDecryption(
151
- em,
152
- StaffTeamMember,
153
- { displayName: { $ilike: like }, deletedAt: null },
154
- { fields: ["id"] },
155
- { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null }
156
- );
157
- const memberIds = members.map((member) => member.id);
158
- if (!memberIds.length) {
159
- filters[F.id] = "__no_match__";
160
- } else {
161
- filters[F.member_id] = { $in: memberIds };
162
- }
163
- }
164
- const access = await resolveLeaveRequestAccess(ctx);
165
- if (!access.canManage) {
166
- if (!access.canView || !access.memberId) {
167
- filters[F.id] = "__no_access__";
168
- } else {
169
- filters[F.member_id] = access.memberId;
170
- }
171
- }
172
- return filters;
173
- }
175
+ buildFilters: buildLeaveRequestFilters
174
176
  },
175
177
  hooks: {
176
178
  afterList: async (payload, ctx) => {
@@ -359,6 +361,7 @@ export {
359
361
  GET,
360
362
  POST,
361
363
  PUT,
364
+ buildLeaveRequestFilters,
362
365
  metadata,
363
366
  openApi
364
367
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/staff/api/leave-requests.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveCrudRecordId, parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { StaffLeaveRequest, StaffTeamMember } from '../data/entities'\nimport {\n staffLeaveRequestCreateSchema,\n staffLeaveRequestUpdateSchema,\n staffLeaveRequestDecisionSchema,\n} from '../data/validators'\nimport { sanitizeSearchTerm } from './helpers'\nimport { E } from '#generated/entities.ids.generated'\nimport { createStaffCrudOpenApi, createPagedListResponseSchema, defaultOkResponseSchema } from './openapi'\n\nconst MANAGE_FEATURE = 'staff.leave_requests.manage'\nconst SEND_FEATURE = 'staff.leave_requests.send'\nconst MY_VIEW_FEATURE = 'staff.my_leave_requests.view'\nconst MY_SEND_FEATURE = 'staff.my_leave_requests.send'\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true },\n PUT: { requireAuth: true },\n DELETE: { requireAuth: true },\n}\n\nexport const metadata = routeMetadata\n\n// Field constants for StaffLeaveRequest entity\nconst F = {\n id: 'id',\n tenant_id: 'tenant_id',\n organization_id: 'organization_id',\n member_id: 'member_id',\n start_date: 'start_date',\n end_date: 'end_date',\n timezone: 'timezone',\n status: 'status',\n unavailability_reason_entry_id: 'unavailability_reason_entry_id',\n unavailability_reason_value: 'unavailability_reason_value',\n note: 'note',\n decision_comment: 'decision_comment',\n submitted_by_user_id: 'submitted_by_user_id',\n decided_by_user_id: 'decided_by_user_id',\n decided_at: 'decided_at',\n created_at: 'created_at',\n updated_at: 'updated_at',\n deleted_at: 'deleted_at',\n} as const\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n status: z.enum(['pending', 'approved', 'rejected']).optional(),\n memberId: z.string().uuid().optional(),\n ids: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\ntype LeaveRequestAccess = {\n canManage: boolean\n canSend: boolean\n canView: boolean\n memberId: string | null\n}\n\nconst uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/\n\nfunction resolveActorUserId(ctx: { auth?: { sub?: string | null; isApiKey?: boolean } | null }): string | null {\n if (!ctx.auth || ctx.auth.isApiKey) return null\n const sub = ctx.auth.sub ?? null\n if (!sub || !uuidRegex.test(sub)) return null\n return sub\n}\n\nasync function resolveLeaveRequestAccess(ctx: any): Promise<LeaveRequestAccess> {\n const auth = ctx.auth\n if (!auth || !auth.sub || auth.isApiKey) {\n return { canManage: false, canSend: false, canView: false, memberId: null }\n }\n const tenantId = ctx.organizationScope?.tenantId ?? auth.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? ctx.organizationScope?.selectedId ?? auth.orgId ?? null\n const rbac = (ctx.container.resolve('rbacService') as RbacService)\n const [canManage, canSendLegacy, canSendSelf, canViewSelf] = await Promise.all([\n rbac.userHasAllFeatures(auth.sub, [MANAGE_FEATURE], { tenantId, organizationId }),\n rbac.userHasAllFeatures(auth.sub, [SEND_FEATURE], { tenantId, organizationId }),\n rbac.userHasAllFeatures(auth.sub, [MY_SEND_FEATURE], { tenantId, organizationId }),\n rbac.userHasAllFeatures(auth.sub, [MY_VIEW_FEATURE], { tenantId, organizationId }),\n ])\n const canSend = canSendLegacy || canSendSelf\n const canView = canManage || canSend || canViewSelf\n if (!canManage && !canView) {\n return { canManage, canSend, canView, memberId: null }\n }\n const member = await findOneWithDecryption(\n ctx.container.resolve('em') as EntityManager,\n StaffTeamMember,\n { userId: auth.sub, deletedAt: null },\n undefined,\n { tenantId, organizationId },\n )\n return { canManage, canSend, canView, memberId: member?.id ?? null }\n}\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: StaffLeaveRequest,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: { entityType: E.staff.staff_leave_request },\n list: {\n schema: listSchema,\n entityId: E.staff.staff_leave_request,\n fields: [\n F.id,\n F.organization_id,\n F.tenant_id,\n F.member_id,\n F.start_date,\n F.end_date,\n F.timezone,\n F.status,\n F.unavailability_reason_entry_id,\n F.unavailability_reason_value,\n F.note,\n F.decision_comment,\n F.submitted_by_user_id,\n F.decided_by_user_id,\n F.decided_at,\n F.created_at,\n F.updated_at,\n ],\n sortFieldMap: {\n startDate: F.start_date,\n endDate: F.end_date,\n createdAt: F.created_at,\n updatedAt: F.updated_at,\n },\n buildFilters: async (query, ctx) => {\n const filters: Record<string, unknown> = {}\n if (typeof query.ids === 'string' && query.ids.trim().length > 0) {\n const ids = query.ids\n .split(',')\n .map((value) => value.trim())\n .filter((value) => value.length > 0)\n if (ids.length > 0) {\n filters[F.id] = { $in: ids }\n }\n }\n if (query.status) {\n filters[F.status] = query.status\n }\n if (query.memberId) {\n filters[F.member_id] = query.memberId\n }\n const term = sanitizeSearchTerm(query.search)\n if (term) {\n const like = `%${escapeLikePattern(term)}%`\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const members = await findWithDecryption(\n em,\n StaffTeamMember,\n { displayName: { $ilike: like }, deletedAt: null },\n { fields: ['id'] },\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n const memberIds = members.map((member) => member.id)\n if (!memberIds.length) {\n filters[F.id] = '__no_match__'\n } else {\n filters[F.member_id] = { $in: memberIds }\n }\n }\n\n const access = await resolveLeaveRequestAccess(ctx)\n if (!access.canManage) {\n if (!access.canView || !access.memberId) {\n filters[F.id] = '__no_access__'\n } else {\n filters[F.member_id] = access.memberId\n }\n }\n return filters\n },\n },\n hooks: {\n afterList: async (payload, ctx) => {\n const items: Array<Record<string, unknown>> = Array.isArray(payload?.items)\n ? (payload.items as Array<Record<string, unknown>>)\n : []\n if (!items.length) {\n payload.viewer = await resolveLeaveRequestAccess(ctx)\n return\n }\n const memberIds = new Set<string>()\n items.forEach((item) => {\n const memberId = typeof item.memberId === 'string'\n ? item.memberId\n : typeof item.member_id === 'string'\n ? item.member_id\n : null\n if (memberId) memberIds.add(memberId)\n })\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const members = memberIds.size\n ? await findWithDecryption(\n em,\n StaffTeamMember,\n { id: { $in: Array.from(memberIds) } },\n undefined,\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n : []\n const memberById = new Map(members.map((member) => [member.id, member]))\n items.forEach((item) => {\n const memberId = typeof item.memberId === 'string'\n ? item.memberId\n : typeof item.member_id === 'string'\n ? item.member_id\n : null\n if (!memberId) return\n const member = memberById.get(memberId)\n if (!member) return\n item.member = {\n id: member.id,\n displayName: member.displayName,\n userId: member.userId ?? null,\n }\n })\n payload.viewer = await resolveLeaveRequestAccess(ctx)\n },\n },\n actions: {\n create: {\n commandId: 'staff.leave-requests.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const access = await resolveLeaveRequestAccess(ctx)\n if (!access.canManage && !access.canSend) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n const payload = raw && typeof raw === 'object' ? { ...(raw as Record<string, unknown>) } : {}\n const actorUserId = resolveActorUserId(ctx)\n if (actorUserId) payload.submittedByUserId = actorUserId\n if (!access.canManage) {\n if (!access.memberId) {\n throw new CrudHttpError(400, { error: translate('staff.leaveRequests.errors.profileRequired', 'Create your profile first.') })\n }\n payload.memberId = access.memberId\n }\n return parseScopedCommandInput(staffLeaveRequestCreateSchema, payload, ctx, translate)\n },\n response: ({ result }) => ({ id: result?.requestId ?? null }),\n status: 201,\n },\n update: {\n commandId: 'staff.leave-requests.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const parsed = parseScopedCommandInput(staffLeaveRequestUpdateSchema, raw ?? {}, ctx, translate)\n const access = await resolveLeaveRequestAccess(ctx)\n if (!access.canManage && !access.canSend) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n if (!access.canManage) {\n if (!access.memberId) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n const em = (ctx.container.resolve('em') as EntityManager)\n const request = await findOneWithDecryption(\n em,\n StaffLeaveRequest,\n { id: parsed.id, deletedAt: null },\n undefined,\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n const memberId = request ? (typeof request.member === 'string' ? request.member : request.member.id) : null\n if (!request || memberId !== access.memberId) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n if (parsed.memberId && parsed.memberId !== memberId) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n }\n return parsed\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'staff.leave-requests.delete',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations()\n const id = resolveCrudRecordId(parsed, ctx, translate)\n const access = await resolveLeaveRequestAccess(ctx)\n if (!access.canManage && !access.canSend) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n if (!access.canManage) {\n if (!access.memberId) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n const em = (ctx.container.resolve('em') as EntityManager)\n const request = await findOneWithDecryption(\n em,\n StaffLeaveRequest,\n { id, deletedAt: null },\n undefined,\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n const memberId = request ? (typeof request.member === 'string' ? request.member : request.member.id) : null\n if (!request || memberId !== access.memberId) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n }\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst leaveRequestListItemSchema = z.object({\n id: z.string().uuid().nullable().optional(),\n organization_id: z.string().uuid().nullable().optional(),\n tenant_id: z.string().uuid().nullable().optional(),\n member_id: z.string().uuid().nullable().optional(),\n start_date: z.string().nullable().optional(),\n end_date: z.string().nullable().optional(),\n timezone: z.string().nullable().optional(),\n status: z.enum(['pending', 'approved', 'rejected']).nullable().optional(),\n unavailability_reason_entry_id: z.string().uuid().nullable().optional(),\n unavailability_reason_value: z.string().nullable().optional(),\n note: z.string().nullable().optional(),\n decision_comment: z.string().nullable().optional(),\n submitted_by_user_id: z.string().uuid().nullable().optional(),\n decided_by_user_id: z.string().uuid().nullable().optional(),\n decided_at: z.string().nullable().optional(),\n created_at: z.string().nullable().optional(),\n updated_at: z.string().nullable().optional(),\n member: z\n .object({\n id: z.string().uuid().nullable().optional(),\n displayName: z.string().nullable().optional(),\n userId: z.string().uuid().nullable().optional(),\n })\n .nullable()\n .optional(),\n})\n\nconst leaveRequestListResponseSchema = createPagedListResponseSchema(leaveRequestListItemSchema).extend({\n viewer: z\n .object({\n canManage: z.boolean(),\n canSend: z.boolean(),\n canView: z.boolean(),\n memberId: z.string().uuid().nullable(),\n })\n .optional(),\n})\n\nexport const openApi = createStaffCrudOpenApi({\n resourceName: 'Leave request',\n pluralName: 'Leave requests',\n querySchema: listSchema,\n listResponseSchema: leaveRequestListResponseSchema,\n create: {\n schema: staffLeaveRequestCreateSchema,\n description: 'Creates a leave request for a staff member.',\n },\n update: {\n schema: staffLeaveRequestUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates a leave request by id.',\n },\n del: {\n schema: staffLeaveRequestDecisionSchema.pick({ id: true }),\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes a leave request by id.',\n },\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveCrudRecordId, parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { StaffLeaveRequest, StaffTeamMember } from '../data/entities'\nimport {\n staffLeaveRequestCreateSchema,\n staffLeaveRequestUpdateSchema,\n staffLeaveRequestDecisionSchema,\n} from '../data/validators'\nimport { sanitizeSearchTerm } from './helpers'\nimport { E } from '#generated/entities.ids.generated'\nimport { createStaffCrudOpenApi, createPagedListResponseSchema, defaultOkResponseSchema } from './openapi'\n\nconst MANAGE_FEATURE = 'staff.leave_requests.manage'\nconst SEND_FEATURE = 'staff.leave_requests.send'\nconst MY_VIEW_FEATURE = 'staff.my_leave_requests.view'\nconst MY_SEND_FEATURE = 'staff.my_leave_requests.send'\n\nconst NIL_UUID = '00000000-0000-0000-0000-000000000000'\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true },\n PUT: { requireAuth: true },\n DELETE: { requireAuth: true },\n}\n\nexport const metadata = routeMetadata\n\n// Field constants for StaffLeaveRequest entity\nconst F = {\n id: 'id',\n tenant_id: 'tenant_id',\n organization_id: 'organization_id',\n member_id: 'member_id',\n start_date: 'start_date',\n end_date: 'end_date',\n timezone: 'timezone',\n status: 'status',\n unavailability_reason_entry_id: 'unavailability_reason_entry_id',\n unavailability_reason_value: 'unavailability_reason_value',\n note: 'note',\n decision_comment: 'decision_comment',\n submitted_by_user_id: 'submitted_by_user_id',\n decided_by_user_id: 'decided_by_user_id',\n decided_at: 'decided_at',\n created_at: 'created_at',\n updated_at: 'updated_at',\n deleted_at: 'deleted_at',\n} as const\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n status: z.enum(['pending', 'approved', 'rejected']).optional(),\n memberId: z.string().uuid().optional(),\n ids: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\ntype LeaveRequestAccess = {\n canManage: boolean\n canSend: boolean\n canView: boolean\n memberId: string | null\n}\n\nconst uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/\n\nfunction resolveActorUserId(ctx: { auth?: { sub?: string | null; isApiKey?: boolean } | null }): string | null {\n if (!ctx.auth || ctx.auth.isApiKey) return null\n const sub = ctx.auth.sub ?? null\n if (!sub || !uuidRegex.test(sub)) return null\n return sub\n}\n\nasync function resolveLeaveRequestAccess(ctx: any): Promise<LeaveRequestAccess> {\n const auth = ctx.auth\n if (!auth || !auth.sub || auth.isApiKey) {\n return { canManage: false, canSend: false, canView: false, memberId: null }\n }\n const tenantId = ctx.organizationScope?.tenantId ?? auth.tenantId ?? null\n const organizationId = ctx.selectedOrganizationId ?? ctx.organizationScope?.selectedId ?? auth.orgId ?? null\n const rbac = (ctx.container.resolve('rbacService') as RbacService)\n const [canManage, canSendLegacy, canSendSelf, canViewSelf] = await Promise.all([\n rbac.userHasAllFeatures(auth.sub, [MANAGE_FEATURE], { tenantId, organizationId }),\n rbac.userHasAllFeatures(auth.sub, [SEND_FEATURE], { tenantId, organizationId }),\n rbac.userHasAllFeatures(auth.sub, [MY_SEND_FEATURE], { tenantId, organizationId }),\n rbac.userHasAllFeatures(auth.sub, [MY_VIEW_FEATURE], { tenantId, organizationId }),\n ])\n const canSend = canSendLegacy || canSendSelf\n const canView = canManage || canSend || canViewSelf\n if (!canManage && !canView) {\n return { canManage, canSend, canView, memberId: null }\n }\n const member = await findOneWithDecryption(\n ctx.container.resolve('em') as EntityManager,\n StaffTeamMember,\n { userId: auth.sub, deletedAt: null },\n undefined,\n { tenantId, organizationId },\n )\n return { canManage, canSend, canView, memberId: member?.id ?? null }\n}\n\nexport async function buildLeaveRequestFilters(\n query: z.infer<typeof listSchema>,\n ctx: CrudCtx,\n) {\n const filters: Record<string, unknown> = {}\n if (typeof query.ids === 'string' && query.ids.trim().length > 0) {\n const ids = query.ids\n .split(',')\n .map((value) => value.trim())\n .filter((value) => value.length > 0)\n if (ids.length > 0) {\n filters[F.id] = { $in: ids }\n }\n }\n if (query.status) {\n filters[F.status] = query.status\n }\n if (query.memberId) {\n filters[F.member_id] = query.memberId\n }\n const term = sanitizeSearchTerm(query.search)\n if (term) {\n const like = `%${escapeLikePattern(term)}%`\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const members = await findWithDecryption(\n em,\n StaffTeamMember,\n { displayName: { $ilike: like }, deletedAt: null },\n { fields: ['id'] },\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n const memberIds = members.map((member) => member.id)\n if (!memberIds.length) {\n filters[F.id] = NIL_UUID\n } else {\n filters[F.member_id] = { $in: memberIds }\n }\n }\n\n const access = await resolveLeaveRequestAccess(ctx)\n if (!access.canManage) {\n if (!access.canView || !access.memberId) {\n filters[F.id] = NIL_UUID\n } else {\n filters[F.member_id] = access.memberId\n }\n }\n return filters\n}\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: StaffLeaveRequest,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: { entityType: E.staff.staff_leave_request },\n list: {\n schema: listSchema,\n entityId: E.staff.staff_leave_request,\n fields: [\n F.id,\n F.organization_id,\n F.tenant_id,\n F.member_id,\n F.start_date,\n F.end_date,\n F.timezone,\n F.status,\n F.unavailability_reason_entry_id,\n F.unavailability_reason_value,\n F.note,\n F.decision_comment,\n F.submitted_by_user_id,\n F.decided_by_user_id,\n F.decided_at,\n F.created_at,\n F.updated_at,\n ],\n sortFieldMap: {\n startDate: F.start_date,\n endDate: F.end_date,\n createdAt: F.created_at,\n updatedAt: F.updated_at,\n },\n buildFilters: buildLeaveRequestFilters,\n },\n hooks: {\n afterList: async (payload, ctx) => {\n const items: Array<Record<string, unknown>> = Array.isArray(payload?.items)\n ? (payload.items as Array<Record<string, unknown>>)\n : []\n if (!items.length) {\n payload.viewer = await resolveLeaveRequestAccess(ctx)\n return\n }\n const memberIds = new Set<string>()\n items.forEach((item) => {\n const memberId = typeof item.memberId === 'string'\n ? item.memberId\n : typeof item.member_id === 'string'\n ? item.member_id\n : null\n if (memberId) memberIds.add(memberId)\n })\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const members = memberIds.size\n ? await findWithDecryption(\n em,\n StaffTeamMember,\n { id: { $in: Array.from(memberIds) } },\n undefined,\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n : []\n const memberById = new Map(members.map((member) => [member.id, member]))\n items.forEach((item) => {\n const memberId = typeof item.memberId === 'string'\n ? item.memberId\n : typeof item.member_id === 'string'\n ? item.member_id\n : null\n if (!memberId) return\n const member = memberById.get(memberId)\n if (!member) return\n item.member = {\n id: member.id,\n displayName: member.displayName,\n userId: member.userId ?? null,\n }\n })\n payload.viewer = await resolveLeaveRequestAccess(ctx)\n },\n },\n actions: {\n create: {\n commandId: 'staff.leave-requests.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const access = await resolveLeaveRequestAccess(ctx)\n if (!access.canManage && !access.canSend) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n const payload = raw && typeof raw === 'object' ? { ...(raw as Record<string, unknown>) } : {}\n const actorUserId = resolveActorUserId(ctx)\n if (actorUserId) payload.submittedByUserId = actorUserId\n if (!access.canManage) {\n if (!access.memberId) {\n throw new CrudHttpError(400, { error: translate('staff.leaveRequests.errors.profileRequired', 'Create your profile first.') })\n }\n payload.memberId = access.memberId\n }\n return parseScopedCommandInput(staffLeaveRequestCreateSchema, payload, ctx, translate)\n },\n response: ({ result }) => ({ id: result?.requestId ?? null }),\n status: 201,\n },\n update: {\n commandId: 'staff.leave-requests.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const parsed = parseScopedCommandInput(staffLeaveRequestUpdateSchema, raw ?? {}, ctx, translate)\n const access = await resolveLeaveRequestAccess(ctx)\n if (!access.canManage && !access.canSend) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n if (!access.canManage) {\n if (!access.memberId) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n const em = (ctx.container.resolve('em') as EntityManager)\n const request = await findOneWithDecryption(\n em,\n StaffLeaveRequest,\n { id: parsed.id, deletedAt: null },\n undefined,\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n const memberId = request ? (typeof request.member === 'string' ? request.member : request.member.id) : null\n if (!request || memberId !== access.memberId) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n if (parsed.memberId && parsed.memberId !== memberId) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n }\n return parsed\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'staff.leave-requests.delete',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations()\n const id = resolveCrudRecordId(parsed, ctx, translate)\n const access = await resolveLeaveRequestAccess(ctx)\n if (!access.canManage && !access.canSend) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n if (!access.canManage) {\n if (!access.memberId) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n const em = (ctx.container.resolve('em') as EntityManager)\n const request = await findOneWithDecryption(\n em,\n StaffLeaveRequest,\n { id, deletedAt: null },\n undefined,\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n const memberId = request ? (typeof request.member === 'string' ? request.member : request.member.id) : null\n if (!request || memberId !== access.memberId) {\n throw new CrudHttpError(403, { error: translate('staff.leaveRequests.errors.forbidden', 'Forbidden') })\n }\n }\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst leaveRequestListItemSchema = z.object({\n id: z.string().uuid().nullable().optional(),\n organization_id: z.string().uuid().nullable().optional(),\n tenant_id: z.string().uuid().nullable().optional(),\n member_id: z.string().uuid().nullable().optional(),\n start_date: z.string().nullable().optional(),\n end_date: z.string().nullable().optional(),\n timezone: z.string().nullable().optional(),\n status: z.enum(['pending', 'approved', 'rejected']).nullable().optional(),\n unavailability_reason_entry_id: z.string().uuid().nullable().optional(),\n unavailability_reason_value: z.string().nullable().optional(),\n note: z.string().nullable().optional(),\n decision_comment: z.string().nullable().optional(),\n submitted_by_user_id: z.string().uuid().nullable().optional(),\n decided_by_user_id: z.string().uuid().nullable().optional(),\n decided_at: z.string().nullable().optional(),\n created_at: z.string().nullable().optional(),\n updated_at: z.string().nullable().optional(),\n member: z\n .object({\n id: z.string().uuid().nullable().optional(),\n displayName: z.string().nullable().optional(),\n userId: z.string().uuid().nullable().optional(),\n })\n .nullable()\n .optional(),\n})\n\nconst leaveRequestListResponseSchema = createPagedListResponseSchema(leaveRequestListItemSchema).extend({\n viewer: z\n .object({\n canManage: z.boolean(),\n canSend: z.boolean(),\n canView: z.boolean(),\n memberId: z.string().uuid().nullable(),\n })\n .optional(),\n})\n\nexport const openApi = createStaffCrudOpenApi({\n resourceName: 'Leave request',\n pluralName: 'Leave requests',\n querySchema: listSchema,\n listResponseSchema: leaveRequestListResponseSchema,\n create: {\n schema: staffLeaveRequestCreateSchema,\n description: 'Creates a leave request for a staff member.',\n },\n update: {\n schema: staffLeaveRequestUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates a leave request by id.',\n },\n del: {\n schema: staffLeaveRequestDecisionSchema.pick({ id: true }),\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes a leave request by id.',\n },\n})\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.7-develop.6694.1.a06be5605e",
3
+ "version": "0.6.7-develop.6696.1.4236709f7e",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -254,16 +254,16 @@
254
254
  "zod": "^4.4.3"
255
255
  },
256
256
  "peerDependencies": {
257
- "@open-mercato/ai-assistant": "0.6.7-develop.6694.1.a06be5605e",
258
- "@open-mercato/shared": "0.6.7-develop.6694.1.a06be5605e",
259
- "@open-mercato/ui": "0.6.7-develop.6694.1.a06be5605e",
257
+ "@open-mercato/ai-assistant": "0.6.7-develop.6696.1.4236709f7e",
258
+ "@open-mercato/shared": "0.6.7-develop.6696.1.4236709f7e",
259
+ "@open-mercato/ui": "0.6.7-develop.6696.1.4236709f7e",
260
260
  "react": "^19.0.0",
261
261
  "react-dom": "^19.0.0"
262
262
  },
263
263
  "devDependencies": {
264
- "@open-mercato/ai-assistant": "0.6.7-develop.6694.1.a06be5605e",
265
- "@open-mercato/shared": "0.6.7-develop.6694.1.a06be5605e",
266
- "@open-mercato/ui": "0.6.7-develop.6694.1.a06be5605e",
264
+ "@open-mercato/ai-assistant": "0.6.7-develop.6696.1.4236709f7e",
265
+ "@open-mercato/shared": "0.6.7-develop.6696.1.4236709f7e",
266
+ "@open-mercato/ui": "0.6.7-develop.6696.1.4236709f7e",
267
267
  "@testing-library/dom": "^10.4.1",
268
268
  "@testing-library/jest-dom": "^6.9.1",
269
269
  "@testing-library/react": "^16.3.1",
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod'
2
2
  import type { EntityManager } from '@mikro-orm/postgresql'
3
3
  import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
4
- import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
4
+ import { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'
5
5
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
6
6
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
7
7
  import { resolveCrudRecordId, parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'
@@ -22,6 +22,8 @@ const SEND_FEATURE = 'staff.leave_requests.send'
22
22
  const MY_VIEW_FEATURE = 'staff.my_leave_requests.view'
23
23
  const MY_SEND_FEATURE = 'staff.my_leave_requests.send'
24
24
 
25
+ const NIL_UUID = '00000000-0000-0000-0000-000000000000'
26
+
25
27
  const routeMetadata = {
26
28
  GET: { requireAuth: true },
27
29
  POST: { requireAuth: true },
@@ -113,6 +115,56 @@ async function resolveLeaveRequestAccess(ctx: any): Promise<LeaveRequestAccess>
113
115
  return { canManage, canSend, canView, memberId: member?.id ?? null }
114
116
  }
115
117
 
118
+ export async function buildLeaveRequestFilters(
119
+ query: z.infer<typeof listSchema>,
120
+ ctx: CrudCtx,
121
+ ) {
122
+ const filters: Record<string, unknown> = {}
123
+ if (typeof query.ids === 'string' && query.ids.trim().length > 0) {
124
+ const ids = query.ids
125
+ .split(',')
126
+ .map((value) => value.trim())
127
+ .filter((value) => value.length > 0)
128
+ if (ids.length > 0) {
129
+ filters[F.id] = { $in: ids }
130
+ }
131
+ }
132
+ if (query.status) {
133
+ filters[F.status] = query.status
134
+ }
135
+ if (query.memberId) {
136
+ filters[F.member_id] = query.memberId
137
+ }
138
+ const term = sanitizeSearchTerm(query.search)
139
+ if (term) {
140
+ const like = `%${escapeLikePattern(term)}%`
141
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
142
+ const members = await findWithDecryption(
143
+ em,
144
+ StaffTeamMember,
145
+ { displayName: { $ilike: like }, deletedAt: null },
146
+ { fields: ['id'] },
147
+ { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },
148
+ )
149
+ const memberIds = members.map((member) => member.id)
150
+ if (!memberIds.length) {
151
+ filters[F.id] = NIL_UUID
152
+ } else {
153
+ filters[F.member_id] = { $in: memberIds }
154
+ }
155
+ }
156
+
157
+ const access = await resolveLeaveRequestAccess(ctx)
158
+ if (!access.canManage) {
159
+ if (!access.canView || !access.memberId) {
160
+ filters[F.id] = NIL_UUID
161
+ } else {
162
+ filters[F.member_id] = access.memberId
163
+ }
164
+ }
165
+ return filters
166
+ }
167
+
116
168
  const crud = makeCrudRoute({
117
169
  metadata: routeMetadata,
118
170
  orm: {
@@ -151,52 +203,7 @@ const crud = makeCrudRoute({
151
203
  createdAt: F.created_at,
152
204
  updatedAt: F.updated_at,
153
205
  },
154
- buildFilters: async (query, ctx) => {
155
- const filters: Record<string, unknown> = {}
156
- if (typeof query.ids === 'string' && query.ids.trim().length > 0) {
157
- const ids = query.ids
158
- .split(',')
159
- .map((value) => value.trim())
160
- .filter((value) => value.length > 0)
161
- if (ids.length > 0) {
162
- filters[F.id] = { $in: ids }
163
- }
164
- }
165
- if (query.status) {
166
- filters[F.status] = query.status
167
- }
168
- if (query.memberId) {
169
- filters[F.member_id] = query.memberId
170
- }
171
- const term = sanitizeSearchTerm(query.search)
172
- if (term) {
173
- const like = `%${escapeLikePattern(term)}%`
174
- const em = (ctx.container.resolve('em') as EntityManager).fork()
175
- const members = await findWithDecryption(
176
- em,
177
- StaffTeamMember,
178
- { displayName: { $ilike: like }, deletedAt: null },
179
- { fields: ['id'] },
180
- { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },
181
- )
182
- const memberIds = members.map((member) => member.id)
183
- if (!memberIds.length) {
184
- filters[F.id] = '__no_match__'
185
- } else {
186
- filters[F.member_id] = { $in: memberIds }
187
- }
188
- }
189
-
190
- const access = await resolveLeaveRequestAccess(ctx)
191
- if (!access.canManage) {
192
- if (!access.canView || !access.memberId) {
193
- filters[F.id] = '__no_access__'
194
- } else {
195
- filters[F.member_id] = access.memberId
196
- }
197
- }
198
- return filters
199
- },
206
+ buildFilters: buildLeaveRequestFilters,
200
207
  },
201
208
  hooks: {
202
209
  afterList: async (payload, ctx) => {