law-common 14.0.1-beta.4 → 14.0.1-beta.6

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.
@@ -17,8 +17,9 @@ export type ITimesheetEntityGet = {
17
17
  };
18
18
  /**
19
19
  * LW-828: this used to re-declare the flow-config shape by hand. It is now an alias of the shared
20
- * `FlowConfig`, whose action config carries the optional `isApplicable` and `validActionPastThreshold`
21
- * this entity relies on. Kept as a named type so every existing consumer keeps compiling unchanged.
20
+ * `FlowConfig`, whose action config carries the `isApplicable` runtime guard this entity relies on
21
+ * (LW-830 collapse — the previous `validActionPastThreshold` boolean is commented out in
22
+ * `IFlowConfigActionConfig`). Kept as a named type so every existing consumer keeps compiling unchanged.
22
23
  */
23
24
  export type ITimesheetFlowConfig = FlowConfig<TimesheetStatusEnum, TimesheetActionEnum, ITimesheetFlowContextData>;
24
25
  export type ITimesheetFlowContextData = {
@@ -114,19 +114,21 @@ export type ActionMap<A extends string, S extends string> = {
114
114
  *
115
115
  * @property isApplicable runtime guard evaluated against the same context as `next()`
116
116
  * @property validActionPastThreshold whether the action survives a consumer's data-entry threshold
117
+ * (commented out below since the LW-830 collapse — see note)
118
+ * @property isAvailable unified runtime guard introduced by LW-830; preferred over
119
+ * `isApplicable` when both are present (commented out below
120
+ * since the LW-830 collapse — see note)
117
121
  * @property description human-readable copy for this transition. Already part of
118
122
  * `IFlowConfigActionConfig` and rendered by the entity-flow
119
123
  * component, so a converted config must be able to emit it.
120
124
  */
121
125
  export type ActionConfigOptions<CTX> = {
122
126
  isApplicable?: (context: CTX) => boolean;
123
- validActionPastThreshold?: boolean;
124
127
  description?: string;
125
128
  };
126
129
  export declare function createFlowConfigFactory<CTX = never>(): <A extends string, S extends string, AM extends ActionMap<A, S>>(actionMap: AM) => {
127
130
  createActionConfig: <Act extends A, Subset extends { [key in A]: AM[key]["reachableStatues"]; }[Act][number] = { [key in A]: AM[key]["reachableStatues"]; }[Act][number]>(action: Act, nextStatus: Subset | ((context: CTX) => Subset), overridePermissions?: string[], options?: ActionConfigOptions<CTX>) => {
128
131
  description?: string | undefined;
129
- validActionPastThreshold?: boolean | undefined;
130
132
  isApplicable?: ((context: CTX) => boolean) | undefined;
131
133
  permissions: string[];
132
134
  next: (context: CTX) => Subset;
@@ -99,7 +99,7 @@ function createFlowConfigFactory() {
99
99
  * (currentStatus, action) pair. Must be a subtype of
100
100
  * `StatusForAction<Act>`.
101
101
  */
102
- const createActionConfig = (action, nextStatus, overridePermissions, options) => (Object.assign(Object.assign(Object.assign({ permissions: overridePermissions !== null && overridePermissions !== void 0 ? overridePermissions : actionMap[action].permissions, next: createTransition(nextStatus) }, ((options === null || options === void 0 ? void 0 : options.isApplicable) !== undefined ? { isApplicable: options.isApplicable } : {})), ((options === null || options === void 0 ? void 0 : options.validActionPastThreshold) !== undefined ? { validActionPastThreshold: options.validActionPastThreshold } : {})), ((options === null || options === void 0 ? void 0 : options.description) !== undefined ? { description: options.description } : {})));
102
+ const createActionConfig = (action, nextStatus, overridePermissions, options) => (Object.assign(Object.assign({ permissions: overridePermissions !== null && overridePermissions !== void 0 ? overridePermissions : actionMap[action].permissions, next: createTransition(nextStatus) }, ((options === null || options === void 0 ? void 0 : options.isApplicable) !== undefined ? { isApplicable: options.isApplicable } : {})), ((options === null || options === void 0 ? void 0 : options.description) !== undefined ? { description: options.description } : {})));
103
103
  return { createActionConfig, createTransition };
104
104
  };
105
105
  }
@@ -10,7 +10,8 @@ export interface IBillingTransactionContextFlowData {
10
10
  /**
11
11
  * LW-828: this used to re-declare the flow-config shape by hand. It is now an alias of the shared
12
12
  * `FlowConfig`, which is a strict superset of the old declaration — same `permissions` and `next`, plus
13
- * the optional `description` / `isApplicable` / `validActionPastThreshold` this entity does not use.
13
+ * the optional `description` / `isApplicable` this entity does not use (`validActionPastThreshold` was
14
+ * commented out of the shared type during the LW-830 collapse).
14
15
  * Kept as a named type so every existing consumer keeps compiling unchanged.
15
16
  */
16
17
  export type IBillingTransactionFlowConfig = FlowConfig<BillingTransactionStatusEnum, BillingTransactionActionEnum, IBillingTransactionContextFlowData>;
@@ -69,13 +69,6 @@ export interface IFlowConfigActionConfig<STATUS_ENUM_TYPE extends string, CONTEX
69
69
  * to the row in its current state.
70
70
  */
71
71
  isApplicable?: (data: CONTEXT_DATA_TYPE) => boolean;
72
- /**
73
- * Optional flag read by consumers that enforce a data-entry threshold: whether this action
74
- * stays available once the row is older than that threshold. Absent means "yes" — consumers
75
- * read it as `validActionPastThreshold ?? true`. It is a plain boolean rather than a
76
- * function so it survives serialisation to clients that receive the flow config over HTTP.
77
- */
78
- validActionPastThreshold?: boolean;
79
72
  }
80
73
  /**
81
74
  * A flow whose parent and child rows each have their own status/action vocabulary.
@@ -112,8 +112,8 @@ const transitionOntimeStatusUpdateAction = createActionConfig(timesheet_action_e
112
112
  return timesheet_status_enum_1.TimesheetStatusEnum.UPDATE_APPROVAL_PENDING;
113
113
  }
114
114
  return isBackdated(data) ? timesheet_status_enum_1.TimesheetStatusEnum.ONTIME : timesheet_status_enum_1.TimesheetStatusEnum.UPDATE_APPROVAL_PENDING;
115
- }, undefined, { validActionPastThreshold: false });
116
- const transitionOntimeStatusMoveTimesheetAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: true });
115
+ }, undefined, { isApplicable: makeThresholdApplicability(false) });
116
+ const transitionOntimeStatusMoveTimesheetAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: makeThresholdApplicability(true) });
117
117
  const transitionOntimeStatusDeleteAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.DELETE, (data) => {
118
118
  // non-billable timesheets: auto-delete
119
119
  const nonBillableStatus = resolveStatusForNonBillableTimesheet(data);
@@ -123,18 +123,16 @@ const transitionOntimeStatusDeleteAction = createActionConfig(timesheet_action_e
123
123
  return timesheet_status_enum_1.TimesheetStatusEnum.DELETE_APPROVAL_PENDING;
124
124
  }
125
125
  return isBackdated(data) ? timesheet_status_enum_1.TimesheetStatusEnum.DELETED : timesheet_status_enum_1.TimesheetStatusEnum.DELETE_APPROVAL_PENDING;
126
- }, undefined, { validActionPastThreshold: false });
126
+ }, undefined, { isApplicable: makeThresholdApplicability(false) });
127
127
  const transitionOntimeStatusBillingUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.BILLING_UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.ONTIME);
128
128
  const transitionOntimeStatusClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.ONTIME, undefined, {
129
- isApplicable: isValidActionAfterTimesheetEnterThreshold,
130
- validActionPastThreshold: true,
129
+ isApplicable: makeThresholdApplicability(true),
131
130
  });
132
131
  const transitionOntimeStatusAutoClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.ONTIME, undefined, {
133
- isApplicable: () => true,
134
- validActionPastThreshold: true,
132
+ isApplicable: makeThresholdApplicability(true),
135
133
  });
136
134
  // CREATE_APPROVAL_PENDING status transitions
137
- const transitionCreateApprovalPendingStatusUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.CREATE_APPROVAL_PENDING, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: false });
135
+ const transitionCreateApprovalPendingStatusUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.CREATE_APPROVAL_PENDING, undefined, { isApplicable: makeThresholdApplicability(false) });
138
136
  const transitionCreateApprovalPendingStatusDeleteAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.DELETE, (data) => {
139
137
  // non-billable timesheets: auto-delete
140
138
  const nonBillableStatus = resolveStatusForNonBillableTimesheet(data);
@@ -144,26 +142,25 @@ const transitionCreateApprovalPendingStatusDeleteAction = createActionConfig(tim
144
142
  // return TimesheetStatusEnum.DELETE_APPROVAL_PENDING;
145
143
  // }
146
144
  return timesheet_status_enum_1.TimesheetStatusEnum.DELETED;
147
- }, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: false });
145
+ }, undefined, { isApplicable: makeThresholdApplicability(false) });
148
146
  const transitionCreateApprovalPendingStatusApproveAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.APPROVE, (data) => {
149
147
  // non-billable timesheets: auto-approve pending or delete
150
148
  const nonBillableStatus = resolveStatusForNonBillableTimesheet(data);
151
149
  if (nonBillableStatus)
152
150
  return nonBillableStatus;
153
151
  return timesheet_status_enum_1.TimesheetStatusEnum.APPROVED;
154
- }, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold });
152
+ }, undefined, { isApplicable: makeThresholdApplicability(true) });
155
153
  const transitionCreateApprovalPendingStatusRejectAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.REJECT, timesheet_status_enum_1.TimesheetStatusEnum.REJECTED, undefined, {
156
- isApplicable: isValidActionAfterTimesheetEnterThreshold,
154
+ isApplicable: makeThresholdApplicability(true),
157
155
  });
158
- const transitionCreateApprovalPendingStatusMoveTimesheetAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: true });
156
+ const transitionCreateApprovalPendingStatusMoveTimesheetAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: makeThresholdApplicability(true) });
159
157
  const transitionCreateApprovalPendingStatusBillingUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.BILLING_UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.CREATE_APPROVAL_PENDING);
160
- const transitionCreateApprovalPendingStatusClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.CREATE_APPROVAL_PENDING, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: true });
161
- const transitionCreateApprovalPendingStatusAutoClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.CREATE_APPROVAL_PENDING, undefined, { isApplicable: () => true, validActionPastThreshold: true });
158
+ const transitionCreateApprovalPendingStatusClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.CREATE_APPROVAL_PENDING, undefined, { isApplicable: makeThresholdApplicability(true) });
159
+ const transitionCreateApprovalPendingStatusAutoClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.CREATE_APPROVAL_PENDING, undefined, { isApplicable: makeThresholdApplicability(true) });
162
160
  // UPDATE_APPROVAL_PENDING status transitions
163
- const transitionUpdateApprovalPendingStatusUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.UPDATE_APPROVAL_PENDING, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: false });
161
+ const transitionUpdateApprovalPendingStatusUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.UPDATE_APPROVAL_PENDING, undefined, { isApplicable: makeThresholdApplicability(false) });
164
162
  const transitionUpdateApprovalPendingStatusRecallAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.RECALL, timesheet_status_enum_1.TimesheetStatusEnum.DELETED, undefined, {
165
- isApplicable: isValidActionAfterTimesheetEnterThreshold,
166
- validActionPastThreshold: false,
163
+ isApplicable: makeThresholdApplicability(false),
167
164
  });
168
165
  const transitionUpdateApprovalPendingStatusApproveAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.APPROVE, (data) => {
169
166
  // non-billable timesheets: auto-approve pending or delete
@@ -171,19 +168,18 @@ const transitionUpdateApprovalPendingStatusApproveAction = createActionConfig(ti
171
168
  if (nonBillableStatus)
172
169
  return nonBillableStatus;
173
170
  return timesheet_status_enum_1.TimesheetStatusEnum.APPROVED;
174
- }, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold });
171
+ }, undefined, { isApplicable: makeThresholdApplicability(true) });
175
172
  const transitionUpdateApprovalPendingStatusRejectAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.REJECT, timesheet_status_enum_1.TimesheetStatusEnum.RESOLVED_REJECTED, undefined, {
176
- isApplicable: isValidActionAfterTimesheetEnterThreshold,
173
+ isApplicable: makeThresholdApplicability(true),
177
174
  });
178
- const transitionUpdateApprovalPendingStatusMoveTimesheetAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: true });
175
+ const transitionUpdateApprovalPendingStatusMoveTimesheetAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: makeThresholdApplicability(true) });
179
176
  const transitionUpdateApprovalPendingStatusBillingUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.BILLING_UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.UPDATE_APPROVAL_PENDING);
180
- const transitionUpdateApprovalPendingStatusClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.UPDATE_APPROVAL_PENDING, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: true });
181
- const transitionUpdateApprovalPendingStatusAutoClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.UPDATE_APPROVAL_PENDING, undefined, { isApplicable: () => true, validActionPastThreshold: true });
177
+ const transitionUpdateApprovalPendingStatusClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.UPDATE_APPROVAL_PENDING, undefined, { isApplicable: makeThresholdApplicability(true) });
178
+ const transitionUpdateApprovalPendingStatusAutoClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.UPDATE_APPROVAL_PENDING, undefined, { isApplicable: makeThresholdApplicability(true) });
182
179
  // DELETE_APPROVAL_PENDING status transitions
183
- const transitionDeleteApprovalPendingStatusUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.DELETE_APPROVAL_PENDING, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: false });
180
+ const transitionDeleteApprovalPendingStatusUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.DELETE_APPROVAL_PENDING, undefined, { isApplicable: makeThresholdApplicability(false) });
184
181
  const transitionDeleteApprovalPendingStatusRecallAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.RECALL, timesheet_status_enum_1.TimesheetStatusEnum.DELETED, undefined, {
185
- isApplicable: isValidActionAfterTimesheetEnterThreshold,
186
- validActionPastThreshold: false,
182
+ isApplicable: makeThresholdApplicability(false),
187
183
  });
188
184
  const transitionDeleteApprovalPendingStatusApproveAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.APPROVE, (data) => {
189
185
  // non-billable timesheets: auto-approve pending or delete
@@ -191,18 +187,17 @@ const transitionDeleteApprovalPendingStatusApproveAction = createActionConfig(ti
191
187
  if (nonBillableStatus)
192
188
  return nonBillableStatus;
193
189
  return timesheet_status_enum_1.TimesheetStatusEnum.DELETED;
194
- }, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold });
190
+ }, undefined, { isApplicable: makeThresholdApplicability(true) });
195
191
  const transitionDeleteApprovalPendingStatusRejectAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.REJECT, timesheet_status_enum_1.TimesheetStatusEnum.RESOLVED_REJECTED, undefined, {
196
- isApplicable: isValidActionAfterTimesheetEnterThreshold,
192
+ isApplicable: makeThresholdApplicability(true),
197
193
  });
198
- const transitionDeleteApprovalPendingStatusMoveTimesheetAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: true });
194
+ const transitionDeleteApprovalPendingStatusMoveTimesheetAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: makeThresholdApplicability(true) });
199
195
  const transitionDeleteApprovalPendingStatusBillingUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.BILLING_UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.DELETE_APPROVAL_PENDING);
200
- const transitionDeleteApprovalPendingStatusClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.DELETE_APPROVAL_PENDING, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: true });
201
- const transitionDeleteApprovalPendingStatusAutoClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.DELETE_APPROVAL_PENDING, undefined, { isApplicable: () => true, validActionPastThreshold: true });
196
+ const transitionDeleteApprovalPendingStatusClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.DELETE_APPROVAL_PENDING, undefined, { isApplicable: makeThresholdApplicability(true) });
197
+ const transitionDeleteApprovalPendingStatusAutoClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.DELETE_APPROVAL_PENDING, undefined, { isApplicable: makeThresholdApplicability(true) });
202
198
  // RESOLVED_REJECTED status transitions
203
199
  const transitionResolvedRejectedStatusUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.UPDATE_APPROVAL_PENDING, undefined, {
204
- isApplicable: isValidActionAfterTimesheetEnterThreshold,
205
- validActionPastThreshold: false,
200
+ isApplicable: makeThresholdApplicability(false),
206
201
  });
207
202
  const transitionResolvedRejectedStatusDeleteAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.DELETE, (data) => {
208
203
  // non-billable timesheets: auto-delete
@@ -210,14 +205,13 @@ const transitionResolvedRejectedStatusDeleteAction = createActionConfig(timeshee
210
205
  if (nonBillableStatus)
211
206
  return nonBillableStatus;
212
207
  return timesheet_status_enum_1.TimesheetStatusEnum.DELETE_APPROVAL_PENDING;
213
- }, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold });
214
- const transitionResolvedRejectedStatusMoveTimesheetAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: true });
208
+ }, undefined, { isApplicable: makeThresholdApplicability(true) });
209
+ const transitionResolvedRejectedStatusMoveTimesheetAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: makeThresholdApplicability(true) });
215
210
  const transitionResolvedRejectedStatusBillingUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.BILLING_UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.RESOLVED_REJECTED);
216
211
  const transitionResolvedRejectedStatusClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.RESOLVED_REJECTED, undefined, {
217
- isApplicable: isValidActionAfterTimesheetEnterThreshold,
218
- validActionPastThreshold: true,
212
+ isApplicable: makeThresholdApplicability(true),
219
213
  });
220
- const transitionResolvedRejectedStatusAutoClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.RESOLVED_REJECTED, undefined, { isApplicable: () => true, validActionPastThreshold: true });
214
+ const transitionResolvedRejectedStatusAutoClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.RESOLVED_REJECTED, undefined, { isApplicable: makeThresholdApplicability(true) });
221
215
  // APPROVED status transitions
222
216
  const transitionApprovedStatusUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.UPDATE, (data) => {
223
217
  // non-billable timesheets: keep ontime/approved on edit
@@ -225,32 +219,30 @@ const transitionApprovedStatusUpdateAction = createActionConfig(timesheet_action
225
219
  if (nonBillableStatus)
226
220
  return nonBillableStatus;
227
221
  return timesheet_status_enum_1.TimesheetStatusEnum.UPDATE_APPROVAL_PENDING;
228
- }, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: false });
229
- const transitionApprovedStatusMoveTimesheetAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: true });
222
+ }, undefined, { isApplicable: makeThresholdApplicability(false) });
223
+ const transitionApprovedStatusMoveTimesheetAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: makeThresholdApplicability(true) });
230
224
  const transitionApprovedStatusDeleteAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.DELETE, (data) => {
231
225
  // non-billable timesheets: auto-delete
232
226
  const nonBillableStatus = resolveStatusForNonBillableTimesheet(data);
233
227
  if (nonBillableStatus)
234
228
  return nonBillableStatus;
235
229
  return timesheet_status_enum_1.TimesheetStatusEnum.DELETE_APPROVAL_PENDING;
236
- }, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: false });
230
+ }, undefined, { isApplicable: makeThresholdApplicability(false) });
237
231
  const transitionApprovedStatusBillingUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.BILLING_UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.APPROVED);
238
232
  const transitionApprovedStatusClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.APPROVED, undefined, {
239
- isApplicable: isValidActionAfterTimesheetEnterThreshold,
240
- validActionPastThreshold: true,
233
+ isApplicable: makeThresholdApplicability(true),
241
234
  });
242
235
  const transitionApprovedStatusAutoClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.APPROVED, undefined, {
243
- isApplicable: () => true,
244
- validActionPastThreshold: true,
236
+ isApplicable: makeThresholdApplicability(true),
245
237
  });
246
238
  // MOVING_TIMESHEET_PENDING_APPROVAL status transitions
247
239
  const transitionMovingTimesheetPendingApprovalStatusApproveAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.APPROVE, (data) => getNextStatusForMovingTimesheetActions(data), // <== currentEntity will always have status
248
240
  // ?? TimesheetStatusEnum.APPROVED,
249
- undefined, { validActionPastThreshold: true });
250
- const transitionMovingTimesheetPendingApprovalStatusRejectAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.REJECT, (data) => getNextStatusForMovingTimesheetActions(data), undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: true });
241
+ undefined, { isApplicable: makeThresholdApplicability(true) });
242
+ const transitionMovingTimesheetPendingApprovalStatusRejectAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.REJECT, (data) => getNextStatusForMovingTimesheetActions(data), undefined, { isApplicable: makeThresholdApplicability(true) });
251
243
  const transitionMovingTimesheetPendingApprovalStatusBillingUpdateAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.BILLING_UPDATE, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL);
252
- const transitionMovingTimesheetPendingApprovalStatusClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: isValidActionAfterTimesheetEnterThreshold, validActionPastThreshold: true });
253
- const transitionMovingTimesheetPendingApprovalStatusAutoClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: () => true, validActionPastThreshold: true });
244
+ const transitionMovingTimesheetPendingApprovalStatusClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: makeThresholdApplicability(true) });
245
+ const transitionMovingTimesheetPendingApprovalStatusAutoClassifyAction = createActionConfig(timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY, timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL, undefined, { isApplicable: makeThresholdApplicability(true) });
254
246
  exports.timesheetFlowConfig = {
255
247
  [timesheet_status_enum_1.TimesheetStatusEnum.ONTIME]: {
256
248
  actions: {
@@ -354,39 +346,20 @@ function isBackdated(data) {
354
346
  }
355
347
  return booleanValue;
356
348
  }
357
- function isValidActionAfterTimesheetEnterThreshold(data) {
358
- var _a;
359
- // here its assumed that record date is post threshold
360
- const { currentTimesheet, dto, timesheetEnterThresholdValue } = data;
361
- let isValidAction = true;
362
- if (currentTimesheet && dto && timesheetEnterThresholdValue) {
363
- const currentTimesheetRecordDate = new utils_1.DateCodeModel(currentTimesheet.dateCode);
364
- // subtract threshold days from today
349
+ function makeThresholdApplicability(availableWhenPastThreshold) {
350
+ return (data) => {
351
+ const { currentTimesheet, timesheetEnterThresholdValue } = data;
352
+ if (!currentTimesheet || !timesheetEnterThresholdValue) {
353
+ return true;
354
+ }
355
+ const recordDateModel = new utils_1.DateCodeModel(currentTimesheet.dateCode);
365
356
  const todayModel = new utils_1.DateCodeModel(utils_1.DateCodeModel.fromDate(new Date()));
366
- const dateUptoWhichTimesheetCanBeCreatedModel = todayModel.getByOffset(timesheetEnterThresholdValue);
367
- if (currentTimesheetRecordDate.dateCode < dateUptoWhichTimesheetCanBeCreatedModel.dateCode) {
368
- const actionConfig = exports.timesheetFlowConfig[currentTimesheet.status];
369
- if (!actionConfig) {
370
- return false;
371
- }
372
- if (!dto.actionData) {
373
- throw new exceptions_1.AppBadRequestException({
374
- key: error_key_enum_1.ErrorKeyEnum.ACTION_DATA,
375
- message: ["Action data is required when updating a timesheet."],
376
- });
377
- }
378
- // return actionConfig.validAction.includes(dto.actionData.action);
379
- const actionConfigForAction = actionConfig.actions[dto.actionData.action];
380
- if (!actionConfigForAction) {
381
- throw new exceptions_1.AppBadRequestException({
382
- key: error_key_enum_1.ErrorKeyEnum.ACTION,
383
- message: [`No config found for action: ${dto.actionData.action}. Please try with a valid action.`],
384
- });
385
- }
386
- isValidAction = (_a = actionConfigForAction.validActionPastThreshold) !== null && _a !== void 0 ? _a : true;
357
+ const thresholdDateModel = todayModel.getByOffset(timesheetEnterThresholdValue);
358
+ if (recordDateModel.dateCode < thresholdDateModel.dateCode) {
359
+ return availableWhenPastThreshold;
387
360
  }
388
- }
389
- return isValidAction;
361
+ return true;
362
+ };
390
363
  }
391
364
  function getNextStatusForMovingTimesheetActions(contextData) {
392
365
  var _a;
@@ -1,4 +1,6 @@
1
+ import { ITimesheetEntityUpdateDto, ITimesheetFlowContextData } from "../../api";
1
2
  import { CurrencyEnum } from "../../enums";
3
+ import { TimesheetActionEnum } from "../enums/timesheet_action_enum";
2
4
  import { TimesheetClassificationStatusEnum } from "../enums/timesheet_classification_status_enum";
3
5
  import { TimesheetStatusEnum } from "../enums/timesheet_status_enum";
4
6
  import { IDesignationEntity } from "../interface/designation.entity.interface";
@@ -23,6 +25,11 @@ export interface SortableTimesheet {
23
25
  resourceName?: string;
24
26
  dateCodeForsorting?: Date;
25
27
  }
28
+ export interface ITimesheetActionItem {
29
+ label: string;
30
+ value: TimesheetActionEnum | string;
31
+ disabled?: boolean;
32
+ }
26
33
  export declare class TimesheetEntityModel extends BaseEntityModel<EntityEnum.TIMESHEET> implements ITimesheetEntity {
27
34
  id: number;
28
35
  userId: number;
@@ -51,6 +58,13 @@ export declare class TimesheetEntityModel extends BaseEntityModel<EntityEnum.TIM
51
58
  getTimesheetAmount(projectCurrency: CurrencyEnum): number;
52
59
  get formattedDate(): string;
53
60
  get userName(): string | undefined;
61
+ getUpdateActionVisibility(currentUser: UserEntityModel, currentUserProjectIds?: number[]): {
62
+ [key: string]: () => boolean;
63
+ };
64
+ getAvailableActions(currentUser: UserEntityModel, contextData?: ITimesheetFlowContextData, currentUserProjectIds?: number[], options?: {
65
+ mergeApproveReject?: boolean;
66
+ }): ITimesheetActionItem[];
67
+ getNextStatus(currentUser: UserEntityModel, dto: ITimesheetEntityUpdateDto, contextData?: ITimesheetFlowContextData): TimesheetStatusEnum;
54
68
  get approvers(): UserEntityModel[];
55
69
  get latestStatusFromHistory(): TimesheetStatusEnum | undefined;
56
70
  getDateCodeDay(): number;
@@ -2,10 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TimesheetEntityModel = void 0;
4
4
  const date_fns_1 = require("date-fns");
5
+ const error_key_enum_1 = require("../../enums/error.key.enum");
6
+ const exceptions_1 = require("../../exceptions");
5
7
  const utils_1 = require("../../utils");
6
8
  const relation_type_enum_1 = require("../enums/relation_type_enum");
9
+ const timesheet_action_enum_1 = require("../enums/timesheet_action_enum");
7
10
  const timesheet_classification_status_enum_1 = require("../enums/timesheet_classification_status_enum");
8
11
  const timesheet_status_enum_1 = require("../enums/timesheet_status_enum");
12
+ const timesheet_flow_config_1 = require("../flow-configs/timesheet_flow.config");
9
13
  const entity_utils_interface_1 = require("../interface/entity.utils.interface");
10
14
  const base_entity_model_1 = require("./base.entity.model");
11
15
  // Use this mdoel for UI & BACKEND
@@ -45,6 +49,148 @@ class TimesheetEntityModel extends base_entity_model_1.BaseEntityModel {
45
49
  var _a;
46
50
  return (_a = this.user) === null || _a === void 0 ? void 0 : _a.name;
47
51
  }
52
+ getUpdateActionVisibility(currentUser, currentUserProjectIds) {
53
+ var _a, _b;
54
+ const projectIds = (_b = currentUserProjectIds !== null && currentUserProjectIds !== void 0 ? currentUserProjectIds : (_a = currentUser.projecUserMappings) === null || _a === void 0 ? void 0 : _a.map((mapping) => mapping.projectId)) !== null && _b !== void 0 ? _b : [];
55
+ const isProjectOwnerOrSelf = (projectId) => projectIds.includes(projectId) || currentUser.id === this.userId;
56
+ return {
57
+ TIMESHEETS_UPDATE_SELF: () => currentUser.id === this.userId,
58
+ TIMESHEETS_UPDATE_ORG: () => true,
59
+ TIMESHEETS_UPDATE_PROJECT: () => isProjectOwnerOrSelf(this.projectId),
60
+ TIMESHEETS_DELETE_SELF: () => currentUser.id === this.userId,
61
+ TIMESHEETS_DELETE_PROJECT: () => isProjectOwnerOrSelf(this.projectId),
62
+ TIMESHEETS_DELETE_ORG: () => true,
63
+ TIMESHEETS_APPROVER_PROJECT: () => isProjectOwnerOrSelf(this.projectId),
64
+ TIMESHEETS_APPROVER_ORG: () => true,
65
+ };
66
+ }
67
+ getAvailableActions(currentUser, contextData = {}, currentUserProjectIds, options) {
68
+ var _a, _b;
69
+ if (this.status === timesheet_status_enum_1.TimesheetStatusEnum.MOVING_TIMESHEET_PENDING_APPROVAL)
70
+ return [];
71
+ const flowForStatus = timesheet_flow_config_1.timesheetFlowConfig[this.status];
72
+ if (!flowForStatus)
73
+ return [];
74
+ const actionVisibilityConfig = this.getUpdateActionVisibility(currentUser, currentUserProjectIds);
75
+ const context = Object.assign(Object.assign({}, contextData), { currentTimesheet: this });
76
+ let actions = Object.entries(flowForStatus.actions)
77
+ .filter(([actionKey]) => {
78
+ if ([timesheet_action_enum_1.TimesheetActionEnum.BILLING_UPDATE, timesheet_action_enum_1.TimesheetActionEnum.MOVE_TIMESHEET, timesheet_action_enum_1.TimesheetActionEnum.AUTO_CLASSIFY].includes(actionKey))
79
+ return false;
80
+ if (actionKey === timesheet_action_enum_1.TimesheetActionEnum.CLASSIFY)
81
+ return false;
82
+ if (actionKey === timesheet_action_enum_1.TimesheetActionEnum.UPDATE && this.classificationStatus && this.classificationStatus !== timesheet_classification_status_enum_1.TimesheetClassificationStatusEnum.PENDING)
83
+ return false;
84
+ return true;
85
+ })
86
+ .map(([actionKey, actionConfig]) => {
87
+ const actionPermissions = actionConfig.permissions || [];
88
+ const hasPermission = actionPermissions.some((permission) => { var _a; return (_a = currentUser.permissions) === null || _a === void 0 ? void 0 : _a.includes(permission); });
89
+ if (!hasPermission)
90
+ return null;
91
+ const isVisible = actionPermissions.some((permission) => actionVisibilityConfig[permission] && actionVisibilityConfig[permission]());
92
+ if (!isVisible)
93
+ return null;
94
+ // LW-830 collapse: `isApplicable` is the single runtime guard. Previous 3-way logic
95
+ // (isAvailable ?? isApplicable, else validActionPastThreshold ?? true) is commented out:
96
+ // const predicate = actionConfig.isAvailable ?? actionConfig.isApplicable;
97
+ // let available = true;
98
+ // if (predicate) {
99
+ // available = predicate(context);
100
+ // } else {
101
+ // available = actionConfig.validActionPastThreshold ?? true;
102
+ // }
103
+ const predicate = actionConfig.isApplicable;
104
+ let available = true;
105
+ if (predicate) {
106
+ available = predicate(context);
107
+ }
108
+ return {
109
+ label: timesheet_action_enum_1.TimesheetActionEnum.getLabel(actionKey),
110
+ value: actionKey,
111
+ disabled: !available,
112
+ };
113
+ })
114
+ .filter(Boolean);
115
+ const hasApprove = actions.some((a) => a.value === timesheet_action_enum_1.TimesheetActionEnum.APPROVE);
116
+ const hasReject = actions.some((a) => a.value === timesheet_action_enum_1.TimesheetActionEnum.REJECT);
117
+ if ((options === null || options === void 0 ? void 0 : options.mergeApproveReject) !== false && hasApprove && hasReject) {
118
+ const approveDisabled = (_a = actions.find((a) => a.value === timesheet_action_enum_1.TimesheetActionEnum.APPROVE)) === null || _a === void 0 ? void 0 : _a.disabled;
119
+ const rejectDisabled = (_b = actions.find((a) => a.value === timesheet_action_enum_1.TimesheetActionEnum.REJECT)) === null || _b === void 0 ? void 0 : _b.disabled;
120
+ actions = actions.filter((a) => a.value !== timesheet_action_enum_1.TimesheetActionEnum.APPROVE && a.value !== timesheet_action_enum_1.TimesheetActionEnum.REJECT);
121
+ actions.push({
122
+ label: "Approve/Reject",
123
+ value: "APPROVE_REJECT",
124
+ disabled: approveDisabled && rejectDisabled,
125
+ });
126
+ }
127
+ return actions;
128
+ }
129
+ getNextStatus(currentUser, dto, contextData = {}) {
130
+ if (!this.status) {
131
+ throw new exceptions_1.AppBadRequestException({
132
+ key: error_key_enum_1.ErrorKeyEnum.STATUS,
133
+ message: [`Status not found: ${this.status}`],
134
+ });
135
+ }
136
+ const flowForStatus = timesheet_flow_config_1.timesheetFlowConfig[this.status];
137
+ if (!flowForStatus) {
138
+ throw new exceptions_1.AppBadRequestException({
139
+ key: error_key_enum_1.ErrorKeyEnum.STATUS,
140
+ message: [`No flow configuration found for status: ${this.status}`],
141
+ });
142
+ }
143
+ if (!(dto === null || dto === void 0 ? void 0 : dto.actionData)) {
144
+ throw new exceptions_1.AppBadRequestException({
145
+ key: error_key_enum_1.ErrorKeyEnum.ACTION_DATA,
146
+ message: ["dto.actionData is required"],
147
+ });
148
+ }
149
+ const matchingPermissionConfig = flowForStatus.actions[dto.actionData.action];
150
+ if (!matchingPermissionConfig) {
151
+ throw new exceptions_1.AppBadRequestException({
152
+ key: error_key_enum_1.ErrorKeyEnum.ACTION,
153
+ message: [`Action '${dto.actionData.action}' is not valid for the current status '${this.status}'.`],
154
+ });
155
+ }
156
+ if (!currentUser.permissions || currentUser.permissions.length === 0) {
157
+ throw new exceptions_1.AppBadRequestException({
158
+ key: error_key_enum_1.ErrorKeyEnum.PERMISSIONS,
159
+ message: ["Permission not available for currentUser"],
160
+ });
161
+ }
162
+ const hasPermission = matchingPermissionConfig.permissions.some((permission) => { var _a; return (_a = currentUser.permissions) === null || _a === void 0 ? void 0 : _a.includes(permission); });
163
+ if (!hasPermission) {
164
+ throw new exceptions_1.AppBadRequestException({
165
+ key: error_key_enum_1.ErrorKeyEnum.PERMISSION,
166
+ message: [
167
+ `Current user does not have permission to ${dto.actionData.action} timesheet. Required permissions: ${matchingPermissionConfig.permissions.join(", ")}`,
168
+ ],
169
+ });
170
+ }
171
+ const context = Object.assign(Object.assign({}, contextData), { currentTimesheet: this });
172
+ // LW-830 collapse: `isApplicable` is the single runtime guard.
173
+ // const predicate = matchingPermissionConfig.isAvailable ?? matchingPermissionConfig.isApplicable;
174
+ // if (predicate) {
175
+ // const isActionAvailable = predicate(context);
176
+ // if (!isActionAvailable) {
177
+ // throw new AppBadRequestException({
178
+ // key: ErrorKeyEnum.ACTION,
179
+ // message: [`Action ${dto.actionData.action} is not applicable for current timesheet status: ${this.status}.`],
180
+ // });
181
+ // }
182
+ // }
183
+ if (matchingPermissionConfig.isApplicable) {
184
+ const isApplicable = matchingPermissionConfig.isApplicable(context);
185
+ if (!isApplicable) {
186
+ throw new exceptions_1.AppBadRequestException({
187
+ key: error_key_enum_1.ErrorKeyEnum.ACTION,
188
+ message: [`Action ${dto.actionData.action} is not applicable for current timesheet status: ${this.status}.`],
189
+ });
190
+ }
191
+ }
192
+ return matchingPermissionConfig.next(context);
193
+ }
48
194
  get approvers() {
49
195
  var _a, _b, _c;
50
196
  return (_c = (_b = (_a = this.project) === null || _a === void 0 ? void 0 : _a.partnersOwnerUsersOfProject) === null || _b === void 0 ? void 0 : _b.filter((u) => u.email)) !== null && _c !== void 0 ? _c : [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "law-common",
3
- "version": "14.0.1-beta.4",
3
+ "version": "14.0.1-beta.6",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "files": [