@azlib/cms 0.4.0 → 0.5.0

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.
package/dist/index.mjs CHANGED
@@ -2856,7 +2856,7 @@ var EcommerceService = class {
2856
2856
  };
2857
2857
  //#endregion
2858
2858
  //#region src/plugins/ecommerce/routes.ts
2859
- function json(data, status = 200) {
2859
+ function json$1(data, status = 200) {
2860
2860
  return new Response(JSON.stringify(data), {
2861
2861
  status,
2862
2862
  headers: {
@@ -2865,11 +2865,11 @@ function json(data, status = 200) {
2865
2865
  }
2866
2866
  });
2867
2867
  }
2868
- function badRequest(message) {
2869
- return json({ error: message }, 400);
2868
+ function badRequest$1(message) {
2869
+ return json$1({ error: message }, 400);
2870
2870
  }
2871
- function notFound(message) {
2872
- return json({ error: message }, 404);
2871
+ function notFound$1(message) {
2872
+ return json$1({ error: message }, 404);
2873
2873
  }
2874
2874
  function registerEcommerceRoutes(ctx, service, options = {}) {
2875
2875
  const prefix = (options.apiPrefix ?? "/api/ecommerce").replace(/\/+$/, "");
@@ -2892,7 +2892,7 @@ function registerEcommerceRoutes(ctx, service, options = {}) {
2892
2892
  const offsetStr = url.searchParams.get("offset");
2893
2893
  const limit = limitStr ? parseInt(limitStr, 10) : 20;
2894
2894
  const offset = offsetStr ? parseInt(offsetStr, 10) : 0;
2895
- return json(await service.listProducts({
2895
+ return json$1(await service.listProducts({
2896
2896
  categorySlug,
2897
2897
  categoryId,
2898
2898
  tagSlug,
@@ -2908,7 +2908,7 @@ function registerEcommerceRoutes(ctx, service, options = {}) {
2908
2908
  offset
2909
2909
  }));
2910
2910
  } catch (err) {
2911
- return badRequest(err instanceof Error ? err.message : String(err));
2911
+ return badRequest$1(err instanceof Error ? err.message : String(err));
2912
2912
  }
2913
2913
  });
2914
2914
  ctx.registerRoute("GET", `${prefix}/products/:id`, async (_req, { params, url }) => {
@@ -2920,32 +2920,32 @@ function registerEcommerceRoutes(ctx, service, options = {}) {
2920
2920
  product = await service.getProduct(id);
2921
2921
  if (!product) product = await service.getProductBySlug(id);
2922
2922
  }
2923
- if (!product) return notFound(`Product '${id}' not found`);
2924
- return json(product);
2923
+ if (!product) return notFound$1(`Product '${id}' not found`);
2924
+ return json$1(product);
2925
2925
  });
2926
2926
  ctx.registerRoute("POST", `${prefix}/products`, async (req) => {
2927
2927
  try {
2928
2928
  const body = await req.json();
2929
- if (!body.title) return badRequest("Product 'title' is required.");
2930
- if (body.price === void 0 || body.price < 0) return badRequest("Valid product 'price' is required.");
2931
- return json(await service.createProduct(body), 201);
2929
+ if (!body.title) return badRequest$1("Product 'title' is required.");
2930
+ if (body.price === void 0 || body.price < 0) return badRequest$1("Valid product 'price' is required.");
2931
+ return json$1(await service.createProduct(body), 201);
2932
2932
  } catch (err) {
2933
- return badRequest(err instanceof Error ? err.message : String(err));
2933
+ return badRequest$1(err instanceof Error ? err.message : String(err));
2934
2934
  }
2935
2935
  });
2936
2936
  ctx.registerRoute("PUT", `${prefix}/products/:id`, async (req, { params }) => {
2937
2937
  try {
2938
2938
  const body = await req.json();
2939
2939
  const updated = await service.updateProduct(params.id, body);
2940
- if (!updated) return notFound(`Product '${params.id}' not found.`);
2941
- return json(updated);
2940
+ if (!updated) return notFound$1(`Product '${params.id}' not found.`);
2941
+ return json$1(updated);
2942
2942
  } catch (err) {
2943
- return badRequest(err instanceof Error ? err.message : String(err));
2943
+ return badRequest$1(err instanceof Error ? err.message : String(err));
2944
2944
  }
2945
2945
  });
2946
2946
  ctx.registerRoute("DELETE", `${prefix}/products/:id`, async (_req, { params }) => {
2947
- if (!await service.deleteProduct(params.id)) return notFound(`Product '${params.id}' not found.`);
2948
- return json({
2947
+ if (!await service.deleteProduct(params.id)) return notFound$1(`Product '${params.id}' not found.`);
2948
+ return json$1({
2949
2949
  success: true,
2950
2950
  id: params.id
2951
2951
  });
@@ -2953,8 +2953,8 @@ function registerEcommerceRoutes(ctx, service, options = {}) {
2953
2953
  ctx.registerRoute("POST", `${prefix}/products/:id/images`, async (req, { params }) => {
2954
2954
  try {
2955
2955
  const body = await req.json();
2956
- if (!body.filename || !body.mimeType) return badRequest("'filename' and 'mimeType' are required.");
2957
- return json(await service.uploadProductImage(params.id, {
2956
+ if (!body.filename || !body.mimeType) return badRequest$1("'filename' and 'mimeType' are required.");
2957
+ return json$1(await service.uploadProductImage(params.id, {
2958
2958
  filename: body.filename,
2959
2959
  mimeType: body.mimeType,
2960
2960
  sizeBytes: body.sizeBytes ?? 0,
@@ -2966,68 +2966,68 @@ function registerEcommerceRoutes(ctx, service, options = {}) {
2966
2966
  isFeatured: body.isFeatured
2967
2967
  }), 201);
2968
2968
  } catch (err) {
2969
- return badRequest(err instanceof Error ? err.message : String(err));
2969
+ return badRequest$1(err instanceof Error ? err.message : String(err));
2970
2970
  }
2971
2971
  });
2972
2972
  ctx.registerRoute("GET", `${prefix}/categories`, async (_req, { url }) => {
2973
2973
  try {
2974
- if (url.searchParams.get("tree") === "true") return json(await service.getCategoryTree());
2974
+ if (url.searchParams.get("tree") === "true") return json$1(await service.getCategoryTree());
2975
2975
  const parentId = url.searchParams.get("parentId");
2976
- return json(await service.getCategories({ parentId: parentId === "null" ? null : parentId ?? void 0 }));
2976
+ return json$1(await service.getCategories({ parentId: parentId === "null" ? null : parentId ?? void 0 }));
2977
2977
  } catch (err) {
2978
- return badRequest(err instanceof Error ? err.message : String(err));
2978
+ return badRequest$1(err instanceof Error ? err.message : String(err));
2979
2979
  }
2980
2980
  });
2981
2981
  ctx.registerRoute("POST", `${prefix}/categories`, async (req) => {
2982
2982
  try {
2983
2983
  const body = await req.json();
2984
- if (!body.name) return badRequest("Category 'name' is required.");
2985
- return json(await service.createCategory(body), 201);
2984
+ if (!body.name) return badRequest$1("Category 'name' is required.");
2985
+ return json$1(await service.createCategory(body), 201);
2986
2986
  } catch (err) {
2987
- return badRequest(err instanceof Error ? err.message : String(err));
2987
+ return badRequest$1(err instanceof Error ? err.message : String(err));
2988
2988
  }
2989
2989
  });
2990
2990
  if (options.enableDiscounts !== false) {
2991
2991
  ctx.registerRoute("POST", `${prefix}/discounts`, async (req) => {
2992
2992
  try {
2993
2993
  const body = await req.json();
2994
- if (!body.title || !body.code) return badRequest("'title' and 'code' are required.");
2995
- if (body.value === void 0 || body.value < 0) return badRequest("Valid discount 'value' is required.");
2996
- return json(await service.createDiscount(body), 201);
2994
+ if (!body.title || !body.code) return badRequest$1("'title' and 'code' are required.");
2995
+ if (body.value === void 0 || body.value < 0) return badRequest$1("Valid discount 'value' is required.");
2996
+ return json$1(await service.createDiscount(body), 201);
2997
2997
  } catch (err) {
2998
- return badRequest(err instanceof Error ? err.message : String(err));
2998
+ return badRequest$1(err instanceof Error ? err.message : String(err));
2999
2999
  }
3000
3000
  });
3001
3001
  ctx.registerRoute("POST", `${prefix}/discounts/validate`, async (req) => {
3002
3002
  try {
3003
3003
  const body = await req.json();
3004
- if (!body.code) return badRequest("Discount 'code' is required.");
3004
+ if (!body.code) return badRequest$1("Discount 'code' is required.");
3005
3005
  const subtotal = Number(body.subtotal ?? 0);
3006
3006
  const productIds = Array.isArray(body.productIds) ? body.productIds : [];
3007
- return json(await service.validateDiscount(body.code, subtotal, productIds));
3007
+ return json$1(await service.validateDiscount(body.code, subtotal, productIds));
3008
3008
  } catch (err) {
3009
- return badRequest(err instanceof Error ? err.message : String(err));
3009
+ return badRequest$1(err instanceof Error ? err.message : String(err));
3010
3010
  }
3011
3011
  });
3012
3012
  }
3013
3013
  ctx.registerRoute("POST", `${prefix}/cart/calculate`, async (req) => {
3014
3014
  try {
3015
3015
  const body = await req.json();
3016
- if (!body.items || !Array.isArray(body.items) || body.items.length === 0) return badRequest("'items' array is required and must not be empty.");
3017
- return json(await service.calculateCart(body));
3016
+ if (!body.items || !Array.isArray(body.items) || body.items.length === 0) return badRequest$1("'items' array is required and must not be empty.");
3017
+ return json$1(await service.calculateCart(body));
3018
3018
  } catch (err) {
3019
- return badRequest(err instanceof Error ? err.message : String(err));
3019
+ return badRequest$1(err instanceof Error ? err.message : String(err));
3020
3020
  }
3021
3021
  });
3022
3022
  if (options.enableOrders !== false) {
3023
3023
  ctx.registerRoute("POST", `${prefix}/orders`, async (req) => {
3024
3024
  try {
3025
3025
  const body = await req.json();
3026
- if (!body.customerEmail) return badRequest("'customerEmail' is required.");
3027
- if (!body.items || !Array.isArray(body.items) || body.items.length === 0) return badRequest("'items' array is required.");
3028
- return json(await service.createOrder(body), 201);
3026
+ if (!body.customerEmail) return badRequest$1("'customerEmail' is required.");
3027
+ if (!body.items || !Array.isArray(body.items) || body.items.length === 0) return badRequest$1("'items' array is required.");
3028
+ return json$1(await service.createOrder(body), 201);
3029
3029
  } catch (err) {
3030
- return badRequest(err instanceof Error ? err.message : String(err));
3030
+ return badRequest$1(err instanceof Error ? err.message : String(err));
3031
3031
  }
3032
3032
  });
3033
3033
  ctx.registerRoute("GET", `${prefix}/orders/:id`, async (_req, { params, url }) => {
@@ -3039,18 +3039,18 @@ function registerEcommerceRoutes(ctx, service, options = {}) {
3039
3039
  order = await service.getOrder(id);
3040
3040
  if (!order) order = await service.getOrderByNumber(id);
3041
3041
  }
3042
- if (!order) return notFound(`Order '${id}' not found.`);
3043
- return json(order);
3042
+ if (!order) return notFound$1(`Order '${id}' not found.`);
3043
+ return json$1(order);
3044
3044
  });
3045
3045
  ctx.registerRoute("PATCH", `${prefix}/orders/:id/status`, async (req, { params }) => {
3046
3046
  try {
3047
3047
  const body = await req.json();
3048
- if (!body.status) return badRequest("New 'status' is required.");
3048
+ if (!body.status) return badRequest$1("New 'status' is required.");
3049
3049
  const updated = await service.updateOrderStatus(params.id, body.status, body.note);
3050
- if (!updated) return notFound(`Order '${params.id}' not found.`);
3051
- return json(updated);
3050
+ if (!updated) return notFound$1(`Order '${params.id}' not found.`);
3051
+ return json$1(updated);
3052
3052
  } catch (err) {
3053
- return badRequest(err instanceof Error ? err.message : String(err));
3053
+ return badRequest$1(err instanceof Error ? err.message : String(err));
3054
3054
  }
3055
3055
  });
3056
3056
  }
@@ -3234,6 +3234,1597 @@ function getEcommerceService(engine, options) {
3234
3234
  return service;
3235
3235
  }
3236
3236
  //#endregion
3237
- export { CMSClient, CMSEngine, CMSRouter, ContentLifecycle, DEFAULT_COLLECTIONS, DEFAULT_ROLE_CAPABILITIES, EcommerceClient, EcommerceService, HooksManager, MediaManager, MemoryStorageAdapter, OptionsManager, RBACManager, RevisionManager, TaxonomyManager, VALID_STATUS_TRANSITIONS, collection, createCMSEngine, createCMSRouter, createCmsClient, createDiscountCollection, createEcommerceTaxonomies, createOrderCollection, createProductCollection, defaultHooks, defineConfig, definePlugin, ecommercePlugin, fields, getEcommerceClient, getEcommerceService, normalizeConfig, resolveUniqueSlug, slugify, validateAndNormalizeData };
3237
+ //#region src/plugins/hrms/schemas.ts
3238
+ /**
3239
+ * Creates the collection configuration for Employers (Organizations / Companies).
3240
+ */
3241
+ function createEmployerCollection(options = {}) {
3242
+ return collection({
3243
+ slug: options.employerCollectionSlug ?? "employers",
3244
+ label: "Employers",
3245
+ singularLabel: "Employer",
3246
+ description: "Companies and organizational entities managing employees, shifts, and leave policies",
3247
+ timestamps: true,
3248
+ revisions: true,
3249
+ draftable: true,
3250
+ defaultSort: {
3251
+ field: "createdAt",
3252
+ direction: "desc"
3253
+ },
3254
+ fields: [
3255
+ fields.text({
3256
+ name: "companyName",
3257
+ label: "Company Name",
3258
+ required: true
3259
+ }),
3260
+ fields.slug({
3261
+ from: "companyName",
3262
+ unique: true
3263
+ }),
3264
+ fields.text({
3265
+ name: "legalName",
3266
+ label: "Legal Entity Name"
3267
+ }),
3268
+ fields.text({
3269
+ name: "taxId",
3270
+ label: "Tax / EIN Number"
3271
+ }),
3272
+ fields.text({
3273
+ name: "email",
3274
+ label: "Primary Email"
3275
+ }),
3276
+ fields.text({
3277
+ name: "phone",
3278
+ label: "Phone Number"
3279
+ }),
3280
+ fields.text({
3281
+ name: "website",
3282
+ label: "Website URL"
3283
+ }),
3284
+ fields.image({
3285
+ name: "logo",
3286
+ label: "Company Logo"
3287
+ }),
3288
+ fields.json({
3289
+ name: "address",
3290
+ label: "Company Address"
3291
+ }),
3292
+ fields.text({
3293
+ name: "timezone",
3294
+ label: "Primary Timezone",
3295
+ defaultValue: "UTC"
3296
+ }),
3297
+ fields.json({
3298
+ name: "workSchedule",
3299
+ label: "Standard Work Schedule",
3300
+ defaultValue: {
3301
+ startTime: options.workScheduleStart ?? "09:00",
3302
+ endTime: options.workScheduleEnd ?? "17:00",
3303
+ standardHoursPerDay: options.standardWorkDayHours ?? 8,
3304
+ gracePeriodMinutes: options.gracePeriodMinutes ?? 15,
3305
+ workDays: [
3306
+ 1,
3307
+ 2,
3308
+ 3,
3309
+ 4,
3310
+ 5
3311
+ ]
3312
+ }
3313
+ }),
3314
+ fields.select({
3315
+ name: "status",
3316
+ label: "Status",
3317
+ options: ["active", "inactive"],
3318
+ defaultValue: "active"
3319
+ })
3320
+ ]
3321
+ });
3322
+ }
3323
+ /**
3324
+ * Creates the collection configuration for Employees.
3325
+ */
3326
+ function createEmployeeCollection(options = {}) {
3327
+ const slug = options.employeeCollectionSlug ?? "employees";
3328
+ const employerSlug = options.employerCollectionSlug ?? "employers";
3329
+ return collection({
3330
+ slug,
3331
+ label: "Employees",
3332
+ singularLabel: "Employee",
3333
+ description: "Employee profiles, job assignments, emergency contacts, and attached records",
3334
+ timestamps: true,
3335
+ revisions: true,
3336
+ draftable: true,
3337
+ taxonomies: [options.departmentsTaxonomySlug ?? "hrms_departments", options.designationsTaxonomySlug ?? "hrms_designations"],
3338
+ defaultSort: {
3339
+ field: "createdAt",
3340
+ direction: "desc"
3341
+ },
3342
+ fields: [
3343
+ fields.relationship({
3344
+ name: "employerId",
3345
+ label: "Employer",
3346
+ targetCollection: employerSlug,
3347
+ required: true
3348
+ }),
3349
+ fields.text({
3350
+ name: "userId",
3351
+ label: "Associated User ID",
3352
+ description: "Optional reference to an authenticated user account"
3353
+ }),
3354
+ fields.text({
3355
+ name: "employeeNumber",
3356
+ label: "Employee Number",
3357
+ required: true,
3358
+ unique: true
3359
+ }),
3360
+ fields.text({
3361
+ name: "firstName",
3362
+ label: "First Name",
3363
+ required: true
3364
+ }),
3365
+ fields.text({
3366
+ name: "lastName",
3367
+ label: "Last Name",
3368
+ required: true
3369
+ }),
3370
+ fields.text({
3371
+ name: "email",
3372
+ label: "Work Email",
3373
+ required: true
3374
+ }),
3375
+ fields.text({
3376
+ name: "phone",
3377
+ label: "Contact Phone"
3378
+ }),
3379
+ fields.image({
3380
+ name: "avatar",
3381
+ label: "Profile Picture"
3382
+ }),
3383
+ fields.text({
3384
+ name: "jobTitle",
3385
+ label: "Job Title"
3386
+ }),
3387
+ fields.select({
3388
+ name: "employmentType",
3389
+ label: "Employment Type",
3390
+ options: [
3391
+ "full_time",
3392
+ "part_time",
3393
+ "contractor",
3394
+ "intern"
3395
+ ],
3396
+ defaultValue: "full_time"
3397
+ }),
3398
+ fields.select({
3399
+ name: "status",
3400
+ label: "Status",
3401
+ options: [
3402
+ "active",
3403
+ "on_leave",
3404
+ "terminated",
3405
+ "suspended"
3406
+ ],
3407
+ defaultValue: "active"
3408
+ }),
3409
+ fields.date({
3410
+ name: "hireDate",
3411
+ label: "Hire Date",
3412
+ required: true
3413
+ }),
3414
+ fields.date({
3415
+ name: "terminationDate",
3416
+ label: "Termination Date"
3417
+ }),
3418
+ fields.relationship({
3419
+ name: "managerId",
3420
+ label: "Reporting Manager",
3421
+ targetCollection: slug
3422
+ }),
3423
+ fields.json({
3424
+ name: "emergencyContact",
3425
+ label: "Emergency Contact"
3426
+ }),
3427
+ fields.repeater({
3428
+ name: "documents",
3429
+ label: "Attached Documents & Contracts",
3430
+ fields: [
3431
+ fields.text({
3432
+ name: "title",
3433
+ label: "Document Title",
3434
+ required: true
3435
+ }),
3436
+ fields.text({
3437
+ name: "fileUrl",
3438
+ label: "File URL",
3439
+ required: true
3440
+ }),
3441
+ fields.text({
3442
+ name: "category",
3443
+ label: "Category"
3444
+ }),
3445
+ fields.text({
3446
+ name: "uploadedAt",
3447
+ label: "Uploaded Date"
3448
+ })
3449
+ ]
3450
+ }),
3451
+ fields.number({
3452
+ name: "salary",
3453
+ label: "Base Salary"
3454
+ }),
3455
+ fields.text({
3456
+ name: "notes",
3457
+ label: "Internal Notes"
3458
+ })
3459
+ ]
3460
+ });
3461
+ }
3462
+ /**
3463
+ * Creates the collection configuration for Daily Attendance.
3464
+ */
3465
+ function createAttendanceCollection(options = {}) {
3466
+ const slug = options.attendanceCollectionSlug ?? "attendance";
3467
+ const employerSlug = options.employerCollectionSlug ?? "employers";
3468
+ const employeeSlug = options.employeeCollectionSlug ?? "employees";
3469
+ return collection({
3470
+ slug,
3471
+ label: "Attendance",
3472
+ singularLabel: "Attendance Record",
3473
+ description: "Daily check-in and check-out logs, hours worked, overtime, and punctuality status",
3474
+ timestamps: true,
3475
+ revisions: false,
3476
+ draftable: false,
3477
+ defaultSort: {
3478
+ field: "date",
3479
+ direction: "desc"
3480
+ },
3481
+ fields: [
3482
+ fields.relationship({
3483
+ name: "employerId",
3484
+ label: "Employer",
3485
+ targetCollection: employerSlug,
3486
+ required: true
3487
+ }),
3488
+ fields.relationship({
3489
+ name: "employeeId",
3490
+ label: "Employee",
3491
+ targetCollection: employeeSlug,
3492
+ required: true
3493
+ }),
3494
+ fields.date({
3495
+ name: "date",
3496
+ label: "Date",
3497
+ required: true
3498
+ }),
3499
+ fields.text({
3500
+ name: "checkInAt",
3501
+ label: "Check-In Timestamp",
3502
+ required: true
3503
+ }),
3504
+ fields.text({
3505
+ name: "checkOutAt",
3506
+ label: "Check-Out Timestamp"
3507
+ }),
3508
+ fields.number({
3509
+ name: "totalHours",
3510
+ label: "Total Hours",
3511
+ defaultValue: 0
3512
+ }),
3513
+ fields.number({
3514
+ name: "overtimeHours",
3515
+ label: "Overtime Hours",
3516
+ defaultValue: 0
3517
+ }),
3518
+ fields.select({
3519
+ name: "status",
3520
+ label: "Status",
3521
+ options: [
3522
+ "present",
3523
+ "late",
3524
+ "half_day",
3525
+ "absent",
3526
+ "on_leave"
3527
+ ],
3528
+ defaultValue: "present"
3529
+ }),
3530
+ fields.text({
3531
+ name: "location",
3532
+ label: "Location / IP / Geofence"
3533
+ }),
3534
+ fields.text({
3535
+ name: "notes",
3536
+ label: "Notes"
3537
+ })
3538
+ ]
3539
+ });
3540
+ }
3541
+ /**
3542
+ * Creates the collection configuration for Leave Types.
3543
+ */
3544
+ function createLeaveTypeCollection(options = {}) {
3545
+ const slug = options.leaveTypeCollectionSlug ?? "leave_types";
3546
+ const employerSlug = options.employerCollectionSlug ?? "employers";
3547
+ return collection({
3548
+ slug,
3549
+ label: "Leave Types",
3550
+ singularLabel: "Leave Type",
3551
+ description: "Available leave categories (Annual, Sick, Unpaid) and yearly quotas",
3552
+ timestamps: true,
3553
+ revisions: true,
3554
+ draftable: false,
3555
+ fields: [
3556
+ fields.relationship({
3557
+ name: "employerId",
3558
+ label: "Employer",
3559
+ targetCollection: employerSlug
3560
+ }),
3561
+ fields.text({
3562
+ name: "name",
3563
+ label: "Leave Name",
3564
+ required: true
3565
+ }),
3566
+ fields.text({
3567
+ name: "code",
3568
+ label: "Leave Code",
3569
+ required: true
3570
+ }),
3571
+ fields.number({
3572
+ name: "daysAllowedPerYear",
3573
+ label: "Days Allowed Per Year",
3574
+ required: true,
3575
+ defaultValue: 15,
3576
+ min: 0
3577
+ }),
3578
+ fields.boolean({
3579
+ name: "paid",
3580
+ label: "Paid Leave",
3581
+ defaultValue: true
3582
+ }),
3583
+ fields.boolean({
3584
+ name: "requiresApproval",
3585
+ label: "Requires Manager Approval",
3586
+ defaultValue: true
3587
+ }),
3588
+ fields.text({
3589
+ name: "color",
3590
+ label: "Display Color Code"
3591
+ }),
3592
+ fields.text({
3593
+ name: "description",
3594
+ label: "Description / Policy"
3595
+ })
3596
+ ]
3597
+ });
3598
+ }
3599
+ /**
3600
+ * Creates the collection configuration for Leave Requests.
3601
+ */
3602
+ function createLeaveRequestCollection(options = {}) {
3603
+ const slug = options.leaveRequestCollectionSlug ?? "leave_requests";
3604
+ const employerSlug = options.employerCollectionSlug ?? "employers";
3605
+ const employeeSlug = options.employeeCollectionSlug ?? "employees";
3606
+ const leaveTypeSlug = options.leaveTypeCollectionSlug ?? "leave_types";
3607
+ return collection({
3608
+ slug,
3609
+ label: "Leave Requests",
3610
+ singularLabel: "Leave Request",
3611
+ description: "Employee time-off requests, approval tracking, and deducted balances",
3612
+ timestamps: true,
3613
+ revisions: true,
3614
+ draftable: false,
3615
+ defaultSort: {
3616
+ field: "createdAt",
3617
+ direction: "desc"
3618
+ },
3619
+ fields: [
3620
+ fields.relationship({
3621
+ name: "employerId",
3622
+ label: "Employer",
3623
+ targetCollection: employerSlug,
3624
+ required: true
3625
+ }),
3626
+ fields.relationship({
3627
+ name: "employeeId",
3628
+ label: "Employee",
3629
+ targetCollection: employeeSlug,
3630
+ required: true
3631
+ }),
3632
+ fields.relationship({
3633
+ name: "leaveTypeId",
3634
+ label: "Leave Type",
3635
+ targetCollection: leaveTypeSlug,
3636
+ required: true
3637
+ }),
3638
+ fields.date({
3639
+ name: "startDate",
3640
+ label: "Start Date",
3641
+ required: true
3642
+ }),
3643
+ fields.date({
3644
+ name: "endDate",
3645
+ label: "End Date",
3646
+ required: true
3647
+ }),
3648
+ fields.number({
3649
+ name: "daysCount",
3650
+ label: "Days Count",
3651
+ required: true,
3652
+ min: .5
3653
+ }),
3654
+ fields.text({
3655
+ name: "reason",
3656
+ label: "Reason"
3657
+ }),
3658
+ fields.select({
3659
+ name: "status",
3660
+ label: "Request Status",
3661
+ options: [
3662
+ "pending",
3663
+ "approved",
3664
+ "rejected",
3665
+ "cancelled"
3666
+ ],
3667
+ defaultValue: "pending"
3668
+ }),
3669
+ fields.relationship({
3670
+ name: "approvedBy",
3671
+ label: "Approved / Rejected By",
3672
+ targetCollection: employeeSlug
3673
+ }),
3674
+ fields.text({
3675
+ name: "approvedAt",
3676
+ label: "Decision Timestamp"
3677
+ }),
3678
+ fields.text({
3679
+ name: "rejectionReason",
3680
+ label: "Rejection Reason"
3681
+ })
3682
+ ]
3683
+ });
3684
+ }
3685
+ /**
3686
+ * Creates the standard HRMS taxonomies: departments and designations.
3687
+ */
3688
+ function createHRMSTaxonomies(options = {}) {
3689
+ const employeeSlug = options.employeeCollectionSlug ?? "employees";
3690
+ const deptSlug = options.departmentsTaxonomySlug ?? "hrms_departments";
3691
+ const desigSlug = options.designationsTaxonomySlug ?? "hrms_designations";
3692
+ return [{
3693
+ slug: deptSlug,
3694
+ label: "Departments",
3695
+ singularLabel: "Department",
3696
+ hierarchical: true,
3697
+ postTypes: [employeeSlug],
3698
+ description: "Hierarchical departmental tree (e.g. Engineering, Sales, HR)"
3699
+ }, {
3700
+ slug: desigSlug,
3701
+ label: "Designations",
3702
+ singularLabel: "Designation",
3703
+ hierarchical: false,
3704
+ postTypes: [employeeSlug],
3705
+ description: "Job titles and designations across the workforce"
3706
+ }];
3707
+ }
3708
+ //#endregion
3709
+ //#region src/plugins/hrms/service.ts
3710
+ var HRMSService = class {
3711
+ engine;
3712
+ options;
3713
+ employerSlug;
3714
+ employeeSlug;
3715
+ attendanceSlug;
3716
+ leaveTypeSlug;
3717
+ leaveRequestSlug;
3718
+ departmentsTaxonomy;
3719
+ designationsTaxonomy;
3720
+ standardWorkDayHours;
3721
+ workScheduleStart;
3722
+ workScheduleEnd;
3723
+ gracePeriodMinutes;
3724
+ constructor(engine, options = {}) {
3725
+ this.engine = engine;
3726
+ this.options = options;
3727
+ this.employerSlug = options.employerCollectionSlug ?? "employers";
3728
+ this.employeeSlug = options.employeeCollectionSlug ?? "employees";
3729
+ this.attendanceSlug = options.attendanceCollectionSlug ?? "attendance";
3730
+ this.leaveTypeSlug = options.leaveTypeCollectionSlug ?? "leave_types";
3731
+ this.leaveRequestSlug = options.leaveRequestCollectionSlug ?? "leave_requests";
3732
+ this.departmentsTaxonomy = options.departmentsTaxonomySlug ?? "hrms_departments";
3733
+ this.designationsTaxonomy = options.designationsTaxonomySlug ?? "hrms_designations";
3734
+ this.standardWorkDayHours = options.standardWorkDayHours ?? 8;
3735
+ this.workScheduleStart = options.workScheduleStart ?? "09:00";
3736
+ this.workScheduleEnd = options.workScheduleEnd ?? "17:00";
3737
+ this.gracePeriodMinutes = options.gracePeriodMinutes ?? 15;
3738
+ }
3739
+ get employersCollection() {
3740
+ return this.engine.collection(this.employerSlug);
3741
+ }
3742
+ get employeesCollection() {
3743
+ return this.engine.collection(this.employeeSlug);
3744
+ }
3745
+ get attendanceCollection() {
3746
+ return this.engine.collection(this.attendanceSlug);
3747
+ }
3748
+ get leaveTypesCollection() {
3749
+ return this.engine.collection(this.leaveTypeSlug);
3750
+ }
3751
+ get leaveRequestsCollection() {
3752
+ return this.engine.collection(this.leaveRequestSlug);
3753
+ }
3754
+ async createEmployer(input, authorId) {
3755
+ const employerData = {
3756
+ companyName: input.companyName,
3757
+ legalName: input.legalName,
3758
+ taxId: input.taxId,
3759
+ email: input.email,
3760
+ phone: input.phone,
3761
+ website: input.website,
3762
+ logo: input.logo,
3763
+ address: input.address,
3764
+ timezone: input.timezone ?? "UTC",
3765
+ workSchedule: input.workSchedule ?? {
3766
+ startTime: this.workScheduleStart,
3767
+ endTime: this.workScheduleEnd,
3768
+ standardHoursPerDay: this.standardWorkDayHours,
3769
+ gracePeriodMinutes: this.gracePeriodMinutes,
3770
+ workDays: [
3771
+ 1,
3772
+ 2,
3773
+ 3,
3774
+ 4,
3775
+ 5
3776
+ ]
3777
+ },
3778
+ status: input.status ?? "active"
3779
+ };
3780
+ const employer = await this.employersCollection.create({
3781
+ title: input.companyName,
3782
+ status: input.status === "inactive" ? "draft" : "published",
3783
+ data: employerData
3784
+ }, authorId);
3785
+ await this.engine.hooks.doAction("hrms.employer_created", employer);
3786
+ return employer;
3787
+ }
3788
+ async getEmployer(id) {
3789
+ return this.employersCollection.findById(id);
3790
+ }
3791
+ async getEmployerBySlug(slug) {
3792
+ return this.employersCollection.findBySlug(slug);
3793
+ }
3794
+ async updateEmployer(id, input, authorId) {
3795
+ const existing = await this.getEmployer(id);
3796
+ if (!existing) return null;
3797
+ const updatedData = {
3798
+ ...existing.data,
3799
+ ...input.companyName !== void 0 ? { companyName: input.companyName } : {},
3800
+ ...input.legalName !== void 0 ? { legalName: input.legalName } : {},
3801
+ ...input.taxId !== void 0 ? { taxId: input.taxId } : {},
3802
+ ...input.email !== void 0 ? { email: input.email } : {},
3803
+ ...input.phone !== void 0 ? { phone: input.phone } : {},
3804
+ ...input.website !== void 0 ? { website: input.website } : {},
3805
+ ...input.logo !== void 0 ? { logo: input.logo } : {},
3806
+ ...input.address !== void 0 ? { address: input.address } : {},
3807
+ ...input.timezone !== void 0 ? { timezone: input.timezone } : {},
3808
+ ...input.workSchedule !== void 0 ? { workSchedule: input.workSchedule } : {},
3809
+ ...input.status !== void 0 ? { status: input.status } : {}
3810
+ };
3811
+ const updated = await this.employersCollection.update(id, {
3812
+ title: input.companyName ?? existing.title,
3813
+ data: updatedData
3814
+ }, authorId);
3815
+ if (updated) await this.engine.hooks.doAction("hrms.employer_updated", updated);
3816
+ return updated;
3817
+ }
3818
+ async listEmployers(query = {}) {
3819
+ const limit = query.limit ?? 20;
3820
+ const offset = query.page ? (query.page - 1) * limit : 0;
3821
+ let items = (await this.employersCollection.find({ limit: 500 })).items;
3822
+ if (query.status) items = items.filter((item) => item.data.status === query.status);
3823
+ const total = items.length;
3824
+ return {
3825
+ items: items.slice(offset, offset + limit),
3826
+ total,
3827
+ limit,
3828
+ offset,
3829
+ hasMore: offset + limit < total
3830
+ };
3831
+ }
3832
+ async createEmployee(input, authorId) {
3833
+ if (!await this.getEmployer(input.employerId)) throw new Error(`[HRMSService] Employer with ID '${input.employerId}' not found.`);
3834
+ if (await this.getEmployeeByNumber(input.employerId, input.employeeNumber)) throw new Error(`[HRMSService] Employee number '${input.employeeNumber}' is already registered for this employer.`);
3835
+ const employeeData = {
3836
+ employerId: input.employerId,
3837
+ userId: input.userId,
3838
+ employeeNumber: input.employeeNumber,
3839
+ firstName: input.firstName,
3840
+ lastName: input.lastName,
3841
+ email: input.email,
3842
+ phone: input.phone,
3843
+ avatar: input.avatar,
3844
+ jobTitle: input.jobTitle,
3845
+ employmentType: input.employmentType ?? "full_time",
3846
+ status: input.status ?? "active",
3847
+ hireDate: input.hireDate,
3848
+ managerId: input.managerId,
3849
+ emergencyContact: input.emergencyContact,
3850
+ documents: input.documents ?? [],
3851
+ salary: input.salary,
3852
+ notes: input.notes
3853
+ };
3854
+ const title = `${input.firstName} ${input.lastName}`;
3855
+ const employee = await this.employeesCollection.create({
3856
+ title,
3857
+ status: input.status === "terminated" ? "draft" : "published",
3858
+ data: employeeData
3859
+ }, authorId);
3860
+ if (input.departmentSlug) {
3861
+ const term = await this.engine.taxonomies.getTermBySlug(this.departmentsTaxonomy, input.departmentSlug);
3862
+ if (term) await this.engine.taxonomies.assignTerms(employee.id, [term.id]);
3863
+ }
3864
+ await this.engine.hooks.doAction("hrms.employee_created", employee);
3865
+ return employee;
3866
+ }
3867
+ async getEmployee(id) {
3868
+ return this.employeesCollection.findById(id);
3869
+ }
3870
+ async getEmployeeByNumber(employerId, employeeNumber) {
3871
+ return (await this.employeesCollection.find({ limit: 500 })).items.find((e) => e.data.employerId === employerId && e.data.employeeNumber === employeeNumber) ?? null;
3872
+ }
3873
+ async updateEmployee(id, input, authorId) {
3874
+ const existing = await this.getEmployee(id);
3875
+ if (!existing) return null;
3876
+ const updatedData = {
3877
+ ...existing.data,
3878
+ ...input.employerId !== void 0 ? { employerId: input.employerId } : {},
3879
+ ...input.userId !== void 0 ? { userId: input.userId } : {},
3880
+ ...input.employeeNumber !== void 0 ? { employeeNumber: input.employeeNumber } : {},
3881
+ ...input.firstName !== void 0 ? { firstName: input.firstName } : {},
3882
+ ...input.lastName !== void 0 ? { lastName: input.lastName } : {},
3883
+ ...input.email !== void 0 ? { email: input.email } : {},
3884
+ ...input.phone !== void 0 ? { phone: input.phone } : {},
3885
+ ...input.avatar !== void 0 ? { avatar: input.avatar } : {},
3886
+ ...input.jobTitle !== void 0 ? { jobTitle: input.jobTitle } : {},
3887
+ ...input.employmentType !== void 0 ? { employmentType: input.employmentType } : {},
3888
+ ...input.status !== void 0 ? { status: input.status } : {},
3889
+ ...input.hireDate !== void 0 ? { hireDate: input.hireDate } : {},
3890
+ ...input.terminationDate !== void 0 ? { terminationDate: input.terminationDate } : {},
3891
+ ...input.managerId !== void 0 ? { managerId: input.managerId } : {},
3892
+ ...input.emergencyContact !== void 0 ? { emergencyContact: input.emergencyContact } : {},
3893
+ ...input.documents !== void 0 ? { documents: input.documents } : {},
3894
+ ...input.salary !== void 0 ? { salary: input.salary } : {},
3895
+ ...input.notes !== void 0 ? { notes: input.notes } : {}
3896
+ };
3897
+ const title = input.firstName || input.lastName ? `${updatedData.firstName} ${updatedData.lastName}` : existing.title;
3898
+ const updated = await this.employeesCollection.update(id, {
3899
+ title,
3900
+ data: updatedData
3901
+ }, authorId);
3902
+ if (updated) {
3903
+ if (input.departmentSlug) {
3904
+ const term = await this.engine.taxonomies.getTermBySlug(this.departmentsTaxonomy, input.departmentSlug);
3905
+ if (term) await this.engine.taxonomies.assignTerms(id, [term.id]);
3906
+ }
3907
+ await this.engine.hooks.doAction("hrms.employee_updated", updated);
3908
+ }
3909
+ return updated;
3910
+ }
3911
+ async deleteEmployee(id) {
3912
+ const deleted = await this.employeesCollection.delete(id);
3913
+ if (deleted) await this.engine.hooks.doAction("hrms.employee_deleted", id);
3914
+ return deleted;
3915
+ }
3916
+ async listEmployees(query = {}) {
3917
+ const limit = query.limit ?? 20;
3918
+ const offset = query.page ? (query.page - 1) * limit : 0;
3919
+ let termIds;
3920
+ if (query.department) {
3921
+ const term = await this.engine.taxonomies.getTermBySlug(this.departmentsTaxonomy, query.department);
3922
+ if (term) termIds = [term.id];
3923
+ else return {
3924
+ items: [],
3925
+ total: 0,
3926
+ limit,
3927
+ offset,
3928
+ hasMore: false
3929
+ };
3930
+ }
3931
+ let items = (await this.employeesCollection.find({
3932
+ termIds,
3933
+ limit: 1e3
3934
+ })).items;
3935
+ if (query.employerId) items = items.filter((e) => e.data.employerId === query.employerId);
3936
+ if (query.employmentType) items = items.filter((e) => e.data.employmentType === query.employmentType);
3937
+ if (query.status) items = items.filter((e) => e.data.status === query.status);
3938
+ if (query.search) {
3939
+ const s = query.search.toLowerCase();
3940
+ items = items.filter((e) => e.data.firstName.toLowerCase().includes(s) || e.data.lastName.toLowerCase().includes(s) || e.data.email.toLowerCase().includes(s) || e.data.employeeNumber.toLowerCase().includes(s) || e.data.jobTitle && e.data.jobTitle.toLowerCase().includes(s));
3941
+ }
3942
+ const total = items.length;
3943
+ return {
3944
+ items: items.slice(offset, offset + limit),
3945
+ total,
3946
+ limit,
3947
+ offset,
3948
+ hasMore: offset + limit < total
3949
+ };
3950
+ }
3951
+ async getDirectReports(managerId) {
3952
+ return (await this.employeesCollection.find({ limit: 1e3 })).items.filter((e) => e.data.managerId === managerId);
3953
+ }
3954
+ parseTimeToMinutes(timeStr) {
3955
+ const [hours, minutes] = timeStr.split(":").map((v) => parseInt(v, 10));
3956
+ return (hours || 0) * 60 + (minutes || 0);
3957
+ }
3958
+ formatDateString(date, timezone) {
3959
+ if (!timezone || timezone === "UTC") return date.toISOString().slice(0, 10);
3960
+ try {
3961
+ return new Intl.DateTimeFormat("en-CA", {
3962
+ timeZone: timezone,
3963
+ year: "numeric",
3964
+ month: "2-digit",
3965
+ day: "2-digit"
3966
+ }).format(date);
3967
+ } catch {
3968
+ return date.toISOString().slice(0, 10);
3969
+ }
3970
+ }
3971
+ getHoursAndMinutes(date, timezone) {
3972
+ if (!timezone || timezone === "UTC") return {
3973
+ hours: date.getUTCHours(),
3974
+ minutes: date.getUTCMinutes()
3975
+ };
3976
+ try {
3977
+ const parts = new Intl.DateTimeFormat("en-US", {
3978
+ timeZone: timezone,
3979
+ hour: "numeric",
3980
+ minute: "numeric",
3981
+ hour12: false
3982
+ }).formatToParts(date);
3983
+ const hoursPart = parts.find((p) => p.type === "hour");
3984
+ const minutesPart = parts.find((p) => p.type === "minute");
3985
+ return {
3986
+ hours: hoursPart ? parseInt(hoursPart.value, 10) : date.getUTCHours(),
3987
+ minutes: minutesPart ? parseInt(minutesPart.value, 10) : date.getUTCMinutes()
3988
+ };
3989
+ } catch {
3990
+ return {
3991
+ hours: date.getUTCHours(),
3992
+ minutes: date.getUTCMinutes()
3993
+ };
3994
+ }
3995
+ }
3996
+ /**
3997
+ * Check in an employee for today (or specified timestamp).
3998
+ */
3999
+ async checkIn(input) {
4000
+ const employee = await this.getEmployee(input.employeeId);
4001
+ if (!employee) throw new Error(`[HRMSService] Employee with ID '${input.employeeId}' not found.`);
4002
+ const employer = await this.getEmployer(employee.data.employerId);
4003
+ const timezone = employer?.data?.timezone ?? "UTC";
4004
+ const checkInDate = input.timestamp ? new Date(input.timestamp) : /* @__PURE__ */ new Date();
4005
+ const dateStr = this.formatDateString(checkInDate, timezone);
4006
+ const existing = await this.getDailyAttendance(employee.id, dateStr);
4007
+ if (existing && existing.data.checkInAt) throw new Error(`[HRMSService] Employee '${employee.id}' is already checked in for date ${dateStr}.`);
4008
+ const schedule = employer?.data?.workSchedule;
4009
+ const schedStart = schedule?.startTime ?? this.workScheduleStart;
4010
+ const grace = schedule?.gracePeriodMinutes ?? this.gracePeriodMinutes;
4011
+ const schedMinutes = this.parseTimeToMinutes(schedStart);
4012
+ const { hours: punchHours, minutes: punchMins } = this.getHoursAndMinutes(checkInDate, timezone);
4013
+ const status = punchHours * 60 + punchMins > schedMinutes + grace ? "late" : "present";
4014
+ const attendanceData = {
4015
+ employerId: employee.data.employerId,
4016
+ employeeId: employee.id,
4017
+ date: dateStr,
4018
+ checkInAt: checkInDate.toISOString(),
4019
+ totalHours: 0,
4020
+ overtimeHours: 0,
4021
+ status,
4022
+ location: input.location,
4023
+ notes: input.notes
4024
+ };
4025
+ const attendance = await this.attendanceCollection.create({
4026
+ title: `${employee.title} - ${dateStr}`,
4027
+ status: "published",
4028
+ data: attendanceData
4029
+ });
4030
+ await this.engine.hooks.doAction("hrms.checked_in", attendance, employee);
4031
+ return attendance;
4032
+ }
4033
+ /**
4034
+ * Check out an employee for today (or specified timestamp).
4035
+ */
4036
+ async checkOut(input) {
4037
+ const employee = await this.getEmployee(input.employeeId);
4038
+ if (!employee) throw new Error(`[HRMSService] Employee with ID '${input.employeeId}' not found.`);
4039
+ const employer = await this.getEmployer(employee.data.employerId);
4040
+ const timezone = employer?.data?.timezone ?? "UTC";
4041
+ const checkOutDate = input.timestamp ? new Date(input.timestamp) : /* @__PURE__ */ new Date();
4042
+ const dateStr = this.formatDateString(checkOutDate, timezone);
4043
+ const record = await this.getDailyAttendance(employee.id, dateStr);
4044
+ if (!record || !record.data.checkInAt) throw new Error(`[HRMSService] No active check-in record found for employee '${employee.id}' on ${dateStr}.`);
4045
+ if (record.data.checkOutAt) throw new Error(`[HRMSService] Employee '${employee.id}' has already checked out for date ${dateStr}.`);
4046
+ const checkInDate = new Date(record.data.checkInAt);
4047
+ const durationMs = Math.max(0, checkOutDate.getTime() - checkInDate.getTime());
4048
+ const totalHours = Math.round(durationMs / (1e3 * 60 * 60) * 100) / 100;
4049
+ const standardHours = employer?.data?.workSchedule?.standardHoursPerDay ?? this.standardWorkDayHours;
4050
+ const overtimeHours = Math.max(0, Math.round((totalHours - standardHours) * 100) / 100);
4051
+ let status = record.data.status;
4052
+ if (totalHours < standardHours / 2 && status === "present") status = "half_day";
4053
+ const updatedData = {
4054
+ ...record.data,
4055
+ checkOutAt: checkOutDate.toISOString(),
4056
+ totalHours,
4057
+ overtimeHours,
4058
+ status,
4059
+ ...input.location ? { location: input.location } : {},
4060
+ ...input.notes ? { notes: input.notes } : {}
4061
+ };
4062
+ const updated = await this.attendanceCollection.update(record.id, { data: updatedData });
4063
+ if (!updated) throw new Error(`[HRMSService] Failed to update attendance record for '${employee.id}'.`);
4064
+ await this.engine.hooks.doAction("hrms.checked_out", updated, employee);
4065
+ return updated;
4066
+ }
4067
+ async getDailyAttendance(employeeId, date) {
4068
+ return (await this.attendanceCollection.find({ limit: 1e3 })).items.find((a) => a.data.employeeId === employeeId && a.data.date === date) ?? null;
4069
+ }
4070
+ async recordAttendanceManual(input) {
4071
+ const existing = await this.getDailyAttendance(input.employeeId, input.date);
4072
+ const attendanceData = {
4073
+ employerId: input.employerId,
4074
+ employeeId: input.employeeId,
4075
+ date: input.date,
4076
+ checkInAt: input.checkInAt,
4077
+ checkOutAt: input.checkOutAt,
4078
+ totalHours: input.totalHours ?? 0,
4079
+ overtimeHours: input.overtimeHours ?? 0,
4080
+ status: input.status ?? "present",
4081
+ location: input.location,
4082
+ notes: input.notes
4083
+ };
4084
+ if (existing) {
4085
+ const updated = await this.attendanceCollection.update(existing.id, { data: attendanceData });
4086
+ if (!updated) throw new Error(`[HRMSService] Failed to update attendance for '${input.employeeId}'.`);
4087
+ return updated;
4088
+ }
4089
+ return this.attendanceCollection.create({
4090
+ title: `${input.employeeId} - ${input.date}`,
4091
+ status: "published",
4092
+ data: attendanceData
4093
+ });
4094
+ }
4095
+ async listAttendance(query = {}) {
4096
+ const limit = query.limit ?? 30;
4097
+ const offset = query.page ? (query.page - 1) * limit : 0;
4098
+ let items = (await this.attendanceCollection.find({ limit: 2e3 })).items;
4099
+ if (query.employerId) items = items.filter((a) => a.data.employerId === query.employerId);
4100
+ if (query.employeeId) items = items.filter((a) => a.data.employeeId === query.employeeId);
4101
+ if (query.date) items = items.filter((a) => a.data.date === query.date);
4102
+ if (query.startDate) items = items.filter((a) => a.data.date >= query.startDate);
4103
+ if (query.endDate) items = items.filter((a) => a.data.date <= query.endDate);
4104
+ if (query.status) items = items.filter((a) => a.data.status === query.status);
4105
+ const total = items.length;
4106
+ return {
4107
+ items: items.slice(offset, offset + limit),
4108
+ total,
4109
+ limit,
4110
+ offset,
4111
+ hasMore: offset + limit < total
4112
+ };
4113
+ }
4114
+ async createLeaveType(input, authorId) {
4115
+ const leaveTypeData = {
4116
+ employerId: input.employerId,
4117
+ name: input.name,
4118
+ code: input.code.toUpperCase(),
4119
+ daysAllowedPerYear: input.daysAllowedPerYear,
4120
+ paid: input.paid ?? true,
4121
+ requiresApproval: input.requiresApproval ?? true,
4122
+ color: input.color,
4123
+ description: input.description
4124
+ };
4125
+ const item = await this.leaveTypesCollection.create({
4126
+ title: input.name,
4127
+ status: "published",
4128
+ data: leaveTypeData
4129
+ }, authorId);
4130
+ await this.engine.hooks.doAction("hrms.leave_type_created", item);
4131
+ return item;
4132
+ }
4133
+ async getLeaveType(id) {
4134
+ return this.leaveTypesCollection.findById(id);
4135
+ }
4136
+ async listLeaveTypes(employerId) {
4137
+ const result = await this.leaveTypesCollection.find({ limit: 100 });
4138
+ if (!employerId) return result.items;
4139
+ return result.items.filter((lt) => !lt.data.employerId || lt.data.employerId === employerId);
4140
+ }
4141
+ /**
4142
+ * Calculate leave balance report for an employee for a given year.
4143
+ */
4144
+ async calculateLeaveBalance(employeeId, year) {
4145
+ const targetYear = year ?? (/* @__PURE__ */ new Date()).getFullYear();
4146
+ const employee = await this.getEmployee(employeeId);
4147
+ if (!employee) throw new Error(`[HRMSService] Employee with ID '${employeeId}' not found.`);
4148
+ const leaveTypes = await this.listLeaveTypes(employee.data.employerId);
4149
+ const allRequests = await this.leaveRequestsCollection.find({ limit: 1e3 });
4150
+ const yearStr = String(targetYear);
4151
+ const employeeRequests = allRequests.items.filter((r) => r.data.employeeId === employeeId && (r.data.startDate.startsWith(yearStr) || r.data.endDate.startsWith(yearStr)));
4152
+ const balances = leaveTypes.map((lt) => {
4153
+ const approved = employeeRequests.filter((r) => r.data.leaveTypeId === lt.id && r.data.status === "approved");
4154
+ const pending = employeeRequests.filter((r) => r.data.leaveTypeId === lt.id && r.data.status === "pending");
4155
+ const usedDays = approved.reduce((sum, r) => sum + (r.data.daysCount || 0), 0);
4156
+ const pendingDays = pending.reduce((sum, r) => sum + (r.data.daysCount || 0), 0);
4157
+ const allocatedDays = lt.data.daysAllowedPerYear ?? 0;
4158
+ const remainingDays = Math.max(0, allocatedDays - usedDays);
4159
+ return {
4160
+ leaveTypeId: lt.id,
4161
+ leaveTypeName: lt.data.name,
4162
+ leaveTypeCode: lt.data.code,
4163
+ allocatedDays,
4164
+ usedDays,
4165
+ pendingDays,
4166
+ remainingDays
4167
+ };
4168
+ });
4169
+ const report = {
4170
+ employeeId,
4171
+ year: targetYear,
4172
+ balances,
4173
+ totalAllocated: balances.reduce((sum, b) => sum + b.allocatedDays, 0),
4174
+ totalUsed: balances.reduce((sum, b) => sum + b.usedDays, 0),
4175
+ totalRemaining: balances.reduce((sum, b) => sum + b.remainingDays, 0)
4176
+ };
4177
+ return this.engine.hooks.applyFilters("hrms.calculate_leave_balance", report, employeeId, targetYear);
4178
+ }
4179
+ /**
4180
+ * Submit a new leave request.
4181
+ */
4182
+ async requestLeave(input, authorId) {
4183
+ const employee = await this.getEmployee(input.employeeId);
4184
+ if (!employee) throw new Error(`[HRMSService] Employee with ID '${input.employeeId}' not found.`);
4185
+ const leaveType = await this.getLeaveType(input.leaveTypeId);
4186
+ if (!leaveType) throw new Error(`[HRMSService] Leave type with ID '${input.leaveTypeId}' not found.`);
4187
+ if (input.startDate > input.endDate) throw new Error(`[HRMSService] Start date '${input.startDate}' cannot be after end date '${input.endDate}'.`);
4188
+ const start = new Date(input.startDate);
4189
+ const end = new Date(input.endDate);
4190
+ const calculatedDays = Math.max(1, Math.round((end.getTime() - start.getTime()) / (1e3 * 60 * 60 * 24)) + 1);
4191
+ const daysCount = input.daysCount ?? calculatedDays;
4192
+ const startYear = start.getFullYear();
4193
+ const balance = (await this.calculateLeaveBalance(employee.id, startYear)).balances.find((b) => b.leaveTypeId === leaveType.id);
4194
+ if (balance && leaveType.data.requiresApproval && daysCount > balance.remainingDays) throw new Error(`[HRMSService] Insufficient leave balance for ${leaveType.data.name}. Requested: ${daysCount}, Remaining: ${balance.remainingDays}.`);
4195
+ const leaveRequestData = {
4196
+ employerId: input.employerId ?? employee.data.employerId,
4197
+ employeeId: employee.id,
4198
+ leaveTypeId: leaveType.id,
4199
+ startDate: input.startDate,
4200
+ endDate: input.endDate,
4201
+ daysCount,
4202
+ reason: input.reason,
4203
+ status: "pending"
4204
+ };
4205
+ const item = await this.leaveRequestsCollection.create({
4206
+ title: `${employee.title} - ${leaveType.data.name} (${input.startDate})`,
4207
+ status: "published",
4208
+ data: leaveRequestData
4209
+ }, authorId);
4210
+ await this.engine.hooks.doAction("hrms.leave_requested", item, employee);
4211
+ return item;
4212
+ }
4213
+ /**
4214
+ * Approve a pending leave request.
4215
+ */
4216
+ async approveLeave(input) {
4217
+ const request = await this.leaveRequestsCollection.findById(input.requestId);
4218
+ if (!request) throw new Error(`[HRMSService] Leave request with ID '${input.requestId}' not found.`);
4219
+ if (request.data.status !== "pending") throw new Error(`[HRMSService] Cannot approve leave request with status '${request.data.status}'.`);
4220
+ const updated = await this.leaveRequestsCollection.update(request.id, { data: {
4221
+ ...request.data,
4222
+ status: "approved",
4223
+ approvedBy: input.approverId,
4224
+ approvedAt: (/* @__PURE__ */ new Date()).toISOString()
4225
+ } });
4226
+ if (!updated) throw new Error(`[HRMSService] Failed to update leave request.`);
4227
+ await this.engine.hooks.doAction("hrms.leave_approved", updated);
4228
+ return updated;
4229
+ }
4230
+ /**
4231
+ * Reject a pending leave request.
4232
+ */
4233
+ async rejectLeave(input) {
4234
+ const request = await this.leaveRequestsCollection.findById(input.requestId);
4235
+ if (!request) throw new Error(`[HRMSService] Leave request with ID '${input.requestId}' not found.`);
4236
+ if (request.data.status !== "pending") throw new Error(`[HRMSService] Cannot reject leave request with status '${request.data.status}'.`);
4237
+ const updated = await this.leaveRequestsCollection.update(request.id, { data: {
4238
+ ...request.data,
4239
+ status: "rejected",
4240
+ approvedBy: input.approverId,
4241
+ rejectionReason: input.reason,
4242
+ approvedAt: (/* @__PURE__ */ new Date()).toISOString()
4243
+ } });
4244
+ if (!updated) throw new Error(`[HRMSService] Failed to update leave request.`);
4245
+ await this.engine.hooks.doAction("hrms.leave_rejected", updated);
4246
+ return updated;
4247
+ }
4248
+ /**
4249
+ * Cancel a leave request.
4250
+ */
4251
+ async cancelLeave(requestId) {
4252
+ const request = await this.leaveRequestsCollection.findById(requestId);
4253
+ if (!request) throw new Error(`[HRMSService] Leave request with ID '${requestId}' not found.`);
4254
+ if (request.data.status === "cancelled") return request;
4255
+ const updated = await this.leaveRequestsCollection.update(request.id, { data: {
4256
+ ...request.data,
4257
+ status: "cancelled"
4258
+ } });
4259
+ if (!updated) throw new Error(`[HRMSService] Failed to cancel leave request.`);
4260
+ await this.engine.hooks.doAction("hrms.leave_cancelled", updated);
4261
+ return updated;
4262
+ }
4263
+ async listLeaveRequests(query = {}) {
4264
+ const limit = query.limit ?? 20;
4265
+ const offset = query.page ? (query.page - 1) * limit : 0;
4266
+ let items = (await this.leaveRequestsCollection.find({ limit: 1e3 })).items;
4267
+ if (query.employerId) items = items.filter((r) => r.data.employerId === query.employerId);
4268
+ if (query.employeeId) items = items.filter((r) => r.data.employeeId === query.employeeId);
4269
+ if (query.leaveTypeId) items = items.filter((r) => r.data.leaveTypeId === query.leaveTypeId);
4270
+ if (query.status) items = items.filter((r) => r.data.status === query.status);
4271
+ if (query.year) {
4272
+ const yearStr = String(query.year);
4273
+ items = items.filter((r) => r.data.startDate.startsWith(yearStr) || r.data.endDate.startsWith(yearStr));
4274
+ }
4275
+ const total = items.length;
4276
+ return {
4277
+ items: items.slice(offset, offset + limit),
4278
+ total,
4279
+ limit,
4280
+ offset,
4281
+ hasMore: offset + limit < total
4282
+ };
4283
+ }
4284
+ };
4285
+ //#endregion
4286
+ //#region src/plugins/hrms/routes.ts
4287
+ function json(data, status = 200) {
4288
+ return new Response(JSON.stringify(data), {
4289
+ status,
4290
+ headers: {
4291
+ "Content-Type": "application/json",
4292
+ "Access-Control-Allow-Origin": "*"
4293
+ }
4294
+ });
4295
+ }
4296
+ function badRequest(message) {
4297
+ return json({ error: message }, 400);
4298
+ }
4299
+ function notFound(message) {
4300
+ return json({ error: message }, 404);
4301
+ }
4302
+ function registerHRMSRoutes(ctx, service, options = {}) {
4303
+ const prefix = (options.apiPrefix ?? "/api/hrms").replace(/\/+$/, "");
4304
+ ctx.registerRoute("GET", `${prefix}/employers`, async (_req, { url }) => {
4305
+ try {
4306
+ const status = url.searchParams.get("status");
4307
+ const pageStr = url.searchParams.get("page");
4308
+ const limitStr = url.searchParams.get("limit");
4309
+ const page = pageStr ? parseInt(pageStr, 10) : void 0;
4310
+ const limit = limitStr ? parseInt(limitStr, 10) : void 0;
4311
+ return json(await service.listEmployers({
4312
+ status,
4313
+ page,
4314
+ limit
4315
+ }));
4316
+ } catch (err) {
4317
+ return badRequest(err instanceof Error ? err.message : String(err));
4318
+ }
4319
+ });
4320
+ ctx.registerRoute("POST", `${prefix}/employers`, async (req) => {
4321
+ try {
4322
+ const body = await req.json();
4323
+ if (!body.companyName) return badRequest("companyName is required.");
4324
+ return json(await service.createEmployer(body), 201);
4325
+ } catch (err) {
4326
+ return badRequest(err instanceof Error ? err.message : String(err));
4327
+ }
4328
+ });
4329
+ ctx.registerRoute("GET", `${prefix}/employers/:id`, async (_req, { params }) => {
4330
+ try {
4331
+ const employer = await service.getEmployer(params.id);
4332
+ if (!employer) return notFound(`Employer '${params.id}' not found.`);
4333
+ return json(employer);
4334
+ } catch (err) {
4335
+ return badRequest(err instanceof Error ? err.message : String(err));
4336
+ }
4337
+ });
4338
+ ctx.registerRoute("PUT", `${prefix}/employers/:id`, async (req, { params }) => {
4339
+ try {
4340
+ const body = await req.json();
4341
+ const updated = await service.updateEmployer(params.id, body);
4342
+ if (!updated) return notFound(`Employer '${params.id}' not found.`);
4343
+ return json(updated);
4344
+ } catch (err) {
4345
+ return badRequest(err instanceof Error ? err.message : String(err));
4346
+ }
4347
+ });
4348
+ ctx.registerRoute("GET", `${prefix}/employees`, async (_req, { url }) => {
4349
+ try {
4350
+ const employerId = url.searchParams.get("employerId") ?? void 0;
4351
+ const department = url.searchParams.get("department") ?? void 0;
4352
+ const employmentType = url.searchParams.get("employmentType");
4353
+ const status = url.searchParams.get("status");
4354
+ const search = url.searchParams.get("search") ?? void 0;
4355
+ const pageStr = url.searchParams.get("page");
4356
+ const limitStr = url.searchParams.get("limit");
4357
+ const page = pageStr ? parseInt(pageStr, 10) : void 0;
4358
+ const limit = limitStr ? parseInt(limitStr, 10) : void 0;
4359
+ return json(await service.listEmployees({
4360
+ employerId,
4361
+ department,
4362
+ employmentType,
4363
+ status,
4364
+ search,
4365
+ page,
4366
+ limit
4367
+ }));
4368
+ } catch (err) {
4369
+ return badRequest(err instanceof Error ? err.message : String(err));
4370
+ }
4371
+ });
4372
+ ctx.registerRoute("POST", `${prefix}/employees`, async (req) => {
4373
+ try {
4374
+ const body = await req.json();
4375
+ if (!body.employerId) return badRequest("employerId is required.");
4376
+ if (!body.employeeNumber) return badRequest("employeeNumber is required.");
4377
+ if (!body.firstName || !body.lastName) return badRequest("firstName and lastName are required.");
4378
+ if (!body.email) return badRequest("email is required.");
4379
+ return json(await service.createEmployee(body), 201);
4380
+ } catch (err) {
4381
+ return badRequest(err instanceof Error ? err.message : String(err));
4382
+ }
4383
+ });
4384
+ ctx.registerRoute("GET", `${prefix}/employees/:id`, async (_req, { params }) => {
4385
+ try {
4386
+ const employee = await service.getEmployee(params.id);
4387
+ if (!employee) return notFound(`Employee '${params.id}' not found.`);
4388
+ return json(employee);
4389
+ } catch (err) {
4390
+ return badRequest(err instanceof Error ? err.message : String(err));
4391
+ }
4392
+ });
4393
+ ctx.registerRoute("PUT", `${prefix}/employees/:id`, async (req, { params }) => {
4394
+ try {
4395
+ const body = await req.json();
4396
+ const updated = await service.updateEmployee(params.id, body);
4397
+ if (!updated) return notFound(`Employee '${params.id}' not found.`);
4398
+ return json(updated);
4399
+ } catch (err) {
4400
+ return badRequest(err instanceof Error ? err.message : String(err));
4401
+ }
4402
+ });
4403
+ ctx.registerRoute("DELETE", `${prefix}/employees/:id`, async (_req, { params }) => {
4404
+ try {
4405
+ if (!await service.deleteEmployee(params.id)) return notFound(`Employee '${params.id}' not found.`);
4406
+ return json({ success: true });
4407
+ } catch (err) {
4408
+ return badRequest(err instanceof Error ? err.message : String(err));
4409
+ }
4410
+ });
4411
+ ctx.registerRoute("GET", `${prefix}/employees/:id/leave-balance`, async (_req, { params, url }) => {
4412
+ try {
4413
+ const yearStr = url.searchParams.get("year");
4414
+ const year = yearStr ? parseInt(yearStr, 10) : void 0;
4415
+ return json(await service.calculateLeaveBalance(params.id, year));
4416
+ } catch (err) {
4417
+ return badRequest(err instanceof Error ? err.message : String(err));
4418
+ }
4419
+ });
4420
+ ctx.registerRoute("GET", `${prefix}/employees/:id/direct-reports`, async (_req, { params }) => {
4421
+ try {
4422
+ const reports = await service.getDirectReports(params.id);
4423
+ return json({
4424
+ items: reports,
4425
+ total: reports.length
4426
+ });
4427
+ } catch (err) {
4428
+ return badRequest(err instanceof Error ? err.message : String(err));
4429
+ }
4430
+ });
4431
+ ctx.registerRoute("POST", `${prefix}/attendance/check-in`, async (req) => {
4432
+ try {
4433
+ const body = await req.json();
4434
+ if (!body.employeeId) return badRequest("employeeId is required.");
4435
+ return json(await service.checkIn(body), 201);
4436
+ } catch (err) {
4437
+ return badRequest(err instanceof Error ? err.message : String(err));
4438
+ }
4439
+ });
4440
+ ctx.registerRoute("POST", `${prefix}/attendance/check-out`, async (req) => {
4441
+ try {
4442
+ const body = await req.json();
4443
+ if (!body.employeeId) return badRequest("employeeId is required.");
4444
+ return json(await service.checkOut(body));
4445
+ } catch (err) {
4446
+ return badRequest(err instanceof Error ? err.message : String(err));
4447
+ }
4448
+ });
4449
+ ctx.registerRoute("GET", `${prefix}/attendance`, async (_req, { url }) => {
4450
+ try {
4451
+ const employerId = url.searchParams.get("employerId") ?? void 0;
4452
+ const employeeId = url.searchParams.get("employeeId") ?? void 0;
4453
+ const date = url.searchParams.get("date") ?? void 0;
4454
+ const startDate = url.searchParams.get("startDate") ?? void 0;
4455
+ const endDate = url.searchParams.get("endDate") ?? void 0;
4456
+ const status = url.searchParams.get("status");
4457
+ const pageStr = url.searchParams.get("page");
4458
+ const limitStr = url.searchParams.get("limit");
4459
+ const page = pageStr ? parseInt(pageStr, 10) : void 0;
4460
+ const limit = limitStr ? parseInt(limitStr, 10) : void 0;
4461
+ return json(await service.listAttendance({
4462
+ employerId,
4463
+ employeeId,
4464
+ date,
4465
+ startDate,
4466
+ endDate,
4467
+ status,
4468
+ page,
4469
+ limit
4470
+ }));
4471
+ } catch (err) {
4472
+ return badRequest(err instanceof Error ? err.message : String(err));
4473
+ }
4474
+ });
4475
+ ctx.registerRoute("POST", `${prefix}/attendance/manual`, async (req) => {
4476
+ try {
4477
+ const body = await req.json();
4478
+ if (!body.employerId || !body.employeeId || !body.date || !body.checkInAt) return badRequest("employerId, employeeId, date, and checkInAt are required.");
4479
+ return json(await service.recordAttendanceManual(body), 201);
4480
+ } catch (err) {
4481
+ return badRequest(err instanceof Error ? err.message : String(err));
4482
+ }
4483
+ });
4484
+ ctx.registerRoute("GET", `${prefix}/leave-types`, async (_req, { url }) => {
4485
+ try {
4486
+ const employerId = url.searchParams.get("employerId") ?? void 0;
4487
+ const types = await service.listLeaveTypes(employerId);
4488
+ return json({
4489
+ items: types,
4490
+ total: types.length
4491
+ });
4492
+ } catch (err) {
4493
+ return badRequest(err instanceof Error ? err.message : String(err));
4494
+ }
4495
+ });
4496
+ ctx.registerRoute("POST", `${prefix}/leave-types`, async (req) => {
4497
+ try {
4498
+ const body = await req.json();
4499
+ if (!body.name || !body.code || body.daysAllowedPerYear === void 0) return badRequest("name, code, and daysAllowedPerYear are required.");
4500
+ return json(await service.createLeaveType(body), 201);
4501
+ } catch (err) {
4502
+ return badRequest(err instanceof Error ? err.message : String(err));
4503
+ }
4504
+ });
4505
+ ctx.registerRoute("GET", `${prefix}/leave-types/:id`, async (_req, { params }) => {
4506
+ try {
4507
+ const leaveType = await service.getLeaveType(params.id);
4508
+ if (!leaveType) return notFound(`Leave type '${params.id}' not found.`);
4509
+ return json(leaveType);
4510
+ } catch (err) {
4511
+ return badRequest(err instanceof Error ? err.message : String(err));
4512
+ }
4513
+ });
4514
+ ctx.registerRoute("GET", `${prefix}/leave-requests`, async (_req, { url }) => {
4515
+ try {
4516
+ const employerId = url.searchParams.get("employerId") ?? void 0;
4517
+ const employeeId = url.searchParams.get("employeeId") ?? void 0;
4518
+ const leaveTypeId = url.searchParams.get("leaveTypeId") ?? void 0;
4519
+ const status = url.searchParams.get("status");
4520
+ const yearStr = url.searchParams.get("year");
4521
+ const year = yearStr ? parseInt(yearStr, 10) : void 0;
4522
+ const pageStr = url.searchParams.get("page");
4523
+ const limitStr = url.searchParams.get("limit");
4524
+ const page = pageStr ? parseInt(pageStr, 10) : void 0;
4525
+ const limit = limitStr ? parseInt(limitStr, 10) : void 0;
4526
+ return json(await service.listLeaveRequests({
4527
+ employerId,
4528
+ employeeId,
4529
+ leaveTypeId,
4530
+ status,
4531
+ year,
4532
+ page,
4533
+ limit
4534
+ }));
4535
+ } catch (err) {
4536
+ return badRequest(err instanceof Error ? err.message : String(err));
4537
+ }
4538
+ });
4539
+ ctx.registerRoute("POST", `${prefix}/leave-requests`, async (req) => {
4540
+ try {
4541
+ const body = await req.json();
4542
+ if (!body.employeeId || !body.leaveTypeId || !body.startDate || !body.endDate) return badRequest("employeeId, leaveTypeId, startDate, and endDate are required.");
4543
+ return json(await service.requestLeave(body), 201);
4544
+ } catch (err) {
4545
+ return badRequest(err instanceof Error ? err.message : String(err));
4546
+ }
4547
+ });
4548
+ ctx.registerRoute("POST", `${prefix}/leave-requests/:id/approve`, async (req, { params }) => {
4549
+ try {
4550
+ const approverId = (await req.json().catch(() => ({}))).approverId ?? "admin";
4551
+ return json(await service.approveLeave({
4552
+ requestId: params.id,
4553
+ approverId
4554
+ }));
4555
+ } catch (err) {
4556
+ return badRequest(err instanceof Error ? err.message : String(err));
4557
+ }
4558
+ });
4559
+ ctx.registerRoute("POST", `${prefix}/leave-requests/:id/reject`, async (req, { params }) => {
4560
+ try {
4561
+ const body = await req.json().catch(() => ({}));
4562
+ const approverId = body.approverId ?? "admin";
4563
+ return json(await service.rejectLeave({
4564
+ requestId: params.id,
4565
+ approverId,
4566
+ reason: body.reason
4567
+ }));
4568
+ } catch (err) {
4569
+ return badRequest(err instanceof Error ? err.message : String(err));
4570
+ }
4571
+ });
4572
+ ctx.registerRoute("POST", `${prefix}/leave-requests/:id/cancel`, async (_req, { params }) => {
4573
+ try {
4574
+ return json(await service.cancelLeave(params.id));
4575
+ } catch (err) {
4576
+ return badRequest(err instanceof Error ? err.message : String(err));
4577
+ }
4578
+ });
4579
+ }
4580
+ //#endregion
4581
+ //#region src/plugins/hrms/client.ts
4582
+ var HRMSClient = class {
4583
+ client;
4584
+ options;
4585
+ service;
4586
+ prefix;
4587
+ constructor(client, options = {}) {
4588
+ this.client = client;
4589
+ this.options = options;
4590
+ this.prefix = (options.apiPrefix ?? "/api/hrms").replace(/\/+$/, "");
4591
+ const engine = client.getEngine();
4592
+ if (engine) this.service = new HRMSService(engine, options);
4593
+ }
4594
+ employers = {
4595
+ find: async (query = {}) => {
4596
+ if (this.service) return this.service.listEmployers(query);
4597
+ const params = new URLSearchParams();
4598
+ if (query.status) params.set("status", query.status);
4599
+ if (query.page) params.set("page", String(query.page));
4600
+ if (query.limit) params.set("limit", String(query.limit));
4601
+ const q = params.toString() ? `?${params.toString()}` : "";
4602
+ return this.client.request(`${this.prefix}/employers${q}`);
4603
+ },
4604
+ get: async (id) => {
4605
+ if (this.service) return this.service.getEmployer(id);
4606
+ return this.client.request(`${this.prefix}/employers/${encodeURIComponent(id)}`);
4607
+ },
4608
+ create: async (input) => {
4609
+ if (this.service) return this.service.createEmployer(input);
4610
+ return this.client.request(`${this.prefix}/employers`, {
4611
+ method: "POST",
4612
+ body: JSON.stringify(input)
4613
+ });
4614
+ },
4615
+ update: async (id, input) => {
4616
+ if (this.service) return this.service.updateEmployer(id, input);
4617
+ return this.client.request(`${this.prefix}/employers/${encodeURIComponent(id)}`, {
4618
+ method: "PUT",
4619
+ body: JSON.stringify(input)
4620
+ });
4621
+ }
4622
+ };
4623
+ employees = {
4624
+ find: async (query = {}) => {
4625
+ if (this.service) return this.service.listEmployees(query);
4626
+ const params = new URLSearchParams();
4627
+ if (query.employerId) params.set("employerId", query.employerId);
4628
+ if (query.department) params.set("department", query.department);
4629
+ if (query.employmentType) params.set("employmentType", query.employmentType);
4630
+ if (query.status) params.set("status", query.status);
4631
+ if (query.search) params.set("search", query.search);
4632
+ if (query.page) params.set("page", String(query.page));
4633
+ if (query.limit) params.set("limit", String(query.limit));
4634
+ const q = params.toString() ? `?${params.toString()}` : "";
4635
+ return this.client.request(`${this.prefix}/employees${q}`);
4636
+ },
4637
+ get: async (id) => {
4638
+ if (this.service) return this.service.getEmployee(id);
4639
+ return this.client.request(`${this.prefix}/employees/${encodeURIComponent(id)}`);
4640
+ },
4641
+ getByNumber: async (employerId, employeeNumber) => {
4642
+ if (this.service) return this.service.getEmployeeByNumber(employerId, employeeNumber);
4643
+ return (await this.employees.find({
4644
+ employerId,
4645
+ search: employeeNumber
4646
+ })).items.find((e) => e.data.employeeNumber === employeeNumber) ?? null;
4647
+ },
4648
+ create: async (input) => {
4649
+ if (this.service) return this.service.createEmployee(input);
4650
+ return this.client.request(`${this.prefix}/employees`, {
4651
+ method: "POST",
4652
+ body: JSON.stringify(input)
4653
+ });
4654
+ },
4655
+ update: async (id, input) => {
4656
+ if (this.service) return this.service.updateEmployee(id, input);
4657
+ return this.client.request(`${this.prefix}/employees/${encodeURIComponent(id)}`, {
4658
+ method: "PUT",
4659
+ body: JSON.stringify(input)
4660
+ });
4661
+ },
4662
+ delete: async (id) => {
4663
+ if (this.service) return this.service.deleteEmployee(id);
4664
+ return (await this.client.request(`${this.prefix}/employees/${encodeURIComponent(id)}`, { method: "DELETE" })).success;
4665
+ },
4666
+ getLeaveBalance: async (employeeId, year) => {
4667
+ if (this.service) return this.service.calculateLeaveBalance(employeeId, year);
4668
+ const q = year ? `?year=${year}` : "";
4669
+ return this.client.request(`${this.prefix}/employees/${encodeURIComponent(employeeId)}/leave-balance${q}`);
4670
+ },
4671
+ getDirectReports: async (managerId) => {
4672
+ if (this.service) return this.service.getDirectReports(managerId);
4673
+ return (await this.client.request(`${this.prefix}/employees/${encodeURIComponent(managerId)}/direct-reports`)).items;
4674
+ }
4675
+ };
4676
+ attendance = {
4677
+ checkIn: async (input) => {
4678
+ if (this.service) return this.service.checkIn(input);
4679
+ return this.client.request(`${this.prefix}/attendance/check-in`, {
4680
+ method: "POST",
4681
+ body: JSON.stringify(input)
4682
+ });
4683
+ },
4684
+ checkOut: async (input) => {
4685
+ if (this.service) return this.service.checkOut(input);
4686
+ return this.client.request(`${this.prefix}/attendance/check-out`, {
4687
+ method: "POST",
4688
+ body: JSON.stringify(input)
4689
+ });
4690
+ },
4691
+ getDaily: async (employeeId, date) => {
4692
+ if (this.service) return this.service.getDailyAttendance(employeeId, date);
4693
+ return (await this.attendance.find({
4694
+ employeeId,
4695
+ date
4696
+ })).items[0] ?? null;
4697
+ },
4698
+ find: async (query = {}) => {
4699
+ if (this.service) return this.service.listAttendance(query);
4700
+ const params = new URLSearchParams();
4701
+ if (query.employerId) params.set("employerId", query.employerId);
4702
+ if (query.employeeId) params.set("employeeId", query.employeeId);
4703
+ if (query.date) params.set("date", query.date);
4704
+ if (query.startDate) params.set("startDate", query.startDate);
4705
+ if (query.endDate) params.set("endDate", query.endDate);
4706
+ if (query.status) params.set("status", query.status);
4707
+ if (query.page) params.set("page", String(query.page));
4708
+ if (query.limit) params.set("limit", String(query.limit));
4709
+ const q = params.toString() ? `?${params.toString()}` : "";
4710
+ return this.client.request(`${this.prefix}/attendance${q}`);
4711
+ },
4712
+ recordManual: async (input) => {
4713
+ if (this.service) return this.service.recordAttendanceManual(input);
4714
+ return this.client.request(`${this.prefix}/attendance/manual`, {
4715
+ method: "POST",
4716
+ body: JSON.stringify(input)
4717
+ });
4718
+ }
4719
+ };
4720
+ leaves = {
4721
+ listTypes: async (employerId) => {
4722
+ if (this.service) return this.service.listLeaveTypes(employerId);
4723
+ const q = employerId ? `?employerId=${encodeURIComponent(employerId)}` : "";
4724
+ return (await this.client.request(`${this.prefix}/leave-types${q}`)).items;
4725
+ },
4726
+ createType: async (input) => {
4727
+ if (this.service) return this.service.createLeaveType(input);
4728
+ return this.client.request(`${this.prefix}/leave-types`, {
4729
+ method: "POST",
4730
+ body: JSON.stringify(input)
4731
+ });
4732
+ },
4733
+ getType: async (id) => {
4734
+ if (this.service) return this.service.getLeaveType(id);
4735
+ return this.client.request(`${this.prefix}/leave-types/${encodeURIComponent(id)}`);
4736
+ },
4737
+ findRequests: async (query = {}) => {
4738
+ if (this.service) return this.service.listLeaveRequests(query);
4739
+ const params = new URLSearchParams();
4740
+ if (query.employerId) params.set("employerId", query.employerId);
4741
+ if (query.employeeId) params.set("employeeId", query.employeeId);
4742
+ if (query.leaveTypeId) params.set("leaveTypeId", query.leaveTypeId);
4743
+ if (query.status) params.set("status", query.status);
4744
+ if (query.year) params.set("year", String(query.year));
4745
+ if (query.page) params.set("page", String(query.page));
4746
+ if (query.limit) params.set("limit", String(query.limit));
4747
+ const q = params.toString() ? `?${params.toString()}` : "";
4748
+ return this.client.request(`${this.prefix}/leave-requests${q}`);
4749
+ },
4750
+ request: async (input) => {
4751
+ if (this.service) return this.service.requestLeave(input);
4752
+ return this.client.request(`${this.prefix}/leave-requests`, {
4753
+ method: "POST",
4754
+ body: JSON.stringify(input)
4755
+ });
4756
+ },
4757
+ approve: async (input) => {
4758
+ if (this.service) return this.service.approveLeave(input);
4759
+ return this.client.request(`${this.prefix}/leave-requests/${encodeURIComponent(input.requestId)}/approve`, {
4760
+ method: "POST",
4761
+ body: JSON.stringify({ approverId: input.approverId })
4762
+ });
4763
+ },
4764
+ reject: async (input) => {
4765
+ if (this.service) return this.service.rejectLeave(input);
4766
+ return this.client.request(`${this.prefix}/leave-requests/${encodeURIComponent(input.requestId)}/reject`, {
4767
+ method: "POST",
4768
+ body: JSON.stringify({
4769
+ approverId: input.approverId,
4770
+ reason: input.reason
4771
+ })
4772
+ });
4773
+ },
4774
+ cancel: async (requestId) => {
4775
+ if (this.service) return this.service.cancelLeave(requestId);
4776
+ return this.client.request(`${this.prefix}/leave-requests/${encodeURIComponent(requestId)}/cancel`, { method: "POST" });
4777
+ }
4778
+ };
4779
+ };
4780
+ /**
4781
+ * Get or create an HRMSClient adapter for a CMSClient.
4782
+ */
4783
+ function getHRMSClient(client, options) {
4784
+ return new HRMSClient(client, options);
4785
+ }
4786
+ //#endregion
4787
+ //#region src/plugins/hrms/index.ts
4788
+ /**
4789
+ * @azlib/cms - Built-in HRMS Plugin
4790
+ */
4791
+ /**
4792
+ * Built-in HRMS plugin factory for @azlib/cms.
4793
+ * Equips the CMS engine with multi-tenant employers, employee profiles,
4794
+ * daily attendance check-in/out tracking, and leave quota approval workflows.
4795
+ */
4796
+ const hrmsPlugin = definePlugin((options) => {
4797
+ const opts = options || {};
4798
+ const collections = [createEmployeeCollection(opts)];
4799
+ if (opts.enableEmployers !== false) collections.unshift(createEmployerCollection(opts));
4800
+ if (opts.enableAttendance !== false) collections.push(createAttendanceCollection(opts));
4801
+ if (opts.enableLeaves !== false) {
4802
+ collections.push(createLeaveTypeCollection(opts));
4803
+ collections.push(createLeaveRequestCollection(opts));
4804
+ }
4805
+ return {
4806
+ name: "hrms",
4807
+ version: "1.0.0",
4808
+ description: "Built-in Human Resource Management System (HRMS) plugin for employers, employees, attendance, and leave management",
4809
+ collections,
4810
+ taxonomies: createHRMSTaxonomies(opts),
4811
+ setup(ctx) {
4812
+ const service = new HRMSService(ctx.engine, opts);
4813
+ ctx.engine.__hrmsService = service;
4814
+ registerHRMSRoutes(ctx, service, opts);
4815
+ }
4816
+ };
4817
+ });
4818
+ /**
4819
+ * Retrieve the active HRMSService instance associated with a CMSEngine.
4820
+ */
4821
+ function getHRMSService(engine, options) {
4822
+ if (engine.__hrmsService) return engine.__hrmsService;
4823
+ const service = new HRMSService(engine, options);
4824
+ engine.__hrmsService = service;
4825
+ return service;
4826
+ }
4827
+ //#endregion
4828
+ export { CMSClient, CMSEngine, CMSRouter, ContentLifecycle, DEFAULT_COLLECTIONS, DEFAULT_ROLE_CAPABILITIES, EcommerceClient, EcommerceService, HRMSClient, HRMSService, HooksManager, MediaManager, MemoryStorageAdapter, OptionsManager, RBACManager, RevisionManager, TaxonomyManager, VALID_STATUS_TRANSITIONS, collection, createAttendanceCollection, createCMSEngine, createCMSRouter, createCmsClient, createDiscountCollection, createEcommerceTaxonomies, createEmployeeCollection, createEmployerCollection, createHRMSTaxonomies, createLeaveRequestCollection, createLeaveTypeCollection, createOrderCollection, createProductCollection, defaultHooks, defineConfig, definePlugin, ecommercePlugin, fields, getEcommerceClient, getEcommerceService, getHRMSClient, getHRMSService, hrmsPlugin, normalizeConfig, resolveUniqueSlug, slugify, validateAndNormalizeData };
3238
4829
 
3239
4830
  //# sourceMappingURL=index.mjs.map