From 5c2617a1e31a53a6db585c1be93c26b4abb8c9a6 Mon Sep 17 00:00:00 2001
From: Flatlogic Bot
Date: Sat, 24 May 2025 09:46:54 +0000
Subject: [PATCH] n
---
app-shell/src/_schema.json | 3 +-
backend/src/db/api/bookings.js | 4 +
backend/src/db/api/loyaltytier.js | 77 +++
backend/src/db/api/redemption.js | 402 ++++++++++++++
backend/src/db/api/referral.js | 306 +++++++++++
backend/src/db/api/users.js | 91 ++++
backend/src/db/migrations/1748078935638.js | 49 ++
backend/src/db/migrations/1748078966955.js | 49 ++
backend/src/db/migrations/1748079001996.js | 49 ++
backend/src/db/migrations/1748079044910.js | 54 ++
backend/src/db/migrations/1748079122373.js | 49 ++
backend/src/db/migrations/1748079157620.js | 49 ++
backend/src/db/migrations/1748079188113.js | 72 +++
backend/src/db/migrations/1748079231733.js | 54 ++
backend/src/db/migrations/1748079291100.js | 49 ++
backend/src/db/migrations/1748079326438.js | 49 ++
backend/src/db/migrations/1748079355731.js | 72 +++
backend/src/db/migrations/1748079386360.js | 54 ++
backend/src/db/migrations/1748079423627.js | 49 ++
backend/src/db/migrations/1748079463221.js | 51 ++
backend/src/db/migrations/1748079630221.js | 49 ++
backend/src/db/migrations/1748079688214.js | 51 ++
backend/src/db/migrations/1748079723177.js | 54 ++
backend/src/db/models/bookings.js | 8 +
backend/src/db/models/loyaltytier.js | 20 +
backend/src/db/models/redemption.js | 81 +++
backend/src/db/models/referral.js | 63 +++
backend/src/db/models/users.js | 32 ++
.../db/seeders/20200430130760-user-roles.js | 52 ++
.../db/seeders/20231127130745-sample-data.js | 508 +++++++++++++++++-
backend/src/db/seeders/20250524093308.js | 87 +++
backend/src/db/seeders/20250524093555.js | 87 +++
backend/src/index.js | 16 +
backend/src/routes/loyaltytier.js | 12 +-
backend/src/routes/redemption.js | 444 +++++++++++++++
backend/src/routes/referral.js | 439 +++++++++++++++
backend/src/routes/users.js | 17 +-
backend/src/services/redemption.js | 114 ++++
backend/src/services/referral.js | 114 ++++
backend/src/services/search.js | 22 +-
.../Loyaltytier/CardLoyaltytier.tsx | 33 ++
.../Loyaltytier/ListLoyaltytier.tsx | 15 +
.../Loyaltytier/configureLoyaltytierCols.tsx | 40 ++
.../components/Redemption/CardRedemption.tsx | 162 ++++++
.../components/Redemption/ListRedemption.tsx | 122 +++++
.../components/Redemption/TableRedemption.tsx | 487 +++++++++++++++++
.../Redemption/configureRedemptionCols.tsx | 160 ++++++
.../src/components/Referral/CardReferral.tsx | 131 +++++
.../src/components/Referral/ListReferral.tsx | 103 ++++
.../src/components/Referral/TableReferral.tsx | 484 +++++++++++++++++
.../Referral/configureReferralCols.tsx | 109 ++++
frontend/src/components/Users/CardUsers.tsx | 35 ++
frontend/src/components/Users/ListUsers.tsx | 21 +
.../components/Users/configureUsersCols.tsx | 46 ++
.../components/WebPageComponents/Footer.tsx | 2 +-
.../components/WebPageComponents/Header.tsx | 2 +-
frontend/src/helpers/dataFormatter.js | 38 ++
frontend/src/menuAside.ts | 16 +
frontend/src/pages/bookings/bookings-view.tsx | 53 ++
frontend/src/pages/dashboard.tsx | 70 +++
.../src/pages/loyaltytier/[loyaltytierId].tsx | 22 +
.../pages/loyaltytier/loyaltytier-edit.tsx | 22 +
.../pages/loyaltytier/loyaltytier-list.tsx | 8 +-
.../src/pages/loyaltytier/loyaltytier-new.tsx | 22 +
.../pages/loyaltytier/loyaltytier-table.tsx | 8 +-
.../pages/loyaltytier/loyaltytier-view.tsx | 78 +++
.../src/pages/redemption/[redemptionId].tsx | 186 +++++++
.../src/pages/redemption/redemption-edit.tsx | 184 +++++++
.../src/pages/redemption/redemption-list.tsx | 177 ++++++
.../src/pages/redemption/redemption-new.tsx | 156 ++++++
.../src/pages/redemption/redemption-table.tsx | 176 ++++++
.../src/pages/redemption/redemption-view.tsx | 110 ++++
frontend/src/pages/referral/[referralId].tsx | 147 +++++
frontend/src/pages/referral/referral-edit.tsx | 145 +++++
frontend/src/pages/referral/referral-list.tsx | 168 ++++++
frontend/src/pages/referral/referral-new.tsx | 120 +++++
.../src/pages/referral/referral-table.tsx | 167 ++++++
frontend/src/pages/referral/referral-view.tsx | 94 ++++
frontend/src/pages/roles/roles-view.tsx | 10 +
frontend/src/pages/users/[usersId].tsx | 29 +
frontend/src/pages/users/users-edit.tsx | 29 +
frontend/src/pages/users/users-list.tsx | 4 +
frontend/src/pages/users/users-new.tsx | 28 +
frontend/src/pages/users/users-table.tsx | 4 +
frontend/src/pages/users/users-view.tsx | 112 ++++
frontend/src/pages/web_pages/home.tsx | 2 +-
.../src/stores/redemption/redemptionSlice.ts | 236 ++++++++
frontend/src/stores/referral/referralSlice.ts | 236 ++++++++
frontend/src/stores/store.ts | 4 +
89 files changed, 8695 insertions(+), 20 deletions(-)
create mode 100644 backend/src/db/api/redemption.js
create mode 100644 backend/src/db/api/referral.js
create mode 100644 backend/src/db/migrations/1748078935638.js
create mode 100644 backend/src/db/migrations/1748078966955.js
create mode 100644 backend/src/db/migrations/1748079001996.js
create mode 100644 backend/src/db/migrations/1748079044910.js
create mode 100644 backend/src/db/migrations/1748079122373.js
create mode 100644 backend/src/db/migrations/1748079157620.js
create mode 100644 backend/src/db/migrations/1748079188113.js
create mode 100644 backend/src/db/migrations/1748079231733.js
create mode 100644 backend/src/db/migrations/1748079291100.js
create mode 100644 backend/src/db/migrations/1748079326438.js
create mode 100644 backend/src/db/migrations/1748079355731.js
create mode 100644 backend/src/db/migrations/1748079386360.js
create mode 100644 backend/src/db/migrations/1748079423627.js
create mode 100644 backend/src/db/migrations/1748079463221.js
create mode 100644 backend/src/db/migrations/1748079630221.js
create mode 100644 backend/src/db/migrations/1748079688214.js
create mode 100644 backend/src/db/migrations/1748079723177.js
create mode 100644 backend/src/db/models/redemption.js
create mode 100644 backend/src/db/models/referral.js
create mode 100644 backend/src/db/seeders/20250524093308.js
create mode 100644 backend/src/db/seeders/20250524093555.js
create mode 100644 backend/src/routes/redemption.js
create mode 100644 backend/src/routes/referral.js
create mode 100644 backend/src/services/redemption.js
create mode 100644 backend/src/services/referral.js
create mode 100644 frontend/src/components/Redemption/CardRedemption.tsx
create mode 100644 frontend/src/components/Redemption/ListRedemption.tsx
create mode 100644 frontend/src/components/Redemption/TableRedemption.tsx
create mode 100644 frontend/src/components/Redemption/configureRedemptionCols.tsx
create mode 100644 frontend/src/components/Referral/CardReferral.tsx
create mode 100644 frontend/src/components/Referral/ListReferral.tsx
create mode 100644 frontend/src/components/Referral/TableReferral.tsx
create mode 100644 frontend/src/components/Referral/configureReferralCols.tsx
create mode 100644 frontend/src/pages/redemption/[redemptionId].tsx
create mode 100644 frontend/src/pages/redemption/redemption-edit.tsx
create mode 100644 frontend/src/pages/redemption/redemption-list.tsx
create mode 100644 frontend/src/pages/redemption/redemption-new.tsx
create mode 100644 frontend/src/pages/redemption/redemption-table.tsx
create mode 100644 frontend/src/pages/redemption/redemption-view.tsx
create mode 100644 frontend/src/pages/referral/[referralId].tsx
create mode 100644 frontend/src/pages/referral/referral-edit.tsx
create mode 100644 frontend/src/pages/referral/referral-list.tsx
create mode 100644 frontend/src/pages/referral/referral-new.tsx
create mode 100644 frontend/src/pages/referral/referral-table.tsx
create mode 100644 frontend/src/pages/referral/referral-view.tsx
create mode 100644 frontend/src/stores/redemption/redemptionSlice.ts
create mode 100644 frontend/src/stores/referral/referralSlice.ts
diff --git a/app-shell/src/_schema.json b/app-shell/src/_schema.json
index 367e410..c2282f1 100644
--- a/app-shell/src/_schema.json
+++ b/app-shell/src/_schema.json
@@ -1,4 +1,5 @@
{
"Initial version": "{\"iv\":\"AHKOiwdeOuoeiNVz\",\"encryptedData\":\"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\"}",
- "": "{\"iv\":\"3O3M4d6w4OMgVGnH\",\"encryptedData\":\"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\"}"
+ "": "{\"iv\":\"3O3M4d6w4OMgVGnH\",\"encryptedData\":\"3/Vqe1sJf4ol2/R5xXisa9grcvhDliRR9HO/yUiCmIYdVVIwUBe6RxWhShaEJgFWzWQs3LdyHq/oevfd+U3SW/eyycMa4vfd8OCtqNIn4NBwxKxLC0XpSgU9UjbkL/vz+qWGKdkW/OHQiCF4WGjfWB7h7QXw9xayp/OlpMqemZkqOka/ZkdB7t3SHtcqz8d8Xh/6oLYVph1uI8e7hmGpVgI9Sr/7bIqe92x++NCb22NyC8fNAvQN/aDYXiwlAYhFu2VXF0sAmHNwQ4tMIEswCzrf2GwPJjF2gvb6pDEMlX+U9H6GD28w9g1D41fb/EmXLoCuSHZaqYyUPSDzf6lUjLTLdOiNLL3f22ExfSgUUjC0KGwvvxmlRfloB0NgEKsOVzYoSgRTVo27k8mhWzG6uxz4WA0kK+gmyvidK1R6r/1GGeXFdBKjB9QdrIYSZxbRx5Iz2hOI0teg7hny+ISAM6WzkDLHGcExltSzWuZ8vkPFF9kvS8lIghhXsqI5p4vqg7t55uTGZBx8D9030eqvPrzpdI4cz4amb9J+5HXyMJiGY//k3y+E9Dyb0FC6Csh6rrHKTj474V56J/OtN89v7wK0l5bXo6H7jtroKSa0YE3QC38kwvDZ4mQC9cGQRABOc6OVF1LYb9Eg0iGBiLEgqCR5Y5NlwK+8p2uHoAOWvet3rfZ3uDWOrWEl52o57v8djvF8AQBYR6RUaEkxEl2V8WY2xQTuQC5CQ03zDbSv62PZhoUxtSYeD2RZp7Yts1U6FXOnoYXiex9xt01gKVNc45oQt1XPcnZodAODqatH659GQedl0tFy1uS09GszJ/IPMR9tO62io+9MOOWgGm9r0dUJ4n8ozc8hh6mZi4R220dVOQkw5O8pE3FZNgpt6S0/sdR1+UO6MeSBtlVu1EYLfPpwTPP8NkK2B/wTVvd0mdV2Q84ngu3slP3/Ww7VbLbLyDVzv0tt7mQKbWFrPx5hbARJT60mHCckUgQJrIsSyzZu5DUX/sO9xT1qoTzPHAH42Lixv1/pdK5SohP6A4HHN3wx0Qhy5tvlyYLszTPPvRXVWWbaLvMetnn38ZuJ1ZvuN+99s7KlgOixPPEPDMCGHEUoaiAOYP6C9++F0zLpUO5bsPx+btNieYh8/2kiRQeeM0gghAFFXHT44JjhiFNnVN+9DhAylkP8u37izPOEVZSpuoZn6aggU9cHjx62YV/nDhFtrKKyLW+AGlVcAuAP14GChcQpZeD6A3+PQwYwHEQpfp9PhUcObXeJpZdlNhOyEKDS3l24Uf6RfSBUQUllbb5sGh/8hnoS3HnyxTLOrknYzvYFQk0hT1kBi9LfVpROeHVmf8PUgZ5+dg/N2cSfAGerN1wDCRqxw36U9svp/ixPnlT0eeA0cqNEftrG20Cz0+x4/sYRiDjoFfhjJMqT1BgPcm8tHB0vUfypJxnKeTwy04IvYleFxSev7zqbOcTaugjQy5e6cB+dE2dPGFeXh0RW75tQ6wI0syAChAzPDEkVf6THh49c0VjkWbSrNMTl5MrqECeZGEohB0i5EGKVgOAR7Nit2/AX0XtIbL0KZGng7rGe8AQURscj3xObRoBkokdd/mFssVwSNo8R8Oqv+dXUoKTfC6kDXnT1ZkrYhyTahq4hirGJHjJpfK3FajT6JYbagB7lEvseOjonBzxiBjxQtRY9p06xqUxtTNk0lpxEtQ8R4iawmgUdSWZ1w4yQdPS3kD/twyCJpaacQk7BXqB4ty0xYBRvz3f+Z4ndF1+2lxQMRGbjZfC8nqK6gAHio/aF5h5eEgHVpsZTQ2ypAqEyJxwjQhLHeiFn35ZzooG6Tk4Z79yFx20VacSTmmKBhVlx4ZpPFu3K9RIja+osWgfeRWSiFUvvXB8aIKUy+jA3HgjxToYu3UugFBk3nGBwCxZ2nHLY73Sydkc8+FkGDgjVIc6JZK4pGgZ2K2XsrHl6vbkX8qhT0k70dBXYkicO+cMBgDrj5B1AooUf7DJhL4atlEBq/kqWqth4qgHACa66ZtOVkBmf3fua1xxjn9SlrIEfwBwFVgDp8n9PI7do0ykIQBLQTKUAmQ9mvJrQF9N2X+Bc3PjC4oNc/r/3nsa8SYwFWdwWBSYOk0ybg8ZCqni3/QT6SasZ2Cq7ZJtOUqMvneIGrleF768oS6vSKhOdyIbyGsoUF85ITaNG/OEBRUYapIQ9xkuGDwkZ9LJGNoMoK9fXvfgUSRfAMRGgmxawItjgaEOJpGfIYEt4kXS0MrjaHh21igSZMoKiHMsMOFxrYAP93ZaipXdiCXpzQv+R2vhgd65E8045go2a+ilapM8YgyRZzziHqbiaCcxFRScw1Nh5aBhk1+Oe/ZPiw5Wzmc2naydoQbajz74R4pju/30cuH/eiD8IHLM9SSfNfz/OO16EAIASNa1iEuNNrASI7Foor98Rz+HUDwKUKAy6S8iHUYJNGrtjhz+9CYTVuIOMtzbm14O0+lukLyO6Yp68WYWQEO81SdyIv2Hvio3+glfMlQwhKExDmai7y3MvUymt31pSkBcloon0VJwDZa3z75xLZXDFZaupNEeFQoQWdPMYjOmr1XOFeW3RXuRpZzAUSZmtM7N9MapI96kLwuocgpoQe4hMtr4m0lZ7cqwWrhJrbTTu7doeqiAT0Z7hte3pE+8oEFxDL9XlviC55W2ciW3AeTE2MI2pU9W4RCL0X/NjkSTV+Sh2t1PpNT7gHnBjSNQUAaL2qP1FlB3MnGuw2e9RaNRBsEhMqKV3FjLPXSo9iaJYts6zfREN+55gxSULGAjAipPq9d6ICfmkJ6nREw1AGWaCKhqg1jeNx7e883jpjuhmJgxF5ZjTe7m4S3FC8mdTOvcvWBY+uxNDVZpuEDiD5IWb0b0j3qdtZ3hffj/qDZ44uV6R/zuRUj4I59Gb4sppQzDeYJ6R4QASSq6JtELRKNsuk+pk1HYS6qYlTE4o9GyeRuknoP91uOJBzuiIPcrNjScv54N1BQHj8tfy+K2vFZhValJuplI2hgmyDxhdeiyZk3zCowIheB6LKQqss0+dV0+ml1Gq4R5txx7PEmMRTrwVItGm4w9ogQlExdnQnM3H0FknR1O+U6pb0nAHuVvku3TOsBcTxjAEifdb380l9xhgJOSFAvLWHq710jLYcIad9Slm5KJru92PvO2MAPlZbTsaL/L3tf9H+XaIII+n/ZwjJiX4EM5S9C8VdP9z+mDTMfUw2AeXBWtSsBHsu8ekk0115ZhQhbV+btbxAh+DPxqioXNn1Uih0+TgLNRedO8PVf9uAJWdzDUlp4XFJIbcrJfbzZ2TTryDXq0e0EjEt1YiQqRGtzOJFm91/OJESC4K4/onc2zdVTBlnHvxBiB6dO6skO/B7C1hDT+hyFxxZ0VDwacwRCdD6ssX5zU6YCB0TTqL3by4493uFGUjBmik3zaNJHQYET9hBSJ0FNYar9CI2yF0JSZcqWcRpIPsYeEq+KZIYXjvoYfGlrj8qg6/8frP35lRtfwF64XWqNZy72Ql639xFVTcKR0LyIICr4FNinIgV8D5lWcYIZO4ojD1wG2ok+eO5PYDgfDWIjVoapzriZrPbN/4zixihKt9Vh7tA0uwc6jfo1Ouh8/QVJto1jNXbSxt+xA4SxhxchhvytxYkAwZ8BlIs799ljSSrBBLtxB0JKpqIJbkOSqylgjFxid9qp1dmfgaEDihcaK4UiWiBb37AgJo1nbFCwLxN3s3N0nsrZhUPFMhNL7VfQAZrklZEQ6k5ez21ygQSvl4gWPEjgPM6veafJp+w2L5AGnXl4rwXfiUW/SLskoK7r9H3lMD/yfupY6FHfnGUSET3Cz/DwbFkerkADLLXWMdXrOJsf9Cb4JXuCHoKzDwjl7PmQRcQNPj/4HdmlqXk9jJINFwJexzgrtHjokamXRAFGsDXano5gR7j6nOvu6Zx616gWuhDc+CecsxOZQJdOmc2Z9ZwbaEfsR9GPllV+bgNP2WvhlaEZxTntVoXEAIqT/UwYBFjT+srD5SiY1Rx6K0Hx3W/ZcNb4Y672ycwW9c7KiGjHWqg3BhzYh+IaH0F5yrebMs3JImbtRWNjX/85o0dS8epBCwpYV2xEvymuHmx+n58Hbg6p+G93DCwKTejCB/NTj7/NDnwr+uPqOfJMwCq9L/gRc3XXNge8pLbVQySsPXyBkHDGGENhs/IIR5ccd8jC5eLQXdPEtCqt42yLT0xoJkHskxIbUivjyJoJmNsg2TGL0ib4lnLc96qw7VUy7Dp3EjgbITWS2uRHqFL8aBy620eQI+SHbCNBrSeARjd7A3MoUuc8ZutMf7o6ns109H2uRj6BL14Su4P+uDkDJWrRbmfHumLfqy30z2NEwrVwfx+A6mys5mVMw0vANE0Oel3Nnt3JIrV6uv0e0JA2+mTQuVDWgGE6xZR+IQLnRO8mT/6Lm5L+KMb5f6RGnQj7XMs/3YdyezfW153IeLHTs8q8cJXIgSmNfVPW7W2uQPdkY66kiKBXCq/XCxj6S/ZSOmfs5/+nX/DCmKLNlhhK+q6MHjKA30fGlxa+DJmGOn2WebJ3Qib3LoO6OjtlRamjhkF16rj+/OnX6E9pFM5h+ns1hGDO8HEURVCr07mooVdqUEIDA99DppKihsU+JVfYDmnyHIkdxFqNKXuGGyesBwi8Y31uanyyvFV/HB7fylO3JlKYcYRdVuo2/7MIJxaftVxwjSakS0tygE/S4q40kUoTaPli52jNE2SAZqSx67iOFDc5jlRn740UKFPxmrblvEzaChHtfJ5Lx+g3srsR1+RlIKaiTXYeBesCeVrJj09UV9m4FRW+fbjmnL+S8JYYRzhn8imPMs1W683CFxTwcMHNiii5VhTgNkgXQm4bPNPpTqk4MPQ95UxepRJz77iyqeBFOBKCTH9+1fzoVzLCASv8nDoKkNA9gQe4ErKYuj576OrVuZCuKaRtYVj993wCY0ATxl5B+gfidQxxHjmGsVjbgymVBEe+TT7tyil+51wOYY+AGw6HqAFkdpaSGgfMXHzoApikfEe3BRBWkOnVGCMd7k8dukGVS9Hi36HJmLgcvpKpSu+TbbCFXFC+wLbBaemRrAIblLnaJBHvOYs0ZzbwDbmIjg/Fl2V/FHHyv75r1ZyZ9h1phmmeMmCa7AlhqKAQXUEFDQFgkQtjiaEGoPqUkjssHJnRYlfiT1XLvncR+xkVjZ3dWpFbi+51ctnld36mnANOYDl84EtKkfXIvw4dQKvnw35ZopABTZ/c+sPlnAuQd1HSsGmJDwKoEtwd4jj8yIhFFCU0bnW0FUDL2Ig3plhO2nmoEY8TW9cty0atHLv6qnJdTIC2uBpgOeRRWQ9e+nSSRoGuYOB2wbcm+JaNdlc++Krk8x7IctM/YAoUpW3cuGT9PgRhPcu8FkJH9q5u76KOcnTF1z9nuq32EVBxHP9Sul8cexs+gQOfIKGlvY7JPCY03gX964J4dWZrddqF+IwevPG1YNzu5Z9kzDdXMV1TNZ4jUPiVMwowIZ11+woOG0d5baNcKTkhE31AeC1iUFWMWIN3197Ayq/r18SPIXrt19Am5KIlEd3K3jL3PuamhmZ1BFuNYEcD7G/C6KZg50OJ/YdSCm9ub1QdYndDVnfSdnZS6ibrvwldiCYXeOsNfi3KIPc8FpV6yaLdieOTZ6Wm4QGGuF/uObzArKUOLKfZJMoCt+WoVTyM/HrmQ0DTNAaLYtM4ah13p+n29HbZirZbV0rzT0a2O3eMFRvIjybf371qtx9kT4FDIGK7q6uLFRtzGTXyKU/SeLFyLKsjnyktmZTggknA8hj8k4JhN7+1FZHk1lUHUAKxNI63cKj653nSHOCo7yvHIuLly4pKIRl+7KljkfPibqOMZdCB3D4/6V6nJeXut/6jz/DPdnD+KhZIkIOp6kYYsFk5Alap9jICwo05syidkHNU/eK4a+lH/Sz3wTv/gCi9RUxghCcQDBg4Y+28CsxNLANQboMawR9Qk2n9XNR+spXnVgFGR0EmDA9vXw8qNiepRmykcQMiSglmBVVnVRTtd+hAOcaUjRFEtCThTLSNallLZp5odtB81leWoKi5wz47EQvWDKPBKjRWDI4SVWCpSLD6lYLwRwLfYpHseajDfOEIuj5gHoz3nBVXJ4tEfP8yxxn/TlHQ1IUPir3GC+4dPwV1IlCpSx3/FG/HDF4H0fnw0qmzjidEv04ZR63eGlmAmnm8+5xQClOJkQi1HX5ZX5+LgYxnKYi9oafg1DCagp8Tiwr7jjOs+dP5cFI43H6O7GzqsxbYm2xbAENRDcUab8+V60rSreqZf4BUz6nfhvbYM6miO/OiH2Jihuf6jwgDqGJEjkn6FpAsbXGRNL5Bf/puZtB8N3SV1V1zL0VBDPPriB36inH+buhRE0uLSJWBXroKu34ZbnMTwBX+e+g38BHuFQSyeLev2RyS2umuoEDJRxuoweHq7XP6wYkYi4iv1IDxYBMxpKZmXCQVnRRV4roj3VBic2EoV/57fLKXPb5RcA5wm3qOpe0EfOFP2GWfxX6cAW7wK7CwU15uuHdJqyAhMrVxAb6MK+gDyt8pRcM9V+sR3f4jSWTyu2u59ws2VdW3g36GKajizE4BzBLNXMGRLYUc+N65BVlpRO/S4gR4PYWOP1DCm3GUKKpM5huYJ5nAm0t9UhmaMU+ERXMHRatm+TGJJBJZXLHnyv81shc3Hsyw+ND+cup+q8tdpvTQpk4lXRDEYV8isMYI6eUfEL3Pg+38mzvZ7KGWZVYSgFp7lnZSW0FI6i/YdxQEMDXTcagJz4PO6u/GASKFECEI6iSoTJpV+/fWZt6oxiFZT/xLpGH8p88sexgs6yOlII7K6TcMhwA8aRU0ofJ1TnSAIPpZjS6JlhZxn9NrIcrNHyeAZrpG7BbY0EJ00Y3lUkOzUhKFYahjUBSph4adRTL+z6znlHqw66OWk0Dn+61HBylkrAJ4SEAI6yCKLP1GdbgYYkMj77+R5cB8R4pTs5xpDB+Nu0UPk2QcwP9e/YKEfPvkVDD3k3WwCFT2ApEEY2nTHrIl1c2VixK+NiUya56yCZiBsyzIfhtEgVQEwYEDGuMZF80BI+smjfNIz8Coytvjn7iYGDTwQfFSNnwrP/YMEQkDCqJBcPwi3t9Q1NO89i4+TRetnVQVW/39UkGRQa0E9aztLc/yBS5XH4wIk7w3Wx6qzKOemjPpE9QbSxMU57m4cI5klvFy1mhKmm3EBTnrIng+YssDlhhAEs4LZWBGaKjfpS3/M1EL7yJwzoUmCibajT2B6KV2vny+LtuOeEBmSPuP/QY/5An+++WRXEER3tNMlBa3MurXxszBvUt8CxJGN5/4WK2v5mBZ4g4eGdWaVMFfMJ0uU69/AaJhIbOa5hwQ/GNHzL2NSebtEo7CQGUlsBE2SeEXPmoP3qn3NxoeflDk9CxvSbTB+Xbh0QDCD57HVPQb4MBOuRaDBtzDs4fm2qn1+UacySHGFO0CBFbzVMAmLMQbUZgAcx80KBybUzcVjAi+MzmB303cLQJDCnglanFkZlxquyhUYXBwDJd+2SoEKZe2+zhGvfJ9JgPf545N25CLhPgt5JXqYcQ6E+G18O+W2aBlgJ1nxq1ht/xyu/RfRojqlu22YKspCsIwLUnfNLbkpiMIvpuMhvI8gc02zMFlNfsS9Nhm2KrhAtu4qDvFrG6EOJ1YErVUEVl43GKAZexhWqrTCyHtcJmjlkBVqADSKrU5qL9YiISJwxRVZt5lc6rIpBBznHCbhCFvOsL5uN9y0HwWqK6hLRpfIAwQNbVuj295zmoV6xPXDDidnHlP4iKcb4BZCMO4/YH1u5e9j7BGZOWYqAdbrw/J9s/4v+ipfuPTRNePwFjBHcB+TKmEXMcmkVS90OJf0CA5SohLVOPa5bpESwNi9NTd1d+Odk0o3lm4WNwzcC4L1fYK8ZOzdz4XidZlvemnMaJSzN0/6rydLUeFBuC2aGfhQf5uAOM97S98q72VJMVYBDIeIxWoF0oTsq73yt0A+Wpia4NSAH5Id3CPXeaKJuwd5nH+l8pTElT1s335yAbG3n/5THCX8J/HtZ+i7jWdIY1xnEwlIEcyT0NEqgZJL6dTo8L/IgvegS4K+rvAJPs/+sFpchxOJcQOVZTmmQM+rdCXhW+iFfDcgfcGOk1DPNuw5yqj5LZO7JuuuovVSgZHuPcKsaN1OabASJuY2IqwLKpimnMGWJubIttUDmC3eu5nD6Mhat/clXVTi7AwPkX3TbzHaF1WTN+9FPulZhnX0RuKkW6Eba8NeMyg+m67ADgktx8GhTGfhfQWp0H2TzTthcHl6m5JWkEdbaudDwoolAWB1c0eSo2XbP9wrz58SjBplnAp+CZYiKoJRySOv03JEa8A7rLC77M5/NWrsS5QKwzzVq9iTfYezCvR1pVmH7RlvZK8yw4PURUcFWnFZZ189W3wne1QGg/ZJUXBnrVMgjhS2P5KHGwKl959CnSXkBlfp28HhM/JhsT26TAZUpfO8HdoLmnVmy41xQneY22W5x8SV8jDRMwHRMZQuWt7SRl81JTEej/O/RdPqHnVekljdFzTOcIXmcrny073MTWYEZ5fKyQLpeFg8gUGmwFLsgjk3cdI1kQamqREkDPRAHEzB7IjvfVWWtq6kYYod3TjVNusitIFlKP641ib6s+5OpP5+wjvsqyW6p64mvVbPtWqsHgHysv5RgZz+ZG+qAC80okzywc7jYTpCmoo6SH6BngNSfHbXe7mdtWStYBjO10e68qRfLVadbAMP/xLZk7Frdi+BR86pxta1VZhyCD2iurMi7EJXKZXv9RpYYyWf30oxfYqbKkf8QvR/tNP2weSDkFUmMbwoUOuffdOhOd0GvORWEPZKxh+ru9Z7BXJLO6T2+rEGwBBkMQYs3fKUJIb/TwnE3BSywd/tkJhOvHDFPzbRdX1eUakCtJuG/XxIPtxVHsUobtg0N7r/6ENW1lSnyvmXPlY2MZoFVsfXJxALtk6LIYmlyaK6JIK4Hsp3DhR+Vi/M/PWV/3Qk6vTKmb/IHiHmH0R+7O/1CLJMSpZD5mAWBXRr2+Ia5iRMoAeeUxs8sB8GQ/gZ/YFM2fdb45jhvNcf3lNtRozZXaRBhMu2+BBIYXrWKz3SlyNjUXuNVvnN7Mik3ZZjkcZhXmCAM75o8mwbmc0lJE/tjW51m7k99GI85S+K/m/B36wDwH0/FfYnw2tuPcNXe+WddKbhMB5ye98b/qhMCAIlM3ZkMzblBxUZ70K+yb5PV3SEtLEjzbG+8N8gw7ZjxOHLgXTWsCDNBWaT7DlMIup7QYNCVE9USyZySX9B7pxvKMrfN+d2G7ZU/4AKM4i21axo+FHy5zQlqwjGDrGcSAYiiys7OBmd0WdTvCzR1Kgt5hj1+y+e5PWwltpa9rPaazVJjYV3+EPFl2vTQHLIMgal3XFPeaxD634Eq9YsdjvkFYUjE84ksfPRNbD6FP2CTaJq9WKH7BZXxIGfuDefzLHi/4ox66d9CB47yRqq7DRklBzXEx1ZLPjSRBnDiqmQ9h+6IVyZ4UCYg5nSG7wkzvOy8etxcKyvlwOMIOPxObcXLBOhtlA0yZSphbDjtU5cd8WjYfaUvWaTW9bvnWcgoo2FfwE/CEXUWr1V4rcapwFSs88Y6NBroGx5CAA3mr/m4XNuenY0XlXOjTyQ8GFh46bHuB+ogbp+jbnSc6m+eavclcpT0sy3ljbnzvcxpdOUf9AnhsE3nk4mVXu5lJTmL8pJtrhAl056DKsuMljlxZ24vk80yinRxE97vts6S2eubDWZZXDZh6rYqGLd/bVa3MljUP7hai1d9Vm83ZU2lQwjbxKsTu+2/N53dCSPhGEtPRU8dpiEW/u3qHBuFHrGiKsr0vEfQL5gRlnFuy96MaQIEajQndsc0zyXYKPtUo2mLNz1hZjJ1a7EWQnu6gmITZ7iXXlVn5Lt6WoWtqAvhNZf03lzGYe82/87N7yzPQ7+sKAHwTe3Bki+tgh2DyV9Fcn4+Qnb5o1qa5IgL5jnP/PfQTxigM6ZP+BkLm+XRN++SsSnQ/Ho06W0OgLdJoyyWEgQ1EaqAJ77S4PPNVY3RLwdm3vHapRV4vHOfbL+QghXvXqQMKb+VxGJ9/jSmTbzDZALEuItmfQIho5bm3XO8xEXwZ+Jb0eG+Fa1xzJtjpDGVo34yR8rulRVA9r1ellpMzaYr+pLwCVgMuWMc0XNKufFwx6DYgbn8FQcOr3faCISuwJeE5B5Mld6ITF62ATHN4XL8DmTvilSHzlO3GhubrtqSLLqU2YrXdqsKHS18BofGZAn7zgXEy5V5QeCzqhN1kW21HlUPvg1CpMaWPl+Ube6iQ6zBOpGtxUo8pgZyJvsAL9JLPiN/pNaB1nbEdtgZd7zsHr+526giGLhJkNteZvRbKPp0a9oQWSgeoDzwpH2T8RwbS4UDZHOLYbEDPz1BlTADouEjdZyoA80EL9R5ruGwu+FURWU0VLI97z9cxCxXbHfrNirxCUNVRo0y1nGOeyny+kdckk9QfTPN8Xne+ruJ3H5zRp4gkZTnM6Zt2R5f1BHQDWLk8imcCe2yKnorZGrLZ/3rfO/bk5uqKlO7yMe3noEJh30DbwZk2BCY/y+RVzneeNs8evZ077FenJaZFVFISHlYEJVuJNj1O+W31XaedMrf7T8fhMpp8r4hCpDqdVuIxejBzDmPrtaY7KbN6GFfGuioPFQtAHtKJybwQGH99uql9PDSAohejj1gS8QEjtk7QzA/uR9xzd4XweDCn6CmgTWW76aKylmsAuAV5nzLiFZQ6d9P2wZcsglXUML6MIdXurmO1MwHqYZ8yY2fBN/tVkgBu+W47MMKUa8h9y9JkcD6FgV646ChDPjJ77TxeI+nPy0JLIkd4//CwgvQd7/exc0kUy0g04dqdJlQd/nOmxAc4c8wqfNw9+3egLxjNPsAeeoPv/do5EO3TUMEO880X+9LRoqpQ0Jf5QwgcgGHUK3OZ0EzFjRijMaQkFpleIPEqD8DIRnKaTVEwOr3n6bbS2vmSeeOoGkEfWdhJiy/irv9QRmFkzUzE6PDLs5L5g/f/ss0HuOwxaumqQYT9Kz6LGR39NQOSUZjBsHKoXz8mY+4YfZtwCSpcHCn/xOHMj5biSjT/idnqWuVqiZ4NNueDEqTQoHyA0rscQzLaAOaS3f3XArqxsgL1mA5kUksJA4aZ9dEbownib2NAZGATbFDNBKo8wAs+aR4rebDhTitPUCr/PpkFY8Ai5m7dzhKbcYU6a/ojihNcQfJdoPzlUp4w9HMX3yuAC2XovSDJNQqW2mWWzIB9uKCaQZXs5jgX8w90ar0wPX7YZW7U2Hj4DmrBaHSmG/4LX14s+pMztcU0BjXH7GAkydKwRuyKXtmxTs+iKCfZr4CXs2V3GT1R5nxFU1WWtROq/XA7pvp2tjwT86KCaPG/Rxpp4chU1xtYnPG6uvvb9g1FDUjxJ1sYlXoYlydBt3QIMjJ3v83zaHD3vkjgCw43lMoSDSVU5rSfLKFfjxsCXKksnuW7FeYE+QtlNlzt6T6Yrkdb5LIX0xM+8uc1Wu2DDNdfxvcBkzDovyPyWwbU9fY5z1r/8NamFnKLjiYzWhCM7ER/x+ZcCoZfRV/c9gJsZ/6gFEm+RZTWmfuoNpRyrpQN99fEV8C054jwbD/dvHNC01VCPbnO9i80V1zjtR6WnYQv8+5sonV93u3d0g6YX0WjzeWdnU0tZog0fqv54+ro8cARRSmuIh5PsECRtlhGw4Usk5gH0lRf3NkIRgPRH/rqRSQZ/xr8b/4JFShAtG7QGqZIVI6cjA5SRjc1S7FFItKIDJ6DoFjQ8k4cAo5pniqeVZLG8n1vAMGCbrgCWxVNb+1CUo9QFTOZM+gNJyEUIOHs0vOV59eSjlGmsEMZr9Y4wkTZgeTEIFkNoIGfH+5rdFZ+vmB43bHwfPfrkf7iIZkRjO+R1ptfzS2VbMIFUTxQkCoDAbS5dNYhYCH7PmmLz14e4BqMqIYOfCeWxvyVgIPh2mRQl33cXCu/Owbi/VUkdW2StLZ6tp1O5vy1svHAzrnEbvBqWMHruMhUS/7kUoehHrRDuWbXnc6fYGd/3OEakeR7bFllBt+GlBvLB7Xs7b5v//9KltFqQLMJf/sbBVsVPLoWGMf1k4jI3sFJf4ARUA20QrKq+jsbKNAMcMg/VSSKwYOr6g7v8deXesrVSm2bPD/NMuSXZ7/aujfq7m/hCfaoMD/bSr0IhKORm1O6zooZa3fFOIlqe2jJK7/8aF4nUI1cdV1KU9AQ5kEQvDJOdP3CUqHD8jA+NdlltRE65QDeM7zolQjne6M1Z4SkUrPO719nI2i/T+cry78QRL/pVK+mB0GCKN3nffpQBmTxav2UDipMmCpE5pTaz2JXYRQCYexDPfOcol0/KBRPXRvGLaFUTAMAFc2EuQhWsP7iJrjdc/vT02E+9kQg7w4dD8PYk5FmTYv6+CPE24aIuK1UAXxe+rSD0iptUywsVL5HahYCAFmZykL3pWo3OX+YU8bQ9R/OL/OqRZgDcUeknkeKmxTEeqSRYqVatHBnBI3GQ3pLyi2hyRGD9ZaNv3XkA/JnPt7ZnI7WJXA0ZiTWnVPta5K/hUqNMfac2936B9Lwdsyjq6ZMyxA7nzA1lVLoy0UxXzdS6j8gnhuJNi9t7vgf3/MKNac0kSXYBsBkm6WnZ66kQcyKHXB0dAzSs4Y+mTa9CVnKLmZ/WkZ+PUKBpWYaPweFH3ayIgHQXOrtexisyagSw19t6xxhvuXf5+NaAyiBqpckV1I3gBcMmqC5xfwReNgDy5YrJQntK9MKFhnj5mxACljQ2lnR3UKI86Z8szZSGNKx2IVTlYVZ8udXBhtrYWg5DqJn1Z7Ya138bA/Jmmq8iz0YfraSjZz3r7PjBofrjCX905YAvhXDFGIi8xAzm0LLzM0RaJLWL1FMKUaGTM3EWG8CYklWIctlbvLmi1hxGw/AkC99rUQRCpbFK56HrK0T9jRY/hE9NpmmgAK2kC/pQb1rGDQCduNLtUknLtCNoWfrqEtgUUDjIPedPD9Zaekh5axiP+kCKP+Gjq5wkQcEj711H19z+PvdkA57I+jHEIvVU7gpCpsCuueKtbANm3esjemZsQsOeZl7TeA82hqjTzT0EZOxvKqLdFQHKtFeToB+uwjmRBWgF8y1qJTz4YUXb8t8+lcgh7JjVRemk4aTspi6+5KFlW6N2NMA68ydrAMk0YsLno1q12I+qUfrEJFEJQj+nyZjpcIazTZ64MGwck8a14mWu027Z2KI9RsDd6hNQXpg8XoG7kATvknvgiYoLwv+eaBRjVFMbJ1yAyaKyA2EnEdBjLJ7vNSjfWGvfoqxIbFY4bwEEL1x1ocz3vvkAKBHLtMZ7cvN2JI1EJcc8oc3SLpl+lH39lI06sJ5gjeCvPJuqnjQVj/aHn0lfdfwiULf3vVc0PFJk8olw1QamIQG3UyTdENBlGo3kTpRmBvKU7RrF/yQ2UM2h+A2hJwFZTrs7ZYZEntSUOsreI+1yUaBxbg0MDmdPEZpP30yG/y8maX5i/fJjJ+qWlRW3TllFctbXGR2EIzHOp6ADQxpM5/sYmsOAt/WVCYrafw2o3gtEMQDFWwE9TjtC3jFiZEl8LEmNSK7+Vy2bQkJtXjOXhDdyglnQKZ7aH5SicZWr5ibh3DQMPw4Tf2Hx7LjqapE556BDtwxEBuaku+h2rqUmInYQ3ZJkpCL29mc5IECEc+wwe48qJX6L8mAPH8wCWX9j7Nc9Ex3pLG2ub1ecYUDJpfOOIkncgVlwo5IXzP+LCbmwnJpn6RQ3+DDK2t1h70KnuArdXgVWwj3AG1gum84bDo5RbERcqgUNVrGtaC8yUIGSRTSmR28Mj3ARMk4khEIKZPbb/S6/kO0yhVYNVeskbMJKrOH1LjomgGHXc0kPF7W/4idcajD7kNHlb8obdE/HGUQhO5g9+trR9ch3A5YN+3IOJH9bwHsqVMyNQiReIwKYHEBEVbkqgIkc9KJveHanGSqOgXfS5LM5lDZpw76iWYnCaAPqf0V3iNKBX+EJjImjUrI1ELG46MQ2EHgxWd+fywev/o9aKnJiP+nCWFoYLSLDPLMXUrtuy8NHiOWjHHxVX+BLaLthkoeuEQGFRqwYdOMjSGL7YPCT4ekuX5va9TXYXX2+ScDAwS60iwSSJ4Yq+qXuqXu3v9/NqumMA1T2M1EwxUSmi4ePrExZfOtsf4xxets0V5ywcfzP9db/T1122DVjKxExgliCdFnT1NN8dK/n/SUT9XHR3nVLSYjKVXVaO8fP7coEiGOeAR9md7YcSgvHT2Q41+MZ/Wjfyd0N9nSy2SA4FHfPKmy24cJizyfDOzJZFaKnC8FJgrIjIeyU/ylmvE2ToakTkJ/i5ebF/Bdgf3ppibz3HQLajVyjnFrmEB9fc5Ypi8OUWd5Ek3vRrEyek5VJ/2BP/XYveqCleBIe2tUUiaGv5rrU4Ggr9h0Ilc5WHOD0Q+kZTAvn6qsTFjveUYxWSbTB0/J1S+b/YpRXfVZpbdbcLM6WDIDYQ/lhYo618nER1H1aA7oea0IjthsEWkwJ4BRaDZ/U5AF2d0jklkGx1S1QKBVYrePzuwvi12D8yon2bRxPmke78t0YguZHqkBcPtKSTN6qvEvDT7q7K65q2pkuuPD/4yqEsxW+Q1aJKFfiad0H8ugLkxiSs6JjwkkhQqd+gsTjbapMcJqiMoblersrlWZoh0b0m1dydUR7fXkWJgV6tUE+udCNlXL/YDM408pooaUvV4dSYg6FOweAp4hVDjdfcSw92DnB/kNmQj+PEDH8r4iH8W3rqMZkBGBewZgH6anZ7G+FY056kaHKcSE3uxeHaL7vu5e4EEOObPcK4HpSZbY9RWt1aA5Ncf7Cj+iRNPEQtjvJy2Dh3swH1q4duOA1PbOFOxLTRqXiR9/VJLLOwFjn3zGmGBabznkq4Wk0xGiAft6NXzdHDKW25vHS/6TV7JRJckbtCvxBW7GdinHpF9badjwNPMv/ZSbXh+LYP6QN8juhWwryI2sca7p21BrSVu08/Rw3/e4B3UR85y/dxmkKHQaAnYXCIFB/D8eFZJ8T4D2Fp6YYP8RdjLVI7sRAXfdNVV1BJ6zznfj/dsU/8zwAY/zdlgCOjmPDfIGECvjfpEDJBd5CVKBSvAAbLHKyqSJV5Pfb4K2e9GXJhhK9H8m9WPiSuz25OQhLfELTfEIla5uD2xXYoH7FIKsdBuuD1b+k4tNQ3kX2+VDfWrrcq6Af7eADZMJExOWRpqqYCFHcIFhHsMUE+huBPs/+U9BX44xr+1PZ4q/oxXsTniJfL2zd1wrSpwwh2ZV+cFuFqtOGHwxTM4JrRONUB5CZQnr+b5EYl3ogJAcXRcC/BObvUymbA1oEvlObN9eTFvFObCS7CZMFMQVRwhgC0UjVL9MnHTOJDlZNMZ+e8OB5dzh1BRuK39HwTvSOqM25/vf7ykF05gypmlPYi7Rc5MgWld//xMzmhah8qLUbMVvJEGx8js/9eQqOhGJKcHSdX8+o69ox67+Hd6pWW0/4cc7dE/528r//bTI0cq3x802c+PxSlvmTfiMok08znps5rwQb7SU3Ny8O53dEZB4KDCA4vHhEhimanXcg7w8dYUwXTrqIuUE7mEQM5o8BIOZRxgGcLCcUNYlyPOnF6woeY8KcwpsCrJRVkmXXzCBqxPRDGvvKAQ2fQRmd+4CBjfIysfpbHe8bojknYIA7PnG5l6BJyr2eLltIKP8RfoeoO6ehNvyDcShpAbTyvFPMzHs6CqFfeNrDRKrNpqDizS3ORXu+GAtvo8RMzMUkfg+wijm5TISl5Umd1lPSSlRO6VOeHoDnPmooDEXkJWuduAXSQfOG8cebxb70ZEr0qBcJez4o8RPCUHvEG2jtIyBaiD1/5aCwHu//1/hqonN5nihDcwde2D/oboWH8yV+tVwc5hz1OHuTimdOLWDS1ZSFXPsls7f0t/e8/FmF6Mz77yDVPKpBQn//AS3q+gtDaO+giOwFMiicCOpo3hSKUWlCzzoSYaTRtGH04JUX8cBt4wt+Q6NQ7XP+8vuWFmRVSEzz4LlVbTAJTdtl2VT8GbcoP5ouvX3R3IBuldfQci6Mf1HIRAHxQErI4k0SR7H9m+LvPXgFwrO/iLYGWXwZ6JA1LpRoLHRfn+GpduOjhF2vKUd+3X7MIbl+yYwliSbaG6bjZo2WPTBWjQyZ1q9WfGMhRB5jQ5z+Q0yPnvAd0xirbgxO7VDr3EBYsTXdBdWJaGPADFnsHnVcqPL1rnUzFVor0ZWB/fL1KJd02hp8TkAHKGpgG2H8Xu7O9F4675ewMLhStB5fBGazA2rdsre27PHtneRlXFib040gBktIL7g/RjSiYchf1/LFHubaQLUtWcfH1C9fZwiD/2irnhnP+ok9q3KAMWhTGwG4ju4/EaNckgWkU6+JwERBRaTejmBv1N/kil6/Orffqgzoz1dVPtbY8TkNyOb31JYpoUf0meujiMLtixMJrsuN2NToQu7cahQi0u5NJCu0PyqlOAVV08NnhKQ2dlE+4n7FbZAtVoFaU/CNjnF/ruH9aHlAQXs/RxuyllE4HqfHkc3rA/iGERIsJgW1ozNTNYyWclJWCzHBQrm0B2UIh2iITuPPJANa6dK4PzJKrf1rnPg9sTyU7HeRG5bk3YAmYWdxITSVt231GdECZlL4bgSePkFt30h7+n3vnGrUC4vc+maG4Ti5teE/ReesrG0wgybybRGewTK9xmc9TGo7CR17yyOBM+mJERbA/GhjNuHk9xTiTu05K6alQfj3N6Mb4G4DHZgPF5iKdstVIxy4QR56TgwQYMuKcLI0xJ3losuEXm8n8Ehdp8uUdZatl4QCfS/94GUCJmIlwHFbZ/i+Rwux1g5YapDj7NGk3fJHFsd0vwWiD2fhOnbIMeW9Lhwwb1BF0HTgY3ROCYekCBaZDCb2p4IXOxSY9Zuwru+/OX9LCwGTK/2wWmqkHnexZiE6TdZIZS7lcALzm6+OKUMAly9WYBPDWmQvTGoJ08v7PheeVWbYsZxSkCvilCTx1j9hiwOhqknV6h6bMKaw8u42zffBqL1pUj4E4wQztv9DAEdM4gaA04wL51N6flcKwXgIHtpfClfv07lmV/ybDSFd2DiD3T1s11E8163Q9Lc80jP2GM5Y75tAQU47+OU9co2JiwlSRGqEpasjaQ1QtGTkqAo+mlSQv381zsF+BffGTNPk3BpKnxbPqIL639sp6i4go1MFpj67iBs1ZYc+eqNSqNiVRruQBMk+67IKTumgisQ77CDJBJFGss8lL1R2D+UvhhDT8LWd8gjDsAw0cRmerI+JlKWSNv+GGU+sbZGXzm2f8WAi4YLDyfRWc5oBYW1jlAsAhgY092VHa+/0Fx2XZfxmZnSridYcZxnVTuKVZ5fb6OcUgA5NwReEZcuKe8J9jAPlIwxjgFKl3SH670tPyVsaEZTicpNAzKaiPuG77QhwF7tosanjnVjaVp2Cu+LPAA8+X9vUCb54VMBCyLVLrqBrPwZp5SW+Kogdt7/oTCYrGyYhWyF6nhJCCxnaUWLCz4zQy5YdrYsqRPwyEsNUyE6/HPbY4jRLQKMo0ELF1CA84W/P3SkDb6uAVrMMId/y3Hf/xWxyjCG9EkVmASVA0aCS6Ho6D5xb4W7UqU6ipxR9LfClQTReYsZYxkj/HOQqLZfWF5jcHo7IaKBLNylFc+Ka4mSNIf1+sEJZmoZtu6G29qnA6LJ87w7a7jPlGT/YtNfcV5lk+IDtQD5YK2ui6ijOFF78Tok0SOpgl+wiRvE76GJSW+dl995hfa0CL/D9xYGPtSfcN/EMb8afgU6+owC8bhU855L8aG1AFLFP52IKcG03wQpQW16p9J4SgEPD3iJIcc7R8ufb3IJfB+bsClFr4Z1KN5HCKXlc65RG02upBS4DYpeSHvAHTYSv4EtntYwUtCMnR8pqgAuGSn07tkoeieeWFns2J4fsaJGzIr4GCxqDMww01c2pEqlKAwAVtU1ox4OJsd4joOfxMZV4bBajnqCCqXJXcvW8L9+KeJBlTLaQjcbBG8OA2ptrvxqDd+SFU4EoE7PSu2Hc5sqfGPSW8KsIntJSvkc1tnFYxHVP9eR37M0y3WUIW64ZTnVwCgSGUExzpXJFt4VTVW8QjjeluukeGGMehlbO88lR2mAeR0lA+ilcEwvG0G5CEH1AE1nE3atjYDTS2WfvcEakvYtaJo8CaADAwgeFDZsjDRUZX9XZmHrFUMcFGyVrgw/W1ejMuk5tmkOkMayEEs8xXpA8xP7igsgN6aEymj25kSB623suSw22lHyEpMuibYBUkoOerNbYVqE41NWr+yW4SdqkGezDqthUHK2ZCgH4CJKclXpuGa02PfzPH3OebGP032VBEtPcCSd3SsQXxOAvW7vpYqbTI3Vg4+UoC1HVr5xmPGY2NAp6Q/HVIAReTDL8Np4IDwnlQHFX44ATysEhyRghC+ldVSUucxF8wYKHzdJbk+6/PXc11uBF9DLN/HGSYjbpajqWHRswk3Qr6MZ3R/OekEPZS/GW0kjBA0zPYW11I02yTM+lzJ5fFV5VmN/zXYHhy/klt4Qr8F7WFcb95ik01TYVlZP46gYGbVaeBAq5Y0QdwK3eTxuSwvpqZRlbHNlRvZ/6rWIHBBcsfG+Oq4KG5SlcNDjG565WWw3icUpLmAP2yU5DZXClgt/SvEt3qpY8YRcdKx4/kH3gY0U92b2xSj+vO/QjzxsEak84s+SDCjuC8sX09oRZZlUyWOLXQKIxyhTu02vZX9sdIURJactZUqnKMBW2XAb5k1VbGj3Va+isM7meyMLQ3gZhCZ//hSrO/GU6nhYR5L7RWJgyd+UlIwVvcIOxrpFj4PRD3xDdyy7Lqb6kfedCgwY04gzLzfVo4EedCY02gRFoKvSKNZ0kz0mAoYCBUnkjBthBP11dcLzXFwiA1vtNvlwwPKkJx9Lga+NzoysggDaCCCecIFAFEE0QvP4ObYOMUSRYamPJPRUETJTp4tyC3/FnFGIo97zqbcNu4Xzgblrg8zbci7tiuWVDGRT4Ob4rize0N5zXOIFA8CEQu2H7X4hcMXwYKIEPt9ZvYpOFP0JGPWlSuNPuHIZCLBKhS6feFhBNGHcFavlJ/zakT0LTpP5asOl+X6Dtpj9mVTlzB8CQYAlYxSAHFD+pbycQiqiMAZRDtuqBVmKHnmR9aumtntAgZ6OmiHhag5VjiJss8LFPDqfIUGakWrq1oilMsX0F1Y3+2e2qiV5Brx4HmxCMBaJ3ZwcbPf2g9Wt6HWlMLrqJvkg8Vjp6MvqMLrLhC8M4vf3MgzREcNX/Czf0LjOvElnthJLIPKMGollnx3CLV6a6lV2ObM28rQTR+e8rttIAJ13P0zV9gBBtcOUrkwKoCdiMyOnRUcJ8kIaKzVI4ItUeEKSqapxFJD3f+b8X9wI4+zCTH4W5LoJnP5BrJVMiBQsBLVMNR1+NIK6FYWsUzeQbO0g9ndeIuiOOO9pYo4flovhH94gVoB3YQSH9nutMlQrrko4peLVfe3Wc+HkSd6JPF/+DdChUv3H/th4SAJ4CGKMbI2Vqi7u/Q3CYe4xipo5eRDq9xEATxt/GL/HScieIUZCnlly/uLfCTKFW8DycCr185wGz1egXCWRZpNND8Ir+Jz3+N2usrWE1sXZCdh3Zwidmr6GOO8NejKPBO1cnnWcvtHHxc5/Bz0txh9xtK9TCQAUeNtOdopMeQkIvJcdkBXCSybLTrJwHk6YPGpxx2NlrZiw0IOdGJF5hCHKj9mqv75YSjXLY7pmvH3e1bnkR5ZmpH19FL7SvUCFHEfSrsOOW+RMGxvYIoHxtcBzWlVPJdOL4Rl9jl3vKxryC45WWMoKIOoERbA1hLfjOZ+lK3r0sUsnXs1gOu1oJn/ZLBTG8JpOp5LTNRh4/CjF99RPtZlQCwes+u/RZjS5gytyllegKRBU+1wliA1XMgFvG2NcP6+YTNO5oXYbN43Hng8TsJAUzkdArLU8eMXveJxANSMOJ0nWmn9KTQ92/R2A9RG71gi0XmwYaI63+Iv3w7Rfg1G9Yqgl+xQVVRd1Xjtj3BlJbrwSHUmP5NXv0uV1Z7ZgPZbUbQm5PICq8JUIv5r8DxgfsdQ50KcrNffvPCCwm8Gnxy2TAsG41RhoBKmYTO1Mt6YSRVjKldT5Og3jy0j2gUS31U+j17nkkqZpvL53Cq6/dxmLYThBYHVC9H00llJjBzNxZxvzFzxUrIbzDdkUrHN4VN/2JrSZN7godGXe0fdxruZE1Nj9s6C1SjE0V6CM16zwYmDwhaM0Ed8xJZaxNMZHYx8TCj85XSaYePLWPX2sGN416TBcU+cykmacDnInQyQjAe6/2DYaK0tU3MWvIB1DqARxQI2amF13/jB2/tdv752hoqjOnZiwzjjdCrzAyMDgBjVEmHnazfsOqks0tWkZJ04X+nUDumeVWASJ+s9\"}",
+ "n": "{\"iv\":\"gRg/zOOkFm2RAPnu\",\"encryptedData\":\"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\"}"
}
\ No newline at end of file
diff --git a/backend/src/db/api/bookings.js b/backend/src/db/api/bookings.js
index 9cb55a0..0cea0a1 100644
--- a/backend/src/db/api/bookings.js
+++ b/backend/src/db/api/bookings.js
@@ -169,6 +169,10 @@ module.exports = class BookingsDBApi {
transaction,
});
+ output.redemption_booking = await bookings.getRedemption_booking({
+ transaction,
+ });
+
output.customer = await bookings.getCustomer({
transaction,
});
diff --git a/backend/src/db/api/loyaltytier.js b/backend/src/db/api/loyaltytier.js
index c9067ae..c0d966a 100644
--- a/backend/src/db/api/loyaltytier.js
+++ b/backend/src/db/api/loyaltytier.js
@@ -16,6 +16,9 @@ module.exports = class LoyaltytierDBApi {
id: data.id || undefined,
name: data.name || null,
+ annualfee: data.annualfee || null,
+ pointspersar: data.pointspersar || null,
+ description: data.description || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
@@ -35,6 +38,9 @@ module.exports = class LoyaltytierDBApi {
id: item.id || undefined,
name: item.name || null,
+ annualfee: item.annualfee || null,
+ pointspersar: item.pointspersar || null,
+ description: item.description || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
@@ -61,6 +67,14 @@ module.exports = class LoyaltytierDBApi {
if (data.name !== undefined) updatePayload.name = data.name;
+ if (data.annualfee !== undefined) updatePayload.annualfee = data.annualfee;
+
+ if (data.pointspersar !== undefined)
+ updatePayload.pointspersar = data.pointspersar;
+
+ if (data.description !== undefined)
+ updatePayload.description = data.description;
+
updatePayload.updatedById = currentUser.id;
await loyaltytier.update(updatePayload, { transaction });
@@ -129,6 +143,10 @@ module.exports = class LoyaltytierDBApi {
const output = loyaltytier.get({ plain: true });
+ output.users_loyaltytier = await loyaltytier.getUsers_loyaltytier({
+ transaction,
+ });
+
return output;
}
@@ -161,6 +179,65 @@ module.exports = class LoyaltytierDBApi {
};
}
+ if (filter.description) {
+ where = {
+ ...where,
+ [Op.and]: Utils.ilike(
+ 'loyaltytier',
+ 'description',
+ filter.description,
+ ),
+ };
+ }
+
+ if (filter.annualfeeRange) {
+ const [start, end] = filter.annualfeeRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ annualfee: {
+ ...where.annualfee,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ annualfee: {
+ ...where.annualfee,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
+ if (filter.pointspersarRange) {
+ const [start, end] = filter.pointspersarRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ pointspersar: {
+ ...where.pointspersar,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ pointspersar: {
+ ...where.pointspersar,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
if (filter.active !== undefined) {
where = {
...where,
diff --git a/backend/src/db/api/redemption.js b/backend/src/db/api/redemption.js
new file mode 100644
index 0000000..e23b84c
--- /dev/null
+++ b/backend/src/db/api/redemption.js
@@ -0,0 +1,402 @@
+const db = require('../models');
+const FileDBApi = require('./file');
+const crypto = require('crypto');
+const Utils = require('../utils');
+
+const Sequelize = db.Sequelize;
+const Op = Sequelize.Op;
+
+module.exports = class RedemptionDBApi {
+ static async create(data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const redemption = await db.redemption.create(
+ {
+ id: data.id || undefined,
+
+ pointsused: data.pointsused || null,
+ redemptiontype: data.redemptiontype || null,
+ discountvalue: data.discountvalue || null,
+ status: data.status || null,
+ importHash: data.importHash || null,
+ createdById: currentUser.id,
+ updatedById: currentUser.id,
+ },
+ { transaction },
+ );
+
+ await redemption.setUser(data.user || null, {
+ transaction,
+ });
+
+ await redemption.setBooking(data.booking || null, {
+ transaction,
+ });
+
+ return redemption;
+ }
+
+ static async bulkImport(data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ // Prepare data - wrapping individual data transformations in a map() method
+ const redemptionData = data.map((item, index) => ({
+ id: item.id || undefined,
+
+ pointsused: item.pointsused || null,
+ redemptiontype: item.redemptiontype || null,
+ discountvalue: item.discountvalue || null,
+ status: item.status || null,
+ importHash: item.importHash || null,
+ createdById: currentUser.id,
+ updatedById: currentUser.id,
+ createdAt: new Date(Date.now() + index * 1000),
+ }));
+
+ // Bulk create items
+ const redemption = await db.redemption.bulkCreate(redemptionData, {
+ transaction,
+ });
+
+ // For each item created, replace relation files
+
+ return redemption;
+ }
+
+ static async update(id, data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const redemption = await db.redemption.findByPk(id, {}, { transaction });
+
+ const updatePayload = {};
+
+ if (data.pointsused !== undefined)
+ updatePayload.pointsused = data.pointsused;
+
+ if (data.redemptiontype !== undefined)
+ updatePayload.redemptiontype = data.redemptiontype;
+
+ if (data.discountvalue !== undefined)
+ updatePayload.discountvalue = data.discountvalue;
+
+ if (data.status !== undefined) updatePayload.status = data.status;
+
+ updatePayload.updatedById = currentUser.id;
+
+ await redemption.update(updatePayload, { transaction });
+
+ if (data.user !== undefined) {
+ await redemption.setUser(
+ data.user,
+
+ { transaction },
+ );
+ }
+
+ if (data.booking !== undefined) {
+ await redemption.setBooking(
+ data.booking,
+
+ { transaction },
+ );
+ }
+
+ return redemption;
+ }
+
+ static async deleteByIds(ids, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const redemption = await db.redemption.findAll({
+ where: {
+ id: {
+ [Op.in]: ids,
+ },
+ },
+ transaction,
+ });
+
+ await db.sequelize.transaction(async (transaction) => {
+ for (const record of redemption) {
+ await record.update({ deletedBy: currentUser.id }, { transaction });
+ }
+ for (const record of redemption) {
+ await record.destroy({ transaction });
+ }
+ });
+
+ return redemption;
+ }
+
+ static async remove(id, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const redemption = await db.redemption.findByPk(id, options);
+
+ await redemption.update(
+ {
+ deletedBy: currentUser.id,
+ },
+ {
+ transaction,
+ },
+ );
+
+ await redemption.destroy({
+ transaction,
+ });
+
+ return redemption;
+ }
+
+ static async findBy(where, options) {
+ const transaction = (options && options.transaction) || undefined;
+
+ const redemption = await db.redemption.findOne({ where }, { transaction });
+
+ if (!redemption) {
+ return redemption;
+ }
+
+ const output = redemption.get({ plain: true });
+
+ output.user = await redemption.getUser({
+ transaction,
+ });
+
+ output.booking = await redemption.getBooking({
+ transaction,
+ });
+
+ return output;
+ }
+
+ static async findAll(filter, options) {
+ const limit = filter.limit || 0;
+ let offset = 0;
+ let where = {};
+ const currentPage = +filter.page;
+
+ offset = currentPage * limit;
+
+ const orderBy = null;
+
+ const transaction = (options && options.transaction) || undefined;
+
+ let include = [
+ {
+ model: db.users,
+ as: 'user',
+
+ where: filter.user
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: filter.user
+ .split('|')
+ .map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ firstName: {
+ [Op.or]: filter.user
+ .split('|')
+ .map((term) => ({ [Op.iLike]: `%${term}%` })),
+ },
+ },
+ ],
+ }
+ : {},
+ },
+
+ {
+ model: db.bookings,
+ as: 'booking',
+
+ where: filter.booking
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: filter.booking
+ .split('|')
+ .map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ service_type: {
+ [Op.or]: filter.booking
+ .split('|')
+ .map((term) => ({ [Op.iLike]: `%${term}%` })),
+ },
+ },
+ ],
+ }
+ : {},
+ },
+ ];
+
+ if (filter) {
+ if (filter.id) {
+ where = {
+ ...where,
+ ['id']: Utils.uuid(filter.id),
+ };
+ }
+
+ if (filter.pointsusedRange) {
+ const [start, end] = filter.pointsusedRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ pointsused: {
+ ...where.pointsused,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ pointsused: {
+ ...where.pointsused,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
+ if (filter.discountvalueRange) {
+ const [start, end] = filter.discountvalueRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ discountvalue: {
+ ...where.discountvalue,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ discountvalue: {
+ ...where.discountvalue,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
+ if (filter.active !== undefined) {
+ where = {
+ ...where,
+ active: filter.active === true || filter.active === 'true',
+ };
+ }
+
+ if (filter.redemptiontype) {
+ where = {
+ ...where,
+ redemptiontype: filter.redemptiontype,
+ };
+ }
+
+ if (filter.status) {
+ where = {
+ ...where,
+ status: filter.status,
+ };
+ }
+
+ if (filter.createdAtRange) {
+ const [start, end] = filter.createdAtRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ ['createdAt']: {
+ ...where.createdAt,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ ['createdAt']: {
+ ...where.createdAt,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+ }
+
+ const queryOptions = {
+ where,
+ include,
+ distinct: true,
+ order:
+ filter.field && filter.sort
+ ? [[filter.field, filter.sort]]
+ : [['createdAt', 'desc']],
+ transaction: options?.transaction,
+ logging: console.log,
+ };
+
+ if (!options?.countOnly) {
+ queryOptions.limit = limit ? Number(limit) : undefined;
+ queryOptions.offset = offset ? Number(offset) : undefined;
+ }
+
+ try {
+ const { rows, count } = await db.redemption.findAndCountAll(queryOptions);
+
+ return {
+ rows: options?.countOnly ? [] : rows,
+ count: count,
+ };
+ } catch (error) {
+ console.error('Error executing query:', error);
+ throw error;
+ }
+ }
+
+ static async findAllAutocomplete(query, limit, offset) {
+ let where = {};
+
+ if (query) {
+ where = {
+ [Op.or]: [
+ { ['id']: Utils.uuid(query) },
+ Utils.ilike('redemption', 'id', query),
+ ],
+ };
+ }
+
+ const records = await db.redemption.findAll({
+ attributes: ['id', 'id'],
+ where,
+ limit: limit ? Number(limit) : undefined,
+ offset: offset ? Number(offset) : undefined,
+ orderBy: [['id', 'ASC']],
+ });
+
+ return records.map((record) => ({
+ id: record.id,
+ label: record.id,
+ }));
+ }
+};
diff --git a/backend/src/db/api/referral.js b/backend/src/db/api/referral.js
new file mode 100644
index 0000000..cf5ac51
--- /dev/null
+++ b/backend/src/db/api/referral.js
@@ -0,0 +1,306 @@
+const db = require('../models');
+const FileDBApi = require('./file');
+const crypto = require('crypto');
+const Utils = require('../utils');
+
+const Sequelize = db.Sequelize;
+const Op = Sequelize.Op;
+
+module.exports = class ReferralDBApi {
+ static async create(data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const referral = await db.referral.create(
+ {
+ id: data.id || undefined,
+
+ referredemail: data.referredemail || null,
+ status: data.status || null,
+ importHash: data.importHash || null,
+ createdById: currentUser.id,
+ updatedById: currentUser.id,
+ },
+ { transaction },
+ );
+
+ await referral.setReferrer(data.referrer || null, {
+ transaction,
+ });
+
+ return referral;
+ }
+
+ static async bulkImport(data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ // Prepare data - wrapping individual data transformations in a map() method
+ const referralData = data.map((item, index) => ({
+ id: item.id || undefined,
+
+ referredemail: item.referredemail || null,
+ status: item.status || null,
+ importHash: item.importHash || null,
+ createdById: currentUser.id,
+ updatedById: currentUser.id,
+ createdAt: new Date(Date.now() + index * 1000),
+ }));
+
+ // Bulk create items
+ const referral = await db.referral.bulkCreate(referralData, {
+ transaction,
+ });
+
+ // For each item created, replace relation files
+
+ return referral;
+ }
+
+ static async update(id, data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const referral = await db.referral.findByPk(id, {}, { transaction });
+
+ const updatePayload = {};
+
+ if (data.referredemail !== undefined)
+ updatePayload.referredemail = data.referredemail;
+
+ if (data.status !== undefined) updatePayload.status = data.status;
+
+ updatePayload.updatedById = currentUser.id;
+
+ await referral.update(updatePayload, { transaction });
+
+ if (data.referrer !== undefined) {
+ await referral.setReferrer(
+ data.referrer,
+
+ { transaction },
+ );
+ }
+
+ return referral;
+ }
+
+ static async deleteByIds(ids, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const referral = await db.referral.findAll({
+ where: {
+ id: {
+ [Op.in]: ids,
+ },
+ },
+ transaction,
+ });
+
+ await db.sequelize.transaction(async (transaction) => {
+ for (const record of referral) {
+ await record.update({ deletedBy: currentUser.id }, { transaction });
+ }
+ for (const record of referral) {
+ await record.destroy({ transaction });
+ }
+ });
+
+ return referral;
+ }
+
+ static async remove(id, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const referral = await db.referral.findByPk(id, options);
+
+ await referral.update(
+ {
+ deletedBy: currentUser.id,
+ },
+ {
+ transaction,
+ },
+ );
+
+ await referral.destroy({
+ transaction,
+ });
+
+ return referral;
+ }
+
+ static async findBy(where, options) {
+ const transaction = (options && options.transaction) || undefined;
+
+ const referral = await db.referral.findOne({ where }, { transaction });
+
+ if (!referral) {
+ return referral;
+ }
+
+ const output = referral.get({ plain: true });
+
+ output.referrer = await referral.getReferrer({
+ transaction,
+ });
+
+ return output;
+ }
+
+ static async findAll(filter, options) {
+ const limit = filter.limit || 0;
+ let offset = 0;
+ let where = {};
+ const currentPage = +filter.page;
+
+ offset = currentPage * limit;
+
+ const orderBy = null;
+
+ const transaction = (options && options.transaction) || undefined;
+
+ let include = [
+ {
+ model: db.users,
+ as: 'referrer',
+
+ where: filter.referrer
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: filter.referrer
+ .split('|')
+ .map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ firstName: {
+ [Op.or]: filter.referrer
+ .split('|')
+ .map((term) => ({ [Op.iLike]: `%${term}%` })),
+ },
+ },
+ ],
+ }
+ : {},
+ },
+ ];
+
+ if (filter) {
+ if (filter.id) {
+ where = {
+ ...where,
+ ['id']: Utils.uuid(filter.id),
+ };
+ }
+
+ if (filter.referredemail) {
+ where = {
+ ...where,
+ [Op.and]: Utils.ilike(
+ 'referral',
+ 'referredemail',
+ filter.referredemail,
+ ),
+ };
+ }
+
+ if (filter.active !== undefined) {
+ where = {
+ ...where,
+ active: filter.active === true || filter.active === 'true',
+ };
+ }
+
+ if (filter.status) {
+ where = {
+ ...where,
+ status: filter.status,
+ };
+ }
+
+ if (filter.createdAtRange) {
+ const [start, end] = filter.createdAtRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ ['createdAt']: {
+ ...where.createdAt,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ ['createdAt']: {
+ ...where.createdAt,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+ }
+
+ const queryOptions = {
+ where,
+ include,
+ distinct: true,
+ order:
+ filter.field && filter.sort
+ ? [[filter.field, filter.sort]]
+ : [['createdAt', 'desc']],
+ transaction: options?.transaction,
+ logging: console.log,
+ };
+
+ if (!options?.countOnly) {
+ queryOptions.limit = limit ? Number(limit) : undefined;
+ queryOptions.offset = offset ? Number(offset) : undefined;
+ }
+
+ try {
+ const { rows, count } = await db.referral.findAndCountAll(queryOptions);
+
+ return {
+ rows: options?.countOnly ? [] : rows,
+ count: count,
+ };
+ } catch (error) {
+ console.error('Error executing query:', error);
+ throw error;
+ }
+ }
+
+ static async findAllAutocomplete(query, limit, offset) {
+ let where = {};
+
+ if (query) {
+ where = {
+ [Op.or]: [
+ { ['id']: Utils.uuid(query) },
+ Utils.ilike('referral', 'id', query),
+ ],
+ };
+ }
+
+ const records = await db.referral.findAll({
+ attributes: ['id', 'id'],
+ where,
+ limit: limit ? Number(limit) : undefined,
+ offset: offset ? Number(offset) : undefined,
+ orderBy: [['id', 'ASC']],
+ });
+
+ return records.map((record) => ({
+ id: record.id,
+ label: record.id,
+ }));
+ }
+};
diff --git a/backend/src/db/api/users.js b/backend/src/db/api/users.js
index 3a11e69..d7e8580 100644
--- a/backend/src/db/api/users.js
+++ b/backend/src/db/api/users.js
@@ -34,6 +34,8 @@ module.exports = class UsersDBApi {
passwordResetTokenExpiresAt:
data.data.passwordResetTokenExpiresAt || null,
provider: data.data.provider || null,
+ pointsbalance: data.data.pointsbalance || null,
+ referralcode: data.data.referralcode || null,
importHash: data.data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
@@ -56,6 +58,10 @@ module.exports = class UsersDBApi {
});
}
+ await users.setLoyaltytier(data.data.loyaltytier || null, {
+ transaction,
+ });
+
await users.setCustom_permissions(data.data.custom_permissions || [], {
transaction,
});
@@ -96,6 +102,8 @@ module.exports = class UsersDBApi {
passwordResetToken: item.passwordResetToken || null,
passwordResetTokenExpiresAt: item.passwordResetTokenExpiresAt || null,
provider: item.provider || null,
+ pointsbalance: item.pointsbalance || null,
+ referralcode: item.referralcode || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
@@ -178,6 +186,12 @@ module.exports = class UsersDBApi {
if (data.provider !== undefined) updatePayload.provider = data.provider;
+ if (data.pointsbalance !== undefined)
+ updatePayload.pointsbalance = data.pointsbalance;
+
+ if (data.referralcode !== undefined)
+ updatePayload.referralcode = data.referralcode;
+
updatePayload.updatedById = currentUser.id;
await users.update(updatePayload, { transaction });
@@ -190,6 +204,14 @@ module.exports = class UsersDBApi {
);
}
+ if (data.loyaltytier !== undefined) {
+ await users.setLoyaltytier(
+ data.loyaltytier,
+
+ { transaction },
+ );
+ }
+
if (data.custom_permissions !== undefined) {
await users.setCustom_permissions(data.custom_permissions, {
transaction,
@@ -267,6 +289,14 @@ module.exports = class UsersDBApi {
const output = users.get({ plain: true });
+ output.referral_referrer = await users.getReferral_referrer({
+ transaction,
+ });
+
+ output.redemption_user = await users.getRedemption_user({
+ transaction,
+ });
+
output.avatar = await users.getAvatar({
transaction,
});
@@ -285,6 +315,10 @@ module.exports = class UsersDBApi {
transaction,
});
+ output.loyaltytier = await users.getLoyaltytier({
+ transaction,
+ });
+
return output;
}
@@ -327,6 +361,32 @@ module.exports = class UsersDBApi {
: {},
},
+ {
+ model: db.loyaltytier,
+ as: 'loyaltytier',
+
+ where: filter.loyaltytier
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: filter.loyaltytier
+ .split('|')
+ .map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ name: {
+ [Op.or]: filter.loyaltytier
+ .split('|')
+ .map((term) => ({ [Op.iLike]: `%${term}%` })),
+ },
+ },
+ ],
+ }
+ : {},
+ },
+
{
model: db.permissions,
as: 'custom_permissions',
@@ -411,6 +471,13 @@ module.exports = class UsersDBApi {
};
}
+ if (filter.referralcode) {
+ where = {
+ ...where,
+ [Op.and]: Utils.ilike('users', 'referralcode', filter.referralcode),
+ };
+ }
+
if (filter.emailVerificationTokenExpiresAtRange) {
const [start, end] = filter.emailVerificationTokenExpiresAtRange;
@@ -459,6 +526,30 @@ module.exports = class UsersDBApi {
}
}
+ if (filter.pointsbalanceRange) {
+ const [start, end] = filter.pointsbalanceRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ pointsbalance: {
+ ...where.pointsbalance,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ pointsbalance: {
+ ...where.pointsbalance,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
if (filter.active !== undefined) {
where = {
...where,
diff --git a/backend/src/db/migrations/1748078935638.js b/backend/src/db/migrations/1748078935638.js
new file mode 100644
index 0000000..a84af4c
--- /dev/null
+++ b/backend/src/db/migrations/1748078935638.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'loyaltytier',
+ 'annualfee',
+ {
+ type: Sequelize.DataTypes.DECIMAL,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('loyaltytier', 'annualfee', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748078966955.js b/backend/src/db/migrations/1748078966955.js
new file mode 100644
index 0000000..5241419
--- /dev/null
+++ b/backend/src/db/migrations/1748078966955.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'loyaltytier',
+ 'pointspersar',
+ {
+ type: Sequelize.DataTypes.DECIMAL,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('loyaltytier', 'pointspersar', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079001996.js b/backend/src/db/migrations/1748079001996.js
new file mode 100644
index 0000000..0a09ff8
--- /dev/null
+++ b/backend/src/db/migrations/1748079001996.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'loyaltytier',
+ 'description',
+ {
+ type: Sequelize.DataTypes.TEXT,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('loyaltytier', 'description', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079044910.js b/backend/src/db/migrations/1748079044910.js
new file mode 100644
index 0000000..72ff531
--- /dev/null
+++ b/backend/src/db/migrations/1748079044910.js
@@ -0,0 +1,54 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'users',
+ 'loyaltytierId',
+ {
+ type: Sequelize.DataTypes.UUID,
+
+ references: {
+ model: 'loyaltytier',
+ key: 'id',
+ },
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('users', 'loyaltytierId', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079122373.js b/backend/src/db/migrations/1748079122373.js
new file mode 100644
index 0000000..ea9264b
--- /dev/null
+++ b/backend/src/db/migrations/1748079122373.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'users',
+ 'pointsbalance',
+ {
+ type: Sequelize.DataTypes.INTEGER,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('users', 'pointsbalance', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079157620.js b/backend/src/db/migrations/1748079157620.js
new file mode 100644
index 0000000..9fc48dc
--- /dev/null
+++ b/backend/src/db/migrations/1748079157620.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'users',
+ 'referralcode',
+ {
+ type: Sequelize.DataTypes.TEXT,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('users', 'referralcode', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079188113.js b/backend/src/db/migrations/1748079188113.js
new file mode 100644
index 0000000..74b0a70
--- /dev/null
+++ b/backend/src/db/migrations/1748079188113.js
@@ -0,0 +1,72 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.createTable(
+ 'referral',
+ {
+ id: {
+ type: Sequelize.DataTypes.UUID,
+ defaultValue: Sequelize.DataTypes.UUIDV4,
+ primaryKey: true,
+ },
+ createdById: {
+ type: Sequelize.DataTypes.UUID,
+ references: {
+ key: 'id',
+ model: 'users',
+ },
+ },
+ updatedById: {
+ type: Sequelize.DataTypes.UUID,
+ references: {
+ key: 'id',
+ model: 'users',
+ },
+ },
+ createdAt: { type: Sequelize.DataTypes.DATE },
+ updatedAt: { type: Sequelize.DataTypes.DATE },
+ deletedAt: { type: Sequelize.DataTypes.DATE },
+ importHash: {
+ type: Sequelize.DataTypes.STRING(255),
+ allowNull: true,
+ unique: true,
+ },
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.dropTable('referral', { transaction });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079231733.js b/backend/src/db/migrations/1748079231733.js
new file mode 100644
index 0000000..2759bff
--- /dev/null
+++ b/backend/src/db/migrations/1748079231733.js
@@ -0,0 +1,54 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'referral',
+ 'referrerId',
+ {
+ type: Sequelize.DataTypes.UUID,
+
+ references: {
+ model: 'users',
+ key: 'id',
+ },
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('referral', 'referrerId', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079291100.js b/backend/src/db/migrations/1748079291100.js
new file mode 100644
index 0000000..811e528
--- /dev/null
+++ b/backend/src/db/migrations/1748079291100.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'referral',
+ 'referredemail',
+ {
+ type: Sequelize.DataTypes.TEXT,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('referral', 'referredemail', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079326438.js b/backend/src/db/migrations/1748079326438.js
new file mode 100644
index 0000000..a824747
--- /dev/null
+++ b/backend/src/db/migrations/1748079326438.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'referral',
+ 'status',
+ {
+ type: Sequelize.DataTypes.ENUM,
+
+ values: ['value'],
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('referral', 'status', { transaction });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079355731.js b/backend/src/db/migrations/1748079355731.js
new file mode 100644
index 0000000..7823ac4
--- /dev/null
+++ b/backend/src/db/migrations/1748079355731.js
@@ -0,0 +1,72 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.createTable(
+ 'redemption',
+ {
+ id: {
+ type: Sequelize.DataTypes.UUID,
+ defaultValue: Sequelize.DataTypes.UUIDV4,
+ primaryKey: true,
+ },
+ createdById: {
+ type: Sequelize.DataTypes.UUID,
+ references: {
+ key: 'id',
+ model: 'users',
+ },
+ },
+ updatedById: {
+ type: Sequelize.DataTypes.UUID,
+ references: {
+ key: 'id',
+ model: 'users',
+ },
+ },
+ createdAt: { type: Sequelize.DataTypes.DATE },
+ updatedAt: { type: Sequelize.DataTypes.DATE },
+ deletedAt: { type: Sequelize.DataTypes.DATE },
+ importHash: {
+ type: Sequelize.DataTypes.STRING(255),
+ allowNull: true,
+ unique: true,
+ },
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.dropTable('redemption', { transaction });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079386360.js b/backend/src/db/migrations/1748079386360.js
new file mode 100644
index 0000000..34e2297
--- /dev/null
+++ b/backend/src/db/migrations/1748079386360.js
@@ -0,0 +1,54 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'redemption',
+ 'userId',
+ {
+ type: Sequelize.DataTypes.UUID,
+
+ references: {
+ model: 'users',
+ key: 'id',
+ },
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('redemption', 'userId', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079423627.js b/backend/src/db/migrations/1748079423627.js
new file mode 100644
index 0000000..b9baad9
--- /dev/null
+++ b/backend/src/db/migrations/1748079423627.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'redemption',
+ 'pointsused',
+ {
+ type: Sequelize.DataTypes.INTEGER,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('redemption', 'pointsused', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079463221.js b/backend/src/db/migrations/1748079463221.js
new file mode 100644
index 0000000..39fa979
--- /dev/null
+++ b/backend/src/db/migrations/1748079463221.js
@@ -0,0 +1,51 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'redemption',
+ 'redemptiontype',
+ {
+ type: Sequelize.DataTypes.ENUM,
+
+ values: ['value'],
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('redemption', 'redemptiontype', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079630221.js b/backend/src/db/migrations/1748079630221.js
new file mode 100644
index 0000000..a01ad58
--- /dev/null
+++ b/backend/src/db/migrations/1748079630221.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'redemption',
+ 'discountvalue',
+ {
+ type: Sequelize.DataTypes.DECIMAL,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('redemption', 'discountvalue', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079688214.js b/backend/src/db/migrations/1748079688214.js
new file mode 100644
index 0000000..e47c8f8
--- /dev/null
+++ b/backend/src/db/migrations/1748079688214.js
@@ -0,0 +1,51 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'redemption',
+ 'status',
+ {
+ type: Sequelize.DataTypes.ENUM,
+
+ values: ['value'],
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('redemption', 'status', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1748079723177.js b/backend/src/db/migrations/1748079723177.js
new file mode 100644
index 0000000..1ecb17b
--- /dev/null
+++ b/backend/src/db/migrations/1748079723177.js
@@ -0,0 +1,54 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'redemption',
+ 'bookingId',
+ {
+ type: Sequelize.DataTypes.UUID,
+
+ references: {
+ model: 'bookings',
+ key: 'id',
+ },
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('redemption', 'bookingId', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/models/bookings.js b/backend/src/db/models/bookings.js
index 1851ceb..247adf6 100644
--- a/backend/src/db/models/bookings.js
+++ b/backend/src/db/models/bookings.js
@@ -60,6 +60,14 @@ module.exports = function (sequelize, DataTypes) {
constraints: false,
});
+ db.bookings.hasMany(db.redemption, {
+ as: 'redemption_booking',
+ foreignKey: {
+ name: 'bookingId',
+ },
+ constraints: false,
+ });
+
//end loop
db.bookings.belongsTo(db.customers, {
diff --git a/backend/src/db/models/loyaltytier.js b/backend/src/db/models/loyaltytier.js
index 8773280..2087e9e 100644
--- a/backend/src/db/models/loyaltytier.js
+++ b/backend/src/db/models/loyaltytier.js
@@ -18,6 +18,18 @@ module.exports = function (sequelize, DataTypes) {
type: DataTypes.TEXT,
},
+ annualfee: {
+ type: DataTypes.DECIMAL,
+ },
+
+ pointspersar: {
+ type: DataTypes.DECIMAL,
+ },
+
+ description: {
+ type: DataTypes.TEXT,
+ },
+
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
@@ -34,6 +46,14 @@ module.exports = function (sequelize, DataTypes) {
loyaltytier.associate = (db) => {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
+ db.loyaltytier.hasMany(db.users, {
+ as: 'users_loyaltytier',
+ foreignKey: {
+ name: 'loyaltytierId',
+ },
+ constraints: false,
+ });
+
//end loop
db.loyaltytier.belongsTo(db.users, {
diff --git a/backend/src/db/models/redemption.js b/backend/src/db/models/redemption.js
new file mode 100644
index 0000000..fb3c033
--- /dev/null
+++ b/backend/src/db/models/redemption.js
@@ -0,0 +1,81 @@
+const config = require('../../config');
+const providers = config.providers;
+const crypto = require('crypto');
+const bcrypt = require('bcrypt');
+const moment = require('moment');
+
+module.exports = function (sequelize, DataTypes) {
+ const redemption = sequelize.define(
+ 'redemption',
+ {
+ id: {
+ type: DataTypes.UUID,
+ defaultValue: DataTypes.UUIDV4,
+ primaryKey: true,
+ },
+
+ pointsused: {
+ type: DataTypes.INTEGER,
+ },
+
+ redemptiontype: {
+ type: DataTypes.ENUM,
+
+ values: ['value'],
+ },
+
+ discountvalue: {
+ type: DataTypes.DECIMAL,
+ },
+
+ status: {
+ type: DataTypes.ENUM,
+
+ values: ['value'],
+ },
+
+ importHash: {
+ type: DataTypes.STRING(255),
+ allowNull: true,
+ unique: true,
+ },
+ },
+ {
+ timestamps: true,
+ paranoid: true,
+ freezeTableName: true,
+ },
+ );
+
+ redemption.associate = (db) => {
+ /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
+
+ //end loop
+
+ db.redemption.belongsTo(db.users, {
+ as: 'user',
+ foreignKey: {
+ name: 'userId',
+ },
+ constraints: false,
+ });
+
+ db.redemption.belongsTo(db.bookings, {
+ as: 'booking',
+ foreignKey: {
+ name: 'bookingId',
+ },
+ constraints: false,
+ });
+
+ db.redemption.belongsTo(db.users, {
+ as: 'createdBy',
+ });
+
+ db.redemption.belongsTo(db.users, {
+ as: 'updatedBy',
+ });
+ };
+
+ return redemption;
+};
diff --git a/backend/src/db/models/referral.js b/backend/src/db/models/referral.js
new file mode 100644
index 0000000..3cfd722
--- /dev/null
+++ b/backend/src/db/models/referral.js
@@ -0,0 +1,63 @@
+const config = require('../../config');
+const providers = config.providers;
+const crypto = require('crypto');
+const bcrypt = require('bcrypt');
+const moment = require('moment');
+
+module.exports = function (sequelize, DataTypes) {
+ const referral = sequelize.define(
+ 'referral',
+ {
+ id: {
+ type: DataTypes.UUID,
+ defaultValue: DataTypes.UUIDV4,
+ primaryKey: true,
+ },
+
+ referredemail: {
+ type: DataTypes.TEXT,
+ },
+
+ status: {
+ type: DataTypes.ENUM,
+
+ values: ['value'],
+ },
+
+ importHash: {
+ type: DataTypes.STRING(255),
+ allowNull: true,
+ unique: true,
+ },
+ },
+ {
+ timestamps: true,
+ paranoid: true,
+ freezeTableName: true,
+ },
+ );
+
+ referral.associate = (db) => {
+ /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
+
+ //end loop
+
+ db.referral.belongsTo(db.users, {
+ as: 'referrer',
+ foreignKey: {
+ name: 'referrerId',
+ },
+ constraints: false,
+ });
+
+ db.referral.belongsTo(db.users, {
+ as: 'createdBy',
+ });
+
+ db.referral.belongsTo(db.users, {
+ as: 'updatedBy',
+ });
+ };
+
+ return referral;
+};
diff --git a/backend/src/db/models/users.js b/backend/src/db/models/users.js
index 74e549e..27d86c7 100644
--- a/backend/src/db/models/users.js
+++ b/backend/src/db/models/users.js
@@ -68,6 +68,14 @@ module.exports = function (sequelize, DataTypes) {
type: DataTypes.TEXT,
},
+ pointsbalance: {
+ type: DataTypes.INTEGER,
+ },
+
+ referralcode: {
+ type: DataTypes.TEXT,
+ },
+
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
@@ -102,6 +110,22 @@ module.exports = function (sequelize, DataTypes) {
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
+ db.users.hasMany(db.referral, {
+ as: 'referral_referrer',
+ foreignKey: {
+ name: 'referrerId',
+ },
+ constraints: false,
+ });
+
+ db.users.hasMany(db.redemption, {
+ as: 'redemption_user',
+ foreignKey: {
+ name: 'userId',
+ },
+ constraints: false,
+ });
+
//end loop
db.users.belongsTo(db.roles, {
@@ -112,6 +136,14 @@ module.exports = function (sequelize, DataTypes) {
constraints: false,
});
+ db.users.belongsTo(db.loyaltytier, {
+ as: 'loyaltytier',
+ foreignKey: {
+ name: 'loyaltytierId',
+ },
+ constraints: false,
+ });
+
db.users.hasMany(db.file, {
as: 'avatar',
foreignKey: 'belongsToId',
diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js
index 0d48f1e..92e53b6 100644
--- a/backend/src/db/seeders/20200430130760-user-roles.js
+++ b/backend/src/db/seeders/20200430130760-user-roles.js
@@ -103,6 +103,8 @@ module.exports = {
'roles',
'permissions',
'loyaltytier',
+ 'referral',
+ 'redemption',
,
];
await queryInterface.bulkInsert(
@@ -712,6 +714,56 @@ primary key ("roles_permissionsId", "permissionId")
permissionId: getId('DELETE_LOYALTYTIER'),
},
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('Administrator'),
+ permissionId: getId('CREATE_REFERRAL'),
+ },
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('Administrator'),
+ permissionId: getId('READ_REFERRAL'),
+ },
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('Administrator'),
+ permissionId: getId('UPDATE_REFERRAL'),
+ },
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('Administrator'),
+ permissionId: getId('DELETE_REFERRAL'),
+ },
+
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('Administrator'),
+ permissionId: getId('CREATE_REDEMPTION'),
+ },
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('Administrator'),
+ permissionId: getId('READ_REDEMPTION'),
+ },
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('Administrator'),
+ permissionId: getId('UPDATE_REDEMPTION'),
+ },
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('Administrator'),
+ permissionId: getId('DELETE_REDEMPTION'),
+ },
+
{
createdAt,
updatedAt,
diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js
index df80374..36435c6 100644
--- a/backend/src/db/seeders/20231127130745-sample-data.js
+++ b/backend/src/db/seeders/20231127130745-sample-data.js
@@ -13,6 +13,10 @@ const Vouchers = db.vouchers;
const Loyaltytier = db.loyaltytier;
+const Referral = db.referral;
+
+const Redemption = db.redemption;
+
const AgentsData = [
{
name: 'Agent A',
@@ -45,13 +49,21 @@ const AgentsData = [
phone_number: '4564564567',
},
+
+ {
+ name: 'Agent E',
+
+ email: 'agent.e@example.com',
+
+ phone_number: '5675675678',
+ },
];
const BookingsData = [
{
// type code here for "relation_one" field
- service_type: 'Hotel',
+ service_type: 'Package',
booking_date: new Date('2023-11-01T10:00:00Z'),
@@ -63,7 +75,7 @@ const BookingsData = [
{
// type code here for "relation_one" field
- service_type: 'Package',
+ service_type: 'Tour',
booking_date: new Date('2023-11-02T12:00:00Z'),
@@ -75,7 +87,7 @@ const BookingsData = [
{
// type code here for "relation_one" field
- service_type: 'Hotel',
+ service_type: 'Tour',
booking_date: new Date('2023-11-03T14:00:00Z'),
@@ -87,7 +99,7 @@ const BookingsData = [
{
// type code here for "relation_one" field
- service_type: 'Hotel',
+ service_type: 'Package',
booking_date: new Date('2023-11-04T16:00:00Z'),
@@ -95,6 +107,18 @@ const BookingsData = [
// type code here for "relation_one" field
},
+
+ {
+ // type code here for "relation_one" field
+
+ service_type: 'Tour',
+
+ booking_date: new Date('2023-11-05T18:00:00Z'),
+
+ total_amount: 450,
+
+ // type code here for "relation_one" field
+ },
];
const CustomersData = [
@@ -137,6 +161,16 @@ const CustomersData = [
phone_number: '5566778899',
},
+
+ {
+ first_name: 'Charlie',
+
+ last_name: 'Davis',
+
+ email: 'charlie.davis@example.com',
+
+ phone_number: '6677889900',
+ },
];
const PaymentsData = [
@@ -147,7 +181,7 @@ const PaymentsData = [
amount: 500,
- status: 'Failed',
+ status: 'Pending',
},
{
@@ -157,7 +191,7 @@ const PaymentsData = [
amount: 300,
- status: 'Pending',
+ status: 'Failed',
},
{
@@ -167,7 +201,7 @@ const PaymentsData = [
amount: 800,
- status: 'Pending',
+ status: 'Completed',
},
{
@@ -179,6 +213,16 @@ const PaymentsData = [
status: 'Pending',
},
+
+ {
+ // type code here for "relation_one" field
+
+ payment_date: new Date('2023-11-05T19:00:00Z'),
+
+ amount: 450,
+
+ status: 'Failed',
+ },
];
const VouchersData = [
@@ -213,28 +257,241 @@ const VouchersData = [
issue_date: new Date('2023-11-04T18:00:00Z'),
},
+
+ {
+ // type code here for "relation_one" field
+
+ voucher_code: 'VCH56789',
+
+ issue_date: new Date('2023-11-05T20:00:00Z'),
+ },
];
const LoyaltytierData = [
+ {
+ name: 'John Dalton',
+
+ annualfee: 74.62,
+
+ pointspersar: 51.91,
+
+ description: 'Jean Baptiste Lamarck',
+ },
+
{
name: 'B. F. Skinner',
+
+ annualfee: 10.09,
+
+ pointspersar: 44.45,
+
+ description: 'Werner Heisenberg',
},
{
- name: 'Carl Gauss (Karl Friedrich Gauss)',
+ name: 'Tycho Brahe',
+
+ annualfee: 86.15,
+
+ pointspersar: 10.74,
+
+ description: 'Emil Fischer',
},
{
- name: 'August Kekule',
+ name: 'Francis Crick',
+
+ annualfee: 20.76,
+
+ pointspersar: 72.74,
+
+ description: 'William Bayliss',
},
{
- name: 'James Watson',
+ name: 'Thomas Hunt Morgan',
+
+ annualfee: 97.56,
+
+ pointspersar: 39.08,
+
+ description: 'Johannes Kepler',
+ },
+];
+
+const ReferralData = [
+ {
+ // type code here for "relation_one" field
+
+ referredemail: 'Ernst Haeckel',
+
+ status: 'value',
+ },
+
+ {
+ // type code here for "relation_one" field
+
+ referredemail: 'Heike Kamerlingh Onnes',
+
+ status: 'value',
+ },
+
+ {
+ // type code here for "relation_one" field
+
+ referredemail: 'Robert Koch',
+
+ status: 'value',
+ },
+
+ {
+ // type code here for "relation_one" field
+
+ referredemail: 'Jean Piaget',
+
+ status: 'value',
+ },
+
+ {
+ // type code here for "relation_one" field
+
+ referredemail: 'Jean Baptiste Lamarck',
+
+ status: 'value',
+ },
+];
+
+const RedemptionData = [
+ {
+ // type code here for "relation_one" field
+
+ pointsused: 5,
+
+ redemptiontype: 'value',
+
+ discountvalue: 62.55,
+
+ status: 'value',
+
+ // type code here for "relation_one" field
+ },
+
+ {
+ // type code here for "relation_one" field
+
+ pointsused: 5,
+
+ redemptiontype: 'value',
+
+ discountvalue: 99.45,
+
+ status: 'value',
+
+ // type code here for "relation_one" field
+ },
+
+ {
+ // type code here for "relation_one" field
+
+ pointsused: 8,
+
+ redemptiontype: 'value',
+
+ discountvalue: 29.35,
+
+ status: 'value',
+
+ // type code here for "relation_one" field
+ },
+
+ {
+ // type code here for "relation_one" field
+
+ pointsused: 5,
+
+ redemptiontype: 'value',
+
+ discountvalue: 91.62,
+
+ status: 'value',
+
+ // type code here for "relation_one" field
+ },
+
+ {
+ // type code here for "relation_one" field
+
+ pointsused: 9,
+
+ redemptiontype: 'value',
+
+ discountvalue: 87.53,
+
+ status: 'value',
+
+ // type code here for "relation_one" field
},
];
// Similar logic for "relation_many"
+async function associateUserWithLoyaltytier() {
+ const relatedLoyaltytier0 = await Loyaltytier.findOne({
+ offset: Math.floor(Math.random() * (await Loyaltytier.count())),
+ });
+ const User0 = await Users.findOne({
+ order: [['id', 'ASC']],
+ offset: 0,
+ });
+ if (User0?.setLoyaltytier) {
+ await User0.setLoyaltytier(relatedLoyaltytier0);
+ }
+
+ const relatedLoyaltytier1 = await Loyaltytier.findOne({
+ offset: Math.floor(Math.random() * (await Loyaltytier.count())),
+ });
+ const User1 = await Users.findOne({
+ order: [['id', 'ASC']],
+ offset: 1,
+ });
+ if (User1?.setLoyaltytier) {
+ await User1.setLoyaltytier(relatedLoyaltytier1);
+ }
+
+ const relatedLoyaltytier2 = await Loyaltytier.findOne({
+ offset: Math.floor(Math.random() * (await Loyaltytier.count())),
+ });
+ const User2 = await Users.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (User2?.setLoyaltytier) {
+ await User2.setLoyaltytier(relatedLoyaltytier2);
+ }
+
+ const relatedLoyaltytier3 = await Loyaltytier.findOne({
+ offset: Math.floor(Math.random() * (await Loyaltytier.count())),
+ });
+ const User3 = await Users.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (User3?.setLoyaltytier) {
+ await User3.setLoyaltytier(relatedLoyaltytier3);
+ }
+
+ const relatedLoyaltytier4 = await Loyaltytier.findOne({
+ offset: Math.floor(Math.random() * (await Loyaltytier.count())),
+ });
+ const User4 = await Users.findOne({
+ order: [['id', 'ASC']],
+ offset: 4,
+ });
+ if (User4?.setLoyaltytier) {
+ await User4.setLoyaltytier(relatedLoyaltytier4);
+ }
+}
+
async function associateBookingWithCustomer() {
const relatedCustomer0 = await Customers.findOne({
offset: Math.floor(Math.random() * (await Customers.count())),
@@ -279,6 +536,17 @@ async function associateBookingWithCustomer() {
if (Booking3?.setCustomer) {
await Booking3.setCustomer(relatedCustomer3);
}
+
+ const relatedCustomer4 = await Customers.findOne({
+ offset: Math.floor(Math.random() * (await Customers.count())),
+ });
+ const Booking4 = await Bookings.findOne({
+ order: [['id', 'ASC']],
+ offset: 4,
+ });
+ if (Booking4?.setCustomer) {
+ await Booking4.setCustomer(relatedCustomer4);
+ }
}
async function associateBookingWithAgent() {
@@ -325,6 +593,17 @@ async function associateBookingWithAgent() {
if (Booking3?.setAgent) {
await Booking3.setAgent(relatedAgent3);
}
+
+ const relatedAgent4 = await Agents.findOne({
+ offset: Math.floor(Math.random() * (await Agents.count())),
+ });
+ const Booking4 = await Bookings.findOne({
+ order: [['id', 'ASC']],
+ offset: 4,
+ });
+ if (Booking4?.setAgent) {
+ await Booking4.setAgent(relatedAgent4);
+ }
}
async function associatePaymentWithBooking() {
@@ -371,6 +650,17 @@ async function associatePaymentWithBooking() {
if (Payment3?.setBooking) {
await Payment3.setBooking(relatedBooking3);
}
+
+ const relatedBooking4 = await Bookings.findOne({
+ offset: Math.floor(Math.random() * (await Bookings.count())),
+ });
+ const Payment4 = await Payments.findOne({
+ order: [['id', 'ASC']],
+ offset: 4,
+ });
+ if (Payment4?.setBooking) {
+ await Payment4.setBooking(relatedBooking4);
+ }
}
async function associateVoucherWithBooking() {
@@ -417,6 +707,188 @@ async function associateVoucherWithBooking() {
if (Voucher3?.setBooking) {
await Voucher3.setBooking(relatedBooking3);
}
+
+ const relatedBooking4 = await Bookings.findOne({
+ offset: Math.floor(Math.random() * (await Bookings.count())),
+ });
+ const Voucher4 = await Vouchers.findOne({
+ order: [['id', 'ASC']],
+ offset: 4,
+ });
+ if (Voucher4?.setBooking) {
+ await Voucher4.setBooking(relatedBooking4);
+ }
+}
+
+async function associateReferralWithReferrer() {
+ const relatedReferrer0 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Referral0 = await Referral.findOne({
+ order: [['id', 'ASC']],
+ offset: 0,
+ });
+ if (Referral0?.setReferrer) {
+ await Referral0.setReferrer(relatedReferrer0);
+ }
+
+ const relatedReferrer1 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Referral1 = await Referral.findOne({
+ order: [['id', 'ASC']],
+ offset: 1,
+ });
+ if (Referral1?.setReferrer) {
+ await Referral1.setReferrer(relatedReferrer1);
+ }
+
+ const relatedReferrer2 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Referral2 = await Referral.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (Referral2?.setReferrer) {
+ await Referral2.setReferrer(relatedReferrer2);
+ }
+
+ const relatedReferrer3 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Referral3 = await Referral.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Referral3?.setReferrer) {
+ await Referral3.setReferrer(relatedReferrer3);
+ }
+
+ const relatedReferrer4 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Referral4 = await Referral.findOne({
+ order: [['id', 'ASC']],
+ offset: 4,
+ });
+ if (Referral4?.setReferrer) {
+ await Referral4.setReferrer(relatedReferrer4);
+ }
+}
+
+async function associateRedemptionWithUser() {
+ const relatedUser0 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Redemption0 = await Redemption.findOne({
+ order: [['id', 'ASC']],
+ offset: 0,
+ });
+ if (Redemption0?.setUser) {
+ await Redemption0.setUser(relatedUser0);
+ }
+
+ const relatedUser1 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Redemption1 = await Redemption.findOne({
+ order: [['id', 'ASC']],
+ offset: 1,
+ });
+ if (Redemption1?.setUser) {
+ await Redemption1.setUser(relatedUser1);
+ }
+
+ const relatedUser2 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Redemption2 = await Redemption.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (Redemption2?.setUser) {
+ await Redemption2.setUser(relatedUser2);
+ }
+
+ const relatedUser3 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Redemption3 = await Redemption.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Redemption3?.setUser) {
+ await Redemption3.setUser(relatedUser3);
+ }
+
+ const relatedUser4 = await Users.findOne({
+ offset: Math.floor(Math.random() * (await Users.count())),
+ });
+ const Redemption4 = await Redemption.findOne({
+ order: [['id', 'ASC']],
+ offset: 4,
+ });
+ if (Redemption4?.setUser) {
+ await Redemption4.setUser(relatedUser4);
+ }
+}
+
+async function associateRedemptionWithBooking() {
+ const relatedBooking0 = await Bookings.findOne({
+ offset: Math.floor(Math.random() * (await Bookings.count())),
+ });
+ const Redemption0 = await Redemption.findOne({
+ order: [['id', 'ASC']],
+ offset: 0,
+ });
+ if (Redemption0?.setBooking) {
+ await Redemption0.setBooking(relatedBooking0);
+ }
+
+ const relatedBooking1 = await Bookings.findOne({
+ offset: Math.floor(Math.random() * (await Bookings.count())),
+ });
+ const Redemption1 = await Redemption.findOne({
+ order: [['id', 'ASC']],
+ offset: 1,
+ });
+ if (Redemption1?.setBooking) {
+ await Redemption1.setBooking(relatedBooking1);
+ }
+
+ const relatedBooking2 = await Bookings.findOne({
+ offset: Math.floor(Math.random() * (await Bookings.count())),
+ });
+ const Redemption2 = await Redemption.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (Redemption2?.setBooking) {
+ await Redemption2.setBooking(relatedBooking2);
+ }
+
+ const relatedBooking3 = await Bookings.findOne({
+ offset: Math.floor(Math.random() * (await Bookings.count())),
+ });
+ const Redemption3 = await Redemption.findOne({
+ order: [['id', 'ASC']],
+ offset: 3,
+ });
+ if (Redemption3?.setBooking) {
+ await Redemption3.setBooking(relatedBooking3);
+ }
+
+ const relatedBooking4 = await Bookings.findOne({
+ offset: Math.floor(Math.random() * (await Bookings.count())),
+ });
+ const Redemption4 = await Redemption.findOne({
+ order: [['id', 'ASC']],
+ offset: 4,
+ });
+ if (Redemption4?.setBooking) {
+ await Redemption4.setBooking(relatedBooking4);
+ }
}
module.exports = {
@@ -433,9 +905,15 @@ module.exports = {
await Loyaltytier.bulkCreate(LoyaltytierData);
+ await Referral.bulkCreate(ReferralData);
+
+ await Redemption.bulkCreate(RedemptionData);
+
await Promise.all([
// Similar logic for "relation_many"
+ await associateUserWithLoyaltytier(),
+
await associateBookingWithCustomer(),
await associateBookingWithAgent(),
@@ -443,6 +921,12 @@ module.exports = {
await associatePaymentWithBooking(),
await associateVoucherWithBooking(),
+
+ await associateReferralWithReferrer(),
+
+ await associateRedemptionWithUser(),
+
+ await associateRedemptionWithBooking(),
]);
},
@@ -458,5 +942,9 @@ module.exports = {
await queryInterface.bulkDelete('vouchers', null, {});
await queryInterface.bulkDelete('loyaltytier', null, {});
+
+ await queryInterface.bulkDelete('referral', null, {});
+
+ await queryInterface.bulkDelete('redemption', null, {});
},
};
diff --git a/backend/src/db/seeders/20250524093308.js b/backend/src/db/seeders/20250524093308.js
new file mode 100644
index 0000000..d550eac
--- /dev/null
+++ b/backend/src/db/seeders/20250524093308.js
@@ -0,0 +1,87 @@
+const { v4: uuid } = require('uuid');
+const db = require('../models');
+const Sequelize = require('sequelize');
+const config = require('../../config');
+
+module.exports = {
+ /**
+ * @param{import("sequelize").QueryInterface} queryInterface
+ * @return {Promise}
+ */
+ async up(queryInterface) {
+ const createdAt = new Date();
+ const updatedAt = new Date();
+
+ /** @type {Map} */
+ const idMap = new Map();
+
+ /**
+ * @param {string} key
+ * @return {string}
+ */
+ function getId(key) {
+ if (idMap.has(key)) {
+ return idMap.get(key);
+ }
+ const id = uuid();
+ idMap.set(key, id);
+ return id;
+ }
+
+ /**
+ * @param {string} name
+ */
+ function createPermissions(name) {
+ return [
+ {
+ id: getId(`CREATE_${name.toUpperCase()}`),
+ createdAt,
+ updatedAt,
+ name: `CREATE_${name.toUpperCase()}`,
+ },
+ {
+ id: getId(`READ_${name.toUpperCase()}`),
+ createdAt,
+ updatedAt,
+ name: `READ_${name.toUpperCase()}`,
+ },
+ {
+ id: getId(`UPDATE_${name.toUpperCase()}`),
+ createdAt,
+ updatedAt,
+ name: `UPDATE_${name.toUpperCase()}`,
+ },
+ {
+ id: getId(`DELETE_${name.toUpperCase()}`),
+ createdAt,
+ updatedAt,
+ name: `DELETE_${name.toUpperCase()}`,
+ },
+ ];
+ }
+
+ const entities = ['referral'];
+
+ const createdPermissions = entities.flatMap(createPermissions);
+
+ // Add permissions to database
+ await queryInterface.bulkInsert('permissions', createdPermissions);
+ // Get permissions ids
+ const permissionsIds = createdPermissions.map((p) => p.id);
+ // Get admin role
+ const adminRole = await db.roles.findOne({
+ where: { name: config.roles.admin },
+ });
+
+ if (adminRole) {
+ // Add permissions to admin role if it exists
+ await adminRole.addPermissions(permissionsIds);
+ }
+ },
+ down: async (queryInterface, Sequelize) => {
+ await queryInterface.bulkDelete(
+ 'permissions',
+ entities.flatMap(createPermissions),
+ );
+ },
+};
diff --git a/backend/src/db/seeders/20250524093555.js b/backend/src/db/seeders/20250524093555.js
new file mode 100644
index 0000000..fbd0aac
--- /dev/null
+++ b/backend/src/db/seeders/20250524093555.js
@@ -0,0 +1,87 @@
+const { v4: uuid } = require('uuid');
+const db = require('../models');
+const Sequelize = require('sequelize');
+const config = require('../../config');
+
+module.exports = {
+ /**
+ * @param{import("sequelize").QueryInterface} queryInterface
+ * @return {Promise}
+ */
+ async up(queryInterface) {
+ const createdAt = new Date();
+ const updatedAt = new Date();
+
+ /** @type {Map} */
+ const idMap = new Map();
+
+ /**
+ * @param {string} key
+ * @return {string}
+ */
+ function getId(key) {
+ if (idMap.has(key)) {
+ return idMap.get(key);
+ }
+ const id = uuid();
+ idMap.set(key, id);
+ return id;
+ }
+
+ /**
+ * @param {string} name
+ */
+ function createPermissions(name) {
+ return [
+ {
+ id: getId(`CREATE_${name.toUpperCase()}`),
+ createdAt,
+ updatedAt,
+ name: `CREATE_${name.toUpperCase()}`,
+ },
+ {
+ id: getId(`READ_${name.toUpperCase()}`),
+ createdAt,
+ updatedAt,
+ name: `READ_${name.toUpperCase()}`,
+ },
+ {
+ id: getId(`UPDATE_${name.toUpperCase()}`),
+ createdAt,
+ updatedAt,
+ name: `UPDATE_${name.toUpperCase()}`,
+ },
+ {
+ id: getId(`DELETE_${name.toUpperCase()}`),
+ createdAt,
+ updatedAt,
+ name: `DELETE_${name.toUpperCase()}`,
+ },
+ ];
+ }
+
+ const entities = ['redemption'];
+
+ const createdPermissions = entities.flatMap(createPermissions);
+
+ // Add permissions to database
+ await queryInterface.bulkInsert('permissions', createdPermissions);
+ // Get permissions ids
+ const permissionsIds = createdPermissions.map((p) => p.id);
+ // Get admin role
+ const adminRole = await db.roles.findOne({
+ where: { name: config.roles.admin },
+ });
+
+ if (adminRole) {
+ // Add permissions to admin role if it exists
+ await adminRole.addPermissions(permissionsIds);
+ }
+ },
+ down: async (queryInterface, Sequelize) => {
+ await queryInterface.bulkDelete(
+ 'permissions',
+ entities.flatMap(createPermissions),
+ );
+ },
+};
diff --git a/backend/src/index.js b/backend/src/index.js
index 53f1aa1..f6d1aab 100644
--- a/backend/src/index.js
+++ b/backend/src/index.js
@@ -37,6 +37,10 @@ const permissionsRoutes = require('./routes/permissions');
const loyaltytierRoutes = require('./routes/loyaltytier');
+const referralRoutes = require('./routes/referral');
+
+const redemptionRoutes = require('./routes/redemption');
+
const getBaseUrl = (url) => {
if (!url) return '';
return url.endsWith('/api') ? url.slice(0, -4) : url;
@@ -156,6 +160,18 @@ app.use(
loyaltytierRoutes,
);
+app.use(
+ '/api/referral',
+ passport.authenticate('jwt', { session: false }),
+ referralRoutes,
+);
+
+app.use(
+ '/api/redemption',
+ passport.authenticate('jwt', { session: false }),
+ redemptionRoutes,
+);
+
app.use(
'/api/openai',
passport.authenticate('jwt', { session: false }),
diff --git a/backend/src/routes/loyaltytier.js b/backend/src/routes/loyaltytier.js
index b44680f..4033e0f 100644
--- a/backend/src/routes/loyaltytier.js
+++ b/backend/src/routes/loyaltytier.js
@@ -23,6 +23,16 @@ router.use(checkCrudPermissions('loyaltytier'));
* name:
* type: string
* default: name
+ * description:
+ * type: string
+ * default: description
+
+ * annualfee:
+ * type: integer
+ * format: int64
+ * pointspersar:
+ * type: integer
+ * format: int64
*/
@@ -308,7 +318,7 @@ router.get(
const currentUser = req.currentUser;
const payload = await LoyaltytierDBApi.findAll(req.query, { currentUser });
if (filetype && filetype === 'csv') {
- const fields = ['id', 'name'];
+ const fields = ['id', 'name', 'description', 'annualfee', 'pointspersar'];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
diff --git a/backend/src/routes/redemption.js b/backend/src/routes/redemption.js
new file mode 100644
index 0000000..fe1f63b
--- /dev/null
+++ b/backend/src/routes/redemption.js
@@ -0,0 +1,444 @@
+const express = require('express');
+
+const RedemptionService = require('../services/redemption');
+const RedemptionDBApi = require('../db/api/redemption');
+const wrapAsync = require('../helpers').wrapAsync;
+
+const router = express.Router();
+
+const { parse } = require('json2csv');
+
+const { checkCrudPermissions } = require('../middlewares/check-permissions');
+
+router.use(checkCrudPermissions('redemption'));
+
+/**
+ * @swagger
+ * components:
+ * schemas:
+ * Redemption:
+ * type: object
+ * properties:
+
+ * pointsused:
+ * type: integer
+ * format: int64
+
+ * discountvalue:
+ * type: integer
+ * format: int64
+
+ *
+ *
+ */
+
+/**
+ * @swagger
+ * tags:
+ * name: Redemption
+ * description: The Redemption managing API
+ */
+
+/**
+ * @swagger
+ * /api/redemption:
+ * post:
+ * security:
+ * - bearerAuth: []
+ * tags: [Redemption]
+ * summary: Add new item
+ * description: Add new item
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * properties:
+ * data:
+ * description: Data of the updated item
+ * type: object
+ * $ref: "#/components/schemas/Redemption"
+ * responses:
+ * 200:
+ * description: The item was successfully added
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Redemption"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 405:
+ * description: Invalid input data
+ * 500:
+ * description: Some server error
+ */
+router.post(
+ '/',
+ wrapAsync(async (req, res) => {
+ const referer =
+ req.headers.referer ||
+ `${req.protocol}://${req.hostname}${req.originalUrl}`;
+ const link = new URL(referer);
+ await RedemptionService.create(
+ req.body.data,
+ req.currentUser,
+ true,
+ link.host,
+ );
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/budgets/bulk-import:
+ * post:
+ * security:
+ * - bearerAuth: []
+ * tags: [Redemption]
+ * summary: Bulk import items
+ * description: Bulk import items
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * properties:
+ * data:
+ * description: Data of the updated items
+ * type: array
+ * items:
+ * $ref: "#/components/schemas/Redemption"
+ * responses:
+ * 200:
+ * description: The items were successfully imported
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Redemption"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 405:
+ * description: Invalid input data
+ * 500:
+ * description: Some server error
+ *
+ */
+router.post(
+ '/bulk-import',
+ wrapAsync(async (req, res) => {
+ const referer =
+ req.headers.referer ||
+ `${req.protocol}://${req.hostname}${req.originalUrl}`;
+ const link = new URL(referer);
+ await RedemptionService.bulkImport(req, res, true, link.host);
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/redemption/{id}:
+ * put:
+ * security:
+ * - bearerAuth: []
+ * tags: [Redemption]
+ * summary: Update the data of the selected item
+ * description: Update the data of the selected item
+ * parameters:
+ * - in: path
+ * name: id
+ * description: Item ID to update
+ * required: true
+ * schema:
+ * type: string
+ * requestBody:
+ * description: Set new item data
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * properties:
+ * id:
+ * description: ID of the updated item
+ * type: string
+ * data:
+ * description: Data of the updated item
+ * type: object
+ * $ref: "#/components/schemas/Redemption"
+ * required:
+ * - id
+ * responses:
+ * 200:
+ * description: The item data was successfully updated
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Redemption"
+ * 400:
+ * description: Invalid ID supplied
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Item not found
+ * 500:
+ * description: Some server error
+ */
+router.put(
+ '/:id',
+ wrapAsync(async (req, res) => {
+ await RedemptionService.update(req.body.data, req.body.id, req.currentUser);
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/redemption/{id}:
+ * delete:
+ * security:
+ * - bearerAuth: []
+ * tags: [Redemption]
+ * summary: Delete the selected item
+ * description: Delete the selected item
+ * parameters:
+ * - in: path
+ * name: id
+ * description: Item ID to delete
+ * required: true
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: The item was successfully deleted
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Redemption"
+ * 400:
+ * description: Invalid ID supplied
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Item not found
+ * 500:
+ * description: Some server error
+ */
+router.delete(
+ '/:id',
+ wrapAsync(async (req, res) => {
+ await RedemptionService.remove(req.params.id, req.currentUser);
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/redemption/deleteByIds:
+ * post:
+ * security:
+ * - bearerAuth: []
+ * tags: [Redemption]
+ * summary: Delete the selected item list
+ * description: Delete the selected item list
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * properties:
+ * ids:
+ * description: IDs of the updated items
+ * type: array
+ * responses:
+ * 200:
+ * description: The items was successfully deleted
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Redemption"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Items not found
+ * 500:
+ * description: Some server error
+ */
+router.post(
+ '/deleteByIds',
+ wrapAsync(async (req, res) => {
+ await RedemptionService.deleteByIds(req.body.data, req.currentUser);
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/redemption:
+ * get:
+ * security:
+ * - bearerAuth: []
+ * tags: [Redemption]
+ * summary: Get all redemption
+ * description: Get all redemption
+ * responses:
+ * 200:
+ * description: Redemption list successfully received
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * $ref: "#/components/schemas/Redemption"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Data not found
+ * 500:
+ * description: Some server error
+ */
+router.get(
+ '/',
+ wrapAsync(async (req, res) => {
+ const filetype = req.query.filetype;
+
+ const currentUser = req.currentUser;
+ const payload = await RedemptionDBApi.findAll(req.query, { currentUser });
+ if (filetype && filetype === 'csv') {
+ const fields = ['id', 'pointsused', 'discountvalue'];
+ const opts = { fields };
+ try {
+ const csv = parse(payload.rows, opts);
+ res.status(200).attachment(csv);
+ res.send(csv);
+ } catch (err) {
+ console.error(err);
+ }
+ } else {
+ res.status(200).send(payload);
+ }
+ }),
+);
+
+/**
+ * @swagger
+ * /api/redemption/count:
+ * get:
+ * security:
+ * - bearerAuth: []
+ * tags: [Redemption]
+ * summary: Count all redemption
+ * description: Count all redemption
+ * responses:
+ * 200:
+ * description: Redemption count successfully received
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * $ref: "#/components/schemas/Redemption"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Data not found
+ * 500:
+ * description: Some server error
+ */
+router.get(
+ '/count',
+ wrapAsync(async (req, res) => {
+ const currentUser = req.currentUser;
+ const payload = await RedemptionDBApi.findAll(req.query, null, {
+ countOnly: true,
+ currentUser,
+ });
+
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/redemption/autocomplete:
+ * get:
+ * security:
+ * - bearerAuth: []
+ * tags: [Redemption]
+ * summary: Find all redemption that match search criteria
+ * description: Find all redemption that match search criteria
+ * responses:
+ * 200:
+ * description: Redemption list successfully received
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * $ref: "#/components/schemas/Redemption"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Data not found
+ * 500:
+ * description: Some server error
+ */
+router.get('/autocomplete', async (req, res) => {
+ const payload = await RedemptionDBApi.findAllAutocomplete(
+ req.query.query,
+ req.query.limit,
+ req.query.offset,
+ );
+
+ res.status(200).send(payload);
+});
+
+/**
+ * @swagger
+ * /api/redemption/{id}:
+ * get:
+ * security:
+ * - bearerAuth: []
+ * tags: [Redemption]
+ * summary: Get selected item
+ * description: Get selected item
+ * parameters:
+ * - in: path
+ * name: id
+ * description: ID of item to get
+ * required: true
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: Selected item successfully received
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Redemption"
+ * 400:
+ * description: Invalid ID supplied
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Item not found
+ * 500:
+ * description: Some server error
+ */
+router.get(
+ '/:id',
+ wrapAsync(async (req, res) => {
+ const payload = await RedemptionDBApi.findBy({ id: req.params.id });
+
+ res.status(200).send(payload);
+ }),
+);
+
+router.use('/', require('../helpers').commonErrorHandler);
+
+module.exports = router;
diff --git a/backend/src/routes/referral.js b/backend/src/routes/referral.js
new file mode 100644
index 0000000..97e008c
--- /dev/null
+++ b/backend/src/routes/referral.js
@@ -0,0 +1,439 @@
+const express = require('express');
+
+const ReferralService = require('../services/referral');
+const ReferralDBApi = require('../db/api/referral');
+const wrapAsync = require('../helpers').wrapAsync;
+
+const router = express.Router();
+
+const { parse } = require('json2csv');
+
+const { checkCrudPermissions } = require('../middlewares/check-permissions');
+
+router.use(checkCrudPermissions('referral'));
+
+/**
+ * @swagger
+ * components:
+ * schemas:
+ * Referral:
+ * type: object
+ * properties:
+
+ * referredemail:
+ * type: string
+ * default: referredemail
+
+ *
+ */
+
+/**
+ * @swagger
+ * tags:
+ * name: Referral
+ * description: The Referral managing API
+ */
+
+/**
+ * @swagger
+ * /api/referral:
+ * post:
+ * security:
+ * - bearerAuth: []
+ * tags: [Referral]
+ * summary: Add new item
+ * description: Add new item
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * properties:
+ * data:
+ * description: Data of the updated item
+ * type: object
+ * $ref: "#/components/schemas/Referral"
+ * responses:
+ * 200:
+ * description: The item was successfully added
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Referral"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 405:
+ * description: Invalid input data
+ * 500:
+ * description: Some server error
+ */
+router.post(
+ '/',
+ wrapAsync(async (req, res) => {
+ const referer =
+ req.headers.referer ||
+ `${req.protocol}://${req.hostname}${req.originalUrl}`;
+ const link = new URL(referer);
+ await ReferralService.create(
+ req.body.data,
+ req.currentUser,
+ true,
+ link.host,
+ );
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/budgets/bulk-import:
+ * post:
+ * security:
+ * - bearerAuth: []
+ * tags: [Referral]
+ * summary: Bulk import items
+ * description: Bulk import items
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * properties:
+ * data:
+ * description: Data of the updated items
+ * type: array
+ * items:
+ * $ref: "#/components/schemas/Referral"
+ * responses:
+ * 200:
+ * description: The items were successfully imported
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Referral"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 405:
+ * description: Invalid input data
+ * 500:
+ * description: Some server error
+ *
+ */
+router.post(
+ '/bulk-import',
+ wrapAsync(async (req, res) => {
+ const referer =
+ req.headers.referer ||
+ `${req.protocol}://${req.hostname}${req.originalUrl}`;
+ const link = new URL(referer);
+ await ReferralService.bulkImport(req, res, true, link.host);
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/referral/{id}:
+ * put:
+ * security:
+ * - bearerAuth: []
+ * tags: [Referral]
+ * summary: Update the data of the selected item
+ * description: Update the data of the selected item
+ * parameters:
+ * - in: path
+ * name: id
+ * description: Item ID to update
+ * required: true
+ * schema:
+ * type: string
+ * requestBody:
+ * description: Set new item data
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * properties:
+ * id:
+ * description: ID of the updated item
+ * type: string
+ * data:
+ * description: Data of the updated item
+ * type: object
+ * $ref: "#/components/schemas/Referral"
+ * required:
+ * - id
+ * responses:
+ * 200:
+ * description: The item data was successfully updated
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Referral"
+ * 400:
+ * description: Invalid ID supplied
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Item not found
+ * 500:
+ * description: Some server error
+ */
+router.put(
+ '/:id',
+ wrapAsync(async (req, res) => {
+ await ReferralService.update(req.body.data, req.body.id, req.currentUser);
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/referral/{id}:
+ * delete:
+ * security:
+ * - bearerAuth: []
+ * tags: [Referral]
+ * summary: Delete the selected item
+ * description: Delete the selected item
+ * parameters:
+ * - in: path
+ * name: id
+ * description: Item ID to delete
+ * required: true
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: The item was successfully deleted
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Referral"
+ * 400:
+ * description: Invalid ID supplied
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Item not found
+ * 500:
+ * description: Some server error
+ */
+router.delete(
+ '/:id',
+ wrapAsync(async (req, res) => {
+ await ReferralService.remove(req.params.id, req.currentUser);
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/referral/deleteByIds:
+ * post:
+ * security:
+ * - bearerAuth: []
+ * tags: [Referral]
+ * summary: Delete the selected item list
+ * description: Delete the selected item list
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * properties:
+ * ids:
+ * description: IDs of the updated items
+ * type: array
+ * responses:
+ * 200:
+ * description: The items was successfully deleted
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Referral"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Items not found
+ * 500:
+ * description: Some server error
+ */
+router.post(
+ '/deleteByIds',
+ wrapAsync(async (req, res) => {
+ await ReferralService.deleteByIds(req.body.data, req.currentUser);
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/referral:
+ * get:
+ * security:
+ * - bearerAuth: []
+ * tags: [Referral]
+ * summary: Get all referral
+ * description: Get all referral
+ * responses:
+ * 200:
+ * description: Referral list successfully received
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * $ref: "#/components/schemas/Referral"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Data not found
+ * 500:
+ * description: Some server error
+ */
+router.get(
+ '/',
+ wrapAsync(async (req, res) => {
+ const filetype = req.query.filetype;
+
+ const currentUser = req.currentUser;
+ const payload = await ReferralDBApi.findAll(req.query, { currentUser });
+ if (filetype && filetype === 'csv') {
+ const fields = ['id', 'referredemail'];
+ const opts = { fields };
+ try {
+ const csv = parse(payload.rows, opts);
+ res.status(200).attachment(csv);
+ res.send(csv);
+ } catch (err) {
+ console.error(err);
+ }
+ } else {
+ res.status(200).send(payload);
+ }
+ }),
+);
+
+/**
+ * @swagger
+ * /api/referral/count:
+ * get:
+ * security:
+ * - bearerAuth: []
+ * tags: [Referral]
+ * summary: Count all referral
+ * description: Count all referral
+ * responses:
+ * 200:
+ * description: Referral count successfully received
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * $ref: "#/components/schemas/Referral"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Data not found
+ * 500:
+ * description: Some server error
+ */
+router.get(
+ '/count',
+ wrapAsync(async (req, res) => {
+ const currentUser = req.currentUser;
+ const payload = await ReferralDBApi.findAll(req.query, null, {
+ countOnly: true,
+ currentUser,
+ });
+
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/referral/autocomplete:
+ * get:
+ * security:
+ * - bearerAuth: []
+ * tags: [Referral]
+ * summary: Find all referral that match search criteria
+ * description: Find all referral that match search criteria
+ * responses:
+ * 200:
+ * description: Referral list successfully received
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * $ref: "#/components/schemas/Referral"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Data not found
+ * 500:
+ * description: Some server error
+ */
+router.get('/autocomplete', async (req, res) => {
+ const payload = await ReferralDBApi.findAllAutocomplete(
+ req.query.query,
+ req.query.limit,
+ req.query.offset,
+ );
+
+ res.status(200).send(payload);
+});
+
+/**
+ * @swagger
+ * /api/referral/{id}:
+ * get:
+ * security:
+ * - bearerAuth: []
+ * tags: [Referral]
+ * summary: Get selected item
+ * description: Get selected item
+ * parameters:
+ * - in: path
+ * name: id
+ * description: ID of item to get
+ * required: true
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: Selected item successfully received
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Referral"
+ * 400:
+ * description: Invalid ID supplied
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Item not found
+ * 500:
+ * description: Some server error
+ */
+router.get(
+ '/:id',
+ wrapAsync(async (req, res) => {
+ const payload = await ReferralDBApi.findBy({ id: req.params.id });
+
+ res.status(200).send(payload);
+ }),
+);
+
+router.use('/', require('../helpers').commonErrorHandler);
+
+module.exports = router;
diff --git a/backend/src/routes/users.js b/backend/src/routes/users.js
index a6d214f..f1bc61f 100644
--- a/backend/src/routes/users.js
+++ b/backend/src/routes/users.js
@@ -32,6 +32,13 @@ router.use(checkCrudPermissions('users'));
* email:
* type: string
* default: email
+ * referralcode:
+ * type: string
+ * default: referralcode
+
+ * pointsbalance:
+ * type: integer
+ * format: int64
*/
@@ -308,7 +315,15 @@ router.get(
const currentUser = req.currentUser;
const payload = await UsersDBApi.findAll(req.query, { currentUser });
if (filetype && filetype === 'csv') {
- const fields = ['id', 'firstName', 'lastName', 'phoneNumber', 'email'];
+ const fields = [
+ 'id',
+ 'firstName',
+ 'lastName',
+ 'phoneNumber',
+ 'email',
+ 'referralcode',
+ 'pointsbalance',
+ ];
const opts = { fields };
try {
const csv = parse(payload.rows, opts);
diff --git a/backend/src/services/redemption.js b/backend/src/services/redemption.js
new file mode 100644
index 0000000..c18a1c6
--- /dev/null
+++ b/backend/src/services/redemption.js
@@ -0,0 +1,114 @@
+const db = require('../db/models');
+const RedemptionDBApi = require('../db/api/redemption');
+const processFile = require('../middlewares/upload');
+const ValidationError = require('./notifications/errors/validation');
+const csv = require('csv-parser');
+const axios = require('axios');
+const config = require('../config');
+const stream = require('stream');
+
+module.exports = class RedemptionService {
+ static async create(data, currentUser) {
+ const transaction = await db.sequelize.transaction();
+ try {
+ await RedemptionDBApi.create(data, {
+ currentUser,
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+
+ static async bulkImport(req, res, sendInvitationEmails = true, host) {
+ const transaction = await db.sequelize.transaction();
+
+ try {
+ await processFile(req, res);
+ const bufferStream = new stream.PassThrough();
+ const results = [];
+
+ await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream
+
+ await new Promise((resolve, reject) => {
+ bufferStream
+ .pipe(csv())
+ .on('data', (data) => results.push(data))
+ .on('end', async () => {
+ console.log('CSV results', results);
+ resolve();
+ })
+ .on('error', (error) => reject(error));
+ });
+
+ await RedemptionDBApi.bulkImport(results, {
+ transaction,
+ ignoreDuplicates: true,
+ validate: true,
+ currentUser: req.currentUser,
+ });
+
+ await transaction.commit();
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+
+ static async update(data, id, currentUser) {
+ const transaction = await db.sequelize.transaction();
+ try {
+ let redemption = await RedemptionDBApi.findBy({ id }, { transaction });
+
+ if (!redemption) {
+ throw new ValidationError('redemptionNotFound');
+ }
+
+ const updatedRedemption = await RedemptionDBApi.update(id, data, {
+ currentUser,
+ transaction,
+ });
+
+ await transaction.commit();
+ return updatedRedemption;
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+
+ static async deleteByIds(ids, currentUser) {
+ const transaction = await db.sequelize.transaction();
+
+ try {
+ await RedemptionDBApi.deleteByIds(ids, {
+ currentUser,
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+
+ static async remove(id, currentUser) {
+ const transaction = await db.sequelize.transaction();
+
+ try {
+ await RedemptionDBApi.remove(id, {
+ currentUser,
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+};
diff --git a/backend/src/services/referral.js b/backend/src/services/referral.js
new file mode 100644
index 0000000..09ecc3e
--- /dev/null
+++ b/backend/src/services/referral.js
@@ -0,0 +1,114 @@
+const db = require('../db/models');
+const ReferralDBApi = require('../db/api/referral');
+const processFile = require('../middlewares/upload');
+const ValidationError = require('./notifications/errors/validation');
+const csv = require('csv-parser');
+const axios = require('axios');
+const config = require('../config');
+const stream = require('stream');
+
+module.exports = class ReferralService {
+ static async create(data, currentUser) {
+ const transaction = await db.sequelize.transaction();
+ try {
+ await ReferralDBApi.create(data, {
+ currentUser,
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+
+ static async bulkImport(req, res, sendInvitationEmails = true, host) {
+ const transaction = await db.sequelize.transaction();
+
+ try {
+ await processFile(req, res);
+ const bufferStream = new stream.PassThrough();
+ const results = [];
+
+ await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream
+
+ await new Promise((resolve, reject) => {
+ bufferStream
+ .pipe(csv())
+ .on('data', (data) => results.push(data))
+ .on('end', async () => {
+ console.log('CSV results', results);
+ resolve();
+ })
+ .on('error', (error) => reject(error));
+ });
+
+ await ReferralDBApi.bulkImport(results, {
+ transaction,
+ ignoreDuplicates: true,
+ validate: true,
+ currentUser: req.currentUser,
+ });
+
+ await transaction.commit();
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+
+ static async update(data, id, currentUser) {
+ const transaction = await db.sequelize.transaction();
+ try {
+ let referral = await ReferralDBApi.findBy({ id }, { transaction });
+
+ if (!referral) {
+ throw new ValidationError('referralNotFound');
+ }
+
+ const updatedReferral = await ReferralDBApi.update(id, data, {
+ currentUser,
+ transaction,
+ });
+
+ await transaction.commit();
+ return updatedReferral;
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+
+ static async deleteByIds(ids, currentUser) {
+ const transaction = await db.sequelize.transaction();
+
+ try {
+ await ReferralDBApi.deleteByIds(ids, {
+ currentUser,
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+
+ static async remove(id, currentUser) {
+ const transaction = await db.sequelize.transaction();
+
+ try {
+ await ReferralDBApi.remove(id, {
+ currentUser,
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+};
diff --git a/backend/src/services/search.js b/backend/src/services/search.js
index 5493e43..595c552 100644
--- a/backend/src/services/search.js
+++ b/backend/src/services/search.js
@@ -41,7 +41,17 @@ module.exports = class SearchService {
throw new ValidationError('iam.errors.searchQueryRequired');
}
const tableColumns = {
- users: ['firstName', 'lastName', 'phoneNumber', 'email'],
+ users: [
+ 'firstName',
+
+ 'lastName',
+
+ 'phoneNumber',
+
+ 'email',
+
+ 'referralcode',
+ ],
agents: ['name', 'email', 'phone_number'],
@@ -49,12 +59,20 @@ module.exports = class SearchService {
vouchers: ['voucher_code'],
- loyaltytier: ['name'],
+ loyaltytier: ['name', 'description'],
+
+ referral: ['referredemail'],
};
const columnsInt = {
+ users: ['pointsbalance'],
+
bookings: ['total_amount'],
payments: ['amount'],
+
+ loyaltytier: ['annualfee', 'pointspersar'],
+
+ redemption: ['pointsused', 'discountvalue'],
};
let allFoundRecords = [];
diff --git a/frontend/src/components/Loyaltytier/CardLoyaltytier.tsx b/frontend/src/components/Loyaltytier/CardLoyaltytier.tsx
index 1a82b54..dfa34a1 100644
--- a/frontend/src/components/Loyaltytier/CardLoyaltytier.tsx
+++ b/frontend/src/components/Loyaltytier/CardLoyaltytier.tsx
@@ -82,6 +82,39 @@ const CardLoyaltytier = ({
{item.name}
+
+
+
+ Annualfee
+
+
+
+ {item.annualfee}
+
+
+
+
+
+
+ Pointspersar
+
+
+
+ {item.pointspersar}
+
+
+
+
+
+
+ Description
+
+
+
+ {item.description}
+
+
+
))}
diff --git a/frontend/src/components/Loyaltytier/ListLoyaltytier.tsx b/frontend/src/components/Loyaltytier/ListLoyaltytier.tsx
index 9974953..9598b39 100644
--- a/frontend/src/components/Loyaltytier/ListLoyaltytier.tsx
+++ b/frontend/src/components/Loyaltytier/ListLoyaltytier.tsx
@@ -57,6 +57,21 @@ const ListLoyaltytier = ({
Name
{item.name}
+
+
+
Annualfee
+
{item.annualfee}
+
+
+
+
Pointspersar
+
{item.pointspersar}
+
+
+
+
Description
+
{item.description}
+
void;
+ currentPage: number;
+ numPages: number;
+ onPageChange: (page: number) => void;
+};
+
+const CardRedemption = ({
+ redemption,
+ loading,
+ onDelete,
+ currentPage,
+ numPages,
+ onPageChange,
+}: Props) => {
+ const asideScrollbarsStyle = useAppSelector(
+ (state) => state.style.asideScrollbarsStyle,
+ );
+ const bgColor = useAppSelector((state) => state.style.cardsColor);
+ const darkMode = useAppSelector((state) => state.style.darkMode);
+ const corners = useAppSelector((state) => state.style.corners);
+ const focusRing = useAppSelector((state) => state.style.focusRingColor);
+
+ const currentUser = useAppSelector((state) => state.auth.currentUser);
+ const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_REDEMPTION');
+
+ return (
+
+ {loading &&
}
+
+ {!loading &&
+ redemption.map((item, index) => (
+ -
+
+
+ {item.id}
+
+
+
+
+
+
+
+
+
- User
+
-
+
+ {dataFormatter.usersOneListFormatter(item.user)}
+
+
+
+
+
+
-
+ Pointsused
+
+
-
+
+ {item.pointsused}
+
+
+
+
+
+
-
+ Redemptiontype
+
+
-
+
+ {item.redemptiontype}
+
+
+
+
+
+
-
+ Discountvalue
+
+
-
+
+ {item.discountvalue}
+
+
+
+
+
+
-
+ Status
+
+
-
+
+ {item.status}
+
+
+
+
+
+
-
+ Booking
+
+
-
+
+ {dataFormatter.bookingsOneListFormatter(item.booking)}
+
+
+
+
+
+ ))}
+ {!loading && redemption.length === 0 && (
+
+ )}
+
+
+
+ );
+};
+
+export default CardRedemption;
diff --git a/frontend/src/components/Redemption/ListRedemption.tsx b/frontend/src/components/Redemption/ListRedemption.tsx
new file mode 100644
index 0000000..330d989
--- /dev/null
+++ b/frontend/src/components/Redemption/ListRedemption.tsx
@@ -0,0 +1,122 @@
+import React from 'react';
+import CardBox from '../CardBox';
+import ImageField from '../ImageField';
+import dataFormatter from '../../helpers/dataFormatter';
+import { saveFile } from '../../helpers/fileSaver';
+import ListActionsPopover from '../ListActionsPopover';
+import { useAppSelector } from '../../stores/hooks';
+import { Pagination } from '../Pagination';
+import LoadingSpinner from '../LoadingSpinner';
+import Link from 'next/link';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+type Props = {
+ redemption: any[];
+ loading: boolean;
+ onDelete: (id: string) => void;
+ currentPage: number;
+ numPages: number;
+ onPageChange: (page: number) => void;
+};
+
+const ListRedemption = ({
+ redemption,
+ loading,
+ onDelete,
+ currentPage,
+ numPages,
+ onPageChange,
+}: Props) => {
+ const currentUser = useAppSelector((state) => state.auth.currentUser);
+ const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_REDEMPTION');
+
+ const corners = useAppSelector((state) => state.style.corners);
+ const bgColor = useAppSelector((state) => state.style.cardsColor);
+
+ return (
+ <>
+
+ {loading &&
}
+ {!loading &&
+ redemption.map((item) => (
+
+
+
+
dark:divide-dark-700 overflow-x-auto'
+ }
+ >
+
+
User
+
+ {dataFormatter.usersOneListFormatter(item.user)}
+
+
+
+
+
Pointsused
+
{item.pointsused}
+
+
+
+
+ Redemptiontype
+
+
{item.redemptiontype}
+
+
+
+
+ Discountvalue
+
+
{item.discountvalue}
+
+
+
+
Status
+
{item.status}
+
+
+
+
Booking
+
+ {dataFormatter.bookingsOneListFormatter(item.booking)}
+
+
+
+
+
+
+
+ ))}
+ {!loading && redemption.length === 0 && (
+
+ )}
+
+
+ >
+ );
+};
+
+export default ListRedemption;
diff --git a/frontend/src/components/Redemption/TableRedemption.tsx b/frontend/src/components/Redemption/TableRedemption.tsx
new file mode 100644
index 0000000..7fcc7f5
--- /dev/null
+++ b/frontend/src/components/Redemption/TableRedemption.tsx
@@ -0,0 +1,487 @@
+import React, { useEffect, useState, useMemo } from 'react';
+import { createPortal } from 'react-dom';
+import { ToastContainer, toast } from 'react-toastify';
+import BaseButton from '../BaseButton';
+import CardBoxModal from '../CardBoxModal';
+import CardBox from '../CardBox';
+import {
+ fetch,
+ update,
+ deleteItem,
+ setRefetch,
+ deleteItemsByIds,
+} from '../../stores/redemption/redemptionSlice';
+import { useAppDispatch, useAppSelector } from '../../stores/hooks';
+import { useRouter } from 'next/router';
+import { Field, Form, Formik } from 'formik';
+import { DataGrid, GridColDef } from '@mui/x-data-grid';
+import { loadColumns } from './configureRedemptionCols';
+import _ from 'lodash';
+import dataFormatter from '../../helpers/dataFormatter';
+import { dataGridStyles } from '../../styles';
+
+const perPage = 10;
+
+const TableSampleRedemption = ({
+ filterItems,
+ setFilterItems,
+ filters,
+ showGrid,
+}) => {
+ const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' });
+
+ const dispatch = useAppDispatch();
+ const router = useRouter();
+
+ const pagesList = [];
+ const [id, setId] = useState(null);
+ const [currentPage, setCurrentPage] = useState(0);
+ const [filterRequest, setFilterRequest] = React.useState('');
+ const [columns, setColumns] = useState([]);
+ const [selectedRows, setSelectedRows] = useState([]);
+ const [sortModel, setSortModel] = useState([
+ {
+ field: '',
+ sort: 'desc',
+ },
+ ]);
+
+ const {
+ redemption,
+ loading,
+ count,
+ notify: redemptionNotify,
+ refetch,
+ } = useAppSelector((state) => state.redemption);
+ const { currentUser } = useAppSelector((state) => state.auth);
+ const focusRing = useAppSelector((state) => state.style.focusRingColor);
+ const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
+ const corners = useAppSelector((state) => state.style.corners);
+ const numPages =
+ Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage);
+ for (let i = 0; i < numPages; i++) {
+ pagesList.push(i);
+ }
+
+ const loadData = async (page = currentPage, request = filterRequest) => {
+ if (page !== currentPage) setCurrentPage(page);
+ if (request !== filterRequest) setFilterRequest(request);
+ const { sort, field } = sortModel[0];
+
+ const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`;
+ dispatch(fetch({ limit: perPage, page, query }));
+ };
+
+ useEffect(() => {
+ if (redemptionNotify.showNotification) {
+ notify(
+ redemptionNotify.typeNotification,
+ redemptionNotify.textNotification,
+ );
+ }
+ }, [redemptionNotify.showNotification]);
+
+ useEffect(() => {
+ if (!currentUser) return;
+ loadData();
+ }, [sortModel, currentUser]);
+
+ useEffect(() => {
+ if (refetch) {
+ loadData(0);
+ dispatch(setRefetch(false));
+ }
+ }, [refetch, dispatch]);
+
+ const [isModalInfoActive, setIsModalInfoActive] = useState(false);
+ const [isModalTrashActive, setIsModalTrashActive] = useState(false);
+
+ const handleModalAction = () => {
+ setIsModalInfoActive(false);
+ setIsModalTrashActive(false);
+ };
+
+ const handleDeleteModalAction = (id: string) => {
+ setId(id);
+ setIsModalTrashActive(true);
+ };
+ const handleDeleteAction = async () => {
+ if (id) {
+ await dispatch(deleteItem(id));
+ await loadData(0);
+ setIsModalTrashActive(false);
+ }
+ };
+
+ const generateFilterRequests = useMemo(() => {
+ let request = '&';
+ filterItems.forEach((item) => {
+ const isRangeFilter = filters.find(
+ (filter) =>
+ filter.title === item.fields.selectedField &&
+ (filter.number || filter.date),
+ );
+
+ if (isRangeFilter) {
+ const from = item.fields.filterValueFrom;
+ const to = item.fields.filterValueTo;
+ if (from) {
+ request += `${item.fields.selectedField}Range=${from}&`;
+ }
+ if (to) {
+ request += `${item.fields.selectedField}Range=${to}&`;
+ }
+ } else {
+ const value = item.fields.filterValue;
+ if (value) {
+ request += `${item.fields.selectedField}=${value}&`;
+ }
+ }
+ });
+ return request;
+ }, [filterItems, filters]);
+
+ const deleteFilter = (value) => {
+ const newItems = filterItems.filter((item) => item.id !== value);
+
+ if (newItems.length) {
+ setFilterItems(newItems);
+ } else {
+ loadData(0, '');
+
+ setFilterItems(newItems);
+ }
+ };
+
+ const handleSubmit = () => {
+ loadData(0, generateFilterRequests);
+ };
+
+ const handleChange = (id) => (e) => {
+ const value = e.target.value;
+ const name = e.target.name;
+
+ setFilterItems(
+ filterItems.map((item) => {
+ if (item.id !== id) return item;
+ if (name === 'selectedField') return { id, fields: { [name]: value } };
+
+ return { id, fields: { ...item.fields, [name]: value } };
+ }),
+ );
+ };
+
+ const handleReset = () => {
+ setFilterItems([]);
+ loadData(0, '');
+ };
+
+ const onPageChange = (page: number) => {
+ loadData(page);
+ setCurrentPage(page);
+ };
+
+ useEffect(() => {
+ if (!currentUser) return;
+
+ loadColumns(handleDeleteModalAction, `redemption`, currentUser).then(
+ (newCols) => setColumns(newCols),
+ );
+ }, [currentUser]);
+
+ const handleTableSubmit = async (id: string, data) => {
+ if (!_.isEmpty(data)) {
+ await dispatch(update({ id, data }))
+ .unwrap()
+ .then((res) => res)
+ .catch((err) => {
+ throw new Error(err);
+ });
+ }
+ };
+
+ const onDeleteRows = async (selectedRows) => {
+ await dispatch(deleteItemsByIds(selectedRows));
+ await loadData(0);
+ };
+
+ const controlClasses =
+ 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' +
+ ` ${bgColor} ${focusRing} ${corners} ` +
+ 'dark:bg-slate-800 border';
+
+ const dataGrid = (
+
+ `datagrid--row`}
+ rows={redemption ?? []}
+ columns={columns}
+ initialState={{
+ pagination: {
+ paginationModel: {
+ pageSize: 10,
+ },
+ },
+ }}
+ disableRowSelectionOnClick
+ onProcessRowUpdateError={(params) => {
+ console.log('Error', params);
+ }}
+ processRowUpdate={async (newRow, oldRow) => {
+ const data = dataFormatter.dataGridEditFormatter(newRow);
+
+ try {
+ await handleTableSubmit(newRow.id, data);
+ return newRow;
+ } catch {
+ return oldRow;
+ }
+ }}
+ sortingMode={'server'}
+ checkboxSelection
+ onRowSelectionModelChange={(ids) => {
+ setSelectedRows(ids);
+ }}
+ onSortModelChange={(params) => {
+ params.length
+ ? setSortModel(params)
+ : setSortModel([{ field: '', sort: 'desc' }]);
+ }}
+ rowCount={count}
+ pageSizeOptions={[10]}
+ paginationMode={'server'}
+ loading={loading}
+ onPaginationModelChange={(params) => {
+ onPageChange(params.page);
+ }}
+ />
+
+ );
+
+ return (
+ <>
+ {filterItems && Array.isArray(filterItems) && filterItems.length ? (
+
+ null}
+ >
+
+
+
+ ) : null}
+
+ Are you sure you want to delete this item?
+
+
+ {dataGrid}
+
+ {selectedRows.length > 0 &&
+ createPortal(
+ onDeleteRows(selectedRows)}
+ />,
+ document.getElementById('delete-rows-button'),
+ )}
+
+ >
+ );
+};
+
+export default TableSampleRedemption;
diff --git a/frontend/src/components/Redemption/configureRedemptionCols.tsx b/frontend/src/components/Redemption/configureRedemptionCols.tsx
new file mode 100644
index 0000000..7bf49eb
--- /dev/null
+++ b/frontend/src/components/Redemption/configureRedemptionCols.tsx
@@ -0,0 +1,160 @@
+import React from 'react';
+import BaseIcon from '../BaseIcon';
+import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
+import axios from 'axios';
+import {
+ GridActionsCellItem,
+ GridRowParams,
+ GridValueGetterParams,
+} from '@mui/x-data-grid';
+import ImageField from '../ImageField';
+import { saveFile } from '../../helpers/fileSaver';
+import dataFormatter from '../../helpers/dataFormatter';
+import DataGridMultiSelect from '../DataGridMultiSelect';
+import ListActionsPopover from '../ListActionsPopover';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+type Params = (id: string) => void;
+
+export const loadColumns = async (
+ onDelete: Params,
+ entityName: string,
+
+ user,
+) => {
+ async function callOptionsApi(entityName: string) {
+ if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
+
+ try {
+ const data = await axios(`/${entityName}/autocomplete?limit=100`);
+ return data.data;
+ } catch (error) {
+ console.log(error);
+ return [];
+ }
+ }
+
+ const hasUpdatePermission = hasPermission(user, 'UPDATE_REDEMPTION');
+
+ return [
+ {
+ field: 'user',
+ headerName: 'User',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ sortable: false,
+ type: 'singleSelect',
+ getOptionValue: (value: any) => value?.id,
+ getOptionLabel: (value: any) => value?.label,
+ valueOptions: await callOptionsApi('users'),
+ valueGetter: (params: GridValueGetterParams) =>
+ params?.value?.id ?? params?.value,
+ },
+
+ {
+ field: 'pointsused',
+ headerName: 'Pointsused',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'number',
+ },
+
+ {
+ field: 'redemptiontype',
+ headerName: 'Redemptiontype',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'singleSelect',
+ valueOptions: ['value'],
+ },
+
+ {
+ field: 'discountvalue',
+ headerName: 'Discountvalue',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'number',
+ },
+
+ {
+ field: 'status',
+ headerName: 'Status',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'singleSelect',
+ valueOptions: ['value'],
+ },
+
+ {
+ field: 'booking',
+ headerName: 'Booking',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ sortable: false,
+ type: 'singleSelect',
+ getOptionValue: (value: any) => value?.id,
+ getOptionLabel: (value: any) => value?.label,
+ valueOptions: await callOptionsApi('bookings'),
+ valueGetter: (params: GridValueGetterParams) =>
+ params?.value?.id ?? params?.value,
+ },
+
+ {
+ field: 'actions',
+ type: 'actions',
+ minWidth: 30,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+ getActions: (params: GridRowParams) => {
+ return [
+
+
+
,
+ ];
+ },
+ },
+ ];
+};
diff --git a/frontend/src/components/Referral/CardReferral.tsx b/frontend/src/components/Referral/CardReferral.tsx
new file mode 100644
index 0000000..e71b95b
--- /dev/null
+++ b/frontend/src/components/Referral/CardReferral.tsx
@@ -0,0 +1,131 @@
+import React from 'react';
+import ImageField from '../ImageField';
+import ListActionsPopover from '../ListActionsPopover';
+import { useAppSelector } from '../../stores/hooks';
+import dataFormatter from '../../helpers/dataFormatter';
+import { Pagination } from '../Pagination';
+import { saveFile } from '../../helpers/fileSaver';
+import LoadingSpinner from '../LoadingSpinner';
+import Link from 'next/link';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+type Props = {
+ referral: any[];
+ loading: boolean;
+ onDelete: (id: string) => void;
+ currentPage: number;
+ numPages: number;
+ onPageChange: (page: number) => void;
+};
+
+const CardReferral = ({
+ referral,
+ loading,
+ onDelete,
+ currentPage,
+ numPages,
+ onPageChange,
+}: Props) => {
+ const asideScrollbarsStyle = useAppSelector(
+ (state) => state.style.asideScrollbarsStyle,
+ );
+ const bgColor = useAppSelector((state) => state.style.cardsColor);
+ const darkMode = useAppSelector((state) => state.style.darkMode);
+ const corners = useAppSelector((state) => state.style.corners);
+ const focusRing = useAppSelector((state) => state.style.focusRingColor);
+
+ const currentUser = useAppSelector((state) => state.auth.currentUser);
+ const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_REFERRAL');
+
+ return (
+
+ {loading &&
}
+
+ {!loading &&
+ referral.map((item, index) => (
+ -
+
+
+ {item.id}
+
+
+
+
+
+
+
+
+
-
+ Referrer
+
+
-
+
+ {dataFormatter.usersOneListFormatter(item.referrer)}
+
+
+
+
+
+
-
+ Referredemail
+
+
-
+
+ {item.referredemail}
+
+
+
+
+
+
-
+ Status
+
+
-
+
+ {item.status}
+
+
+
+
+
+ ))}
+ {!loading && referral.length === 0 && (
+
+ )}
+
+
+
+ );
+};
+
+export default CardReferral;
diff --git a/frontend/src/components/Referral/ListReferral.tsx b/frontend/src/components/Referral/ListReferral.tsx
new file mode 100644
index 0000000..4df1e9a
--- /dev/null
+++ b/frontend/src/components/Referral/ListReferral.tsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import CardBox from '../CardBox';
+import ImageField from '../ImageField';
+import dataFormatter from '../../helpers/dataFormatter';
+import { saveFile } from '../../helpers/fileSaver';
+import ListActionsPopover from '../ListActionsPopover';
+import { useAppSelector } from '../../stores/hooks';
+import { Pagination } from '../Pagination';
+import LoadingSpinner from '../LoadingSpinner';
+import Link from 'next/link';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+type Props = {
+ referral: any[];
+ loading: boolean;
+ onDelete: (id: string) => void;
+ currentPage: number;
+ numPages: number;
+ onPageChange: (page: number) => void;
+};
+
+const ListReferral = ({
+ referral,
+ loading,
+ onDelete,
+ currentPage,
+ numPages,
+ onPageChange,
+}: Props) => {
+ const currentUser = useAppSelector((state) => state.auth.currentUser);
+ const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_REFERRAL');
+
+ const corners = useAppSelector((state) => state.style.corners);
+ const bgColor = useAppSelector((state) => state.style.cardsColor);
+
+ return (
+ <>
+
+ {loading &&
}
+ {!loading &&
+ referral.map((item) => (
+
+
+
+
dark:divide-dark-700 overflow-x-auto'
+ }
+ >
+
+
Referrer
+
+ {dataFormatter.usersOneListFormatter(item.referrer)}
+
+
+
+
+
+ Referredemail
+
+
{item.referredemail}
+
+
+
+
Status
+
{item.status}
+
+
+
+
+
+
+ ))}
+ {!loading && referral.length === 0 && (
+
+ )}
+
+
+ >
+ );
+};
+
+export default ListReferral;
diff --git a/frontend/src/components/Referral/TableReferral.tsx b/frontend/src/components/Referral/TableReferral.tsx
new file mode 100644
index 0000000..6eff4c8
--- /dev/null
+++ b/frontend/src/components/Referral/TableReferral.tsx
@@ -0,0 +1,484 @@
+import React, { useEffect, useState, useMemo } from 'react';
+import { createPortal } from 'react-dom';
+import { ToastContainer, toast } from 'react-toastify';
+import BaseButton from '../BaseButton';
+import CardBoxModal from '../CardBoxModal';
+import CardBox from '../CardBox';
+import {
+ fetch,
+ update,
+ deleteItem,
+ setRefetch,
+ deleteItemsByIds,
+} from '../../stores/referral/referralSlice';
+import { useAppDispatch, useAppSelector } from '../../stores/hooks';
+import { useRouter } from 'next/router';
+import { Field, Form, Formik } from 'formik';
+import { DataGrid, GridColDef } from '@mui/x-data-grid';
+import { loadColumns } from './configureReferralCols';
+import _ from 'lodash';
+import dataFormatter from '../../helpers/dataFormatter';
+import { dataGridStyles } from '../../styles';
+
+const perPage = 10;
+
+const TableSampleReferral = ({
+ filterItems,
+ setFilterItems,
+ filters,
+ showGrid,
+}) => {
+ const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' });
+
+ const dispatch = useAppDispatch();
+ const router = useRouter();
+
+ const pagesList = [];
+ const [id, setId] = useState(null);
+ const [currentPage, setCurrentPage] = useState(0);
+ const [filterRequest, setFilterRequest] = React.useState('');
+ const [columns, setColumns] = useState([]);
+ const [selectedRows, setSelectedRows] = useState([]);
+ const [sortModel, setSortModel] = useState([
+ {
+ field: '',
+ sort: 'desc',
+ },
+ ]);
+
+ const {
+ referral,
+ loading,
+ count,
+ notify: referralNotify,
+ refetch,
+ } = useAppSelector((state) => state.referral);
+ const { currentUser } = useAppSelector((state) => state.auth);
+ const focusRing = useAppSelector((state) => state.style.focusRingColor);
+ const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
+ const corners = useAppSelector((state) => state.style.corners);
+ const numPages =
+ Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage);
+ for (let i = 0; i < numPages; i++) {
+ pagesList.push(i);
+ }
+
+ const loadData = async (page = currentPage, request = filterRequest) => {
+ if (page !== currentPage) setCurrentPage(page);
+ if (request !== filterRequest) setFilterRequest(request);
+ const { sort, field } = sortModel[0];
+
+ const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`;
+ dispatch(fetch({ limit: perPage, page, query }));
+ };
+
+ useEffect(() => {
+ if (referralNotify.showNotification) {
+ notify(referralNotify.typeNotification, referralNotify.textNotification);
+ }
+ }, [referralNotify.showNotification]);
+
+ useEffect(() => {
+ if (!currentUser) return;
+ loadData();
+ }, [sortModel, currentUser]);
+
+ useEffect(() => {
+ if (refetch) {
+ loadData(0);
+ dispatch(setRefetch(false));
+ }
+ }, [refetch, dispatch]);
+
+ const [isModalInfoActive, setIsModalInfoActive] = useState(false);
+ const [isModalTrashActive, setIsModalTrashActive] = useState(false);
+
+ const handleModalAction = () => {
+ setIsModalInfoActive(false);
+ setIsModalTrashActive(false);
+ };
+
+ const handleDeleteModalAction = (id: string) => {
+ setId(id);
+ setIsModalTrashActive(true);
+ };
+ const handleDeleteAction = async () => {
+ if (id) {
+ await dispatch(deleteItem(id));
+ await loadData(0);
+ setIsModalTrashActive(false);
+ }
+ };
+
+ const generateFilterRequests = useMemo(() => {
+ let request = '&';
+ filterItems.forEach((item) => {
+ const isRangeFilter = filters.find(
+ (filter) =>
+ filter.title === item.fields.selectedField &&
+ (filter.number || filter.date),
+ );
+
+ if (isRangeFilter) {
+ const from = item.fields.filterValueFrom;
+ const to = item.fields.filterValueTo;
+ if (from) {
+ request += `${item.fields.selectedField}Range=${from}&`;
+ }
+ if (to) {
+ request += `${item.fields.selectedField}Range=${to}&`;
+ }
+ } else {
+ const value = item.fields.filterValue;
+ if (value) {
+ request += `${item.fields.selectedField}=${value}&`;
+ }
+ }
+ });
+ return request;
+ }, [filterItems, filters]);
+
+ const deleteFilter = (value) => {
+ const newItems = filterItems.filter((item) => item.id !== value);
+
+ if (newItems.length) {
+ setFilterItems(newItems);
+ } else {
+ loadData(0, '');
+
+ setFilterItems(newItems);
+ }
+ };
+
+ const handleSubmit = () => {
+ loadData(0, generateFilterRequests);
+ };
+
+ const handleChange = (id) => (e) => {
+ const value = e.target.value;
+ const name = e.target.name;
+
+ setFilterItems(
+ filterItems.map((item) => {
+ if (item.id !== id) return item;
+ if (name === 'selectedField') return { id, fields: { [name]: value } };
+
+ return { id, fields: { ...item.fields, [name]: value } };
+ }),
+ );
+ };
+
+ const handleReset = () => {
+ setFilterItems([]);
+ loadData(0, '');
+ };
+
+ const onPageChange = (page: number) => {
+ loadData(page);
+ setCurrentPage(page);
+ };
+
+ useEffect(() => {
+ if (!currentUser) return;
+
+ loadColumns(handleDeleteModalAction, `referral`, currentUser).then(
+ (newCols) => setColumns(newCols),
+ );
+ }, [currentUser]);
+
+ const handleTableSubmit = async (id: string, data) => {
+ if (!_.isEmpty(data)) {
+ await dispatch(update({ id, data }))
+ .unwrap()
+ .then((res) => res)
+ .catch((err) => {
+ throw new Error(err);
+ });
+ }
+ };
+
+ const onDeleteRows = async (selectedRows) => {
+ await dispatch(deleteItemsByIds(selectedRows));
+ await loadData(0);
+ };
+
+ const controlClasses =
+ 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' +
+ ` ${bgColor} ${focusRing} ${corners} ` +
+ 'dark:bg-slate-800 border';
+
+ const dataGrid = (
+
+ `datagrid--row`}
+ rows={referral ?? []}
+ columns={columns}
+ initialState={{
+ pagination: {
+ paginationModel: {
+ pageSize: 10,
+ },
+ },
+ }}
+ disableRowSelectionOnClick
+ onProcessRowUpdateError={(params) => {
+ console.log('Error', params);
+ }}
+ processRowUpdate={async (newRow, oldRow) => {
+ const data = dataFormatter.dataGridEditFormatter(newRow);
+
+ try {
+ await handleTableSubmit(newRow.id, data);
+ return newRow;
+ } catch {
+ return oldRow;
+ }
+ }}
+ sortingMode={'server'}
+ checkboxSelection
+ onRowSelectionModelChange={(ids) => {
+ setSelectedRows(ids);
+ }}
+ onSortModelChange={(params) => {
+ params.length
+ ? setSortModel(params)
+ : setSortModel([{ field: '', sort: 'desc' }]);
+ }}
+ rowCount={count}
+ pageSizeOptions={[10]}
+ paginationMode={'server'}
+ loading={loading}
+ onPaginationModelChange={(params) => {
+ onPageChange(params.page);
+ }}
+ />
+
+ );
+
+ return (
+ <>
+ {filterItems && Array.isArray(filterItems) && filterItems.length ? (
+
+ null}
+ >
+
+
+
+ ) : null}
+
+ Are you sure you want to delete this item?
+
+
+ {dataGrid}
+
+ {selectedRows.length > 0 &&
+ createPortal(
+ onDeleteRows(selectedRows)}
+ />,
+ document.getElementById('delete-rows-button'),
+ )}
+
+ >
+ );
+};
+
+export default TableSampleReferral;
diff --git a/frontend/src/components/Referral/configureReferralCols.tsx b/frontend/src/components/Referral/configureReferralCols.tsx
new file mode 100644
index 0000000..ee2a9ec
--- /dev/null
+++ b/frontend/src/components/Referral/configureReferralCols.tsx
@@ -0,0 +1,109 @@
+import React from 'react';
+import BaseIcon from '../BaseIcon';
+import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
+import axios from 'axios';
+import {
+ GridActionsCellItem,
+ GridRowParams,
+ GridValueGetterParams,
+} from '@mui/x-data-grid';
+import ImageField from '../ImageField';
+import { saveFile } from '../../helpers/fileSaver';
+import dataFormatter from '../../helpers/dataFormatter';
+import DataGridMultiSelect from '../DataGridMultiSelect';
+import ListActionsPopover from '../ListActionsPopover';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+type Params = (id: string) => void;
+
+export const loadColumns = async (
+ onDelete: Params,
+ entityName: string,
+
+ user,
+) => {
+ async function callOptionsApi(entityName: string) {
+ if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
+
+ try {
+ const data = await axios(`/${entityName}/autocomplete?limit=100`);
+ return data.data;
+ } catch (error) {
+ console.log(error);
+ return [];
+ }
+ }
+
+ const hasUpdatePermission = hasPermission(user, 'UPDATE_REFERRAL');
+
+ return [
+ {
+ field: 'referrer',
+ headerName: 'Referrer',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ sortable: false,
+ type: 'singleSelect',
+ getOptionValue: (value: any) => value?.id,
+ getOptionLabel: (value: any) => value?.label,
+ valueOptions: await callOptionsApi('users'),
+ valueGetter: (params: GridValueGetterParams) =>
+ params?.value?.id ?? params?.value,
+ },
+
+ {
+ field: 'referredemail',
+ headerName: 'Referredemail',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+ },
+
+ {
+ field: 'status',
+ headerName: 'Status',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'singleSelect',
+ valueOptions: ['value'],
+ },
+
+ {
+ field: 'actions',
+ type: 'actions',
+ minWidth: 30,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+ getActions: (params: GridRowParams) => {
+ return [
+
+
+
,
+ ];
+ },
+ },
+ ];
+};
diff --git a/frontend/src/components/Users/CardUsers.tsx b/frontend/src/components/Users/CardUsers.tsx
index 91f2dda..4d86d52 100644
--- a/frontend/src/components/Users/CardUsers.tsx
+++ b/frontend/src/components/Users/CardUsers.tsx
@@ -173,6 +173,41 @@ const CardUsers = ({
+
+
+
+ Loyaltytier
+
+
+
+ {dataFormatter.loyaltytierOneListFormatter(
+ item.loyaltytier,
+ )}
+
+
+
+
+
+
+ Pointsbalance
+
+
+
+ {item.pointsbalance}
+
+
+
+
+
+
+ Referralcode
+
+
+
+ {item.referralcode}
+
+
+
))}
diff --git a/frontend/src/components/Users/ListUsers.tsx b/frontend/src/components/Users/ListUsers.tsx
index 2533b8a..d2c2bb8 100644
--- a/frontend/src/components/Users/ListUsers.tsx
+++ b/frontend/src/components/Users/ListUsers.tsx
@@ -115,6 +115,27 @@ const ListUsers = ({
.join(', ')}
+
+
+
Loyaltytier
+
+ {dataFormatter.loyaltytierOneListFormatter(
+ item.loyaltytier,
+ )}
+
+
+
+
+
+ Pointsbalance
+
+
{item.pointsbalance}
+
+
+
+
Referralcode
+
{item.referralcode}
+
value?.id,
+ getOptionLabel: (value: any) => value?.label,
+ valueOptions: await callOptionsApi('loyaltytier'),
+ valueGetter: (params: GridValueGetterParams) =>
+ params?.value?.id ?? params?.value,
+ },
+
+ {
+ field: 'pointsbalance',
+ headerName: 'Pointsbalance',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'number',
+ },
+
+ {
+ field: 'referralcode',
+ headerName: 'Referralcode',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+ },
+
{
field: 'actions',
type: 'actions',
diff --git a/frontend/src/components/WebPageComponents/Footer.tsx b/frontend/src/components/WebPageComponents/Footer.tsx
index 8a84dff..bd9c5a1 100644
--- a/frontend/src/components/WebPageComponents/Footer.tsx
+++ b/frontend/src/components/WebPageComponents/Footer.tsx
@@ -17,7 +17,7 @@ export default function WebSiteFooter({ projectName }: WebSiteFooterProps) {
const borders = useAppSelector((state) => state.style.borders);
const websiteHeder = useAppSelector((state) => state.style.websiteHeder);
- const style = FooterStyle.WITH_PAGES;
+ const style = FooterStyle.WITH_PROJECT_NAME;
const design = FooterDesigns.DEFAULT_DESIGN;
diff --git a/frontend/src/components/WebPageComponents/Header.tsx b/frontend/src/components/WebPageComponents/Header.tsx
index ebfb323..7feed61 100644
--- a/frontend/src/components/WebPageComponents/Header.tsx
+++ b/frontend/src/components/WebPageComponents/Header.tsx
@@ -19,7 +19,7 @@ export default function WebSiteHeader({ projectName }: WebSiteHeaderProps) {
const style = HeaderStyle.PAGES_LEFT;
- const design = HeaderDesigns.DESIGN_DIVERSITY;
+ const design = HeaderDesigns.DEFAULT_DESIGN;
return (