av6-core 1.0.19 → 1.0.21
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.d.mts +82 -1
- package/dist/index.d.ts +82 -1
- package/dist/index.js +226 -4
- package/dist/index.mjs +224 -4
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -266,6 +266,87 @@ declare const getPattern: {
|
|
|
266
266
|
};
|
|
267
267
|
declare const interpolate: (template: string, vars: Record<string, unknown>) => string;
|
|
268
268
|
|
|
269
|
+
type DiscountMode = "PERCENTAGE" | "AMOUNT";
|
|
270
|
+
type AdditionalDiscountMode = "PERCENTAGE" | "AMOUNT";
|
|
271
|
+
type CoPayType = "PERCENTAGE" | "AMOUNT";
|
|
272
|
+
type TaxMethod = "NONE" | "INCLUSIVE" | "EXCLUSIVE";
|
|
273
|
+
type CoPayMode = "EXCLUSIVE-INCLUSIVE" | "PERCENTAGE-AMOUNT";
|
|
274
|
+
declare const RoundFormat: {
|
|
275
|
+
readonly ROUND: "ROUND";
|
|
276
|
+
readonly SPECIAL_ROUND: "SPECIAL_ROUND";
|
|
277
|
+
readonly TO_FIXED: "TO_FIXED";
|
|
278
|
+
readonly CEIL: "CEIL";
|
|
279
|
+
readonly FLOOR: "FLOOR";
|
|
280
|
+
readonly TRUNC: "TRUNC";
|
|
281
|
+
readonly NONE: "NONE";
|
|
282
|
+
};
|
|
283
|
+
type RoundFormat = (typeof RoundFormat)[keyof typeof RoundFormat];
|
|
284
|
+
interface ChildCalcInput {
|
|
285
|
+
qty: number;
|
|
286
|
+
rate: number;
|
|
287
|
+
otherCharge?: number;
|
|
288
|
+
addonPercentage?: number;
|
|
289
|
+
discountMode?: DiscountMode;
|
|
290
|
+
discountValue?: number;
|
|
291
|
+
taxMethod?: TaxMethod;
|
|
292
|
+
taxValue?: number;
|
|
293
|
+
/** Absolute insurer-covered at line level */
|
|
294
|
+
coPaymentType?: CoPayType;
|
|
295
|
+
coPayValue?: number;
|
|
296
|
+
coPayMethod?: "EXCLUSIVE" | "INCLUSIVE";
|
|
297
|
+
}
|
|
298
|
+
interface MasterAdditionalDiscount {
|
|
299
|
+
mode: AdditionalDiscountMode;
|
|
300
|
+
value: number;
|
|
301
|
+
coPayMode?: CoPayMode;
|
|
302
|
+
}
|
|
303
|
+
interface CalcOptions {
|
|
304
|
+
calculationMethod?: "STEP_WISE" | "FINAL_ONLY";
|
|
305
|
+
lineRound?: RoundFormat;
|
|
306
|
+
headerRound?: RoundFormat;
|
|
307
|
+
precision?: number;
|
|
308
|
+
}
|
|
309
|
+
interface ChildCalculated {
|
|
310
|
+
baseRate: number;
|
|
311
|
+
subtotalAmount: number;
|
|
312
|
+
otherChargeAmount: number;
|
|
313
|
+
discountMode: DiscountMode;
|
|
314
|
+
discountValue: number;
|
|
315
|
+
discountAmount: number;
|
|
316
|
+
taxMethod: TaxMethod;
|
|
317
|
+
taxValue: number;
|
|
318
|
+
taxAmount: number;
|
|
319
|
+
grossAmount: number;
|
|
320
|
+
netAmount: number;
|
|
321
|
+
roundOffAmount: number;
|
|
322
|
+
copayAmount: number;
|
|
323
|
+
}
|
|
324
|
+
interface MasterCalculated {
|
|
325
|
+
additionalDiscountMode: AdditionalDiscountMode;
|
|
326
|
+
additionalDiscountValue: number;
|
|
327
|
+
subtotalAmount: number;
|
|
328
|
+
otherChargeAmount: number;
|
|
329
|
+
discountTotalAmount: number;
|
|
330
|
+
taxAmount: number;
|
|
331
|
+
grossAmount: number;
|
|
332
|
+
netAmount: number;
|
|
333
|
+
roundOffAmount: number;
|
|
334
|
+
copayAmount: number;
|
|
335
|
+
}
|
|
336
|
+
interface BillingCalcResult {
|
|
337
|
+
master: MasterCalculated;
|
|
338
|
+
children: ChildCalculated[];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* calculateBillingFromChildren (master-level additional discount applied AFTER child calculations)
|
|
343
|
+
* - Child: item discount -> tax (INCLUSIVE/EXCLUSIVE/NONE) -> coPay -> patient line net + line rounding
|
|
344
|
+
* - Master: sum children, compute patientRawTotal = gross - Σcopay
|
|
345
|
+
* apply master additional discount ON patientRawTotal (no propagation to children)
|
|
346
|
+
* then header rounding
|
|
347
|
+
*/
|
|
348
|
+
declare function calculateBillingFromChildren(inputs: ChildCalcInput[], masterExtra: MasterAdditionalDiscount, options?: CalcOptions): BillingCalcResult;
|
|
349
|
+
|
|
269
350
|
interface CreateTransaction {
|
|
270
351
|
field: string;
|
|
271
352
|
changedFrom?: string | null;
|
|
@@ -385,4 +466,4 @@ declare class NotificationEmitter {
|
|
|
385
466
|
notifyNow(eventName: string, body: Omit<EmitPayload, "eventName">): Promise<void>;
|
|
386
467
|
}
|
|
387
468
|
|
|
388
|
-
export { type CacheAdapter, type CalculationRes, type ColValue, type CommonExcelRequest, type CommonFilterRequest, type CommonServiceResponse, type Config, type Context, type CreateUINConfigRequest, type DataType, type DeleteParams, type DeleteRequestRepository, type Deps, type DropdownRequest, type DropdownRequestService, type DynamicShortCode, type EmitPayload, type ExcelConfig, type ExportExcel, type ExportExcelRequestService, type FetchRequest, type FetchRequestRepository, type FixedSearchRequest, type FixedSearchRequestService, type Helpers, type ImportExcel, type ImportExcelRequestService, type Mapper, type NewFixedSearchRequest, type NewFixedSearchRequestService, type NewSearchRequest, NotificationEmitter, type PaginatedResponse, type Recipient, type SearchRequest, type SearchRequestService, type ServiceCacheAdapter, type Store, type ToggleActive, type UINConfigDTO, type UINPreviewRequest, type UINSegment, type UINSegmentType, type UIN_RESET_POLICY, type UinDeps, type UpdateStatusRequestRepository, type UpdateUINConfigRequest, type ValidationErrorItem, commonService, customOmit, findDifferences, getDynamicValue, getPattern, interpolate, objectTo2DArray, toNumberOrNull, toUINConfigDTO, uinConfigService, type updateStatusParams };
|
|
469
|
+
export { type AdditionalDiscountMode, type BillingCalcResult, type CacheAdapter, type CalcOptions, type CalculationRes, type ChildCalcInput, type ChildCalculated, type CoPayMode, type CoPayType, type ColValue, type CommonExcelRequest, type CommonFilterRequest, type CommonServiceResponse, type Config, type Context, type CreateUINConfigRequest, type DataType, type DeleteParams, type DeleteRequestRepository, type Deps, type DiscountMode, type DropdownRequest, type DropdownRequestService, type DynamicShortCode, type EmitPayload, type ExcelConfig, type ExportExcel, type ExportExcelRequestService, type FetchRequest, type FetchRequestRepository, type FixedSearchRequest, type FixedSearchRequestService, type Helpers, type ImportExcel, type ImportExcelRequestService, type Mapper, type MasterAdditionalDiscount, type MasterCalculated, type NewFixedSearchRequest, type NewFixedSearchRequestService, type NewSearchRequest, NotificationEmitter, type PaginatedResponse, type Recipient, RoundFormat, type SearchRequest, type SearchRequestService, type ServiceCacheAdapter, type Store, type TaxMethod, type ToggleActive, type UINConfigDTO, type UINPreviewRequest, type UINSegment, type UINSegmentType, type UIN_RESET_POLICY, type UinDeps, type UpdateStatusRequestRepository, type UpdateUINConfigRequest, type ValidationErrorItem, calculateBillingFromChildren, commonService, customOmit, findDifferences, getDynamicValue, getPattern, interpolate, objectTo2DArray, toNumberOrNull, toUINConfigDTO, uinConfigService, type updateStatusParams };
|
package/dist/index.d.ts
CHANGED
|
@@ -266,6 +266,87 @@ declare const getPattern: {
|
|
|
266
266
|
};
|
|
267
267
|
declare const interpolate: (template: string, vars: Record<string, unknown>) => string;
|
|
268
268
|
|
|
269
|
+
type DiscountMode = "PERCENTAGE" | "AMOUNT";
|
|
270
|
+
type AdditionalDiscountMode = "PERCENTAGE" | "AMOUNT";
|
|
271
|
+
type CoPayType = "PERCENTAGE" | "AMOUNT";
|
|
272
|
+
type TaxMethod = "NONE" | "INCLUSIVE" | "EXCLUSIVE";
|
|
273
|
+
type CoPayMode = "EXCLUSIVE-INCLUSIVE" | "PERCENTAGE-AMOUNT";
|
|
274
|
+
declare const RoundFormat: {
|
|
275
|
+
readonly ROUND: "ROUND";
|
|
276
|
+
readonly SPECIAL_ROUND: "SPECIAL_ROUND";
|
|
277
|
+
readonly TO_FIXED: "TO_FIXED";
|
|
278
|
+
readonly CEIL: "CEIL";
|
|
279
|
+
readonly FLOOR: "FLOOR";
|
|
280
|
+
readonly TRUNC: "TRUNC";
|
|
281
|
+
readonly NONE: "NONE";
|
|
282
|
+
};
|
|
283
|
+
type RoundFormat = (typeof RoundFormat)[keyof typeof RoundFormat];
|
|
284
|
+
interface ChildCalcInput {
|
|
285
|
+
qty: number;
|
|
286
|
+
rate: number;
|
|
287
|
+
otherCharge?: number;
|
|
288
|
+
addonPercentage?: number;
|
|
289
|
+
discountMode?: DiscountMode;
|
|
290
|
+
discountValue?: number;
|
|
291
|
+
taxMethod?: TaxMethod;
|
|
292
|
+
taxValue?: number;
|
|
293
|
+
/** Absolute insurer-covered at line level */
|
|
294
|
+
coPaymentType?: CoPayType;
|
|
295
|
+
coPayValue?: number;
|
|
296
|
+
coPayMethod?: "EXCLUSIVE" | "INCLUSIVE";
|
|
297
|
+
}
|
|
298
|
+
interface MasterAdditionalDiscount {
|
|
299
|
+
mode: AdditionalDiscountMode;
|
|
300
|
+
value: number;
|
|
301
|
+
coPayMode?: CoPayMode;
|
|
302
|
+
}
|
|
303
|
+
interface CalcOptions {
|
|
304
|
+
calculationMethod?: "STEP_WISE" | "FINAL_ONLY";
|
|
305
|
+
lineRound?: RoundFormat;
|
|
306
|
+
headerRound?: RoundFormat;
|
|
307
|
+
precision?: number;
|
|
308
|
+
}
|
|
309
|
+
interface ChildCalculated {
|
|
310
|
+
baseRate: number;
|
|
311
|
+
subtotalAmount: number;
|
|
312
|
+
otherChargeAmount: number;
|
|
313
|
+
discountMode: DiscountMode;
|
|
314
|
+
discountValue: number;
|
|
315
|
+
discountAmount: number;
|
|
316
|
+
taxMethod: TaxMethod;
|
|
317
|
+
taxValue: number;
|
|
318
|
+
taxAmount: number;
|
|
319
|
+
grossAmount: number;
|
|
320
|
+
netAmount: number;
|
|
321
|
+
roundOffAmount: number;
|
|
322
|
+
copayAmount: number;
|
|
323
|
+
}
|
|
324
|
+
interface MasterCalculated {
|
|
325
|
+
additionalDiscountMode: AdditionalDiscountMode;
|
|
326
|
+
additionalDiscountValue: number;
|
|
327
|
+
subtotalAmount: number;
|
|
328
|
+
otherChargeAmount: number;
|
|
329
|
+
discountTotalAmount: number;
|
|
330
|
+
taxAmount: number;
|
|
331
|
+
grossAmount: number;
|
|
332
|
+
netAmount: number;
|
|
333
|
+
roundOffAmount: number;
|
|
334
|
+
copayAmount: number;
|
|
335
|
+
}
|
|
336
|
+
interface BillingCalcResult {
|
|
337
|
+
master: MasterCalculated;
|
|
338
|
+
children: ChildCalculated[];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* calculateBillingFromChildren (master-level additional discount applied AFTER child calculations)
|
|
343
|
+
* - Child: item discount -> tax (INCLUSIVE/EXCLUSIVE/NONE) -> coPay -> patient line net + line rounding
|
|
344
|
+
* - Master: sum children, compute patientRawTotal = gross - Σcopay
|
|
345
|
+
* apply master additional discount ON patientRawTotal (no propagation to children)
|
|
346
|
+
* then header rounding
|
|
347
|
+
*/
|
|
348
|
+
declare function calculateBillingFromChildren(inputs: ChildCalcInput[], masterExtra: MasterAdditionalDiscount, options?: CalcOptions): BillingCalcResult;
|
|
349
|
+
|
|
269
350
|
interface CreateTransaction {
|
|
270
351
|
field: string;
|
|
271
352
|
changedFrom?: string | null;
|
|
@@ -385,4 +466,4 @@ declare class NotificationEmitter {
|
|
|
385
466
|
notifyNow(eventName: string, body: Omit<EmitPayload, "eventName">): Promise<void>;
|
|
386
467
|
}
|
|
387
468
|
|
|
388
|
-
export { type CacheAdapter, type CalculationRes, type ColValue, type CommonExcelRequest, type CommonFilterRequest, type CommonServiceResponse, type Config, type Context, type CreateUINConfigRequest, type DataType, type DeleteParams, type DeleteRequestRepository, type Deps, type DropdownRequest, type DropdownRequestService, type DynamicShortCode, type EmitPayload, type ExcelConfig, type ExportExcel, type ExportExcelRequestService, type FetchRequest, type FetchRequestRepository, type FixedSearchRequest, type FixedSearchRequestService, type Helpers, type ImportExcel, type ImportExcelRequestService, type Mapper, type NewFixedSearchRequest, type NewFixedSearchRequestService, type NewSearchRequest, NotificationEmitter, type PaginatedResponse, type Recipient, type SearchRequest, type SearchRequestService, type ServiceCacheAdapter, type Store, type ToggleActive, type UINConfigDTO, type UINPreviewRequest, type UINSegment, type UINSegmentType, type UIN_RESET_POLICY, type UinDeps, type UpdateStatusRequestRepository, type UpdateUINConfigRequest, type ValidationErrorItem, commonService, customOmit, findDifferences, getDynamicValue, getPattern, interpolate, objectTo2DArray, toNumberOrNull, toUINConfigDTO, uinConfigService, type updateStatusParams };
|
|
469
|
+
export { type AdditionalDiscountMode, type BillingCalcResult, type CacheAdapter, type CalcOptions, type CalculationRes, type ChildCalcInput, type ChildCalculated, type CoPayMode, type CoPayType, type ColValue, type CommonExcelRequest, type CommonFilterRequest, type CommonServiceResponse, type Config, type Context, type CreateUINConfigRequest, type DataType, type DeleteParams, type DeleteRequestRepository, type Deps, type DiscountMode, type DropdownRequest, type DropdownRequestService, type DynamicShortCode, type EmitPayload, type ExcelConfig, type ExportExcel, type ExportExcelRequestService, type FetchRequest, type FetchRequestRepository, type FixedSearchRequest, type FixedSearchRequestService, type Helpers, type ImportExcel, type ImportExcelRequestService, type Mapper, type MasterAdditionalDiscount, type MasterCalculated, type NewFixedSearchRequest, type NewFixedSearchRequestService, type NewSearchRequest, NotificationEmitter, type PaginatedResponse, type Recipient, RoundFormat, type SearchRequest, type SearchRequestService, type ServiceCacheAdapter, type Store, type TaxMethod, type ToggleActive, type UINConfigDTO, type UINPreviewRequest, type UINSegment, type UINSegmentType, type UIN_RESET_POLICY, type UinDeps, type UpdateStatusRequestRepository, type UpdateUINConfigRequest, type ValidationErrorItem, calculateBillingFromChildren, commonService, customOmit, findDifferences, getDynamicValue, getPattern, interpolate, objectTo2DArray, toNumberOrNull, toUINConfigDTO, uinConfigService, type updateStatusParams };
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
NotificationEmitter: () => NotificationEmitter,
|
|
34
|
+
RoundFormat: () => RoundFormat,
|
|
35
|
+
calculateBillingFromChildren: () => calculateBillingFromChildren,
|
|
34
36
|
commonService: () => commonService,
|
|
35
37
|
customOmit: () => customOmit,
|
|
36
38
|
findDifferences: () => findDifferences,
|
|
@@ -1094,6 +1096,149 @@ var commonService = (serviceDeps) => {
|
|
|
1094
1096
|
};
|
|
1095
1097
|
};
|
|
1096
1098
|
|
|
1099
|
+
// src/types/calculation.ts
|
|
1100
|
+
var RoundFormat = {
|
|
1101
|
+
ROUND: "ROUND",
|
|
1102
|
+
SPECIAL_ROUND: "SPECIAL_ROUND",
|
|
1103
|
+
TO_FIXED: "TO_FIXED",
|
|
1104
|
+
CEIL: "CEIL",
|
|
1105
|
+
FLOOR: "FLOOR",
|
|
1106
|
+
TRUNC: "TRUNC",
|
|
1107
|
+
NONE: "NONE"
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
// src/utils/calculation.utils.ts
|
|
1111
|
+
function applyRound(value, format, precision = 2) {
|
|
1112
|
+
switch (format) {
|
|
1113
|
+
case RoundFormat.NONE:
|
|
1114
|
+
return value;
|
|
1115
|
+
case RoundFormat.ROUND:
|
|
1116
|
+
return Math.round(value);
|
|
1117
|
+
case RoundFormat.SPECIAL_ROUND:
|
|
1118
|
+
return value < 1 ? Math.ceil(value) : Math.round(value);
|
|
1119
|
+
case RoundFormat.CEIL:
|
|
1120
|
+
return Math.ceil(value);
|
|
1121
|
+
case RoundFormat.FLOOR:
|
|
1122
|
+
return Math.floor(value);
|
|
1123
|
+
case RoundFormat.TRUNC:
|
|
1124
|
+
return Math.trunc(value);
|
|
1125
|
+
case RoundFormat.TO_FIXED:
|
|
1126
|
+
default:
|
|
1127
|
+
return Number(value.toFixed(Math.max(0, precision | 0)));
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
var maybeStep = (v, stepwise, fmt, p) => stepwise ? applyRound(v, fmt, p) : v;
|
|
1131
|
+
function clamp(n, min, max) {
|
|
1132
|
+
return Math.max(min, Math.min(max, n));
|
|
1133
|
+
}
|
|
1134
|
+
function calculateBillingFromChildren(inputs, masterExtra, options = {}) {
|
|
1135
|
+
const stepwise = options.calculationMethod === "STEP_WISE";
|
|
1136
|
+
const lineFmt = options.lineRound ?? RoundFormat.TO_FIXED;
|
|
1137
|
+
const headFmt = options.headerRound ?? lineFmt;
|
|
1138
|
+
const precision = options.precision ?? 2;
|
|
1139
|
+
const children = inputs.map((it) => {
|
|
1140
|
+
let baseRate = it.rate;
|
|
1141
|
+
if (it.addonPercentage) {
|
|
1142
|
+
baseRate = baseRate + baseRate * (it.addonPercentage / 100);
|
|
1143
|
+
baseRate = maybeStep(baseRate, stepwise, lineFmt, precision);
|
|
1144
|
+
}
|
|
1145
|
+
const subtotal = applyRound(it.qty * baseRate, lineFmt, precision);
|
|
1146
|
+
const other = applyRound(it.otherCharge ?? 0, lineFmt, precision);
|
|
1147
|
+
const basePreTax = applyRound(subtotal + other, lineFmt, precision);
|
|
1148
|
+
let copayAmount2 = 0;
|
|
1149
|
+
if (masterExtra.coPayMode === "PERCENTAGE-AMOUNT") {
|
|
1150
|
+
const copayType = it.coPaymentType ?? "AMOUNT";
|
|
1151
|
+
const copayValue = it.coPayValue ?? 0;
|
|
1152
|
+
copayAmount2 = copayType === "PERCENTAGE" ? basePreTax * (copayValue / 100) : Math.min(copayValue, basePreTax);
|
|
1153
|
+
}
|
|
1154
|
+
const dMode = it.discountMode ?? "AMOUNT";
|
|
1155
|
+
const dVal = it.discountValue ?? 0;
|
|
1156
|
+
const tMethod = it.taxMethod ?? "NONE";
|
|
1157
|
+
const tVal = Math.max(0, it.taxValue ?? 0);
|
|
1158
|
+
let baseAfterTaxDisc = 0;
|
|
1159
|
+
let discountValue = 0;
|
|
1160
|
+
if (tMethod === "INCLUSIVE") {
|
|
1161
|
+
const inclusiveTaxMultiplier = (100 + tVal) / 100;
|
|
1162
|
+
baseAfterTaxDisc = basePreTax / inclusiveTaxMultiplier;
|
|
1163
|
+
}
|
|
1164
|
+
baseAfterTaxDisc = maybeStep(baseAfterTaxDisc, stepwise, lineFmt, precision);
|
|
1165
|
+
if (dMode === "AMOUNT") {
|
|
1166
|
+
discountValue = dVal;
|
|
1167
|
+
} else if (dMode === "PERCENTAGE") {
|
|
1168
|
+
discountValue = baseAfterTaxDisc * dVal / 100;
|
|
1169
|
+
}
|
|
1170
|
+
discountValue = maybeStep(discountValue, stepwise, lineFmt, precision);
|
|
1171
|
+
let afterDisc = Math.max(0, baseAfterTaxDisc - discountValue);
|
|
1172
|
+
afterDisc = maybeStep(afterDisc, stepwise, lineFmt, precision);
|
|
1173
|
+
let calculatedTax = tVal * afterDisc / 100;
|
|
1174
|
+
calculatedTax = maybeStep(calculatedTax, stepwise, lineFmt, precision);
|
|
1175
|
+
let grossAmount2 = afterDisc + calculatedTax;
|
|
1176
|
+
grossAmount2 = maybeStep(grossAmount2, stepwise, lineFmt, precision);
|
|
1177
|
+
if (masterExtra.coPayMode === "EXCLUSIVE-INCLUSIVE" && it.coPayMethod === "INCLUSIVE") {
|
|
1178
|
+
copayAmount2 = grossAmount2;
|
|
1179
|
+
}
|
|
1180
|
+
copayAmount2 = applyRound(copayAmount2, lineFmt, precision);
|
|
1181
|
+
const copay = clamp(Math.max(0, copayAmount2), 0, grossAmount2);
|
|
1182
|
+
const patientRaw = grossAmount2 - copay;
|
|
1183
|
+
const patientRounded = applyRound(patientRaw, lineFmt, precision);
|
|
1184
|
+
const lineRoundOff = patientRounded - patientRaw;
|
|
1185
|
+
return {
|
|
1186
|
+
baseRate: applyRound(baseRate, lineFmt, precision),
|
|
1187
|
+
subtotalAmount: subtotal,
|
|
1188
|
+
otherChargeAmount: other,
|
|
1189
|
+
discountMode: dMode,
|
|
1190
|
+
discountValue: dVal,
|
|
1191
|
+
discountAmount: applyRound(discountValue, lineFmt, precision),
|
|
1192
|
+
// item-only discount
|
|
1193
|
+
taxMethod: tMethod,
|
|
1194
|
+
taxValue: tVal,
|
|
1195
|
+
taxAmount: applyRound(calculatedTax, lineFmt, precision),
|
|
1196
|
+
grossAmount: applyRound(grossAmount2, lineFmt, precision),
|
|
1197
|
+
netAmount: patientRounded,
|
|
1198
|
+
// patient payable at line
|
|
1199
|
+
roundOffAmount: applyRound(lineRoundOff, "TO_FIXED", 2),
|
|
1200
|
+
// rounded - raw
|
|
1201
|
+
copayAmount: copay
|
|
1202
|
+
// insurer covered at line
|
|
1203
|
+
};
|
|
1204
|
+
});
|
|
1205
|
+
const subtotalAmount = children.reduce((a, c) => a + c.subtotalAmount, 0);
|
|
1206
|
+
const otherChargeAmount = children.reduce((a, c) => a + c.otherChargeAmount, 0);
|
|
1207
|
+
const itemDiscountSum = children.reduce((a, c) => a + c.discountAmount, 0);
|
|
1208
|
+
const taxAmount = children.reduce((a, c) => a + c.taxAmount, 0);
|
|
1209
|
+
const grossAmount = children.reduce((a, c) => a + c.grossAmount, 0);
|
|
1210
|
+
const copayAmount = children.reduce((a, c) => a + c.copayAmount, 0);
|
|
1211
|
+
const patientRawTotal = grossAmount - copayAmount;
|
|
1212
|
+
let masterExtraApplied = 0;
|
|
1213
|
+
if (masterExtra.mode === "PERCENTAGE") {
|
|
1214
|
+
masterExtraApplied = patientRawTotal * (masterExtra.value / 100);
|
|
1215
|
+
} else if (masterExtra.mode === "AMOUNT") {
|
|
1216
|
+
masterExtraApplied = Math.min(masterExtra.value, patientRawTotal);
|
|
1217
|
+
}
|
|
1218
|
+
masterExtraApplied = maybeStep(masterExtraApplied, stepwise, headFmt, precision);
|
|
1219
|
+
const discountTotalAmount = maybeStep(itemDiscountSum + masterExtraApplied, stepwise, headFmt, precision);
|
|
1220
|
+
const patientAfterExtra = Math.max(0, patientRawTotal - masterExtraApplied);
|
|
1221
|
+
const netAmount = applyRound(patientAfterExtra, headFmt, precision);
|
|
1222
|
+
const roundOffAmount = netAmount - patientAfterExtra;
|
|
1223
|
+
const master = {
|
|
1224
|
+
additionalDiscountMode: masterExtra.mode,
|
|
1225
|
+
additionalDiscountValue: masterExtra.value,
|
|
1226
|
+
subtotalAmount: applyRound(subtotalAmount, headFmt, precision),
|
|
1227
|
+
otherChargeAmount: applyRound(otherChargeAmount, headFmt, precision),
|
|
1228
|
+
discountTotalAmount: applyRound(discountTotalAmount, headFmt, precision),
|
|
1229
|
+
// includes master extra
|
|
1230
|
+
taxAmount: applyRound(taxAmount, headFmt, precision),
|
|
1231
|
+
grossAmount: applyRound(grossAmount, headFmt, precision),
|
|
1232
|
+
netAmount,
|
|
1233
|
+
// patient payable after master extra + rounding
|
|
1234
|
+
roundOffAmount: applyRound(roundOffAmount, "TO_FIXED", 2),
|
|
1235
|
+
// rounded - raw
|
|
1236
|
+
copayAmount: applyRound(copayAmount, headFmt, precision)
|
|
1237
|
+
// Σ line copay (insurer covered)
|
|
1238
|
+
};
|
|
1239
|
+
return { master, children };
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1097
1242
|
// src/utils/audit.utils.ts
|
|
1098
1243
|
function isValidDate(value) {
|
|
1099
1244
|
if (value instanceof Date) {
|
|
@@ -1544,8 +1689,32 @@ var EmailProvider = class {
|
|
|
1544
1689
|
|
|
1545
1690
|
// src/providers/sms.provider.ts
|
|
1546
1691
|
var SmsProvider = class {
|
|
1692
|
+
constructor(logger = console, args) {
|
|
1693
|
+
this.logger = logger;
|
|
1694
|
+
this.args = args;
|
|
1695
|
+
this.logger.info(`[NotificationService] args for sms : ${JSON.stringify(args)}`);
|
|
1696
|
+
this.url = args.apiUrl || "https://web.nsemfua.com/api/http/sms/send";
|
|
1697
|
+
this.token = "13|R3XfH6wHx3zdflBzpGMlNiTWKuOUEkGutORyGcuS";
|
|
1698
|
+
this.sender = args.senderId || "AlmaMedLab";
|
|
1699
|
+
}
|
|
1700
|
+
url;
|
|
1701
|
+
token;
|
|
1702
|
+
sender;
|
|
1547
1703
|
async send(args) {
|
|
1548
|
-
|
|
1704
|
+
const to = args.recipient;
|
|
1705
|
+
if (!to) {
|
|
1706
|
+
return {
|
|
1707
|
+
ok: false,
|
|
1708
|
+
provider: "SMS" /* SMS */,
|
|
1709
|
+
error: "Missing recipient.phone"
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
const allRecipients = [to];
|
|
1713
|
+
if (this.args.masterNumber) {
|
|
1714
|
+
allRecipients.push({ phone: this.args.masterNumber });
|
|
1715
|
+
}
|
|
1716
|
+
const recipients = this.normalizeRecipients(allRecipients);
|
|
1717
|
+
if (!recipients.length) {
|
|
1549
1718
|
return {
|
|
1550
1719
|
ok: false,
|
|
1551
1720
|
provider: "SMS" /* SMS */,
|
|
@@ -1553,11 +1722,41 @@ var SmsProvider = class {
|
|
|
1553
1722
|
};
|
|
1554
1723
|
}
|
|
1555
1724
|
try {
|
|
1556
|
-
const
|
|
1725
|
+
const payload = {
|
|
1726
|
+
api_token: this.token,
|
|
1727
|
+
recipient: recipients.join(", "),
|
|
1728
|
+
// API accepts comma-separated list
|
|
1729
|
+
sender_id: this.sender,
|
|
1730
|
+
type: "plain",
|
|
1731
|
+
message: this.cleanMessage(args.body)
|
|
1732
|
+
// schedule_time: "" // add if you need scheduling
|
|
1733
|
+
};
|
|
1734
|
+
const res = await fetch(this.url, {
|
|
1735
|
+
method: "POST",
|
|
1736
|
+
headers: {
|
|
1737
|
+
"Content-Type": "application/json",
|
|
1738
|
+
Accept: "application/json"
|
|
1739
|
+
},
|
|
1740
|
+
body: JSON.stringify(payload)
|
|
1741
|
+
});
|
|
1742
|
+
const data = await res.json().catch(() => ({}));
|
|
1743
|
+
const isError = data?.status === "error" || !res.ok;
|
|
1744
|
+
if (isError) {
|
|
1745
|
+
return {
|
|
1746
|
+
ok: false,
|
|
1747
|
+
provider: "SMS" /* SMS */,
|
|
1748
|
+
error: data?.message || data?.error || `Gateway error (${res.status} ${res.statusText})`
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
const externalId = data?.id || data?.data?.id || data?.message_id || data?.data?.message_id || void 0;
|
|
1557
1752
|
return {
|
|
1558
1753
|
ok: true,
|
|
1559
1754
|
provider: "SMS" /* SMS */,
|
|
1560
|
-
externalId
|
|
1755
|
+
externalId,
|
|
1756
|
+
meta: {
|
|
1757
|
+
gatewayStatus: data?.status,
|
|
1758
|
+
recipient: payload.recipient
|
|
1759
|
+
}
|
|
1561
1760
|
};
|
|
1562
1761
|
} catch (err) {
|
|
1563
1762
|
return {
|
|
@@ -1567,6 +1766,23 @@ var SmsProvider = class {
|
|
|
1567
1766
|
};
|
|
1568
1767
|
}
|
|
1569
1768
|
}
|
|
1769
|
+
/** Accepts single/multiple Recipient objects and returns sanitized numbers */
|
|
1770
|
+
normalizeRecipients(input) {
|
|
1771
|
+
return input.map((r) => r?.phone || "").map((p) => this.stripCountryCode(p)).map((p) => this.onlyDialable(p)).filter(Boolean);
|
|
1772
|
+
}
|
|
1773
|
+
/** Match PHP: remove leading +233 (Ghana) only */
|
|
1774
|
+
stripCountryCode(phone) {
|
|
1775
|
+
return (phone || "").trim().replace(this.args.countryCode || "+233", "");
|
|
1776
|
+
}
|
|
1777
|
+
/** Remove spaces, dashes, parentheses; keep numbers only (and commas handled elsewhere) */
|
|
1778
|
+
onlyDialable(phone) {
|
|
1779
|
+
return phone.replace(/[^\d]/g, "");
|
|
1780
|
+
}
|
|
1781
|
+
/** Mirror PHP cleanup (strip tags, collapse nbsp, trim) */
|
|
1782
|
+
cleanMessage(msg) {
|
|
1783
|
+
const noTags = msg.replace(/<\/?[^>]+(>|$)/g, "");
|
|
1784
|
+
return noTags.replace(/ |&nbsp;/g, " ").trim();
|
|
1785
|
+
}
|
|
1570
1786
|
};
|
|
1571
1787
|
|
|
1572
1788
|
// src/providers/whatsapp.provider.ts
|
|
@@ -1703,7 +1919,11 @@ var NotificationService = class {
|
|
|
1703
1919
|
});
|
|
1704
1920
|
}
|
|
1705
1921
|
const emailProvider = new EmailProvider(this.prisma, this.logger, this.envMode);
|
|
1706
|
-
const smsProvider = new SmsProvider(
|
|
1922
|
+
const smsProvider = new SmsProvider(this.logger, {
|
|
1923
|
+
apiUrl: cfg.serviceEvent.smsApiUrl,
|
|
1924
|
+
countryCode: cfg.serviceEvent.countryCode,
|
|
1925
|
+
senderId: cfg.serviceEvent.smsSenderId
|
|
1926
|
+
});
|
|
1707
1927
|
const waProvider = new WhatsAppProvider(
|
|
1708
1928
|
{
|
|
1709
1929
|
apiUrl: cfg.serviceEvent.wpApiUrl,
|
|
@@ -1882,6 +2102,8 @@ var NotificationEmitter = class {
|
|
|
1882
2102
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1883
2103
|
0 && (module.exports = {
|
|
1884
2104
|
NotificationEmitter,
|
|
2105
|
+
RoundFormat,
|
|
2106
|
+
calculateBillingFromChildren,
|
|
1885
2107
|
commonService,
|
|
1886
2108
|
customOmit,
|
|
1887
2109
|
findDifferences,
|
package/dist/index.mjs
CHANGED
|
@@ -1048,6 +1048,149 @@ var commonService = (serviceDeps) => {
|
|
|
1048
1048
|
};
|
|
1049
1049
|
};
|
|
1050
1050
|
|
|
1051
|
+
// src/types/calculation.ts
|
|
1052
|
+
var RoundFormat = {
|
|
1053
|
+
ROUND: "ROUND",
|
|
1054
|
+
SPECIAL_ROUND: "SPECIAL_ROUND",
|
|
1055
|
+
TO_FIXED: "TO_FIXED",
|
|
1056
|
+
CEIL: "CEIL",
|
|
1057
|
+
FLOOR: "FLOOR",
|
|
1058
|
+
TRUNC: "TRUNC",
|
|
1059
|
+
NONE: "NONE"
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
// src/utils/calculation.utils.ts
|
|
1063
|
+
function applyRound(value, format, precision = 2) {
|
|
1064
|
+
switch (format) {
|
|
1065
|
+
case RoundFormat.NONE:
|
|
1066
|
+
return value;
|
|
1067
|
+
case RoundFormat.ROUND:
|
|
1068
|
+
return Math.round(value);
|
|
1069
|
+
case RoundFormat.SPECIAL_ROUND:
|
|
1070
|
+
return value < 1 ? Math.ceil(value) : Math.round(value);
|
|
1071
|
+
case RoundFormat.CEIL:
|
|
1072
|
+
return Math.ceil(value);
|
|
1073
|
+
case RoundFormat.FLOOR:
|
|
1074
|
+
return Math.floor(value);
|
|
1075
|
+
case RoundFormat.TRUNC:
|
|
1076
|
+
return Math.trunc(value);
|
|
1077
|
+
case RoundFormat.TO_FIXED:
|
|
1078
|
+
default:
|
|
1079
|
+
return Number(value.toFixed(Math.max(0, precision | 0)));
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
var maybeStep = (v, stepwise, fmt, p) => stepwise ? applyRound(v, fmt, p) : v;
|
|
1083
|
+
function clamp(n, min, max) {
|
|
1084
|
+
return Math.max(min, Math.min(max, n));
|
|
1085
|
+
}
|
|
1086
|
+
function calculateBillingFromChildren(inputs, masterExtra, options = {}) {
|
|
1087
|
+
const stepwise = options.calculationMethod === "STEP_WISE";
|
|
1088
|
+
const lineFmt = options.lineRound ?? RoundFormat.TO_FIXED;
|
|
1089
|
+
const headFmt = options.headerRound ?? lineFmt;
|
|
1090
|
+
const precision = options.precision ?? 2;
|
|
1091
|
+
const children = inputs.map((it) => {
|
|
1092
|
+
let baseRate = it.rate;
|
|
1093
|
+
if (it.addonPercentage) {
|
|
1094
|
+
baseRate = baseRate + baseRate * (it.addonPercentage / 100);
|
|
1095
|
+
baseRate = maybeStep(baseRate, stepwise, lineFmt, precision);
|
|
1096
|
+
}
|
|
1097
|
+
const subtotal = applyRound(it.qty * baseRate, lineFmt, precision);
|
|
1098
|
+
const other = applyRound(it.otherCharge ?? 0, lineFmt, precision);
|
|
1099
|
+
const basePreTax = applyRound(subtotal + other, lineFmt, precision);
|
|
1100
|
+
let copayAmount2 = 0;
|
|
1101
|
+
if (masterExtra.coPayMode === "PERCENTAGE-AMOUNT") {
|
|
1102
|
+
const copayType = it.coPaymentType ?? "AMOUNT";
|
|
1103
|
+
const copayValue = it.coPayValue ?? 0;
|
|
1104
|
+
copayAmount2 = copayType === "PERCENTAGE" ? basePreTax * (copayValue / 100) : Math.min(copayValue, basePreTax);
|
|
1105
|
+
}
|
|
1106
|
+
const dMode = it.discountMode ?? "AMOUNT";
|
|
1107
|
+
const dVal = it.discountValue ?? 0;
|
|
1108
|
+
const tMethod = it.taxMethod ?? "NONE";
|
|
1109
|
+
const tVal = Math.max(0, it.taxValue ?? 0);
|
|
1110
|
+
let baseAfterTaxDisc = 0;
|
|
1111
|
+
let discountValue = 0;
|
|
1112
|
+
if (tMethod === "INCLUSIVE") {
|
|
1113
|
+
const inclusiveTaxMultiplier = (100 + tVal) / 100;
|
|
1114
|
+
baseAfterTaxDisc = basePreTax / inclusiveTaxMultiplier;
|
|
1115
|
+
}
|
|
1116
|
+
baseAfterTaxDisc = maybeStep(baseAfterTaxDisc, stepwise, lineFmt, precision);
|
|
1117
|
+
if (dMode === "AMOUNT") {
|
|
1118
|
+
discountValue = dVal;
|
|
1119
|
+
} else if (dMode === "PERCENTAGE") {
|
|
1120
|
+
discountValue = baseAfterTaxDisc * dVal / 100;
|
|
1121
|
+
}
|
|
1122
|
+
discountValue = maybeStep(discountValue, stepwise, lineFmt, precision);
|
|
1123
|
+
let afterDisc = Math.max(0, baseAfterTaxDisc - discountValue);
|
|
1124
|
+
afterDisc = maybeStep(afterDisc, stepwise, lineFmt, precision);
|
|
1125
|
+
let calculatedTax = tVal * afterDisc / 100;
|
|
1126
|
+
calculatedTax = maybeStep(calculatedTax, stepwise, lineFmt, precision);
|
|
1127
|
+
let grossAmount2 = afterDisc + calculatedTax;
|
|
1128
|
+
grossAmount2 = maybeStep(grossAmount2, stepwise, lineFmt, precision);
|
|
1129
|
+
if (masterExtra.coPayMode === "EXCLUSIVE-INCLUSIVE" && it.coPayMethod === "INCLUSIVE") {
|
|
1130
|
+
copayAmount2 = grossAmount2;
|
|
1131
|
+
}
|
|
1132
|
+
copayAmount2 = applyRound(copayAmount2, lineFmt, precision);
|
|
1133
|
+
const copay = clamp(Math.max(0, copayAmount2), 0, grossAmount2);
|
|
1134
|
+
const patientRaw = grossAmount2 - copay;
|
|
1135
|
+
const patientRounded = applyRound(patientRaw, lineFmt, precision);
|
|
1136
|
+
const lineRoundOff = patientRounded - patientRaw;
|
|
1137
|
+
return {
|
|
1138
|
+
baseRate: applyRound(baseRate, lineFmt, precision),
|
|
1139
|
+
subtotalAmount: subtotal,
|
|
1140
|
+
otherChargeAmount: other,
|
|
1141
|
+
discountMode: dMode,
|
|
1142
|
+
discountValue: dVal,
|
|
1143
|
+
discountAmount: applyRound(discountValue, lineFmt, precision),
|
|
1144
|
+
// item-only discount
|
|
1145
|
+
taxMethod: tMethod,
|
|
1146
|
+
taxValue: tVal,
|
|
1147
|
+
taxAmount: applyRound(calculatedTax, lineFmt, precision),
|
|
1148
|
+
grossAmount: applyRound(grossAmount2, lineFmt, precision),
|
|
1149
|
+
netAmount: patientRounded,
|
|
1150
|
+
// patient payable at line
|
|
1151
|
+
roundOffAmount: applyRound(lineRoundOff, "TO_FIXED", 2),
|
|
1152
|
+
// rounded - raw
|
|
1153
|
+
copayAmount: copay
|
|
1154
|
+
// insurer covered at line
|
|
1155
|
+
};
|
|
1156
|
+
});
|
|
1157
|
+
const subtotalAmount = children.reduce((a, c) => a + c.subtotalAmount, 0);
|
|
1158
|
+
const otherChargeAmount = children.reduce((a, c) => a + c.otherChargeAmount, 0);
|
|
1159
|
+
const itemDiscountSum = children.reduce((a, c) => a + c.discountAmount, 0);
|
|
1160
|
+
const taxAmount = children.reduce((a, c) => a + c.taxAmount, 0);
|
|
1161
|
+
const grossAmount = children.reduce((a, c) => a + c.grossAmount, 0);
|
|
1162
|
+
const copayAmount = children.reduce((a, c) => a + c.copayAmount, 0);
|
|
1163
|
+
const patientRawTotal = grossAmount - copayAmount;
|
|
1164
|
+
let masterExtraApplied = 0;
|
|
1165
|
+
if (masterExtra.mode === "PERCENTAGE") {
|
|
1166
|
+
masterExtraApplied = patientRawTotal * (masterExtra.value / 100);
|
|
1167
|
+
} else if (masterExtra.mode === "AMOUNT") {
|
|
1168
|
+
masterExtraApplied = Math.min(masterExtra.value, patientRawTotal);
|
|
1169
|
+
}
|
|
1170
|
+
masterExtraApplied = maybeStep(masterExtraApplied, stepwise, headFmt, precision);
|
|
1171
|
+
const discountTotalAmount = maybeStep(itemDiscountSum + masterExtraApplied, stepwise, headFmt, precision);
|
|
1172
|
+
const patientAfterExtra = Math.max(0, patientRawTotal - masterExtraApplied);
|
|
1173
|
+
const netAmount = applyRound(patientAfterExtra, headFmt, precision);
|
|
1174
|
+
const roundOffAmount = netAmount - patientAfterExtra;
|
|
1175
|
+
const master = {
|
|
1176
|
+
additionalDiscountMode: masterExtra.mode,
|
|
1177
|
+
additionalDiscountValue: masterExtra.value,
|
|
1178
|
+
subtotalAmount: applyRound(subtotalAmount, headFmt, precision),
|
|
1179
|
+
otherChargeAmount: applyRound(otherChargeAmount, headFmt, precision),
|
|
1180
|
+
discountTotalAmount: applyRound(discountTotalAmount, headFmt, precision),
|
|
1181
|
+
// includes master extra
|
|
1182
|
+
taxAmount: applyRound(taxAmount, headFmt, precision),
|
|
1183
|
+
grossAmount: applyRound(grossAmount, headFmt, precision),
|
|
1184
|
+
netAmount,
|
|
1185
|
+
// patient payable after master extra + rounding
|
|
1186
|
+
roundOffAmount: applyRound(roundOffAmount, "TO_FIXED", 2),
|
|
1187
|
+
// rounded - raw
|
|
1188
|
+
copayAmount: applyRound(copayAmount, headFmt, precision)
|
|
1189
|
+
// Σ line copay (insurer covered)
|
|
1190
|
+
};
|
|
1191
|
+
return { master, children };
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1051
1194
|
// src/utils/audit.utils.ts
|
|
1052
1195
|
function isValidDate(value) {
|
|
1053
1196
|
if (value instanceof Date) {
|
|
@@ -1498,8 +1641,32 @@ var EmailProvider = class {
|
|
|
1498
1641
|
|
|
1499
1642
|
// src/providers/sms.provider.ts
|
|
1500
1643
|
var SmsProvider = class {
|
|
1644
|
+
constructor(logger = console, args) {
|
|
1645
|
+
this.logger = logger;
|
|
1646
|
+
this.args = args;
|
|
1647
|
+
this.logger.info(`[NotificationService] args for sms : ${JSON.stringify(args)}`);
|
|
1648
|
+
this.url = args.apiUrl || "https://web.nsemfua.com/api/http/sms/send";
|
|
1649
|
+
this.token = "13|R3XfH6wHx3zdflBzpGMlNiTWKuOUEkGutORyGcuS";
|
|
1650
|
+
this.sender = args.senderId || "AlmaMedLab";
|
|
1651
|
+
}
|
|
1652
|
+
url;
|
|
1653
|
+
token;
|
|
1654
|
+
sender;
|
|
1501
1655
|
async send(args) {
|
|
1502
|
-
|
|
1656
|
+
const to = args.recipient;
|
|
1657
|
+
if (!to) {
|
|
1658
|
+
return {
|
|
1659
|
+
ok: false,
|
|
1660
|
+
provider: "SMS" /* SMS */,
|
|
1661
|
+
error: "Missing recipient.phone"
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
const allRecipients = [to];
|
|
1665
|
+
if (this.args.masterNumber) {
|
|
1666
|
+
allRecipients.push({ phone: this.args.masterNumber });
|
|
1667
|
+
}
|
|
1668
|
+
const recipients = this.normalizeRecipients(allRecipients);
|
|
1669
|
+
if (!recipients.length) {
|
|
1503
1670
|
return {
|
|
1504
1671
|
ok: false,
|
|
1505
1672
|
provider: "SMS" /* SMS */,
|
|
@@ -1507,11 +1674,41 @@ var SmsProvider = class {
|
|
|
1507
1674
|
};
|
|
1508
1675
|
}
|
|
1509
1676
|
try {
|
|
1510
|
-
const
|
|
1677
|
+
const payload = {
|
|
1678
|
+
api_token: this.token,
|
|
1679
|
+
recipient: recipients.join(", "),
|
|
1680
|
+
// API accepts comma-separated list
|
|
1681
|
+
sender_id: this.sender,
|
|
1682
|
+
type: "plain",
|
|
1683
|
+
message: this.cleanMessage(args.body)
|
|
1684
|
+
// schedule_time: "" // add if you need scheduling
|
|
1685
|
+
};
|
|
1686
|
+
const res = await fetch(this.url, {
|
|
1687
|
+
method: "POST",
|
|
1688
|
+
headers: {
|
|
1689
|
+
"Content-Type": "application/json",
|
|
1690
|
+
Accept: "application/json"
|
|
1691
|
+
},
|
|
1692
|
+
body: JSON.stringify(payload)
|
|
1693
|
+
});
|
|
1694
|
+
const data = await res.json().catch(() => ({}));
|
|
1695
|
+
const isError = data?.status === "error" || !res.ok;
|
|
1696
|
+
if (isError) {
|
|
1697
|
+
return {
|
|
1698
|
+
ok: false,
|
|
1699
|
+
provider: "SMS" /* SMS */,
|
|
1700
|
+
error: data?.message || data?.error || `Gateway error (${res.status} ${res.statusText})`
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
const externalId = data?.id || data?.data?.id || data?.message_id || data?.data?.message_id || void 0;
|
|
1511
1704
|
return {
|
|
1512
1705
|
ok: true,
|
|
1513
1706
|
provider: "SMS" /* SMS */,
|
|
1514
|
-
externalId
|
|
1707
|
+
externalId,
|
|
1708
|
+
meta: {
|
|
1709
|
+
gatewayStatus: data?.status,
|
|
1710
|
+
recipient: payload.recipient
|
|
1711
|
+
}
|
|
1515
1712
|
};
|
|
1516
1713
|
} catch (err) {
|
|
1517
1714
|
return {
|
|
@@ -1521,6 +1718,23 @@ var SmsProvider = class {
|
|
|
1521
1718
|
};
|
|
1522
1719
|
}
|
|
1523
1720
|
}
|
|
1721
|
+
/** Accepts single/multiple Recipient objects and returns sanitized numbers */
|
|
1722
|
+
normalizeRecipients(input) {
|
|
1723
|
+
return input.map((r) => r?.phone || "").map((p) => this.stripCountryCode(p)).map((p) => this.onlyDialable(p)).filter(Boolean);
|
|
1724
|
+
}
|
|
1725
|
+
/** Match PHP: remove leading +233 (Ghana) only */
|
|
1726
|
+
stripCountryCode(phone) {
|
|
1727
|
+
return (phone || "").trim().replace(this.args.countryCode || "+233", "");
|
|
1728
|
+
}
|
|
1729
|
+
/** Remove spaces, dashes, parentheses; keep numbers only (and commas handled elsewhere) */
|
|
1730
|
+
onlyDialable(phone) {
|
|
1731
|
+
return phone.replace(/[^\d]/g, "");
|
|
1732
|
+
}
|
|
1733
|
+
/** Mirror PHP cleanup (strip tags, collapse nbsp, trim) */
|
|
1734
|
+
cleanMessage(msg) {
|
|
1735
|
+
const noTags = msg.replace(/<\/?[^>]+(>|$)/g, "");
|
|
1736
|
+
return noTags.replace(/ |&nbsp;/g, " ").trim();
|
|
1737
|
+
}
|
|
1524
1738
|
};
|
|
1525
1739
|
|
|
1526
1740
|
// src/providers/whatsapp.provider.ts
|
|
@@ -1657,7 +1871,11 @@ var NotificationService = class {
|
|
|
1657
1871
|
});
|
|
1658
1872
|
}
|
|
1659
1873
|
const emailProvider = new EmailProvider(this.prisma, this.logger, this.envMode);
|
|
1660
|
-
const smsProvider = new SmsProvider(
|
|
1874
|
+
const smsProvider = new SmsProvider(this.logger, {
|
|
1875
|
+
apiUrl: cfg.serviceEvent.smsApiUrl,
|
|
1876
|
+
countryCode: cfg.serviceEvent.countryCode,
|
|
1877
|
+
senderId: cfg.serviceEvent.smsSenderId
|
|
1878
|
+
});
|
|
1661
1879
|
const waProvider = new WhatsAppProvider(
|
|
1662
1880
|
{
|
|
1663
1881
|
apiUrl: cfg.serviceEvent.wpApiUrl,
|
|
@@ -1835,6 +2053,8 @@ var NotificationEmitter = class {
|
|
|
1835
2053
|
};
|
|
1836
2054
|
export {
|
|
1837
2055
|
NotificationEmitter,
|
|
2056
|
+
RoundFormat,
|
|
2057
|
+
calculateBillingFromChildren,
|
|
1838
2058
|
commonService,
|
|
1839
2059
|
customOmit,
|
|
1840
2060
|
findDifferences,
|