@lyxa.ai/marketing 1.0.53 → 1.0.55
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/lib/index.d.ts +679 -28
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +2 -2
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/modules/coupon/routers/coupon.router.d.ts +651 -0
- package/dist/lib/modules/coupon/routers/coupon.router.d.ts.map +1 -1
- package/dist/lib/modules/coupon/routers/coupon.router.js +8 -1
- package/dist/lib/modules/coupon/routers/coupon.router.js.map +1 -1
- package/dist/lib/modules/coupon/services/coupon.service.d.ts +8 -1
- package/dist/lib/modules/coupon/services/coupon.service.d.ts.map +1 -1
- package/dist/lib/modules/coupon/services/coupon.service.js +234 -27
- package/dist/lib/modules/coupon/services/coupon.service.js.map +1 -1
- package/dist/lib/modules/coupon/services/custom-coupon.service.d.ts +5 -0
- package/dist/lib/modules/coupon/services/custom-coupon.service.d.ts.map +1 -1
- package/dist/lib/modules/coupon/services/helper.service.d.ts +5 -0
- package/dist/lib/modules/coupon/services/helper.service.d.ts.map +1 -1
- package/dist/lib/modules/coupon/services/individual-store-coupon.service.d.ts +5 -0
- package/dist/lib/modules/coupon/services/individual-store-coupon.service.d.ts.map +1 -1
- package/dist/lib/modules/coupon/services/patterns/repository/CouponMetricsRepository.d.ts +1 -0
- package/dist/lib/modules/coupon/services/patterns/repository/CouponMetricsRepository.d.ts.map +1 -1
- package/dist/lib/modules/coupon/services/patterns/repository/CouponMetricsRepository.js +27 -9
- package/dist/lib/modules/coupon/services/patterns/repository/CouponMetricsRepository.js.map +1 -1
- package/dist/lib/modules/coupon/services/shops-categories-coupon.service.d.ts +5 -0
- package/dist/lib/modules/coupon/services/shops-categories-coupon.service.d.ts.map +1 -1
- package/dist/lib/modules/coupon/validations/all-coupons.validation.d.ts +293 -1125
- package/dist/lib/modules/coupon/validations/all-coupons.validation.d.ts.map +1 -1
- package/dist/lib/modules/coupon/validations/all-coupons.validation.js +2 -16
- package/dist/lib/modules/coupon/validations/all-coupons.validation.js.map +1 -1
- package/dist/lib/modules/coupon/validations/coupon.validation.d.ts +44 -0
- package/dist/lib/modules/coupon/validations/coupon.validation.d.ts.map +1 -1
- package/dist/lib/modules/coupon/validations/coupon.validation.js +16 -1
- package/dist/lib/modules/coupon/validations/coupon.validation.js.map +1 -1
- package/dist/lib/modules/coupon/validations/create-coupon.validation.d.ts.map +1 -1
- package/dist/lib/modules/coupon/validations/create-coupon.validation.js +4 -1
- package/dist/lib/modules/coupon/validations/create-coupon.validation.js.map +1 -1
- package/dist/lib/modules/coupon/validations/index.d.ts +1 -0
- package/dist/lib/modules/coupon/validations/index.d.ts.map +1 -1
- package/dist/lib/modules/coupon/validations/index.js +1 -0
- package/dist/lib/modules/coupon/validations/index.js.map +1 -1
- package/dist/lib/modules/limited-time-offer/campaign/campaign.router.d.ts +8 -8
- package/dist/lib/modules/limited-time-offer/campaign/limited-time-offer-campaign.validation.d.ts +34 -34
- package/dist/lib/modules/limited-time-offer/config/limited-time-offer-config.validation.d.ts +9 -1
- package/dist/lib/modules/limited-time-offer/config/limited-time-offer-config.validation.d.ts.map +1 -1
- package/dist/lib/modules/limited-time-offer/config/limited-time-offer-config.validation.js +26 -3
- package/dist/lib/modules/limited-time-offer/config/limited-time-offer-config.validation.js.map +1 -1
- package/dist/lib/modules/limited-time-offer/limited-time-offer.router.d.ts +28 -28
- package/dist/lib/modules/limited-time-offer/limited-time-offer.service.d.ts +1 -0
- package/dist/lib/modules/limited-time-offer/limited-time-offer.service.d.ts.map +1 -1
- package/dist/lib/modules/limited-time-offer/limited-time-offer.service.js +6 -0
- package/dist/lib/modules/limited-time-offer/limited-time-offer.service.js.map +1 -1
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.builder.d.ts +13 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.builder.d.ts.map +1 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.builder.js +22 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.builder.js.map +1 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.constants.d.ts +20 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.constants.d.ts.map +1 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.constants.js +26 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.constants.js.map +1 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.service.d.ts +8 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.service.d.ts.map +1 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.service.js +60 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.service.js.map +1 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.type.d.ts +5 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.type.d.ts.map +1 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.type.js +3 -0
- package/dist/lib/modules/limited-time-offer/notification/limited-time-offer-notification.type.js.map +1 -0
- package/dist/lib/modules/limited-time-offer/participant/limited-time-offer-participant.builder.js +1 -1
- package/dist/lib/modules/limited-time-offer/participant/limited-time-offer-participant.builder.js.map +1 -1
- package/dist/lib/modules/limited-time-offer/participant/limited-time-offer-participant.repository.d.ts +1 -0
- package/dist/lib/modules/limited-time-offer/participant/limited-time-offer-participant.repository.d.ts.map +1 -1
- package/dist/lib/modules/limited-time-offer/participant/limited-time-offer-participant.repository.js +8 -1
- package/dist/lib/modules/limited-time-offer/participant/limited-time-offer-participant.repository.js.map +1 -1
- package/dist/lib/modules/limited-time-offer/participant/limited-time-offer-participant.router.d.ts +20 -20
- package/dist/lib/modules/limited-time-offer/participant/limited-time-offer-participant.validation.d.ts +82 -82
- package/dist/lib/modules/limited-time-offer/participant/services/limited-time-offer-participant.service.d.ts +2 -1
- package/dist/lib/modules/limited-time-offer/participant/services/limited-time-offer-participant.service.d.ts.map +1 -1
- package/dist/lib/modules/limited-time-offer/participant/services/limited-time-offer-participant.service.js +12 -5
- package/dist/lib/modules/limited-time-offer/participant/services/limited-time-offer-participant.service.js.map +1 -1
- package/dist/lib/modules/marketing/services/marketing.service.d.ts +1 -0
- package/dist/lib/modules/marketing/services/marketing.service.d.ts.map +1 -1
- package/dist/lib/modules/marketing/services/marketing.service.js +9 -0
- package/dist/lib/modules/marketing/services/marketing.service.js.map +1 -1
- package/dist/lib/modules/tournament/match/events/evaluate-predictions.consumer.js +1 -1
- package/dist/lib/modules/tournament/match/events/evaluate-predictions.consumer.js.map +1 -1
- package/dist/lib/modules/tournament/notification/__tests__/notification.service.spec.d.ts +2 -0
- package/dist/lib/modules/tournament/notification/__tests__/notification.service.spec.d.ts.map +1 -0
- package/dist/lib/modules/tournament/notification/__tests__/notification.service.spec.js +266 -0
- package/dist/lib/modules/tournament/notification/__tests__/notification.service.spec.js.map +1 -0
- package/dist/lib/modules/tournament/notification/notification.service.d.ts +14 -3
- package/dist/lib/modules/tournament/notification/notification.service.d.ts.map +1 -1
- package/dist/lib/modules/tournament/notification/notification.service.js +89 -31
- package/dist/lib/modules/tournament/notification/notification.service.js.map +1 -1
- package/dist/lib/modules/tournament/participant/participant.repository.d.ts +1 -0
- package/dist/lib/modules/tournament/participant/participant.repository.d.ts.map +1 -1
- package/dist/lib/modules/tournament/participant/participant.repository.js +9 -0
- package/dist/lib/modules/tournament/participant/participant.repository.js.map +1 -1
- package/dist/lib/modules/tournament/participant/participant.service.d.ts +1 -0
- package/dist/lib/modules/tournament/participant/participant.service.d.ts.map +1 -1
- package/dist/lib/modules/tournament/participant/participant.service.js +6 -0
- package/dist/lib/modules/tournament/participant/participant.service.js.map +1 -1
- package/dist/lib/modules/tournament/reward/reward.builder.d.ts.map +1 -1
- package/dist/lib/modules/tournament/reward/reward.builder.js +3 -3
- package/dist/lib/modules/tournament/reward/reward.builder.js.map +1 -1
- package/dist/lib/modules/tournament/reward/reward.types.d.ts +1 -0
- package/dist/lib/modules/tournament/reward/reward.types.d.ts.map +1 -1
- package/dist/lib/modules/tournament/reward/reward.types.js.map +1 -1
- package/dist/lib/modules/tournament/schedule/schedule.service.d.ts +7 -0
- package/dist/lib/modules/tournament/schedule/schedule.service.d.ts.map +1 -1
- package/dist/lib/modules/tournament/schedule/schedule.service.js +52 -19
- package/dist/lib/modules/tournament/schedule/schedule.service.js.map +1 -1
- package/dist/lib/utility/utils.js +2 -2
- package/dist/lib/utility/utils.js.map +1 -1
- package/dist/types/index.d.ts +679 -28
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/modules/coupon/routers/coupon.router.d.ts +651 -0
- package/dist/types/modules/coupon/routers/coupon.router.d.ts.map +1 -1
- package/dist/types/modules/coupon/services/coupon.service.d.ts +8 -1
- package/dist/types/modules/coupon/services/coupon.service.d.ts.map +1 -1
- package/dist/types/modules/coupon/services/custom-coupon.service.d.ts +5 -0
- package/dist/types/modules/coupon/services/custom-coupon.service.d.ts.map +1 -1
- package/dist/types/modules/coupon/services/helper.service.d.ts +5 -0
- package/dist/types/modules/coupon/services/helper.service.d.ts.map +1 -1
- package/dist/types/modules/coupon/services/individual-store-coupon.service.d.ts +5 -0
- package/dist/types/modules/coupon/services/individual-store-coupon.service.d.ts.map +1 -1
- package/dist/types/modules/coupon/services/patterns/repository/CouponMetricsRepository.d.ts +1 -0
- package/dist/types/modules/coupon/services/patterns/repository/CouponMetricsRepository.d.ts.map +1 -1
- package/dist/types/modules/coupon/services/shops-categories-coupon.service.d.ts +5 -0
- package/dist/types/modules/coupon/services/shops-categories-coupon.service.d.ts.map +1 -1
- package/dist/types/modules/coupon/validations/all-coupons.validation.d.ts +293 -1125
- package/dist/types/modules/coupon/validations/all-coupons.validation.d.ts.map +1 -1
- package/dist/types/modules/coupon/validations/coupon.validation.d.ts +44 -0
- package/dist/types/modules/coupon/validations/coupon.validation.d.ts.map +1 -1
- package/dist/types/modules/coupon/validations/create-coupon.validation.d.ts.map +1 -1
- package/dist/types/modules/coupon/validations/index.d.ts +1 -0
- package/dist/types/modules/coupon/validations/index.d.ts.map +1 -1
- package/dist/types/modules/limited-time-offer/campaign/campaign.router.d.ts +8 -8
- package/dist/types/modules/limited-time-offer/campaign/limited-time-offer-campaign.validation.d.ts +34 -34
- package/dist/types/modules/limited-time-offer/config/limited-time-offer-config.validation.d.ts +9 -1
- package/dist/types/modules/limited-time-offer/config/limited-time-offer-config.validation.d.ts.map +1 -1
- package/dist/types/modules/limited-time-offer/limited-time-offer.router.d.ts +28 -28
- package/dist/types/modules/limited-time-offer/limited-time-offer.service.d.ts +1 -0
- package/dist/types/modules/limited-time-offer/limited-time-offer.service.d.ts.map +1 -1
- package/dist/types/modules/limited-time-offer/notification/limited-time-offer-notification.builder.d.ts +13 -0
- package/dist/types/modules/limited-time-offer/notification/limited-time-offer-notification.builder.d.ts.map +1 -0
- package/dist/types/modules/limited-time-offer/notification/limited-time-offer-notification.constants.d.ts +20 -0
- package/dist/types/modules/limited-time-offer/notification/limited-time-offer-notification.constants.d.ts.map +1 -0
- package/dist/types/modules/limited-time-offer/notification/limited-time-offer-notification.service.d.ts +8 -0
- package/dist/types/modules/limited-time-offer/notification/limited-time-offer-notification.service.d.ts.map +1 -0
- package/dist/types/modules/limited-time-offer/notification/limited-time-offer-notification.type.d.ts +5 -0
- package/dist/types/modules/limited-time-offer/notification/limited-time-offer-notification.type.d.ts.map +1 -0
- package/dist/types/modules/limited-time-offer/participant/limited-time-offer-participant.repository.d.ts +1 -0
- package/dist/types/modules/limited-time-offer/participant/limited-time-offer-participant.repository.d.ts.map +1 -1
- package/dist/types/modules/limited-time-offer/participant/limited-time-offer-participant.router.d.ts +20 -20
- package/dist/types/modules/limited-time-offer/participant/limited-time-offer-participant.validation.d.ts +82 -82
- package/dist/types/modules/limited-time-offer/participant/services/limited-time-offer-participant.service.d.ts +2 -1
- package/dist/types/modules/limited-time-offer/participant/services/limited-time-offer-participant.service.d.ts.map +1 -1
- package/dist/types/modules/marketing/services/marketing.service.d.ts +1 -0
- package/dist/types/modules/marketing/services/marketing.service.d.ts.map +1 -1
- package/dist/types/modules/tournament/notification/notification.service.d.ts +14 -3
- package/dist/types/modules/tournament/notification/notification.service.d.ts.map +1 -1
- package/dist/types/modules/tournament/participant/participant.repository.d.ts +1 -0
- package/dist/types/modules/tournament/participant/participant.repository.d.ts.map +1 -1
- package/dist/types/modules/tournament/participant/participant.service.d.ts +1 -0
- package/dist/types/modules/tournament/participant/participant.service.d.ts.map +1 -1
- package/dist/types/modules/tournament/reward/reward.builder.d.ts.map +1 -1
- package/dist/types/modules/tournament/reward/reward.types.d.ts +1 -0
- package/dist/types/modules/tournament/reward/reward.types.d.ts.map +1 -1
- package/dist/types/modules/tournament/schedule/schedule.service.d.ts +7 -0
- package/dist/types/modules/tournament/schedule/schedule.service.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"marketing.service.js","sourceRoot":"/","sources":["modules/marketing/services/marketing.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iDAA4C;AAC5C,yCAAyC;AACzC,8DAA2D;AAC3D,wCAA6C;AAC7C,oDAA+E;AAC/E,sEAYmD;AAenD,wEAA6G;AAE7G,8FAI4D;AAC5D,iGAAoG;AACpG,4DAI2C;AAC3C,8DAAkE;AAElE,8FAGqD;AACrD,wBAQW;AACX,4DAAkE;AAClE,4EAKmD;AACnD,6JAAsI;AACtI,kGAG2D;AAGpD,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IACpB,KAAK,CAAiD;IACtD,SAAS,CAA2C;IACpD,UAAU,CAA2D;IACrE,0BAA0B,CAA6B;IAE/D;QACC,IAAI,CAAC,KAAK,GAAG,uBAAgE,CAAC;QAC9E,IAAI,CAAC,SAAS,GAAG,kBAAqD,CAAC;QACvE,IAAI,CAAC,UAAU,GAAG,0BAA6E,CAAC;QAChG,IAAI,CAAC,0BAA0B,GAAG,uDAA0B,CAAC;IAC9D,CAAC;IAMM,KAAK,CAAC,MAAM,CAClB,IAAgC,EAChC,KAA+B;QAE/B,MAAM,GAAG,GAAG,IAAA,aAAK,GAAE,CAAC,MAAM,EAAE,CAAC;QAE7B,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;YAC/C,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,cAAc,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE;YAC5B,MAAM,EAAE,sBAAe,CAAC,MAAM;YAC9B,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;SAC3C,CAAC,CAAC;QAEH,IAAI,cAAc,EAAE,CAAC;YACpB,MAAM,IAAI,kBAAS,CAAC;gBACnB,IAAI,EAAE,UAAU;gBAChB,OAAO,EAAE,2BAA2B,IAAI,CAAC,aAAa,YAAY;aAClE,CAAC,CAAC;QACJ,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;gBAClD,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,gBAAgB,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE;gBAC9B,cAAc,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE;gBAC5B,MAAM,EAAE,sBAAe,CAAC,MAAM;gBAC9B,aAAa,EAAE,EAAE,GAAG,EAAE,CAAC,oBAAa,CAAC,QAAQ,EAAE,oBAAa,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,aAAa,EAAE;aACjG,CAAC,CAAC;YAEH,IAAI,iBAAiB,EAAE,CAAC;gBACvB,MAAM,IAAI,kBAAS,CAAC;oBACnB,IAAI,EAAE,UAAU;oBAChB,OAAO,EAAE,mGAAmG;iBAC5G,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;aAAM,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtD,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAkC,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAE9F,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,MAAM,CAAC;YAEjF,IAAI,aAAa,EAAE,CAAC;gBACnB,MAAM,IAAI,kBAAS,CAAC;oBACnB,IAAI,EAAE,aAAa;oBACnB,OAAO,EAAE,0CAA0C;iBACnD,CAAC,CAAC;YACJ,CAAC;YAED,MAAM,WAAW,GAAG,MAAM,0BAAuB,CAAC,iCAAiC,CAClF,UAAU,EACV,IAAI,CAAC,kBAAkB,CACvB,CAAC;YAEF,IAAI,CAAC,WAAW,EAAE,CAAC;gBAClB,MAAM,IAAI,kBAAS,CAAC;oBACnB,IAAI,EAAE,UAAU;oBAChB,OAAO,EAAE,qEAAqE;iBAC9E,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;QAED,IAAI,KAAK,EAAE,CAAC;YACX,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACpB,CAAC;QAED,IAAI,SAA2B,CAAC;QAEhC,QAAQ,IAAI,CAAC,aAAa,EAAE,CAAC;YAC5B,KAAK,oBAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC7B,SAAS,GAAG,MAAM,2BAAwB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACxD,MAAM;YACP,CAAC;YAED,KAAK,oBAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC7B,SAAS,GAAG,MAAM,2BAAwB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACxD,MAAM;YACP,CAAC;YAED,KAAK,oBAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;oBACZ,MAAM,IAAI,kBAAS,CAAC;wBACnB,IAAI,EAAE,cAAc;wBACpB,OAAO,EAAE,mCAAmC,IAAI,CAAC,aAAa,YAAY;qBAC1E,CAAC,CAAC;gBACJ,CAAC;gBACD,SAAS,GAAG,MAAM,2BAAwB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACxD,MAAM;YACP,CAAC;YAED,KAAK,oBAAa,CAAC,aAAa,CAAC,CAAC,CAAC;gBAClC,SAAS,GAAG,MAAM,+BAA4B,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC5D,MAAM;YACP,CAAC;YAED,KAAK,oBAAa,CAAC,eAAe,CAAC,CAAC,CAAC;gBACpC,SAAS,GAAG,MAAM,wBAAqB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACrD,MAAM;YACP,CAAC;YAED,OAAO,CAAC,CAAC,CAAC;gBACT,SAAS,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAClD,CAAC;QACF,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,IAAA,mBAAY,GAAE,CAAC,YAAY,EAAE,CAAC;YAC7C,MAAM,SAAS,GAAG,GAAG,CAAC;YACtB,MAAM,WAAW,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;YAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YAEpC,MAAM,KAAK,GAAG,MAAM,qBAAY,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YACrE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;YAE3C,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBACf,MAAM,OAAO,CAAC,GAAG,CAChB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CACzC,IAAA,oBAAY,EACX,IAAI,2DAA4B,CAAC;oBAChC,WAAW;oBACX,MAAM;oBACN,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,IAAI,KAAK;oBACpD,UAAU,EAAE,IAAI,CAAC,aAAa,KAAK,oBAAa,CAAC,QAAQ;oBACzD,KAAK,EAAG,IAAY,CAAC,KAAK;oBAC1B,SAAS,EAAG,IAAY,CAAC,SAAS;oBAClC,IAAI;oBACJ,QAAQ,EAAE,SAAS;iBACnB,CAAC,CACF,CACD,CACD,CAAC;gBAEF,MAAM,CAAC,KAAK,CAAC,2CAA2C,KAAK,cAAc,KAAK,WAAW,EAAE;oBAC5F,GAAG,EAAE,4BAA4B;oBACjC,IAAI,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE;iBACzD,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;aAAM,CAAC;YACP,MAAM,0BAAuB,CAAC,wBAAwB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACvE,CAAC;QAED,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE;YACpC,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,IAAI,CAAC,SAAS;YACrB,MAAM,EAAE,iBAAiB;SACzB,CAAC,CAAC;QAEH,IAAI,KAAK,EAAE,CAAC;YACX,IAAI,CAAC,uBAAuB,CAC3B,KAAK,EACL,iCAA0B,CAAC,8BAA8B,EACzD,EAAE,EACF,SAAS,CACT,CAAC;QACH,CAAC;QACD,IAAI,SAAS,IAAI,CAAC,IAAI,CAAC,YAAY;YAClC,IAAA,wEAAgD,EAAC;gBAChD,MAAM,EAAE,IAAI,CAAC,IAAI;gBACjB,SAAS,EAAE,2EAA0B,CAAC,MAAM;aAC5C,CAAC,CAAC;QACJ,OAAO,SAAS,CAAC;IAClB,CAAC;IAMM,KAAK,CAAC,QAAQ,CAAC,IAAqB;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK;aAC7B,OAAO,CAAC;YACR,GAAG,EAAE,IAAI,CAAC,GAAG;SACb,CAAC;aACD,MAAM,CAAC,MAAM,CAAC;aACd,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACrB,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,MAAM,IAAI,kBAAS,CAAC;gBACnB,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,kBAAkB;aAC3B,CAAC,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC;IACf,CAAC;IAMM,KAAK,CAAC,IAAI,CAAC,IAAyB;QAC1C,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,IAAA,6CAAgC,EAAC,IAAI,CAAC,KAA2C,CAAC,CAAC;YACpG,MAAM,OAAO,GAAmC;gBAC/C,IAAI,EAAE,IAAI,EAAE,IAAI;gBAChB,IAAI,EAAE,IAAI,EAAE,IAAI;gBAChB,IAAI,EAAE,IAAI,EAAE,IAAI;gBAChB,KAAK,EAAE,IAAI,EAAE,KAAK;gBAClB,MAAM,EAAE,IAAI,EAAE,MAAM;gBACpB,QAAQ,EAAE,IAAI,EAAE,QAAQ;aACxB,CAAC;YACF,MAAM,eAAe,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;YAChD,OAAO,eAAe,CAAC;QACxB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,kBAAS,CAAC;gBACnB,IAAI,EAAE,uBAAuB;gBAC7B,OAAO,EAAE,QAAQ,GAAG,EAAE;aACtB,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAOM,KAAK,CAAC,MAAM,CAClB,GAA4B,EAC5B,IAAe,EACf,KAA+B;QAE/B,IAAI,SAA2B,CAAC;QAEhC,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;YAC5D,IAAI,EAAE,MAAM;YACZ,MAAM,EAAE,iBAAiB;SACzB,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,EAAE,CAAC;YACnB,IAAA,4BAAa,EAAC,0BAA0B,CAAC,CAAC;QAC3C,CAAC;QAED,IACC,IAAI,CAAC,MAAM;YACX,IAAI,CAAC,MAAM,KAAK,sBAAe,CAAC,MAAM;YACtC,YAAY,CAAC,MAAM,KAAK,sBAAe,CAAC,MAAM,EAC7C,CAAC;YACF,MAAM,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC;QACzC,CAAC;QAED,QAAQ,YAAY,CAAC,aAAa,EAAE,CAAC;YACpC,KAAK,oBAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC7B,SAAS,GAAG,MAAM,2BAAwB,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBAC7D,MAAM;YACP,CAAC;YAED,KAAK,oBAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC7B,SAAS,GAAG,MAAM,2BAAwB,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBAC7D,MAAM;YACP,CAAC;YAED,KAAK,oBAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;oBACZ,IAAA,gCAAiB,EAAC,mCAAmC,IAAI,CAAC,aAAa,YAAY,CAAC,CAAC;gBACtF,CAAC;gBACD,SAAS,GAAG,MAAM,2BAAwB,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBAC7D,MAAM;YACP,CAAC;YAED,KAAK,oBAAa,CAAC,aAAa,CAAC,CAAC,CAAC;gBAClC,SAAS,GAAG,MAAM,+BAA4B,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBACjE,MAAM;YACP,CAAC;YAED,KAAK,oBAAa,CAAC,eAAe,CAAC,CAAC,CAAC;gBACpC,SAAS,GAAG,MAAM,wBAAqB,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBAC1D,MAAM;YACP,CAAC;YAED,OAAO,CAAC,CAAC,CAAC;gBACT,SAAS,GAAG,MAAM,IAAI,CAAC,KAAK;qBAC1B,gBAAgB,CAChB;oBACC,GAAG,EAAE,GAAG;oBACR,SAAS,EAAE,IAAI;iBACf,EACD,IAAI,EACJ;oBACC,GAAG,EAAE,IAAI;oBACT,aAAa,EAAE,IAAI;iBACnB,CACD;qBACA,QAAQ,CAAC;oBACT,IAAI,EAAE,MAAM;oBACZ,MAAM,EAAE,iBAAiB;iBACzB,CAAC,CAAC;YACL,CAAC;QACF,CAAC;QAED,IAAI,CAAC,SAAS,EAAE,CAAC;YAChB,IAAA,kCAAmB,EAAC,4BAA4B,CAAC,CAAC;QACnD,CAAC;QAED,IAAI,KAAK,EAAE,CAAC;YACX,MAAM,UAAU,GAAG,IAAA,wCAAgB,EAAC,YAAY,EAAE,SAAS,CAAC,CAAC;YAC7D,MAAM,eAAe,GAAG,IAAA,6CAAqB,EAAC,UAAU,EAAE,IAAI,CAAC,CAAC;YAEhE,IAAI,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACrD,IAAI,IAAI,CAAC,MAAM,IAAI,sBAAe,CAAC,MAAM,EAAE,CAAC;oBAC3C,IAAI,CAAC,uBAAuB,CAC3B,KAAK,EACL,iCAA0B,CAAC,8BAA8B,EACzD,YAAY,EACZ,SAAS,CACT,CAAC;gBACH,CAAC;qBAAM,IAAI,IAAI,CAAC,MAAM,IAAI,sBAAe,CAAC,MAAM,EAAE,CAAC;oBAClD,IAAI,CAAC,uBAAuB,CAC3B,KAAK,EACL,iCAA0B,CAAC,2BAA2B,EACtD,YAAY,EACZ,SAAS,CACT,CAAC;gBACH,CAAC;YACF,CAAC;YACD,IAAI,CAAC,uBAAuB,CAC3B,KAAK,EACL,iCAA0B,CAAC,2BAA2B,EACtD,YAAY,EACZ,SAAS,CACT,CAAC;QACH,CAAC;QAED,MAAM,0BAAuB,CAAC,wBAAwB,CAAC,GAAG,CAAC,CAAC;QAE5D,IAAA,wEAAgD,EAAC;YAChD,MAAM,EAAE,SAAS,CAAC,IAAI;YACtB,SAAS,EAAE,2EAA0B,CAAC,MAAM;SAC5C,CAAC,CAAC;QAEH,OAAO,SAAS,CAAC;IAClB,CAAC;IAEO,KAAK,CAAC,uBAAuB,CAAC,WAAoC;QACzE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QACvG,IAAI,CAAC,SAAS;YAAE,OAAO;QAEvB,IAAI,CAAC,oBAAa,CAAC,QAAQ,EAAE,oBAAa,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,aAAa,CAAC,EAAE,CAAC;YACxF,MAAM,cAAc,GAAG,SAAgE,CAAC;YACxF,MAAM,UAAU,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAY,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAClF,MAAM,WAAW,GAAG,MAAM,0BAAuB,CAAC,iCAAiC,CAClF,UAAU,EACV,SAAS,CAAC,kBAAkB,CAC5B,CAAC;YAEF,IAAI,CAAC,WAAW,EAAE,CAAC;gBAClB,IAAA,4BAAa,EAAC,yEAAyE,CAAC,CAAC;YAC1F,CAAC;QACF,CAAC;IACF,CAAC;IAEO,KAAK,CAAC,uBAAuB,CACpC,KAA8B,EAC9B,UAAsC,EACtC,WAAiB,EACjB,WAAiB;QAEjB,MAAM,sBAAsB,GAAuC,IAAI,yCAAsB,CAAC;YAC7F,KAAK;YACL,YAAY,EAAE,UAAU;YACxB,WAAW,EAAE,WAAW,IAAI,EAAE;YAC9B,WAAW,EAAE,WAAW,IAAI,EAAE;SAC9B,CAAC,CAAC;QACH,MAAM,IAAA,oBAAY,EAAC,sBAAsB,CAAC,CAAC;IAC5C,CAAC;IAMM,KAAK,CAAC,UAAU,CAAC,IAAe,EAAE,KAA+B;QACvE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;QAEjC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;YACtD,IAAI,EAAE,MAAM;YACZ,MAAM,EAAE,iBAAiB;SACzB,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,MAAM,IAAI,kBAAS,CAAC;gBACnB,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,kBAAkB;aAC3B,CAAC,CAAC;QACJ,CAAC;QAED,MAAM,CAAC,MAAM,GAAG,sBAAe,CAAC,OAAO,CAAC;QACxC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAEpB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,UAAU,CAAC,CAAC;QACnF,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,MAAM,IAAI,kBAAS,CAAC;gBACnB,IAAI,EAAE,uBAAuB;gBAC7B,OAAO,EAAE,4BAA4B;aACrC,CAAC,CAAC;QACJ,CAAC;QAED,IAAI,KAAK,EAAE,CAAC;YACX,IAAI,CAAC,uBAAuB,CAAC,KAAK,EAAE,iCAA0B,CAAC,4BAA4B,EAAE,MAAM,CAAC,CAAC;QACtG,CAAC;QAED,0BAAuB,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7E,IAAA,wEAAgD,EAAC;YAChD,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG;YACvB,SAAS,EAAE,2EAA0B,CAAC,MAAM;SAC5C,CAAC,CAAC;QACH,OAAO,6BAA6B,CAAC;IACtC,CAAC;IAEM,KAAK,CAAC,eAAe,CAAC,YAAuC,EAAE,UAAU,GAAG,IAAI;QACtF,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEtC,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,KAAK;aACjC,IAAI,CAAC,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,CAAC;aACpC,MAAM,CAAC,MAAM,CAAC;aACd,IAAI,EAAE,CAAC;QAET,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAC1B,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,EAC9B,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,sBAAe,CAAC,OAAO,EAAE,EAAE,CAC7C,CAAC;QACF,MAAM,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC;QAE3E,MAAM,0BAAuB,CAAC,wBAAwB,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAEjF,MAAM,aAAa,GAAG;YACrB,GAAG,IAAI,GAAG,CACT,UAAU;iBACR,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;iBACvB,MAAM,CAAC,OAAO,CAAC;iBACf,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE;gBACf,MAAM,EAAE,GAAG,CAAC,YAAY,oBAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,oBAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACrF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE,EAAE,CAAsC,CAAC;YACjE,CAAC,CAAC,CACH,CAAC,MAAM,EAAE;SACV,CAAC;QAEF,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;YACpC,IAAA,wEAAgD,EAAC;gBAChD,MAAM;gBACN,SAAS,EAAE,2EAA0B,CAAC,MAAM;aAC5C,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAEM,KAAK,CAAC,sCAAsC,CAAC,IAA0C;QAC7F,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;YAClD,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,aAAa,EAAE,EAAE,GAAG,EAAE,CAAC,oBAAa,CAAC,QAAQ,EAAE,oBAAa,CAAC,QAAQ,CAAC,EAAE;YACxE,cAAc,EAAE,EAAE,GAAG,EAAE,IAAI,IAAI,EAAE,EAAE;YACnC,MAAM,EAAE,sBAAe,CAAC,MAAM;YAC9B,YAAY,EAAE,IAAI;SAClB,CAAC,CAAC;QAGH,IAAI,iBAAiB,EAAE,CAAC;YACvB,MAAM,oBAAoB,GAA8B;gBACvD,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,SAAS,EAAE,iBAAiB,CAAC,GAAG;aAChC,CAAC;YACF,IAAI,iBAAiB,CAAC,aAAa,IAAI,oBAAa,CAAC,QAAQ,EAAE,CAAC;gBAC/D,oBAAoB,CAAC,UAAU,GAAG,IAAI,CAAC;YACxC,CAAC;YACD,IAAI,iBAAiB,CAAC,aAAa,IAAI,oBAAa,CAAC,QAAQ,EAAE,CAAC;gBAC/D,oBAAoB,CAAC,KAAK,GAAI,iBAAqD,CAAC,KAAK,CAAC;gBAC1F,oBAAoB,CAAC,SAAS,GAAI,iBAAqD,CAAC,SAAS,CAAC;YACnG,CAAC;YAED,MAAM,0BAAuB,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC3D,MAAM,qCAAkC,CAAC,mCAAmC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5F,CAAC;QAED,OAAO,8BAA8B,CAAC;IACvC,CAAC;IAEM,KAAK,CAAC,gCAAgC,CAAC,OAAgC;QAC7E,MAAM,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,UAAU;aAClC,QAAQ,CAAC,OAAO,CAAC;aACjB,QAAQ,CAAC;YACT,EAAE,IAAI,EAAE,WAAW,EAAE;YACrB,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,oBAAoB,EAAE;SACpD,CAAC,CAGF,CAAC;QAEF,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;YACzC,OAAO;QACR,CAAC;QAED,MAAM,iBAAiB,GAAG,IAAI,CAAC,0BAA0B,CAAC,+BAA+B,CAAC,KAAK,CAAC,CAAC;QAEjG,MAAM,OAAO,GAAG,CAAC,GAAG,iBAAiB,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;YACrF,SAAS,EAAE;gBACV,MAAM,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE;gBAC5B,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,EAAE;aACjC;SACD,CAAC,CAAC,CAAC;QAEJ,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACrC,CAAC;QAED,MAAM,IAAI,CAAC,oCAAoC,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAEtF,OAAO,iDAAiD,CAAC;IAC1D,CAAC;IAEM,KAAK,CAAC,gCAAgC,CAAC,OAAgC;QAC7E,MAAM,KAAK,GAAG,CAAC,MAAM,IAAI,CAAC,UAAU;aAClC,QAAQ,CAAC,OAAO,CAAC;aACjB,QAAQ,CAAC;YACT,EAAE,IAAI,EAAE,WAAW,EAAE;YACrB,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,oBAAoB,EAAE;SACpD,CAAC,CAGF,CAAC;QAEF,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;YACzC,OAAO;QACR,CAAC;QAED,MAAM,iBAAiB,GAAG,IAAI,CAAC,0BAA0B,CAAC,+BAA+B,CAAC,KAAK,CAAC,CAAC;QAEjG,MAAM,OAAO,GAAG,CAAC,GAAG,iBAAiB,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;YACrF,SAAS,EAAE;gBACV,MAAM,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE;gBAC5B,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,CAAC,WAAW,EAAE,EAAE;aAC/C;SACD,CAAC,CAAC,CAAC;QAEJ,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACrC,CAAC;QAED,MAAM,IAAI,CAAC,kCAAkC,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAEpF,OAAO,iDAAiD,CAAC;IAC1D,CAAC;IAEO,KAAK,CAAC,oCAAoC,CAAC,YAAuC;QACzF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CACzC;YACC,GAAG,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE;YAC1B,mBAAmB,EAAE,IAAI;YACzB,MAAM,EAAE,EAAE,GAAG,EAAE,sBAAe,CAAC,OAAO,EAAE;YACxC,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,cAAc,EAAE,aAAa,CAAC,EAAE;SAChD,EACD,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,sBAAe,CAAC,OAAO,EAAE,SAAS,EAAE,IAAA,aAAK,GAAE,CAAC,MAAM,EAAE,EAAE,EAAE,CAC1E,CAAC;QAEF,IAAI,MAAM,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,qCAAkC,CAAC,sCAAsC,CAAC,YAAY,CAAC,CAAC;QAC/F,CAAC;IACF,CAAC;IAEO,KAAK,CAAC,kCAAkC,CAAC,YAAuC;QACvF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CACzC;YACC,GAAG,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE;YAC1B,mBAAmB,EAAE,IAAI;YACzB,MAAM,EAAE,sBAAe,CAAC,OAAO;YAC/B,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,cAAc,EAAE,aAAa,CAAC,EAAE;SAC/C,EACD,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,sBAAe,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,CACtE,CAAC;QAEF,IAAI,MAAM,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,qCAAkC,CAAC,sCAAsC,CAAC,YAAY,CAAC,CAAC;QAC/F,CAAC;IACF,CAAC;IAEM,KAAK,CAAC,iCAAiC;QAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK;aACvB,IAAI,CAAC,EAAE,aAAa,EAAE,EAAE,GAAG,EAAE,oBAAa,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;aACpE,IAAI,EAAE;aACN,MAAM,EAAE,CAAC;QAEX,MAAM,OAAO,GAAqC,EAAE,CAAC;QACrD,MAAM,YAAY,GAA8B,EAAE,CAAC;QAEnD,IAAI,KAAK,EAAE,MAAM,SAAS,IAAI,MAAM,EAAE,CAAC;YACtC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,0BAA0B,CAAC,uBAAuB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAEhG,OAAO,CAAC,IAAI,CAAC;gBACZ,SAAS,EAAE;oBACV,MAAM,EAAE,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE;oBAC9B,MAAM,EAAE;wBACP,IAAI,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE;qBAC7B;iBACD;aACD,CAAC,CAAC;YAEH,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAEjC,IAAI,OAAO,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;gBAC3B,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;gBACpC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;YACpB,CAAC;QACF,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACrC,CAAC;QAED,MAAM,IAAI,CAAC,oCAAoC,CAAC,YAAY,CAAC,CAAC;QAC9D,MAAM,IAAI,CAAC,kCAAkC,CAAC,YAAY,CAAC,CAAC;IAC7D,CAAC;IAEM,KAAK,CAAC,yBAAyB,CAAC,WAAoC;QAC1E,MAAM,MAAM,GAAG,IAAA,mBAAY,GAAE,CAAC,YAAY,EAAE,CAAC;QAE7C,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClF,IAAI,CAAC,SAAS;YAAE,IAAA,kCAAmB,EAAC,qBAAqB,CAAC,CAAC;QAE3D,MAAM,SAAS,GAAG,GAAG,CAAC;QACtB,MAAM,GAAG,GAAG,WAAW,CAAC,QAAQ,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,MAAM,qBAAY,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;QAE3C,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,MAAM,OAAO,CAAC,GAAG,CAChB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CACzC,IAAA,oBAAY,EACX,IAAI,2DAA4B,CAAC;YAChC,WAAW,EAAE,GAAG;YAChB,MAAM;YACN,kBAAkB,EAAE,SAAS,CAAC,kBAAkB,IAAI,KAAK;YACzD,UAAU,EAAE,SAAS,CAAC,aAAa,KAAK,oBAAa,CAAC,QAAQ;YAC9D,KAAK,EAAG,SAAiB,CAAC,KAAK;YAC/B,SAAS,EAAG,SAAiB,CAAC,SAAS;YACvC,IAAI;YACJ,QAAQ,EAAE,SAAS;SACnB,CAAC,CACF,CACD,CACD,CAAC;QAEF,MAAM,CAAC,KAAK,CAAC,mCAAmC,KAAK,cAAc,KAAK,WAAW,EAAE;YACpF,GAAG,EAAE,4BAA4B;YACjC,IAAI,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE;SAC9D,CAAC,CAAC;IACJ,CAAC;CACD,CAAA;AAnpBY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,gBAAO,GAAE;;GACG,gBAAgB,CAmpB5B;AAEY,QAAA,gBAAgB,GAAG,gBAAS,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC","sourcesContent":["import Container, { Service } from 'typedi';\nimport { TRPCError } from '@trpc/server';\nimport { dayjs } from '@lyxa.ai/core/dist/utilities/dayjs';\nimport { getLibraries } from '@lyxa.ai/core';\nimport { DocumentType, mongoose, ReturnModelType } from '@typegoose/typegoose';\nimport {\n\tBuy1Get1Marketing,\n\tDiscountMarketing,\n\tLineItem,\n\tMarketing,\n\tMarketingModel,\n\tMarketing as Model,\n\tProductModel,\n\tRegularOrder,\n\tRegularOrderModel,\n\tShop,\n\tShopModel,\n} from '@lyxa.ai/core/dist/libraries/mongo/models';\nimport {\n\tAllMarketingsDTO,\n\tAllMarketingsValidationDTO,\n\tCreateProductMarketingDTO,\n\tProductEntireMenuMarketingHandlerDTO,\n\tUpdateMarketingDTO as UpdateDTO,\n} from '@modules/marketing/validations';\nimport {\n\tFilterDTO,\n\tPaginatedResponse,\n\tDeleteDTO,\n\tGetByIdInputDTO,\n} from '@lyxa.ai/core/dist/utilities/validation';\nimport { SoftDeleteModel } from '@lyxa.ai/core/dist/libraries/mongo/plugins/soft-delete-plugin';\nimport { createRawDocumentPaginatorObject, PaginatorOptions } from '@lyxa.ai/core/dist/utilities/pagination';\nimport { ModelType } from '@typegoose/typegoose/lib/types';\nimport {\n\tfilterRelevantChanges,\n\tgetChangedFields,\n\tpublishSyncSingleShopSectionsInCacheProcessEvent,\n} from '@lyxa.ai/core/dist/utilities/events-common-methods';\nimport { AdminActivityLogsEvent } from '@lyxa.ai/core/dist/libraries/event/events/admin-logs-event';\nimport {\n\tAdminLogsUpdatedFieldsType,\n\tMarketingStatus,\n\tRegularOrderStatus,\n} from '@lyxa.ai/core/dist/utilities/enum';\nimport { publishEvent } from '@lyxa.ai/core/dist/libraries/event';\nimport { BaseEvent } from '@lyxa.ai/core/dist/libraries/event/BaseEvent';\nimport {\n\tMarketingEntireMenuSeedEvent,\n\tMarketingProductSeedPayload,\n} from '@events/events/marketing-product-seed.event';\nimport {\n\tbuy1Get1MarketingService,\n\tdiscountMarketingService,\n\tfeaturedMarketingService,\n\tfreeDeliveryMarketingService,\n\tproductMarketingService,\n\tpunchMarketingService,\n\tsynchronizeProductMarketingService,\n} from '.';\nimport { MarketingType } from '@lyxa.ai/core/dist/utilities/enum';\nimport {\n\tconflictError,\n\tinternalServerError,\n\tnotFoundError,\n\tunauthorizedError,\n} from '@lyxa.ai/core/dist/utilities/error-common';\nimport { ChangeStreamOperationTypes } from '@lyxa.ai/core/dist/libraries/event/events/mongo-stream-changes-batch-process-shops-event';\nimport {\n\tMarketingMetricsRepository,\n\tmarketingMetricsRepository,\n} from '../patterns/repository/MarketingMetricsRepository';\n\n@Service()\nexport class MarketingService {\n\tprivate model: typeof MarketingModel & SoftDeleteModel<Model>;\n\tprivate shopModel: typeof ShopModel & SoftDeleteModel<Shop>;\n\tprivate orderModel: typeof RegularOrderModel & SoftDeleteModel<RegularOrder>;\n\tprivate marketingMetricsRepository: MarketingMetricsRepository;\n\n\tconstructor() {\n\t\tthis.model = MarketingModel as typeof MarketingModel & SoftDeleteModel<Model>;\n\t\tthis.shopModel = ShopModel as typeof ShopModel & SoftDeleteModel<Shop>;\n\t\tthis.orderModel = RegularOrderModel as typeof RegularOrderModel & SoftDeleteModel<RegularOrder>;\n\t\tthis.marketingMetricsRepository = marketingMetricsRepository;\n\t}\n\n\t/**\n\t * Create a new record\n\t * @param data Data for the new record\n\t */\n\tpublic async create(\n\t\tdata: AllMarketingsValidationDTO,\n\t\tadmin?: mongoose.Types.ObjectId\n\t): Promise<DocumentType<Model>> {\n\t\tconst now = dayjs().toDate();\n\n\t\tconst existingRecord = await this.model.findOne({\n\t\t\tshop: data.shop,\n\t\t\tmarketingType: data.marketingType,\n\t\t\t'duration.end': { $gt: now },\n\t\t\tstatus: MarketingStatus.ACTIVE,\n\t\t\tisAppliedByCompany: data.isAppliedByCompany,\n\t\t});\n\n\t\tif (existingRecord) {\n\t\t\tthrow new TRPCError({\n\t\t\t\tcode: 'CONFLICT',\n\t\t\t\tmessage: `This shop already has a ${data.marketingType} marketing`,\n\t\t\t});\n\t\t}\n\n\t\tif (data.isEntireMenu) {\n\t\t\tconst existingMarketing = await this.model.findOne({\n\t\t\t\tshop: data.shop,\n\t\t\t\t'duration.start': { $lt: now },\n\t\t\t\t'duration.end': { $gt: now },\n\t\t\t\tstatus: MarketingStatus.ACTIVE,\n\t\t\t\tmarketingType: { $in: [MarketingType.BUY1GET1, MarketingType.DISCOUNT], $ne: data.marketingType },\n\t\t\t});\n\n\t\t\tif (existingMarketing) {\n\t\t\t\tthrow new TRPCError({\n\t\t\t\t\tcode: 'CONFLICT',\n\t\t\t\t\tmessage: `Cannot create marketing for entire menu while another product based marketing exists for the shop`,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (data.products && data.products.length > 0) {\n\t\t\tconst productIds = data.products.map((product: CreateProductMarketingDTO) => product.product);\n\n\t\t\tconst hasDuplicates = new Set(productIds.map(String)).size !== productIds.length;\n\n\t\t\tif (hasDuplicates) {\n\t\t\t\tthrow new TRPCError({\n\t\t\t\t\tcode: 'BAD_REQUEST',\n\t\t\t\t\tmessage: `Product list contains duplicate products`,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst isAvailable = await productMarketingService.checkProductMarketingAvailability(\n\t\t\t\tproductIds,\n\t\t\t\tdata.isAppliedByCompany\n\t\t\t);\n\n\t\t\tif (!isAvailable) {\n\t\t\t\tthrow new TRPCError({\n\t\t\t\t\tcode: 'CONFLICT',\n\t\t\t\t\tmessage: `Product list contains products which already have marketing applied`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (admin) {\n\t\t\tdata.admin = admin;\n\t\t}\n\n\t\tlet marketing: AllMarketingsDTO;\n\n\t\tswitch (data.marketingType) {\n\t\t\tcase MarketingType.DISCOUNT: {\n\t\t\t\tmarketing = await discountMarketingService.create(data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.BUY1GET1: {\n\t\t\t\tmarketing = await buy1Get1MarketingService.create(data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.FEATURED: {\n\t\t\t\tif (!admin) {\n\t\t\t\t\tthrow new TRPCError({\n\t\t\t\t\t\tcode: 'UNAUTHORIZED',\n\t\t\t\t\t\tmessage: `Only admin is allowed to create ${data.marketingType} marketing`,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tmarketing = await featuredMarketingService.create(data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.FREE_DELIVERY: {\n\t\t\t\tmarketing = await freeDeliveryMarketingService.create(data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.PUNCH_MARKETING: {\n\t\t\t\tmarketing = await punchMarketingService.create(data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault: {\n\t\t\t\tmarketing = await this.model.create(data, admin);\n\t\t\t}\n\t\t}\n\n\t\tif (data.isEntireMenu) {\n\t\t\tconst logger = getLibraries().getLoggerKit();\n\t\t\tconst PAGE_SIZE = 500;\n\t\t\tconst marketingId = marketing._id.toString();\n\t\t\tconst shopId = data.shop.toString();\n\n\t\t\tconst count = await ProductModel.countDocuments({ shop: data.shop });\n\t\t\tconst pages = Math.ceil(count / PAGE_SIZE);\n\n\t\t\tif (pages > 0) {\n\t\t\t\tawait Promise.all(\n\t\t\t\t\tArray.from({ length: pages }, (_, page) =>\n\t\t\t\t\t\tpublishEvent(\n\t\t\t\t\t\t\tnew MarketingEntireMenuSeedEvent({\n\t\t\t\t\t\t\t\tmarketingId,\n\t\t\t\t\t\t\t\tshopId,\n\t\t\t\t\t\t\t\tisAppliedByCompany: data.isAppliedByCompany ?? false,\n\t\t\t\t\t\t\t\tisBuy1Get1: data.marketingType === MarketingType.BUY1GET1,\n\t\t\t\t\t\t\t\tvalue: (data as any).value,\n\t\t\t\t\t\t\t\tvalueType: (data as any).valueType,\n\t\t\t\t\t\t\t\tpage,\n\t\t\t\t\t\t\t\tpageSize: PAGE_SIZE,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\t\tlogger.debug(`entire-menu marketing seed dispatched — ${pages} pages for ${count} products`, {\n\t\t\t\t\ttag: 'marketing.seed.entire-menu',\n\t\t\t\t\tdata: { marketingId, shopId, pages, productCount: count },\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tawait productMarketingService.syncProductMarketingInfo(marketing._id);\n\t\t}\n\n\t\tawait this.model.populate(marketing, {\n\t\t\tpath: 'shop',\n\t\t\tmodel: this.shopModel,\n\t\t\tselect: '_id name shopId',\n\t\t});\n\n\t\tif (admin) {\n\t\t\tthis.logMarketingAdminAction(\n\t\t\t\tadmin,\n\t\t\t\tAdminLogsUpdatedFieldsType.ACTIVATED_MARKETING_PROMOTIONS,\n\t\t\t\t'',\n\t\t\t\tmarketing\n\t\t\t);\n\t\t}\n\t\tif (marketing && !data.isEntireMenu)\n\t\t\tpublishSyncSingleShopSectionsInCacheProcessEvent({\n\t\t\t\tshopId: data.shop,\n\t\t\t\toperation: ChangeStreamOperationTypes.UPDATE,\n\t\t\t});\n\t\treturn marketing;\n\t}\n\n\t/**\n\t * Get record by ID\n\t * @param id ID of the record\n\t */\n\tpublic async findById(data: GetByIdInputDTO): Promise<DocumentType<Model>> {\n\t\tconst select = data.select || '';\n\t\tconst populate = data.populate || [];\n\t\tconst result = await this.model\n\t\t\t.findOne({\n\t\t\t\t_id: data._id,\n\t\t\t})\n\t\t\t.select(select)\n\t\t\t.populate(populate);\n\t\tif (!result) {\n\t\t\tthrow new TRPCError({\n\t\t\t\tcode: 'NOT_FOUND',\n\t\t\t\tmessage: 'Record not found',\n\t\t\t});\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get records\n\t * @param query Query to find records\n\t */\n\tpublic async find(data?: Partial<FilterDTO>): Promise<PaginatedResponse<DocumentType<ModelType<Model>>>> {\n\t\ttry {\n\t\t\tconst paginate = createRawDocumentPaginatorObject(this.model as ReturnModelType<typeof this.model>);\n\t\t\tconst options: PaginatorOptions<typeof Model> = {\n\t\t\t\tpage: data?.page,\n\t\t\t\tsize: data?.size,\n\t\t\t\tsort: data?.sort,\n\t\t\t\tquery: data?.query,\n\t\t\t\tselect: data?.select,\n\t\t\t\tpopulate: data?.populate,\n\t\t\t};\n\t\t\tconst paginatedResult = await paginate(options);\n\t\t\treturn paginatedResult;\n\t\t} catch (err) {\n\t\t\tthrow new TRPCError({\n\t\t\t\tcode: 'INTERNAL_SERVER_ERROR',\n\t\t\t\tmessage: `err: ${err}`,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Update record\n\t * @param id ID of the record\n\t * @param data Data to update\n\t */\n\tpublic async update(\n\t\t_id: mongoose.Types.ObjectId,\n\t\tdata: UpdateDTO,\n\t\tadmin?: mongoose.Types.ObjectId\n\t): Promise<DocumentType<Model>> {\n\t\tlet marketing: AllMarketingsDTO;\n\n\t\tconst oldMarketing = await this.model.findById(_id).populate({\n\t\t\tpath: 'shop',\n\t\t\tselect: '_id name shopId',\n\t\t});\n\n\t\tif (!oldMarketing) {\n\t\t\tnotFoundError('Could not find marketing');\n\t\t}\n\n\t\tif (\n\t\t\tdata.status &&\n\t\t\tdata.status === MarketingStatus.ACTIVE &&\n\t\t\toldMarketing.status === MarketingStatus.PAUSED\n\t\t) {\n\t\t\tawait this.verifyUnpauseConditions(_id);\n\t\t}\n\n\t\tswitch (oldMarketing.marketingType) {\n\t\t\tcase MarketingType.DISCOUNT: {\n\t\t\t\tmarketing = await discountMarketingService.update(_id, data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.BUY1GET1: {\n\t\t\t\tmarketing = await buy1Get1MarketingService.update(_id, data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.FEATURED: {\n\t\t\t\tif (!admin) {\n\t\t\t\t\tunauthorizedError(`Only admin is allowed to update ${data.marketingType} marketing`);\n\t\t\t\t}\n\t\t\t\tmarketing = await featuredMarketingService.update(_id, data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.FREE_DELIVERY: {\n\t\t\t\tmarketing = await freeDeliveryMarketingService.update(_id, data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.PUNCH_MARKETING: {\n\t\t\t\tmarketing = await punchMarketingService.update(_id, data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault: {\n\t\t\t\tmarketing = await this.model\n\t\t\t\t\t.findOneAndUpdate(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t_id: _id,\n\t\t\t\t\t\t\tdeletedAt: null,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdata,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnew: true,\n\t\t\t\t\t\t\trunValidators: true,\n\t\t\t\t\t\t}\n\t\t\t\t\t)\n\t\t\t\t\t.populate({\n\t\t\t\t\t\tpath: 'shop',\n\t\t\t\t\t\tselect: '_id name shopId',\n\t\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (!marketing) {\n\t\t\tinternalServerError('Could not update marketing');\n\t\t}\n\n\t\tif (admin) {\n\t\t\tconst allChanges = getChangedFields(oldMarketing, marketing);\n\t\t\tconst relevantChanges = filterRelevantChanges(allChanges, data);\n\n\t\t\tif (Object.keys(relevantChanges).includes('status')) {\n\t\t\t\tif (data.status == MarketingStatus.ACTIVE) {\n\t\t\t\t\tthis.logMarketingAdminAction(\n\t\t\t\t\t\tadmin,\n\t\t\t\t\t\tAdminLogsUpdatedFieldsType.CONTINUED_MARKETING_PROMOTIONS,\n\t\t\t\t\t\toldMarketing,\n\t\t\t\t\t\tmarketing\n\t\t\t\t\t);\n\t\t\t\t} else if (data.status == MarketingStatus.PAUSED) {\n\t\t\t\t\tthis.logMarketingAdminAction(\n\t\t\t\t\t\tadmin,\n\t\t\t\t\t\tAdminLogsUpdatedFieldsType.PAUSED_MARKETING_PROMOTIONS,\n\t\t\t\t\t\toldMarketing,\n\t\t\t\t\t\tmarketing\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.logMarketingAdminAction(\n\t\t\t\tadmin,\n\t\t\t\tAdminLogsUpdatedFieldsType.EDITED_MARKETING_PROMOTIONS,\n\t\t\t\toldMarketing,\n\t\t\t\tmarketing\n\t\t\t);\n\t\t}\n\n\t\tawait productMarketingService.syncProductMarketingInfo(_id);\n\n\t\tpublishSyncSingleShopSectionsInCacheProcessEvent({\n\t\t\tshopId: marketing.shop,\n\t\t\toperation: ChangeStreamOperationTypes.UPDATE,\n\t\t});\n\n\t\treturn marketing;\n\t}\n\n\tprivate async verifyUnpauseConditions(marketingId: mongoose.Types.ObjectId): Promise<void> {\n\t\tconst marketing = await this.model.findOne({ _id: marketingId, deletedAt: null }).populate('products');\n\t\tif (!marketing) return;\n\n\t\tif ([MarketingType.BUY1GET1, MarketingType.DISCOUNT].includes(marketing.marketingType)) {\n\t\t\tconst typedMarketing = marketing as DocumentType<DiscountMarketing | Buy1Get1Marketing>;\n\t\t\tconst productIds = typedMarketing.products.map((product: any) => product.product);\n\t\t\tconst isAvailable = await productMarketingService.checkProductMarketingAvailability(\n\t\t\t\tproductIds,\n\t\t\t\tmarketing.isAppliedByCompany\n\t\t\t);\n\n\t\t\tif (!isAvailable) {\n\t\t\t\tconflictError(\"You can't activate discount and B1G1 on the same items at the same time\");\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async logMarketingAdminAction(\n\t\tadmin: mongoose.Types.ObjectId,\n\t\tactionType: AdminLogsUpdatedFieldsType,\n\t\toldDocument?: any,\n\t\tnewDocument?: any\n\t) {\n\t\tconst adminActivityLogsEvent: BaseEvent<Record<string, unknown>> = new AdminActivityLogsEvent({\n\t\t\tadmin,\n\t\t\tupdatedField: actionType,\n\t\t\toldDocument: oldDocument ?? '',\n\t\t\tnewDocument: newDocument ?? '',\n\t\t});\n\t\tawait publishEvent(adminActivityLogsEvent);\n\t}\n\n\t/**\n\t * Delete record\n\t * @param data Data for delete\n\t */\n\tpublic async deleteById(data: DeleteDTO, admin?: mongoose.Types.ObjectId): Promise<string> {\n\t\tconst { _id, softDelete } = data;\n\n\t\tconst record = await this.model.findById(_id).populate({\n\t\t\tpath: 'shop',\n\t\t\tselect: '_id name shopId',\n\t\t});\n\n\t\tif (!record) {\n\t\t\tthrow new TRPCError({\n\t\t\t\tcode: 'NOT_FOUND',\n\t\t\t\tmessage: 'Record not found',\n\t\t\t});\n\t\t}\n\n\t\trecord.status = MarketingStatus.DELETED;\n\t\tawait record.save();\n\n\t\tconst result = await this.model.deleteRecord({ _id, deletedAt: null }, softDelete);\n\t\tif (!result) {\n\t\t\tthrow new TRPCError({\n\t\t\t\tcode: 'INTERNAL_SERVER_ERROR',\n\t\t\t\tmessage: 'Unable to delete marketing',\n\t\t\t});\n\t\t}\n\n\t\tif (admin) {\n\t\t\tthis.logMarketingAdminAction(admin, AdminLogsUpdatedFieldsType.DELETED_MARKETING_PROMOTIONS, record);\n\t\t}\n\n\t\tproductMarketingService.deleteMany({ marketing: data._id }, data.softDelete);\n\t\tpublishSyncSingleShopSectionsInCacheProcessEvent({\n\t\t\tshopId: record.shop._id,\n\t\t\toperation: ChangeStreamOperationTypes.DELETE,\n\t\t});\n\t\treturn 'Record deleted successfully';\n\t}\n\n\tpublic async deleteManyByIds(marketingIds: mongoose.Types.ObjectId[], softDelete = true): Promise<void> {\n\t\tif (marketingIds.length === 0) return;\n\n\t\tconst marketings = await this.model\n\t\t\t.find({ _id: { $in: marketingIds } })\n\t\t\t.select('shop')\n\t\t\t.lean();\n\n\t\tawait this.model.updateMany(\n\t\t\t{ _id: { $in: marketingIds } },\n\t\t\t{ $set: { status: MarketingStatus.DELETED } },\n\t\t);\n\t\tawait this.model.deleteRecords({ _id: { $in: marketingIds } }, softDelete);\n\n\t\tawait productMarketingService.deleteManyByMarketingIds(marketingIds, softDelete);\n\n\t\tconst uniqueShopIds = [\n\t\t\t...new Map(\n\t\t\t\tmarketings\n\t\t\t\t\t.map((m: any) => m.shop)\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.map((s: any) => {\n\t\t\t\t\t\tconst id = s instanceof mongoose.Types.ObjectId ? s : new mongoose.Types.ObjectId(s);\n\t\t\t\t\t\treturn [id.toString(), id] as [string, mongoose.Types.ObjectId];\n\t\t\t\t\t})\n\t\t\t).values(),\n\t\t];\n\n\t\tfor (const shopId of uniqueShopIds) {\n\t\t\tpublishSyncSingleShopSectionsInCacheProcessEvent({\n\t\t\t\tshopId,\n\t\t\t\toperation: ChangeStreamOperationTypes.DELETE,\n\t\t\t});\n\t\t}\n\t}\n\n\tpublic async handleNewProductForEntireMenuMarketing(data: ProductEntireMenuMarketingHandlerDTO) {\n\t\tconst existingMarketing = await this.model.findOne({\n\t\t\tshop: data.shop,\n\t\t\tmarketingType: { $in: [MarketingType.BUY1GET1, MarketingType.DISCOUNT] },\n\t\t\t'duration.end': { $gt: new Date() },\n\t\t\tstatus: MarketingStatus.ACTIVE,\n\t\t\tisEntireMenu: true,\n\t\t});\n\n\t\t// check if existing marketing\n\t\tif (existingMarketing) {\n\t\t\tconst productMarketingData: CreateProductMarketingDTO = {\n\t\t\t\tproduct: data.product,\n\t\t\t\tmarketing: existingMarketing._id,\n\t\t\t};\n\t\t\tif (existingMarketing.marketingType == MarketingType.BUY1GET1) {\n\t\t\t\tproductMarketingData.isBuy1Get1 = true;\n\t\t\t}\n\t\t\tif (existingMarketing.marketingType == MarketingType.DISCOUNT) {\n\t\t\t\tproductMarketingData.value = (existingMarketing as DocumentType<DiscountMarketing>).value;\n\t\t\t\tproductMarketingData.valueType = (existingMarketing as DocumentType<DiscountMarketing>).valueType;\n\t\t\t}\n\n\t\t\tawait productMarketingService.create(productMarketingData);\n\t\t\tawait synchronizeProductMarketingService.syncProductMarketingInfoByProductId(data.product);\n\t\t}\n\n\t\treturn 'Product handled successfully';\n\t}\n\n\tpublic async incrementMarketingSpentFromOrder(orderId: mongoose.Types.ObjectId): Promise<any> {\n\t\tconst order = (await this.orderModel\n\t\t\t.findById(orderId)\n\t\t\t.populate([\n\t\t\t\t{ path: 'lineItems' },\n\t\t\t\t{ path: 'marketings', select: 'isAppliedByCompany' },\n\t\t\t])) as DocumentType<RegularOrder> & {\n\t\t\tlineItems: DocumentType<LineItem>[];\n\t\t\tmarketings: DocumentType<Marketing>[];\n\t\t};\n\n\t\tif (!order || !order.marketings?.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst marketingSpendMap = this.marketingMetricsRepository.calculateMarketingSpendPerOrder(order);\n\n\t\tconst bulkOps = [...marketingSpendMap.entries()].map(([marketingId, amountSpent]) => ({\n\t\t\tupdateOne: {\n\t\t\t\tfilter: { _id: marketingId },\n\t\t\t\tupdate: { $inc: { amountSpent } },\n\t\t\t},\n\t\t}));\n\n\t\tif (bulkOps.length) {\n\t\t\tawait this.model.bulkWrite(bulkOps);\n\t\t}\n\n\t\tawait this.expireMarketingsExceedingSpendLimits(Array.from(marketingSpendMap.keys()));\n\n\t\treturn 'Marketing amount spent incremented successfully';\n\t}\n\n\tpublic async decrementMarketingSpentFromOrder(orderId: mongoose.Types.ObjectId): Promise<any> {\n\t\tconst order = (await this.orderModel\n\t\t\t.findById(orderId)\n\t\t\t.populate([\n\t\t\t\t{ path: 'lineItems' },\n\t\t\t\t{ path: 'marketings', select: 'isAppliedByCompany' },\n\t\t\t])) as DocumentType<RegularOrder> & {\n\t\t\tlineItems: DocumentType<LineItem>[];\n\t\t\tmarketings: DocumentType<Marketing>[];\n\t\t};\n\n\t\tif (!order || !order.marketings?.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst marketingSpendMap = this.marketingMetricsRepository.calculateMarketingSpendPerOrder(order);\n\n\t\tconst bulkOps = [...marketingSpendMap.entries()].map(([marketingId, amountSpent]) => ({\n\t\t\tupdateOne: {\n\t\t\t\tfilter: { _id: marketingId },\n\t\t\t\tupdate: { $inc: { amountSpent: -amountSpent } },\n\t\t\t},\n\t\t}));\n\n\t\tif (bulkOps.length) {\n\t\t\tawait this.model.bulkWrite(bulkOps);\n\t\t}\n\n\t\tawait this.unExpireMarketingsBelowSpendLimits(Array.from(marketingSpendMap.keys()));\n\n\t\treturn 'Marketing amount spent decremented successfully';\n\t}\n\n\tprivate async expireMarketingsExceedingSpendLimits(marketingIds: mongoose.Types.ObjectId[]) {\n\t\tconst update = await this.model.updateMany(\n\t\t\t{\n\t\t\t\t_id: { $in: marketingIds },\n\t\t\t\tisSpendLimitEnabled: true,\n\t\t\t\tstatus: { $ne: MarketingStatus.EXPIRED },\n\t\t\t\t$expr: { $gte: ['$amountSpent', '$spendLimit'] },\n\t\t\t},\n\t\t\t{ $set: { status: MarketingStatus.EXPIRED, expiredAt: dayjs().toDate() } }\n\t\t);\n\n\t\tif (update.modifiedCount > 0) {\n\t\t\tawait synchronizeProductMarketingService.syncProductMarketingInfoByMarketingIds(marketingIds);\n\t\t}\n\t}\n\n\tprivate async unExpireMarketingsBelowSpendLimits(marketingIds: mongoose.Types.ObjectId[]) {\n\t\tconst update = await this.model.updateMany(\n\t\t\t{\n\t\t\t\t_id: { $in: marketingIds },\n\t\t\t\tisSpendLimitEnabled: true,\n\t\t\t\tstatus: MarketingStatus.EXPIRED,\n\t\t\t\t$expr: { $lt: ['$amountSpent', '$spendLimit'] },\n\t\t\t},\n\t\t\t{ $set: { status: MarketingStatus.ACTIVE }, $unset: { expiredAt: 1 } }\n\t\t);\n\n\t\tif (update.modifiedCount > 0) {\n\t\t\tawait synchronizeProductMarketingService.syncProductMarketingInfoByMarketingIds(marketingIds);\n\t\t}\n\t}\n\n\tpublic async recomputeAndSyncAllMarketingSpent(): Promise<void> {\n\t\tconst cursor = this.model\n\t\t\t.find({ marketingType: { $ne: MarketingType.FEATURED } }, { _id: 1 })\n\t\t\t.lean()\n\t\t\t.cursor();\n\n\t\tconst bulkOps: mongoose.AnyBulkWriteOperation[] = [];\n\t\tconst marketingIds: mongoose.Types.ObjectId[] = [];\n\n\t\tfor await (const marketing of cursor) {\n\t\t\tconst { amount } = await this.marketingMetricsRepository.calculateMarketingSpent(marketing._id);\n\n\t\t\tbulkOps.push({\n\t\t\t\tupdateOne: {\n\t\t\t\t\tfilter: { _id: marketing._id },\n\t\t\t\t\tupdate: {\n\t\t\t\t\t\t$set: { amountSpent: amount },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tmarketingIds.push(marketing._id);\n\n\t\t\tif (bulkOps.length >= 500) {\n\t\t\t\tawait this.model.bulkWrite(bulkOps);\n\t\t\t\tbulkOps.length = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (bulkOps.length) {\n\t\t\tawait this.model.bulkWrite(bulkOps);\n\t\t}\n\n\t\tawait this.expireMarketingsExceedingSpendLimits(marketingIds);\n\t\tawait this.unExpireMarketingsBelowSpendLimits(marketingIds);\n\t}\n\n\tpublic async reseedEntireMenuMarketing(marketingId: mongoose.Types.ObjectId): Promise<void> {\n\t\tconst logger = getLibraries().getLoggerKit();\n\n\t\tconst marketing = await this.model.findOne({ _id: marketingId, deletedAt: null });\n\t\tif (!marketing) internalServerError('Marketing not found');\n\n\t\tconst PAGE_SIZE = 500;\n\t\tconst mid = marketingId.toString();\n\t\tconst shopId = marketing.shop.toString();\n\t\tconst count = await ProductModel.countDocuments({ shop: marketing.shop });\n\t\tconst pages = Math.ceil(count / PAGE_SIZE);\n\n\t\tif (!pages) return;\n\n\t\tawait Promise.all(\n\t\t\tArray.from({ length: pages }, (_, page) =>\n\t\t\t\tpublishEvent(\n\t\t\t\t\tnew MarketingEntireMenuSeedEvent({\n\t\t\t\t\t\tmarketingId: mid,\n\t\t\t\t\t\tshopId,\n\t\t\t\t\t\tisAppliedByCompany: marketing.isAppliedByCompany ?? false,\n\t\t\t\t\t\tisBuy1Get1: marketing.marketingType === MarketingType.BUY1GET1,\n\t\t\t\t\t\tvalue: (marketing as any).value,\n\t\t\t\t\t\tvalueType: (marketing as any).valueType,\n\t\t\t\t\t\tpage,\n\t\t\t\t\t\tpageSize: PAGE_SIZE,\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\n\t\tlogger.debug(`entire-menu reseed dispatched — ${pages} pages for ${count} products`, {\n\t\t\ttag: 'marketing.seed.entire-menu',\n\t\t\tdata: { marketingId: mid, shopId, pages, productCount: count },\n\t\t});\n\t}\n}\n\nexport const marketingService = Container.get(MarketingService);\n"]}
|
|
1
|
+
{"version":3,"file":"marketing.service.js","sourceRoot":"/","sources":["modules/marketing/services/marketing.service.ts"],"names":[],"mappings":"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","sourcesContent":["import Container, { Service } from 'typedi';\nimport { TRPCError } from '@trpc/server';\nimport { dayjs } from '@lyxa.ai/core/dist/utilities/dayjs';\nimport { getLibraries } from '@lyxa.ai/core';\nimport { DocumentType, mongoose, ReturnModelType } from '@typegoose/typegoose';\nimport {\n\tBuy1Get1Marketing,\n\tDiscountMarketing,\n\tLimitedTimeOfferParticipant,\n\tLimitedTimeOfferParticipantModel,\n\tLineItem,\n\tMarketing,\n\tMarketingModel,\n\tMarketing as Model,\n\tProductModel,\n\tRegularOrder,\n\tRegularOrderModel,\n\tShop,\n\tShopModel,\n} from '@lyxa.ai/core/dist/libraries/mongo/models';\nimport {\n\tAllMarketingsDTO,\n\tAllMarketingsValidationDTO,\n\tCreateProductMarketingDTO,\n\tProductEntireMenuMarketingHandlerDTO,\n\tUpdateMarketingDTO as UpdateDTO,\n} from '@modules/marketing/validations';\nimport {\n\tFilterDTO,\n\tPaginatedResponse,\n\tDeleteDTO,\n\tGetByIdInputDTO,\n} from '@lyxa.ai/core/dist/utilities/validation';\nimport { SoftDeleteModel } from '@lyxa.ai/core/dist/libraries/mongo/plugins/soft-delete-plugin';\nimport { createRawDocumentPaginatorObject, PaginatorOptions } from '@lyxa.ai/core/dist/utilities/pagination';\nimport { ModelType } from '@typegoose/typegoose/lib/types';\nimport {\n\tfilterRelevantChanges,\n\tgetChangedFields,\n\tpublishSyncSingleShopSectionsInCacheProcessEvent,\n} from '@lyxa.ai/core/dist/utilities/events-common-methods';\nimport { AdminActivityLogsEvent } from '@lyxa.ai/core/dist/libraries/event/events/admin-logs-event';\nimport {\n\tAdminLogsUpdatedFieldsType,\n\tMarketingStatus,\n\tRegularOrderStatus,\n} from '@lyxa.ai/core/dist/utilities/enum';\nimport { publishEvent } from '@lyxa.ai/core/dist/libraries/event';\nimport { BaseEvent } from '@lyxa.ai/core/dist/libraries/event/BaseEvent';\nimport {\n\tMarketingEntireMenuSeedEvent,\n\tMarketingProductSeedPayload,\n} from '@events/events/marketing-product-seed.event';\nimport {\n\tbuy1Get1MarketingService,\n\tdiscountMarketingService,\n\tfeaturedMarketingService,\n\tfreeDeliveryMarketingService,\n\tproductMarketingService,\n\tpunchMarketingService,\n\tsynchronizeProductMarketingService,\n} from '.';\nimport { MarketingType } from '@lyxa.ai/core/dist/utilities/enum';\nimport {\n\tconflictError,\n\tforbiddenError,\n\tinternalServerError,\n\tnotFoundError,\n\tunauthorizedError,\n} from '@lyxa.ai/core/dist/utilities/error-common';\nimport { ChangeStreamOperationTypes } from '@lyxa.ai/core/dist/libraries/event/events/mongo-stream-changes-batch-process-shops-event';\nimport {\n\tMarketingMetricsRepository,\n\tmarketingMetricsRepository,\n} from '../patterns/repository/MarketingMetricsRepository';\n\n@Service()\nexport class MarketingService {\n\tprivate model: typeof MarketingModel & SoftDeleteModel<Model>;\n\tprivate shopModel: typeof ShopModel & SoftDeleteModel<Shop>;\n\tprivate orderModel: typeof RegularOrderModel & SoftDeleteModel<RegularOrder>;\n\tprivate marketingMetricsRepository: MarketingMetricsRepository;\n\tprivate limitedTimeOfferParticipantModel: typeof LimitedTimeOfferParticipantModel &\n\t\tSoftDeleteModel<LimitedTimeOfferParticipant>;\n\n\tconstructor() {\n\t\tthis.model = MarketingModel as typeof MarketingModel & SoftDeleteModel<Model>;\n\t\tthis.shopModel = ShopModel as typeof ShopModel & SoftDeleteModel<Shop>;\n\t\tthis.orderModel = RegularOrderModel as typeof RegularOrderModel & SoftDeleteModel<RegularOrder>;\n\t\tthis.marketingMetricsRepository = marketingMetricsRepository;\n\t\tthis.limitedTimeOfferParticipantModel =\n\t\t\tLimitedTimeOfferParticipantModel as typeof LimitedTimeOfferParticipantModel &\n\t\t\t\tSoftDeleteModel<LimitedTimeOfferParticipant>;\n\t}\n\n\t/**\n\t * Create a new record\n\t * @param data Data for the new record\n\t */\n\tpublic async create(\n\t\tdata: AllMarketingsValidationDTO,\n\t\tadmin?: mongoose.Types.ObjectId\n\t): Promise<DocumentType<Model>> {\n\t\tconst now = dayjs().toDate();\n\n\t\tconst existingRecord = await this.model.findOne({\n\t\t\tshop: data.shop,\n\t\t\tmarketingType: data.marketingType,\n\t\t\t'duration.end': { $gt: now },\n\t\t\tstatus: MarketingStatus.ACTIVE,\n\t\t\tisAppliedByCompany: data.isAppliedByCompany,\n\t\t});\n\n\t\tif (existingRecord) {\n\t\t\tthrow new TRPCError({\n\t\t\t\tcode: 'CONFLICT',\n\t\t\t\tmessage: `This shop already has a ${data.marketingType} marketing`,\n\t\t\t});\n\t\t}\n\n\t\tif (data.isEntireMenu) {\n\t\t\tconst existingMarketing = await this.model.findOne({\n\t\t\t\tshop: data.shop,\n\t\t\t\t'duration.start': { $lt: now },\n\t\t\t\t'duration.end': { $gt: now },\n\t\t\t\tstatus: MarketingStatus.ACTIVE,\n\t\t\t\tmarketingType: { $in: [MarketingType.BUY1GET1, MarketingType.DISCOUNT], $ne: data.marketingType },\n\t\t\t});\n\n\t\t\tif (existingMarketing) {\n\t\t\t\tthrow new TRPCError({\n\t\t\t\t\tcode: 'CONFLICT',\n\t\t\t\t\tmessage: `Cannot create marketing for entire menu while another product based marketing exists for the shop`,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (data.products && data.products.length > 0) {\n\t\t\tconst productIds = data.products.map((product: CreateProductMarketingDTO) => product.product);\n\n\t\t\tconst hasDuplicates = new Set(productIds.map(String)).size !== productIds.length;\n\n\t\t\tif (hasDuplicates) {\n\t\t\t\tthrow new TRPCError({\n\t\t\t\t\tcode: 'BAD_REQUEST',\n\t\t\t\t\tmessage: `Product list contains duplicate products`,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst isAvailable = await productMarketingService.checkProductMarketingAvailability(\n\t\t\t\tproductIds,\n\t\t\t\tdata.isAppliedByCompany\n\t\t\t);\n\n\t\t\tif (!isAvailable) {\n\t\t\t\tthrow new TRPCError({\n\t\t\t\t\tcode: 'CONFLICT',\n\t\t\t\t\tmessage: `Product list contains products which already have marketing applied`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (admin) {\n\t\t\tdata.admin = admin;\n\t\t}\n\n\t\tlet marketing: AllMarketingsDTO;\n\n\t\tswitch (data.marketingType) {\n\t\t\tcase MarketingType.DISCOUNT: {\n\t\t\t\tmarketing = await discountMarketingService.create(data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.BUY1GET1: {\n\t\t\t\tmarketing = await buy1Get1MarketingService.create(data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.FEATURED: {\n\t\t\t\tif (!admin) {\n\t\t\t\t\tthrow new TRPCError({\n\t\t\t\t\t\tcode: 'UNAUTHORIZED',\n\t\t\t\t\t\tmessage: `Only admin is allowed to create ${data.marketingType} marketing`,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tmarketing = await featuredMarketingService.create(data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.FREE_DELIVERY: {\n\t\t\t\tmarketing = await freeDeliveryMarketingService.create(data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.PUNCH_MARKETING: {\n\t\t\t\tmarketing = await punchMarketingService.create(data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault: {\n\t\t\t\tmarketing = await this.model.create(data, admin);\n\t\t\t}\n\t\t}\n\n\t\tif (data.isEntireMenu) {\n\t\t\tconst logger = getLibraries().getLoggerKit();\n\t\t\tconst PAGE_SIZE = 500;\n\t\t\tconst marketingId = marketing._id.toString();\n\t\t\tconst shopId = data.shop.toString();\n\n\t\t\tconst count = await ProductModel.countDocuments({ shop: data.shop });\n\t\t\tconst pages = Math.ceil(count / PAGE_SIZE);\n\n\t\t\tif (pages > 0) {\n\t\t\t\tawait Promise.all(\n\t\t\t\t\tArray.from({ length: pages }, (_, page) =>\n\t\t\t\t\t\tpublishEvent(\n\t\t\t\t\t\t\tnew MarketingEntireMenuSeedEvent({\n\t\t\t\t\t\t\t\tmarketingId,\n\t\t\t\t\t\t\t\tshopId,\n\t\t\t\t\t\t\t\tisAppliedByCompany: data.isAppliedByCompany ?? false,\n\t\t\t\t\t\t\t\tisBuy1Get1: data.marketingType === MarketingType.BUY1GET1,\n\t\t\t\t\t\t\t\tvalue: (data as any).value,\n\t\t\t\t\t\t\t\tvalueType: (data as any).valueType,\n\t\t\t\t\t\t\t\tpage,\n\t\t\t\t\t\t\t\tpageSize: PAGE_SIZE,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\t\tlogger.debug(`entire-menu marketing seed dispatched — ${pages} pages for ${count} products`, {\n\t\t\t\t\ttag: 'marketing.seed.entire-menu',\n\t\t\t\t\tdata: { marketingId, shopId, pages, productCount: count },\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tawait productMarketingService.syncProductMarketingInfo(marketing._id);\n\t\t}\n\n\t\tawait this.model.populate(marketing, {\n\t\t\tpath: 'shop',\n\t\t\tmodel: this.shopModel,\n\t\t\tselect: '_id name shopId',\n\t\t});\n\n\t\tif (admin) {\n\t\t\tthis.logMarketingAdminAction(\n\t\t\t\tadmin,\n\t\t\t\tAdminLogsUpdatedFieldsType.ACTIVATED_MARKETING_PROMOTIONS,\n\t\t\t\t'',\n\t\t\t\tmarketing\n\t\t\t);\n\t\t}\n\t\tif (marketing && !data.isEntireMenu)\n\t\t\tpublishSyncSingleShopSectionsInCacheProcessEvent({\n\t\t\t\tshopId: data.shop,\n\t\t\t\toperation: ChangeStreamOperationTypes.UPDATE,\n\t\t\t});\n\t\treturn marketing;\n\t}\n\n\t/**\n\t * Get record by ID\n\t * @param id ID of the record\n\t */\n\tpublic async findById(data: GetByIdInputDTO): Promise<DocumentType<Model>> {\n\t\tconst select = data.select || '';\n\t\tconst populate = data.populate || [];\n\t\tconst result = await this.model\n\t\t\t.findOne({\n\t\t\t\t_id: data._id,\n\t\t\t})\n\t\t\t.select(select)\n\t\t\t.populate(populate);\n\t\tif (!result) {\n\t\t\tthrow new TRPCError({\n\t\t\t\tcode: 'NOT_FOUND',\n\t\t\t\tmessage: 'Record not found',\n\t\t\t});\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get records\n\t * @param query Query to find records\n\t */\n\tpublic async find(data?: Partial<FilterDTO>): Promise<PaginatedResponse<DocumentType<ModelType<Model>>>> {\n\t\ttry {\n\t\t\tconst paginate = createRawDocumentPaginatorObject(this.model as ReturnModelType<typeof this.model>);\n\t\t\tconst options: PaginatorOptions<typeof Model> = {\n\t\t\t\tpage: data?.page,\n\t\t\t\tsize: data?.size,\n\t\t\t\tsort: data?.sort,\n\t\t\t\tquery: data?.query,\n\t\t\t\tselect: data?.select,\n\t\t\t\tpopulate: data?.populate,\n\t\t\t};\n\t\t\tconst paginatedResult = await paginate(options);\n\t\t\treturn paginatedResult;\n\t\t} catch (err) {\n\t\t\tthrow new TRPCError({\n\t\t\t\tcode: 'INTERNAL_SERVER_ERROR',\n\t\t\t\tmessage: `err: ${err}`,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Update record\n\t * @param id ID of the record\n\t * @param data Data to update\n\t */\n\tpublic async update(\n\t\t_id: mongoose.Types.ObjectId,\n\t\tdata: UpdateDTO,\n\t\tadmin?: mongoose.Types.ObjectId\n\t): Promise<DocumentType<Model>> {\n\t\tlet marketing: AllMarketingsDTO;\n\n\t\tconst partOfLimitedTimeOffer = await this.limitedTimeOfferParticipantModel.exists({\n\t\t\tmarketing: _id,\n\t\t});\n\n\t\tif (partOfLimitedTimeOffer) {\n\t\t\tforbiddenError(\n\t\t\t\t'You are currently participating in a Limited Time Offer. Please make your edits from the Limited Time Offer section.'\n\t\t\t);\n\t\t}\n\n\t\tconst oldMarketing = await this.model.findById(_id).populate({\n\t\t\tpath: 'shop',\n\t\t\tselect: '_id name shopId',\n\t\t});\n\n\t\tif (!oldMarketing) {\n\t\t\tnotFoundError('Could not find marketing');\n\t\t}\n\n\t\tif (\n\t\t\tdata.status &&\n\t\t\tdata.status === MarketingStatus.ACTIVE &&\n\t\t\toldMarketing.status === MarketingStatus.PAUSED\n\t\t) {\n\t\t\tawait this.verifyUnpauseConditions(_id);\n\t\t}\n\n\t\tswitch (oldMarketing.marketingType) {\n\t\t\tcase MarketingType.DISCOUNT: {\n\t\t\t\tmarketing = await discountMarketingService.update(_id, data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.BUY1GET1: {\n\t\t\t\tmarketing = await buy1Get1MarketingService.update(_id, data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.FEATURED: {\n\t\t\t\tif (!admin) {\n\t\t\t\t\tunauthorizedError(`Only admin is allowed to update ${data.marketingType} marketing`);\n\t\t\t\t}\n\t\t\t\tmarketing = await featuredMarketingService.update(_id, data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.FREE_DELIVERY: {\n\t\t\t\tmarketing = await freeDeliveryMarketingService.update(_id, data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase MarketingType.PUNCH_MARKETING: {\n\t\t\t\tmarketing = await punchMarketingService.update(_id, data);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault: {\n\t\t\t\tmarketing = await this.model\n\t\t\t\t\t.findOneAndUpdate(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t_id: _id,\n\t\t\t\t\t\t\tdeletedAt: null,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdata,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnew: true,\n\t\t\t\t\t\t\trunValidators: true,\n\t\t\t\t\t\t}\n\t\t\t\t\t)\n\t\t\t\t\t.populate({\n\t\t\t\t\t\tpath: 'shop',\n\t\t\t\t\t\tselect: '_id name shopId',\n\t\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (!marketing) {\n\t\t\tinternalServerError('Could not update marketing');\n\t\t}\n\n\t\tif (admin) {\n\t\t\tconst allChanges = getChangedFields(oldMarketing, marketing);\n\t\t\tconst relevantChanges = filterRelevantChanges(allChanges, data);\n\n\t\t\tif (Object.keys(relevantChanges).includes('status')) {\n\t\t\t\tif (data.status == MarketingStatus.ACTIVE) {\n\t\t\t\t\tthis.logMarketingAdminAction(\n\t\t\t\t\t\tadmin,\n\t\t\t\t\t\tAdminLogsUpdatedFieldsType.CONTINUED_MARKETING_PROMOTIONS,\n\t\t\t\t\t\toldMarketing,\n\t\t\t\t\t\tmarketing\n\t\t\t\t\t);\n\t\t\t\t} else if (data.status == MarketingStatus.PAUSED) {\n\t\t\t\t\tthis.logMarketingAdminAction(\n\t\t\t\t\t\tadmin,\n\t\t\t\t\t\tAdminLogsUpdatedFieldsType.PAUSED_MARKETING_PROMOTIONS,\n\t\t\t\t\t\toldMarketing,\n\t\t\t\t\t\tmarketing\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.logMarketingAdminAction(\n\t\t\t\tadmin,\n\t\t\t\tAdminLogsUpdatedFieldsType.EDITED_MARKETING_PROMOTIONS,\n\t\t\t\toldMarketing,\n\t\t\t\tmarketing\n\t\t\t);\n\t\t}\n\n\t\tawait productMarketingService.syncProductMarketingInfo(_id);\n\n\t\tpublishSyncSingleShopSectionsInCacheProcessEvent({\n\t\t\tshopId: marketing.shop,\n\t\t\toperation: ChangeStreamOperationTypes.UPDATE,\n\t\t});\n\n\t\treturn marketing;\n\t}\n\n\tprivate async verifyUnpauseConditions(marketingId: mongoose.Types.ObjectId): Promise<void> {\n\t\tconst marketing = await this.model.findOne({ _id: marketingId, deletedAt: null }).populate('products');\n\t\tif (!marketing) return;\n\n\t\tif ([MarketingType.BUY1GET1, MarketingType.DISCOUNT].includes(marketing.marketingType)) {\n\t\t\tconst typedMarketing = marketing as DocumentType<DiscountMarketing | Buy1Get1Marketing>;\n\t\t\tconst productIds = typedMarketing.products.map((product: any) => product.product);\n\t\t\tconst isAvailable = await productMarketingService.checkProductMarketingAvailability(\n\t\t\t\tproductIds,\n\t\t\t\tmarketing.isAppliedByCompany\n\t\t\t);\n\n\t\t\tif (!isAvailable) {\n\t\t\t\tconflictError(\"You can't activate discount and B1G1 on the same items at the same time\");\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async logMarketingAdminAction(\n\t\tadmin: mongoose.Types.ObjectId,\n\t\tactionType: AdminLogsUpdatedFieldsType,\n\t\toldDocument?: any,\n\t\tnewDocument?: any\n\t) {\n\t\tconst adminActivityLogsEvent: BaseEvent<Record<string, unknown>> = new AdminActivityLogsEvent({\n\t\t\tadmin,\n\t\t\tupdatedField: actionType,\n\t\t\toldDocument: oldDocument ?? '',\n\t\t\tnewDocument: newDocument ?? '',\n\t\t});\n\t\tawait publishEvent(adminActivityLogsEvent);\n\t}\n\n\t/**\n\t * Delete record\n\t * @param data Data for delete\n\t */\n\tpublic async deleteById(data: DeleteDTO, admin?: mongoose.Types.ObjectId): Promise<string> {\n\t\tconst { _id, softDelete } = data;\n\n\t\tconst record = await this.model.findById(_id).populate({\n\t\t\tpath: 'shop',\n\t\t\tselect: '_id name shopId',\n\t\t});\n\n\t\tif (!record) {\n\t\t\tthrow new TRPCError({\n\t\t\t\tcode: 'NOT_FOUND',\n\t\t\t\tmessage: 'Record not found',\n\t\t\t});\n\t\t}\n\n\t\trecord.status = MarketingStatus.DELETED;\n\t\tawait record.save();\n\n\t\tconst result = await this.model.deleteRecord({ _id, deletedAt: null }, softDelete);\n\t\tif (!result) {\n\t\t\tthrow new TRPCError({\n\t\t\t\tcode: 'INTERNAL_SERVER_ERROR',\n\t\t\t\tmessage: 'Unable to delete marketing',\n\t\t\t});\n\t\t}\n\n\t\tif (admin) {\n\t\t\tthis.logMarketingAdminAction(admin, AdminLogsUpdatedFieldsType.DELETED_MARKETING_PROMOTIONS, record);\n\t\t}\n\n\t\tproductMarketingService.deleteMany({ marketing: data._id }, data.softDelete);\n\t\tpublishSyncSingleShopSectionsInCacheProcessEvent({\n\t\t\tshopId: record.shop._id,\n\t\t\toperation: ChangeStreamOperationTypes.DELETE,\n\t\t});\n\t\treturn 'Record deleted successfully';\n\t}\n\n\tpublic async deleteManyByIds(marketingIds: mongoose.Types.ObjectId[], softDelete = true): Promise<void> {\n\t\tif (marketingIds.length === 0) return;\n\n\t\tconst marketings = await this.model\n\t\t\t.find({ _id: { $in: marketingIds } })\n\t\t\t.select('shop')\n\t\t\t.lean();\n\n\t\tawait this.model.updateMany(\n\t\t\t{ _id: { $in: marketingIds } },\n\t\t\t{ $set: { status: MarketingStatus.DELETED } }\n\t\t);\n\t\tawait this.model.deleteRecords({ _id: { $in: marketingIds } }, softDelete);\n\n\t\tawait productMarketingService.deleteManyByMarketingIds(marketingIds, softDelete);\n\n\t\tconst uniqueShopIds = [\n\t\t\t...new Map(\n\t\t\t\tmarketings\n\t\t\t\t\t.map((m: any) => m.shop)\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.map((s: any) => {\n\t\t\t\t\t\tconst id = s instanceof mongoose.Types.ObjectId ? s : new mongoose.Types.ObjectId(s);\n\t\t\t\t\t\treturn [id.toString(), id] as [string, mongoose.Types.ObjectId];\n\t\t\t\t\t})\n\t\t\t).values(),\n\t\t];\n\n\t\tfor (const shopId of uniqueShopIds) {\n\t\t\tpublishSyncSingleShopSectionsInCacheProcessEvent({\n\t\t\t\tshopId,\n\t\t\t\toperation: ChangeStreamOperationTypes.DELETE,\n\t\t\t});\n\t\t}\n\t}\n\n\tpublic async handleNewProductForEntireMenuMarketing(data: ProductEntireMenuMarketingHandlerDTO) {\n\t\tconst existingMarketing = await this.model.findOne({\n\t\t\tshop: data.shop,\n\t\t\tmarketingType: { $in: [MarketingType.BUY1GET1, MarketingType.DISCOUNT] },\n\t\t\t'duration.end': { $gt: new Date() },\n\t\t\tstatus: MarketingStatus.ACTIVE,\n\t\t\tisEntireMenu: true,\n\t\t});\n\n\t\t// check if existing marketing\n\t\tif (existingMarketing) {\n\t\t\tconst productMarketingData: CreateProductMarketingDTO = {\n\t\t\t\tproduct: data.product,\n\t\t\t\tmarketing: existingMarketing._id,\n\t\t\t};\n\t\t\tif (existingMarketing.marketingType == MarketingType.BUY1GET1) {\n\t\t\t\tproductMarketingData.isBuy1Get1 = true;\n\t\t\t}\n\t\t\tif (existingMarketing.marketingType == MarketingType.DISCOUNT) {\n\t\t\t\tproductMarketingData.value = (existingMarketing as DocumentType<DiscountMarketing>).value;\n\t\t\t\tproductMarketingData.valueType = (existingMarketing as DocumentType<DiscountMarketing>).valueType;\n\t\t\t}\n\n\t\t\tawait productMarketingService.create(productMarketingData);\n\t\t\tawait synchronizeProductMarketingService.syncProductMarketingInfoByProductId(data.product);\n\t\t}\n\n\t\treturn 'Product handled successfully';\n\t}\n\n\tpublic async incrementMarketingSpentFromOrder(orderId: mongoose.Types.ObjectId): Promise<any> {\n\t\tconst order = (await this.orderModel\n\t\t\t.findById(orderId)\n\t\t\t.populate([\n\t\t\t\t{ path: 'lineItems' },\n\t\t\t\t{ path: 'marketings', select: 'isAppliedByCompany' },\n\t\t\t])) as DocumentType<RegularOrder> & {\n\t\t\tlineItems: DocumentType<LineItem>[];\n\t\t\tmarketings: DocumentType<Marketing>[];\n\t\t};\n\n\t\tif (!order || !order.marketings?.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst marketingSpendMap = this.marketingMetricsRepository.calculateMarketingSpendPerOrder(order);\n\n\t\tconst bulkOps = [...marketingSpendMap.entries()].map(([marketingId, amountSpent]) => ({\n\t\t\tupdateOne: {\n\t\t\t\tfilter: { _id: marketingId },\n\t\t\t\tupdate: { $inc: { amountSpent } },\n\t\t\t},\n\t\t}));\n\n\t\tif (bulkOps.length) {\n\t\t\tawait this.model.bulkWrite(bulkOps);\n\t\t}\n\n\t\tawait this.expireMarketingsExceedingSpendLimits(Array.from(marketingSpendMap.keys()));\n\n\t\treturn 'Marketing amount spent incremented successfully';\n\t}\n\n\tpublic async decrementMarketingSpentFromOrder(orderId: mongoose.Types.ObjectId): Promise<any> {\n\t\tconst order = (await this.orderModel\n\t\t\t.findById(orderId)\n\t\t\t.populate([\n\t\t\t\t{ path: 'lineItems' },\n\t\t\t\t{ path: 'marketings', select: 'isAppliedByCompany' },\n\t\t\t])) as DocumentType<RegularOrder> & {\n\t\t\tlineItems: DocumentType<LineItem>[];\n\t\t\tmarketings: DocumentType<Marketing>[];\n\t\t};\n\n\t\tif (!order || !order.marketings?.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst marketingSpendMap = this.marketingMetricsRepository.calculateMarketingSpendPerOrder(order);\n\n\t\tconst bulkOps = [...marketingSpendMap.entries()].map(([marketingId, amountSpent]) => ({\n\t\t\tupdateOne: {\n\t\t\t\tfilter: { _id: marketingId },\n\t\t\t\tupdate: { $inc: { amountSpent: -amountSpent } },\n\t\t\t},\n\t\t}));\n\n\t\tif (bulkOps.length) {\n\t\t\tawait this.model.bulkWrite(bulkOps);\n\t\t}\n\n\t\tawait this.unExpireMarketingsBelowSpendLimits(Array.from(marketingSpendMap.keys()));\n\n\t\treturn 'Marketing amount spent decremented successfully';\n\t}\n\n\tprivate async expireMarketingsExceedingSpendLimits(marketingIds: mongoose.Types.ObjectId[]) {\n\t\tconst update = await this.model.updateMany(\n\t\t\t{\n\t\t\t\t_id: { $in: marketingIds },\n\t\t\t\tisSpendLimitEnabled: true,\n\t\t\t\tstatus: { $ne: MarketingStatus.EXPIRED },\n\t\t\t\t$expr: { $gte: ['$amountSpent', '$spendLimit'] },\n\t\t\t},\n\t\t\t{ $set: { status: MarketingStatus.EXPIRED, expiredAt: dayjs().toDate() } }\n\t\t);\n\n\t\tif (update.modifiedCount > 0) {\n\t\t\tawait synchronizeProductMarketingService.syncProductMarketingInfoByMarketingIds(marketingIds);\n\t\t}\n\t}\n\n\tprivate async unExpireMarketingsBelowSpendLimits(marketingIds: mongoose.Types.ObjectId[]) {\n\t\tconst update = await this.model.updateMany(\n\t\t\t{\n\t\t\t\t_id: { $in: marketingIds },\n\t\t\t\tisSpendLimitEnabled: true,\n\t\t\t\tstatus: MarketingStatus.EXPIRED,\n\t\t\t\t$expr: { $lt: ['$amountSpent', '$spendLimit'] },\n\t\t\t},\n\t\t\t{ $set: { status: MarketingStatus.ACTIVE }, $unset: { expiredAt: 1 } }\n\t\t);\n\n\t\tif (update.modifiedCount > 0) {\n\t\t\tawait synchronizeProductMarketingService.syncProductMarketingInfoByMarketingIds(marketingIds);\n\t\t}\n\t}\n\n\tpublic async recomputeAndSyncAllMarketingSpent(): Promise<void> {\n\t\tconst cursor = this.model\n\t\t\t.find({ marketingType: { $ne: MarketingType.FEATURED } }, { _id: 1 })\n\t\t\t.lean()\n\t\t\t.cursor();\n\n\t\tconst bulkOps: mongoose.AnyBulkWriteOperation[] = [];\n\t\tconst marketingIds: mongoose.Types.ObjectId[] = [];\n\n\t\tfor await (const marketing of cursor) {\n\t\t\tconst { amount } = await this.marketingMetricsRepository.calculateMarketingSpent(marketing._id);\n\n\t\t\tbulkOps.push({\n\t\t\t\tupdateOne: {\n\t\t\t\t\tfilter: { _id: marketing._id },\n\t\t\t\t\tupdate: {\n\t\t\t\t\t\t$set: { amountSpent: amount },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tmarketingIds.push(marketing._id);\n\n\t\t\tif (bulkOps.length >= 500) {\n\t\t\t\tawait this.model.bulkWrite(bulkOps);\n\t\t\t\tbulkOps.length = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (bulkOps.length) {\n\t\t\tawait this.model.bulkWrite(bulkOps);\n\t\t}\n\n\t\tawait this.expireMarketingsExceedingSpendLimits(marketingIds);\n\t\tawait this.unExpireMarketingsBelowSpendLimits(marketingIds);\n\t}\n\n\tpublic async reseedEntireMenuMarketing(marketingId: mongoose.Types.ObjectId): Promise<void> {\n\t\tconst logger = getLibraries().getLoggerKit();\n\n\t\tconst marketing = await this.model.findOne({ _id: marketingId, deletedAt: null });\n\t\tif (!marketing) internalServerError('Marketing not found');\n\n\t\tconst PAGE_SIZE = 500;\n\t\tconst mid = marketingId.toString();\n\t\tconst shopId = marketing.shop.toString();\n\t\tconst count = await ProductModel.countDocuments({ shop: marketing.shop });\n\t\tconst pages = Math.ceil(count / PAGE_SIZE);\n\n\t\tif (!pages) return;\n\n\t\tawait Promise.all(\n\t\t\tArray.from({ length: pages }, (_, page) =>\n\t\t\t\tpublishEvent(\n\t\t\t\t\tnew MarketingEntireMenuSeedEvent({\n\t\t\t\t\t\tmarketingId: mid,\n\t\t\t\t\t\tshopId,\n\t\t\t\t\t\tisAppliedByCompany: marketing.isAppliedByCompany ?? false,\n\t\t\t\t\t\tisBuy1Get1: marketing.marketingType === MarketingType.BUY1GET1,\n\t\t\t\t\t\tvalue: (marketing as any).value,\n\t\t\t\t\t\tvalueType: (marketing as any).valueType,\n\t\t\t\t\t\tpage,\n\t\t\t\t\t\tpageSize: PAGE_SIZE,\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\n\t\tlogger.debug(`entire-menu reseed dispatched — ${pages} pages for ${count} products`, {\n\t\t\ttag: 'marketing.seed.entire-menu',\n\t\t\tdata: { marketingId: mid, shopId, pages, productCount: count },\n\t\t});\n\t}\n}\n\nexport const marketingService = Container.get(MarketingService);\n"]}
|
|
@@ -48,7 +48,7 @@ async function EvaluatePredictionsConsumer() {
|
|
|
48
48
|
data: { tournamentId: tournamentId.toString(), matchId: matchId.toString(), winnerCount: winnerIds.length, couponDetails },
|
|
49
49
|
});
|
|
50
50
|
await notification_service_1.tournamentNotificationService.notifyUsers({
|
|
51
|
-
title: winner?.title ?? '🎉 Your prediction was
|
|
51
|
+
title: winner?.title ?? '🎉 Your prediction was correct.',
|
|
52
52
|
body: winner?.body ?? 'Your coupon is now available',
|
|
53
53
|
tournamentId: tournamentId.toString(),
|
|
54
54
|
tournamentNotificationType: enum_1.TournamentNotificationTag.MATCH_RESULT_WINNER,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluate-predictions.consumer.js","sourceRoot":"/","sources":["modules/tournament/match/events/evaluate-predictions.consumer.ts"],"names":[],"mappings":"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","sourcesContent":["import {mongoose} from '@typegoose/typegoose';\nimport {EvaluatePredictionsPayload, TOURNAMENT_EVALUATE_PREDICTIONS_EVENT} from './evaluate-predictions.event';\nimport {predictionService} from '../../prediction/prediction.service';\nimport {rewardService} from '../../reward/reward.service';\nimport {tournamentNotificationService} from '../../notification/notification.service';\nimport {notificationTemplateRepository} from '../../notification/notification-template.repository';\nimport {TournamentNotificationTag} from '@lyxa.ai/core/dist/utilities/enum';\nimport {RewardBuilder} from '../../reward/reward.builder';\nimport {CouponRewardResult, RewardType} from '../../reward/reward.types';\nimport {subscribeToEvent} from '@lyxa.ai/core/dist/libraries/event';\nimport {tournamentConfigRepository} from '@modules/tournament/config/config.repository';\nimport {notFoundError} from '@lyxa.ai/core/dist/utilities/error-common';\nimport {matchEvaluationOrchestrator} from \"@modules/tournament/match/events/match-evaluation.orchestrator\";\nimport {getLibraries} from '@lyxa.ai/core';\n\nexport async function EvaluatePredictionsConsumer() {\n await subscribeToEvent(TOURNAMENT_EVALUATE_PREDICTIONS_EVENT, async (payload: EvaluatePredictionsPayload) => {\n if (!payload.matches?.length) return;\n const tournamentId = new mongoose.Types.ObjectId(payload.tournamentId.toString());\n const logger = getLibraries().getLoggerKit();\n\n logger.info('Evaluate predictions event received', {\n tag: 'tournament.prediction.evaluate',\n data: { tournamentId: tournamentId.toString(), matchCount: payload.matches.length, matches: payload.matches },\n });\n\n const tournament = await tournamentConfigRepository.findById(tournamentId);\n if (!tournament) {\n notFoundError('Tournament not found');\n }\n\n const templates = await notificationTemplateRepository.findTemplates(tournamentId);\n const winner = templates.get(TournamentNotificationTag.MATCH_RESULT_WINNER);\n const config = await matchEvaluationOrchestrator.resolveCouponConfig(tournamentId);\n\t\tif (!config) return;\n\n for (const m of payload.matches) {\n const matchId = new mongoose.Types.ObjectId(m.matchId.toString());\n const { winnerIds } = await predictionService.evaluate(tournamentId, matchId, m.finalScore);\n\n logger.info('Predictions evaluated', {\n tag: 'tournament.prediction.evaluate',\n data: { tournamentId: tournamentId.toString(), matchId: matchId.toString(), matchNumber: m.matchNumber, finalScore: m.finalScore, winnerCount: winnerIds.length, winnerIds },\n });\n\n if (config && winnerIds.length > 0) {\n const couponDetails = await rewardService.grant(\n RewardType.COUPON,\n RewardBuilder.buildCouponRewardContext(matchId, tournamentId, winnerIds, m.matchNumber, config)\n );\n\n logger.info('Coupon reward granted', {\n tag: 'tournament.prediction.evaluate',\n data: { tournamentId: tournamentId.toString(), matchId: matchId.toString(), winnerCount: winnerIds.length, couponDetails },\n });\n\n await tournamentNotificationService.notifyUsers(\n {\n title: winner?.title ?? '🎉 Your prediction was
|
|
1
|
+
{"version":3,"file":"evaluate-predictions.consumer.js","sourceRoot":"/","sources":["modules/tournament/match/events/evaluate-predictions.consumer.ts"],"names":[],"mappings":"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","sourcesContent":["import {mongoose} from '@typegoose/typegoose';\nimport {EvaluatePredictionsPayload, TOURNAMENT_EVALUATE_PREDICTIONS_EVENT} from './evaluate-predictions.event';\nimport {predictionService} from '../../prediction/prediction.service';\nimport {rewardService} from '../../reward/reward.service';\nimport {tournamentNotificationService} from '../../notification/notification.service';\nimport {notificationTemplateRepository} from '../../notification/notification-template.repository';\nimport {TournamentNotificationTag} from '@lyxa.ai/core/dist/utilities/enum';\nimport {RewardBuilder} from '../../reward/reward.builder';\nimport {CouponRewardResult, RewardType} from '../../reward/reward.types';\nimport {subscribeToEvent} from '@lyxa.ai/core/dist/libraries/event';\nimport {tournamentConfigRepository} from '@modules/tournament/config/config.repository';\nimport {notFoundError} from '@lyxa.ai/core/dist/utilities/error-common';\nimport {matchEvaluationOrchestrator} from \"@modules/tournament/match/events/match-evaluation.orchestrator\";\nimport {getLibraries} from '@lyxa.ai/core';\n\nexport async function EvaluatePredictionsConsumer() {\n await subscribeToEvent(TOURNAMENT_EVALUATE_PREDICTIONS_EVENT, async (payload: EvaluatePredictionsPayload) => {\n if (!payload.matches?.length) return;\n const tournamentId = new mongoose.Types.ObjectId(payload.tournamentId.toString());\n const logger = getLibraries().getLoggerKit();\n\n logger.info('Evaluate predictions event received', {\n tag: 'tournament.prediction.evaluate',\n data: { tournamentId: tournamentId.toString(), matchCount: payload.matches.length, matches: payload.matches },\n });\n\n const tournament = await tournamentConfigRepository.findById(tournamentId);\n if (!tournament) {\n notFoundError('Tournament not found');\n }\n\n const templates = await notificationTemplateRepository.findTemplates(tournamentId);\n const winner = templates.get(TournamentNotificationTag.MATCH_RESULT_WINNER);\n const config = await matchEvaluationOrchestrator.resolveCouponConfig(tournamentId);\n\t\tif (!config) return;\n\n for (const m of payload.matches) {\n const matchId = new mongoose.Types.ObjectId(m.matchId.toString());\n const { winnerIds } = await predictionService.evaluate(tournamentId, matchId, m.finalScore);\n\n logger.info('Predictions evaluated', {\n tag: 'tournament.prediction.evaluate',\n data: { tournamentId: tournamentId.toString(), matchId: matchId.toString(), matchNumber: m.matchNumber, finalScore: m.finalScore, winnerCount: winnerIds.length, winnerIds },\n });\n\n if (config && winnerIds.length > 0) {\n const couponDetails = await rewardService.grant(\n RewardType.COUPON,\n RewardBuilder.buildCouponRewardContext(matchId, tournamentId, winnerIds, m.matchNumber, config)\n );\n\n logger.info('Coupon reward granted', {\n tag: 'tournament.prediction.evaluate',\n data: { tournamentId: tournamentId.toString(), matchId: matchId.toString(), winnerCount: winnerIds.length, couponDetails },\n });\n\n await tournamentNotificationService.notifyUsers(\n {\n title: winner?.title ?? '🎉 Your prediction was correct.',\n body: winner?.body ?? 'Your coupon is now available',\n tournamentId: tournamentId.toString(),\n tournamentNotificationType: TournamentNotificationTag.MATCH_RESULT_WINNER,\n deepLinkRoute: tournament.deepLinkRoute,\n matchId: matchId.toString(),\n couponCode: (couponDetails as CouponRewardResult).code,\n couponOfferLabel: (couponDetails as CouponRewardResult).couponOfferLabel,\n couponValidityLabel: (couponDetails as CouponRewardResult).couponValidityLabel,\n couponStartDate: (couponDetails as CouponRewardResult).duration.start,\n couponEndDate: (couponDetails as CouponRewardResult).duration.end,\n },\n winnerIds\n );\n }\n }\n });\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification.service.spec.d.ts","sourceRoot":"/","sources":["modules/tournament/notification/__tests__/notification.service.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
jest.mock('typedi', () => ({
|
|
7
|
+
__esModule: true,
|
|
8
|
+
default: { get: jest.fn() },
|
|
9
|
+
Container: { get: jest.fn() },
|
|
10
|
+
Service: () => (target) => target,
|
|
11
|
+
}));
|
|
12
|
+
jest.mock('../../participant/participant.service', () => ({
|
|
13
|
+
ParticipantService: class {
|
|
14
|
+
},
|
|
15
|
+
participantService: {},
|
|
16
|
+
}));
|
|
17
|
+
const publishEventMock = jest.fn().mockResolvedValue(undefined);
|
|
18
|
+
jest.mock('@lyxa.ai/core/dist/libraries/event', () => ({
|
|
19
|
+
publishEvent: (...args) => publishEventMock(...args),
|
|
20
|
+
}));
|
|
21
|
+
const loggerMock = { info: jest.fn(), error: jest.fn() };
|
|
22
|
+
jest.mock('@lyxa.ai/core', () => ({
|
|
23
|
+
getLibraries: () => ({ getLoggerKit: () => loggerMock }),
|
|
24
|
+
}));
|
|
25
|
+
const mongoose_1 = require("mongoose");
|
|
26
|
+
const typedi_1 = __importDefault(require("typedi"));
|
|
27
|
+
const enum_1 = require("@lyxa.ai/core/dist/utilities/enum");
|
|
28
|
+
const notification_service_1 = require("../notification.service");
|
|
29
|
+
describe('TournamentNotificationService', () => {
|
|
30
|
+
const participantService = {
|
|
31
|
+
getPredicted: jest.fn(),
|
|
32
|
+
getUnpredicted: jest.fn(),
|
|
33
|
+
};
|
|
34
|
+
let service;
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
jest.clearAllMocks();
|
|
37
|
+
typedi_1.default.get.mockReturnValue(participantService);
|
|
38
|
+
service = new notification_service_1.TournamentNotificationService();
|
|
39
|
+
});
|
|
40
|
+
function makeMatch(overrides = {}) {
|
|
41
|
+
return {
|
|
42
|
+
_id: new mongoose_1.Types.ObjectId(),
|
|
43
|
+
stage: 'group',
|
|
44
|
+
team1: { teamId: new mongoose_1.Types.ObjectId(), name: 'Lions' },
|
|
45
|
+
team2: { teamId: new mongoose_1.Types.ObjectId(), name: 'Tigers' },
|
|
46
|
+
score: { team1: null, team2: null },
|
|
47
|
+
winnerId: null,
|
|
48
|
+
startingDate: new Date('2026-06-20T18:00:00Z'),
|
|
49
|
+
matchEnded: false,
|
|
50
|
+
notificationFlags: new Map(),
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function makeTournament(overrides = {}) {
|
|
55
|
+
return {
|
|
56
|
+
_id: new mongoose_1.Types.ObjectId(),
|
|
57
|
+
deepLinkRoute: '/tournament/123',
|
|
58
|
+
...overrides,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function makeParticipant(overrides = {}) {
|
|
62
|
+
return {
|
|
63
|
+
_id: new mongoose_1.Types.ObjectId(),
|
|
64
|
+
userId: new mongoose_1.Types.ObjectId(),
|
|
65
|
+
tournamentId: new mongoose_1.Types.ObjectId(),
|
|
66
|
+
status: 'joined',
|
|
67
|
+
predictions: [],
|
|
68
|
+
...overrides,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function makeTemplates(tag, title, body) {
|
|
72
|
+
return new Map([[tag, { title, body }]]);
|
|
73
|
+
}
|
|
74
|
+
describe('notifyUsers', () => {
|
|
75
|
+
it('publishes one notification per recipient and logs success', async () => {
|
|
76
|
+
const userIds = [new mongoose_1.Types.ObjectId(), new mongoose_1.Types.ObjectId()];
|
|
77
|
+
await service.notifyUsers({
|
|
78
|
+
title: 'Prediction reminder',
|
|
79
|
+
body: 'You have not predicted yet',
|
|
80
|
+
tournamentNotificationType: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,
|
|
81
|
+
tournamentId: 'tournament-1',
|
|
82
|
+
}, userIds);
|
|
83
|
+
expect(publishEventMock).toHaveBeenCalledTimes(userIds.length);
|
|
84
|
+
const recipientIds = publishEventMock.mock.calls.map(([event]) => event.payload.data.recipientId);
|
|
85
|
+
expect(recipientIds).toEqual(userIds);
|
|
86
|
+
expect(loggerMock.info).toHaveBeenCalledWith('Tournament user notifications dispatched', expect.objectContaining({ data: expect.objectContaining({ recipientCount: userIds.length }) }));
|
|
87
|
+
expect(loggerMock.error).not.toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
it('does not publish anything for an empty recipient list but still logs', async () => {
|
|
90
|
+
await service.notifyUsers({
|
|
91
|
+
title: 'Prediction reminder',
|
|
92
|
+
body: 'You have not predicted yet',
|
|
93
|
+
tournamentNotificationType: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,
|
|
94
|
+
tournamentId: 'tournament-1',
|
|
95
|
+
}, []);
|
|
96
|
+
expect(publishEventMock).not.toHaveBeenCalled();
|
|
97
|
+
expect(loggerMock.info).toHaveBeenCalledWith('Tournament user notifications dispatched', expect.objectContaining({ data: expect.objectContaining({ recipientCount: 0 }) }));
|
|
98
|
+
});
|
|
99
|
+
it('catches publish failures and logs the error instead of throwing', async () => {
|
|
100
|
+
publishEventMock.mockRejectedValueOnce(new Error('broker unreachable'));
|
|
101
|
+
await expect(service.notifyUsers({
|
|
102
|
+
title: 'Prediction reminder',
|
|
103
|
+
body: 'You have not predicted yet',
|
|
104
|
+
tournamentNotificationType: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,
|
|
105
|
+
tournamentId: 'tournament-1',
|
|
106
|
+
}, [new mongoose_1.Types.ObjectId()])).resolves.toBeUndefined();
|
|
107
|
+
expect(loggerMock.error).toHaveBeenCalledWith('Failed to dispatch tournament user notifications', expect.any(Error), expect.anything());
|
|
108
|
+
expect(loggerMock.info).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('dispatchScheduledReminder', () => {
|
|
112
|
+
it('sends a single global notification for GLOBAL target without resolving recipients', async () => {
|
|
113
|
+
const match = makeMatch();
|
|
114
|
+
const tournament = makeTournament();
|
|
115
|
+
const ctx = {
|
|
116
|
+
type: enum_1.TournamentNotificationTag.TEAM_SELECTION_REMINDER_1_DAY,
|
|
117
|
+
target: enum_1.TournamentScheduledNotificationTarget.GLOBAL,
|
|
118
|
+
templates: makeTemplates(enum_1.TournamentNotificationTag.TEAM_SELECTION_REMINDER_1_DAY, '{team1} vs {team2} kicks off soon', 'Pick your team for {team1} vs {team2}'),
|
|
119
|
+
tournament,
|
|
120
|
+
match,
|
|
121
|
+
};
|
|
122
|
+
await service.dispatchScheduledReminder(ctx);
|
|
123
|
+
expect(participantService.getPredicted).not.toHaveBeenCalled();
|
|
124
|
+
expect(participantService.getUnpredicted).not.toHaveBeenCalled();
|
|
125
|
+
expect(publishEventMock).toHaveBeenCalledTimes(1);
|
|
126
|
+
const event = publishEventMock.mock.calls[0][0];
|
|
127
|
+
expect(event.payload.title).toBe('Lions vs Tigers kicks off soon');
|
|
128
|
+
expect(event.payload.body).toBe('Pick your team for Lions vs Tigers');
|
|
129
|
+
expect(event.payload.data.isGlobal).toBe(true);
|
|
130
|
+
expect(event.payload.data.matchId).toBeUndefined();
|
|
131
|
+
expect(event.payload.data.predictionExpiryTime).toBeUndefined();
|
|
132
|
+
});
|
|
133
|
+
it('resolves predicted participants and notifies them for PREDICTION_REMINDER_1_HOUR', async () => {
|
|
134
|
+
const match = makeMatch();
|
|
135
|
+
const tournament = makeTournament();
|
|
136
|
+
const predicted = [makeParticipant(), makeParticipant()];
|
|
137
|
+
participantService.getPredicted.mockResolvedValue(predicted);
|
|
138
|
+
const ctx = {
|
|
139
|
+
type: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_1_HOUR,
|
|
140
|
+
target: enum_1.TournamentScheduledNotificationTarget.PREDICTED_PARTICIPANTS,
|
|
141
|
+
templates: makeTemplates(enum_1.TournamentNotificationTag.PREDICTION_REMINDER_1_HOUR, '{team1} vs {team2} starts in 1 hour', 'Your prediction for {team1} vs {team2} locks soon'),
|
|
142
|
+
tournament,
|
|
143
|
+
match,
|
|
144
|
+
};
|
|
145
|
+
await service.dispatchScheduledReminder(ctx);
|
|
146
|
+
expect(participantService.getPredicted).toHaveBeenCalledWith(tournament._id, match._id);
|
|
147
|
+
expect(participantService.getUnpredicted).not.toHaveBeenCalled();
|
|
148
|
+
expect(publishEventMock).toHaveBeenCalledTimes(predicted.length);
|
|
149
|
+
const event = publishEventMock.mock.calls[0][0];
|
|
150
|
+
expect(event.payload.title).toBe('Lions vs Tigers starts in 1 hour');
|
|
151
|
+
expect(event.payload.data.matchId).toBe(match._id.toString());
|
|
152
|
+
expect(event.payload.data.predictionExpiryTime).toBe(match.startingDate);
|
|
153
|
+
});
|
|
154
|
+
it('resolves unpredicted participants and notifies them for PREDICTION_REMINDER_3_HOURS', async () => {
|
|
155
|
+
const match = makeMatch();
|
|
156
|
+
const tournament = makeTournament();
|
|
157
|
+
const unpredicted = [makeParticipant(), makeParticipant()];
|
|
158
|
+
participantService.getUnpredicted.mockResolvedValue(unpredicted);
|
|
159
|
+
const ctx = {
|
|
160
|
+
type: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_3_HOURS,
|
|
161
|
+
target: enum_1.TournamentScheduledNotificationTarget.PREDICTION_PENDING_PARTICIPANTS,
|
|
162
|
+
templates: makeTemplates(enum_1.TournamentNotificationTag.PREDICTION_REMINDER_3_HOURS, '{team1} vs {team2} starts in 3 hours', 'Predict {team1} vs {team2} now'),
|
|
163
|
+
tournament,
|
|
164
|
+
match,
|
|
165
|
+
};
|
|
166
|
+
await service.dispatchScheduledReminder(ctx);
|
|
167
|
+
expect(participantService.getUnpredicted).toHaveBeenCalledWith(tournament._id, [match.team1.teamId, match.team2.teamId], match._id);
|
|
168
|
+
expect(participantService.getPredicted).not.toHaveBeenCalled();
|
|
169
|
+
expect(publishEventMock).toHaveBeenCalledTimes(unpredicted.length);
|
|
170
|
+
const event = publishEventMock.mock.calls[0][0];
|
|
171
|
+
expect(event.payload.title).toBe('Lions vs Tigers starts in 3 hours');
|
|
172
|
+
expect(event.payload.data.matchId).toBe(match._id.toString());
|
|
173
|
+
expect(event.payload.data.predictionExpiryTime).toBe(match.startingDate);
|
|
174
|
+
});
|
|
175
|
+
it('resolves unpredicted participants and notifies them for PREDICTION_REMINDER_15_MINUTES', async () => {
|
|
176
|
+
const match = makeMatch();
|
|
177
|
+
const tournament = makeTournament();
|
|
178
|
+
const unpredicted = [makeParticipant()];
|
|
179
|
+
participantService.getUnpredicted.mockResolvedValue(unpredicted);
|
|
180
|
+
const ctx = {
|
|
181
|
+
type: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,
|
|
182
|
+
target: enum_1.TournamentScheduledNotificationTarget.PREDICTION_PENDING_PARTICIPANTS,
|
|
183
|
+
templates: makeTemplates(enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES, '{team1} vs {team2} starts in 15 minutes', 'Last call to predict {team1} vs {team2}'),
|
|
184
|
+
tournament,
|
|
185
|
+
match,
|
|
186
|
+
};
|
|
187
|
+
await service.dispatchScheduledReminder(ctx);
|
|
188
|
+
expect(participantService.getUnpredicted).toHaveBeenCalledWith(tournament._id, [match.team1.teamId, match.team2.teamId], match._id);
|
|
189
|
+
expect(participantService.getPredicted).not.toHaveBeenCalled();
|
|
190
|
+
expect(publishEventMock).toHaveBeenCalledTimes(unpredicted.length);
|
|
191
|
+
const event = publishEventMock.mock.calls[0][0];
|
|
192
|
+
expect(event.payload.title).toBe('Lions vs Tigers starts in 15 minutes');
|
|
193
|
+
expect(event.payload.data.recipientId).toEqual(unpredicted[0].userId);
|
|
194
|
+
});
|
|
195
|
+
it('short-circuits without notifying when there are no recipients', async () => {
|
|
196
|
+
const match = makeMatch();
|
|
197
|
+
const tournament = makeTournament();
|
|
198
|
+
participantService.getUnpredicted.mockResolvedValue([]);
|
|
199
|
+
const ctx = {
|
|
200
|
+
type: enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,
|
|
201
|
+
target: enum_1.TournamentScheduledNotificationTarget.PREDICTION_PENDING_PARTICIPANTS,
|
|
202
|
+
templates: makeTemplates(enum_1.TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES, 'Last call for {team1} vs {team2}', 'Predict now'),
|
|
203
|
+
tournament,
|
|
204
|
+
match,
|
|
205
|
+
};
|
|
206
|
+
await service.dispatchScheduledReminder(ctx);
|
|
207
|
+
expect(publishEventMock).not.toHaveBeenCalled();
|
|
208
|
+
expect(loggerMock.info).not.toHaveBeenCalled();
|
|
209
|
+
});
|
|
210
|
+
it('throws when no template exists for the requested tag', async () => {
|
|
211
|
+
const match = makeMatch();
|
|
212
|
+
const tournament = makeTournament();
|
|
213
|
+
const ctx = {
|
|
214
|
+
type: enum_1.TournamentNotificationTag.MATCH_RESULT_WINNER,
|
|
215
|
+
target: enum_1.TournamentScheduledNotificationTarget.GLOBAL,
|
|
216
|
+
templates: new Map(),
|
|
217
|
+
tournament,
|
|
218
|
+
match,
|
|
219
|
+
};
|
|
220
|
+
await expect(service.dispatchScheduledReminder(ctx)).rejects.toThrow(`No notification template for tag: ${enum_1.TournamentNotificationTag.MATCH_RESULT_WINNER}`);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
describe('match result notifications', () => {
|
|
224
|
+
it('notifies winners with their coupon reward details attached', async () => {
|
|
225
|
+
const match = makeMatch();
|
|
226
|
+
const winnerIds = [new mongoose_1.Types.ObjectId(), new mongoose_1.Types.ObjectId()];
|
|
227
|
+
await service.notifyUsers({
|
|
228
|
+
title: 'Your prediction was correct',
|
|
229
|
+
body: 'Your coupon is now available',
|
|
230
|
+
tournamentNotificationType: enum_1.TournamentNotificationTag.MATCH_RESULT_WINNER,
|
|
231
|
+
tournamentId: 'tournament-1',
|
|
232
|
+
deepLinkRoute: '/deep/link',
|
|
233
|
+
matchId: match._id.toString(),
|
|
234
|
+
couponCode: 'WIN-50',
|
|
235
|
+
couponOfferLabel: '50% off',
|
|
236
|
+
}, winnerIds);
|
|
237
|
+
expect(publishEventMock).toHaveBeenCalledTimes(winnerIds.length);
|
|
238
|
+
const payloads = publishEventMock.mock.calls.map(([event]) => event.payload);
|
|
239
|
+
expect(payloads.map(p => p.data.recipientId)).toEqual(winnerIds);
|
|
240
|
+
payloads.forEach(payload => {
|
|
241
|
+
expect(payload.data).toMatchObject({
|
|
242
|
+
tournamentNotificationType: enum_1.TournamentNotificationTag.MATCH_RESULT_WINNER,
|
|
243
|
+
matchId: match._id.toString(),
|
|
244
|
+
couponCode: 'WIN-50',
|
|
245
|
+
couponOfferLabel: '50% off',
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
it('notifies losers without coupon reward details', async () => {
|
|
250
|
+
const match = makeMatch();
|
|
251
|
+
const loserIds = [new mongoose_1.Types.ObjectId()];
|
|
252
|
+
await service.notifyUsers({
|
|
253
|
+
title: 'Better luck next time',
|
|
254
|
+
body: 'Your prediction did not match the result',
|
|
255
|
+
tournamentNotificationType: enum_1.TournamentNotificationTag.MATCH_RESULT_LOSER,
|
|
256
|
+
tournamentId: 'tournament-1',
|
|
257
|
+
matchId: match._id.toString(),
|
|
258
|
+
}, loserIds);
|
|
259
|
+
expect(publishEventMock).toHaveBeenCalledTimes(loserIds.length);
|
|
260
|
+
const event = publishEventMock.mock.calls[0][0];
|
|
261
|
+
expect(event.payload.data.tournamentNotificationType).toBe(enum_1.TournamentNotificationTag.MATCH_RESULT_LOSER);
|
|
262
|
+
expect(event.payload.data.couponCode).toBeUndefined();
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
//# sourceMappingURL=notification.service.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification.service.spec.js","sourceRoot":"/","sources":["modules/tournament/notification/__tests__/notification.service.spec.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TournamentNotificationService unit tests\n *\n * Covers:\n * - notifyGlobal: builds and publishes a NotificationGlobalEvent with tournament-specific overrides\n * - notifyUsers: publishes one NotificationEvent per recipient, logs success, and on failure\n * catches the error and logs it instead of throwing\n * - dispatchScheduledReminder:\n * - GLOBAL target dispatches a single global notification without resolving recipients\n * - PREDICTION_REMINDER_1_HOUR resolves recipients via getPredicted (predicted participants)\n * - any other tag resolves recipients via getUnpredicted (unpredicted participants)\n * - empty recipient list short-circuits without calling notifyUsers\n * - targeted messages include matchId + predictionExpiryTime, global/base messages don't\n * - {team1}/{team2} placeholders are substituted in both title and body\n * - missing template for the requested tag throws\n */\n\njest.mock('typedi', () => ({\n __esModule: true,\n default: {get: jest.fn()},\n Container: {get: jest.fn()},\n Service: () => (target: any) => target,\n}));\n\njest.mock('../../participant/participant.service', () => ({\n ParticipantService: class {},\n participantService: {},\n}));\n\nconst publishEventMock = jest.fn().mockResolvedValue(undefined);\njest.mock('@lyxa.ai/core/dist/libraries/event', () => ({\n publishEvent: (...args: any[]) => publishEventMock(...args),\n}));\n\nconst loggerMock = {info: jest.fn(), error: jest.fn()};\njest.mock('@lyxa.ai/core', () => ({\n getLibraries: () => ({getLoggerKit: () => loggerMock}),\n}));\n\nimport {Types} from 'mongoose';\nimport Container from 'typedi';\nimport {TournamentNotificationTag, TournamentScheduledNotificationTarget} from '@lyxa.ai/core/dist/utilities/enum';\nimport {NotificationMessage, TournamentMatch} from '@lyxa.ai/core/dist/libraries/mongo/models';\nimport {TournamentNotificationService, ScheduledReminderContext} from '../notification.service';\nimport {TournamentParticipantOutputDTO} from '../../participant/participant.validation';\nimport {TournamentDocument} from '../../config/config.types';\n\ndescribe('TournamentNotificationService', () => {\n const participantService = {\n getPredicted: jest.fn(),\n getUnpredicted: jest.fn(),\n };\n\n let service: TournamentNotificationService;\n\n beforeEach(() => {\n jest.clearAllMocks();\n (Container.get as jest.Mock).mockReturnValue(participantService);\n service = new TournamentNotificationService();\n });\n\n function makeMatch(overrides: Partial<TournamentMatch> = {}): TournamentMatch {\n return {\n _id: new Types.ObjectId(),\n stage: 'group',\n team1: {teamId: new Types.ObjectId(), name: 'Lions'},\n team2: {teamId: new Types.ObjectId(), name: 'Tigers'},\n score: {team1: null, team2: null},\n winnerId: null,\n startingDate: new Date('2026-06-20T18:00:00Z'),\n matchEnded: false,\n notificationFlags: new Map(),\n ...overrides,\n } as TournamentMatch;\n }\n\n function makeTournament(overrides: Partial<TournamentDocument> = {}): TournamentDocument {\n return {\n _id: new Types.ObjectId(),\n deepLinkRoute: '/tournament/123',\n ...overrides,\n } as TournamentDocument;\n }\n\n function makeParticipant(overrides: Partial<TournamentParticipantOutputDTO> = {}): TournamentParticipantOutputDTO {\n return {\n _id: new Types.ObjectId(),\n userId: new Types.ObjectId(),\n tournamentId: new Types.ObjectId(),\n status: 'joined',\n predictions: [],\n ...overrides,\n } as TournamentParticipantOutputDTO;\n }\n\n function makeTemplates(tag: TournamentNotificationTag, title: string, body: string): Map<TournamentNotificationTag, NotificationMessage> {\n return new Map([[tag, {title, body} as NotificationMessage]]);\n }\n\n describe('notifyUsers', () => {\n it('publishes one notification per recipient and logs success', async () => {\n const userIds = [new Types.ObjectId(), new Types.ObjectId()];\n\n await service.notifyUsers(\n {\n title: 'Prediction reminder',\n body: 'You have not predicted yet',\n tournamentNotificationType: TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n tournamentId: 'tournament-1',\n },\n userIds\n );\n\n expect(publishEventMock).toHaveBeenCalledTimes(userIds.length);\n const recipientIds = publishEventMock.mock.calls.map(([event]) => event.payload.data.recipientId);\n expect(recipientIds).toEqual(userIds);\n expect(loggerMock.info).toHaveBeenCalledWith(\n 'Tournament user notifications dispatched',\n expect.objectContaining({data: expect.objectContaining({recipientCount: userIds.length})})\n );\n expect(loggerMock.error).not.toHaveBeenCalled();\n });\n\n it('does not publish anything for an empty recipient list but still logs', async () => {\n await service.notifyUsers(\n {\n title: 'Prediction reminder',\n body: 'You have not predicted yet',\n tournamentNotificationType: TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n tournamentId: 'tournament-1',\n },\n []\n );\n\n expect(publishEventMock).not.toHaveBeenCalled();\n expect(loggerMock.info).toHaveBeenCalledWith(\n 'Tournament user notifications dispatched',\n expect.objectContaining({data: expect.objectContaining({recipientCount: 0})})\n );\n });\n\n it('catches publish failures and logs the error instead of throwing', async () => {\n publishEventMock.mockRejectedValueOnce(new Error('broker unreachable'));\n\n await expect(\n service.notifyUsers(\n {\n title: 'Prediction reminder',\n body: 'You have not predicted yet',\n tournamentNotificationType: TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n tournamentId: 'tournament-1',\n },\n [new Types.ObjectId()]\n )\n ).resolves.toBeUndefined();\n\n expect(loggerMock.error).toHaveBeenCalledWith(\n 'Failed to dispatch tournament user notifications',\n expect.any(Error),\n expect.anything()\n );\n expect(loggerMock.info).not.toHaveBeenCalled();\n });\n });\n\n describe('dispatchScheduledReminder', () => {\n it('sends a single global notification for GLOBAL target without resolving recipients', async () => {\n const match = makeMatch();\n const tournament = makeTournament();\n const ctx: ScheduledReminderContext = {\n type: TournamentNotificationTag.TEAM_SELECTION_REMINDER_1_DAY,\n target: TournamentScheduledNotificationTarget.GLOBAL,\n templates: makeTemplates(\n TournamentNotificationTag.TEAM_SELECTION_REMINDER_1_DAY,\n '{team1} vs {team2} kicks off soon',\n 'Pick your team for {team1} vs {team2}'\n ),\n tournament,\n match,\n };\n\n await service.dispatchScheduledReminder(ctx);\n\n expect(participantService.getPredicted).not.toHaveBeenCalled();\n expect(participantService.getUnpredicted).not.toHaveBeenCalled();\n expect(publishEventMock).toHaveBeenCalledTimes(1);\n\n const event = publishEventMock.mock.calls[0][0];\n expect(event.payload.title).toBe('Lions vs Tigers kicks off soon');\n expect(event.payload.body).toBe('Pick your team for Lions vs Tigers');\n expect(event.payload.data.isGlobal).toBe(true);\n expect(event.payload.data.matchId).toBeUndefined();\n expect(event.payload.data.predictionExpiryTime).toBeUndefined();\n });\n\n it('resolves predicted participants and notifies them for PREDICTION_REMINDER_1_HOUR', async () => {\n const match = makeMatch();\n const tournament = makeTournament();\n const predicted = [makeParticipant(), makeParticipant()];\n participantService.getPredicted.mockResolvedValue(predicted);\n\n const ctx: ScheduledReminderContext = {\n type: TournamentNotificationTag.PREDICTION_REMINDER_1_HOUR,\n target: TournamentScheduledNotificationTarget.PREDICTED_PARTICIPANTS,\n templates: makeTemplates(\n TournamentNotificationTag.PREDICTION_REMINDER_1_HOUR,\n '{team1} vs {team2} starts in 1 hour',\n 'Your prediction for {team1} vs {team2} locks soon'\n ),\n tournament,\n match,\n };\n\n await service.dispatchScheduledReminder(ctx);\n\n expect(participantService.getPredicted).toHaveBeenCalledWith(tournament._id, match._id);\n expect(participantService.getUnpredicted).not.toHaveBeenCalled();\n expect(publishEventMock).toHaveBeenCalledTimes(predicted.length);\n\n const event = publishEventMock.mock.calls[0][0];\n expect(event.payload.title).toBe('Lions vs Tigers starts in 1 hour');\n expect(event.payload.data.matchId).toBe(match._id.toString());\n expect(event.payload.data.predictionExpiryTime).toBe(match.startingDate);\n });\n\n it('resolves unpredicted participants and notifies them for PREDICTION_REMINDER_3_HOURS', async () => {\n const match = makeMatch();\n const tournament = makeTournament();\n const unpredicted = [makeParticipant(), makeParticipant()];\n participantService.getUnpredicted.mockResolvedValue(unpredicted);\n\n const ctx: ScheduledReminderContext = {\n type: TournamentNotificationTag.PREDICTION_REMINDER_3_HOURS,\n target: TournamentScheduledNotificationTarget.PREDICTION_PENDING_PARTICIPANTS,\n templates: makeTemplates(\n TournamentNotificationTag.PREDICTION_REMINDER_3_HOURS,\n '{team1} vs {team2} starts in 3 hours',\n 'Predict {team1} vs {team2} now'\n ),\n tournament,\n match,\n };\n\n await service.dispatchScheduledReminder(ctx);\n\n expect(participantService.getUnpredicted).toHaveBeenCalledWith(\n tournament._id,\n [match.team1.teamId, match.team2.teamId],\n match._id\n );\n expect(participantService.getPredicted).not.toHaveBeenCalled();\n expect(publishEventMock).toHaveBeenCalledTimes(unpredicted.length);\n\n const event = publishEventMock.mock.calls[0][0];\n expect(event.payload.title).toBe('Lions vs Tigers starts in 3 hours');\n expect(event.payload.data.matchId).toBe(match._id.toString());\n expect(event.payload.data.predictionExpiryTime).toBe(match.startingDate);\n });\n\n it('resolves unpredicted participants and notifies them for PREDICTION_REMINDER_15_MINUTES', async () => {\n const match = makeMatch();\n const tournament = makeTournament();\n const unpredicted = [makeParticipant()];\n participantService.getUnpredicted.mockResolvedValue(unpredicted);\n\n const ctx: ScheduledReminderContext = {\n type: TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n target: TournamentScheduledNotificationTarget.PREDICTION_PENDING_PARTICIPANTS,\n templates: makeTemplates(\n TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n '{team1} vs {team2} starts in 15 minutes',\n 'Last call to predict {team1} vs {team2}'\n ),\n tournament,\n match,\n };\n\n await service.dispatchScheduledReminder(ctx);\n\n expect(participantService.getUnpredicted).toHaveBeenCalledWith(\n tournament._id,\n [match.team1.teamId, match.team2.teamId],\n match._id\n );\n expect(participantService.getPredicted).not.toHaveBeenCalled();\n expect(publishEventMock).toHaveBeenCalledTimes(unpredicted.length);\n\n const event = publishEventMock.mock.calls[0][0];\n expect(event.payload.title).toBe('Lions vs Tigers starts in 15 minutes');\n expect(event.payload.data.recipientId).toEqual(unpredicted[0].userId);\n });\n\n it('short-circuits without notifying when there are no recipients', async () => {\n const match = makeMatch();\n const tournament = makeTournament();\n participantService.getUnpredicted.mockResolvedValue([]);\n\n const ctx: ScheduledReminderContext = {\n type: TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n target: TournamentScheduledNotificationTarget.PREDICTION_PENDING_PARTICIPANTS,\n templates: makeTemplates(\n TournamentNotificationTag.PREDICTION_REMINDER_15_MINUTES,\n 'Last call for {team1} vs {team2}',\n 'Predict now'\n ),\n tournament,\n match,\n };\n\n await service.dispatchScheduledReminder(ctx);\n\n expect(publishEventMock).not.toHaveBeenCalled();\n expect(loggerMock.info).not.toHaveBeenCalled();\n });\n\n it('throws when no template exists for the requested tag', async () => {\n const match = makeMatch();\n const tournament = makeTournament();\n\n const ctx: ScheduledReminderContext = {\n type: TournamentNotificationTag.MATCH_RESULT_WINNER,\n target: TournamentScheduledNotificationTarget.GLOBAL,\n templates: new Map(),\n tournament,\n match,\n };\n\n await expect(service.dispatchScheduledReminder(ctx)).rejects.toThrow(\n `No notification template for tag: ${TournamentNotificationTag.MATCH_RESULT_WINNER}`\n );\n });\n });\n\n // Match-result notifications (winner/loser) aren't dispatched through dispatchScheduledReminder -\n // evaluate-predictions.consumer.ts calls notifyUsers directly once a match is evaluated, attaching\n // reward-specific extra fields via the NotificationObject index signature.\n describe('match result notifications', () => {\n it('notifies winners with their coupon reward details attached', async () => {\n const match = makeMatch();\n const winnerIds = [new Types.ObjectId(), new Types.ObjectId()];\n\n await service.notifyUsers(\n {\n title: 'Your prediction was correct',\n body: 'Your coupon is now available',\n tournamentNotificationType: TournamentNotificationTag.MATCH_RESULT_WINNER,\n tournamentId: 'tournament-1',\n deepLinkRoute: '/deep/link',\n matchId: match._id.toString(),\n couponCode: 'WIN-50',\n couponOfferLabel: '50% off',\n },\n winnerIds\n );\n\n expect(publishEventMock).toHaveBeenCalledTimes(winnerIds.length);\n const payloads = publishEventMock.mock.calls.map(([event]) => event.payload);\n expect(payloads.map(p => p.data.recipientId)).toEqual(winnerIds);\n payloads.forEach(payload => {\n expect(payload.data).toMatchObject({\n tournamentNotificationType: TournamentNotificationTag.MATCH_RESULT_WINNER,\n matchId: match._id.toString(),\n couponCode: 'WIN-50',\n couponOfferLabel: '50% off',\n });\n });\n });\n\n it('notifies losers without coupon reward details', async () => {\n const match = makeMatch();\n const loserIds = [new Types.ObjectId()];\n\n await service.notifyUsers(\n {\n title: 'Better luck next time',\n body: 'Your prediction did not match the result',\n tournamentNotificationType: TournamentNotificationTag.MATCH_RESULT_LOSER,\n tournamentId: 'tournament-1',\n matchId: match._id.toString(),\n },\n loserIds\n );\n\n expect(publishEventMock).toHaveBeenCalledTimes(loserIds.length);\n const event = publishEventMock.mock.calls[0][0];\n expect(event.payload.data.tournamentNotificationType).toBe(TournamentNotificationTag.MATCH_RESULT_LOSER);\n expect(event.payload.data.couponCode).toBeUndefined();\n });\n });\n});\n"]}
|