Compare commits

...

171 Commits

Author SHA1 Message Date
Michael Auer
12fb774b73 Merge branch 'release/2.0' 2022-08-18 13:44:03 +02:00
Lorenz Hilpert
8cfb160989 Merged PR 1365: #3322 Leseproben - Link geht über Rand
#3322 Leseproben - Link geht über Rand

Related work items: #3322
2022-08-01 13:06:19 +00:00
Lorenz Hilpert
6325167eda Merged PR 1364: changed query from qss to customer_name
changed query from qss to customer_name

Related work items: #3320
2022-08-01 12:10:58 +00:00
Lorenz Hilpert
8925eae4c5 Merged PR 1362: Merge develop => release/2.0
Related work items: #3180, #3203, #3245, #3293, #3299, #3312
2022-07-29 11:58:46 +00:00
Andreas Schickinger
9282bcd779 Merged PR 1361: #3312 NotificationChannel Auswertung angepasst
#3312 NotificationChannel Auswertung angepasst

Related work items: #3312
2022-07-29 11:55:29 +00:00
Lorenz Hilpert
5aa6499598 #3313 Nachbestellen auf der Wareneingangsliste wirft Fehler 2022-07-29 11:47:03 +02:00
Andreas Schickinger
f766781928 Merged PR 1360: Merge release 2.0 -> develop
Related work items: #3180, #3203, #3245, #3293, #3299
2022-07-28 14:05:13 +00:00
Andreas Schickinger
02834b7102 Merged PR 1359: Merge develop -> release 2.0
Merge develop -> release 2.0

Related work items: #3180, #3203, #3245, #3299
2022-07-28 13:48:10 +00:00
Andreas Schickinger
a9e3430505 Merged PR 1358: #3180 getTakeAwayAvailability angepasst
#3180 getTakeAwayAvailability angepasst

Related work items: #3180
2022-07-28 13:30:06 +00:00
Nino Righi
4b9a23001a Merged PR 1357: #3267 Email Validator Regex updated, Notification Channel Control Logic modified
#3267 Email Validator Regex updated, Notification Channel Control Logic modified
2022-07-28 13:25:55 +00:00
Nino Righi
8de7ec9124 Merged PR 1356: #3267 Bugfix If Both Communication Details are Valid update Customer correctly
#3267 Bugfix If Both Communication Details are Valid update Customer correctly
2022-07-27 16:30:18 +00:00
Andreas Schickinger
19a0a3c7c3 Merged PR 1355: #3180 Lupe wird erst angezeigt, wenn das Bild erfolgreich geladen wurde
#3180 Lupe wird erst angezeigt, wenn das Bild erfolgreich geladen wurde

Related work items: #3180
2022-07-27 16:21:55 +00:00
Nino Righi
42a7d6e4b7 Merged PR 1352: #3267 Checkout Cart Fix Notification Channel disable Order CTA if communication details missing
#3267 Checkout Cart Fix Notification Channel disable Order CTA if communication details missing
2022-07-27 15:21:52 +00:00
Nino Righi
66818b1647 Merged PR 1354: #3304 Customer Search Message Fix
#3304 Customer Search Message Fix
2022-07-27 14:06:07 +00:00
Andreas Schickinger
bc8ba9adc8 Merged PR 1353: #3180 Entkoppelte Ladebereiche für Verfügbarkeiten
#3180 Entkoppelte Ladebereiche für Verfügbarkeiten

Related work items: #3180
2022-07-27 13:16:35 +00:00
Nino Righi
4ae5759361 Merged PR 1351: #3304 Fix WA Autocomplete Dropdown closing after queryParams change
#3304 Fix WA Autocomplete Dropdown closing after queryParams change
2022-07-26 14:57:11 +00:00
Andreas Schickinger
b5cfcf8036 Merged PR 1350: #3245 Zubuchen disabled fixes
#3245 Zubuchen disabled fixes

Related work items: #3245
2022-07-26 14:47:46 +00:00
Lorenz Hilpert
061982cf2c Merged PR 1349: Dashboard // nicht alle Infos werden angezeigt
Related work items: #3299
2022-07-26 12:00:09 +00:00
Nino Righi
0e1422c2c4 Merged PR 1348: #3267 Checkout Cart Notification Disable Order Button if Checkbox is Active b...
#3267 Checkout Cart Notification Disable Order Button if Checkbox is Active but E-Mail or Mobilenumber is missing
2022-07-26 11:09:43 +00:00
Nino Righi
3e534029a0 Merged PR 1347: #3167 Fix Reorder Modal Styling Stock Column Text Centered
#3167 Fix Reorder Modal Styling Stock Column Text Centered
2022-07-25 15:44:42 +00:00
Nino Righi
8d9ee9fe5c Merged PR 1343: #3297 Fix Reorder Modal takes now the correct Quantity
#3297 Fix Reorder Modal takes now the correct Quantity
2022-07-25 15:44:10 +00:00
Lorenz Hilpert
675aa04564 Added Generated package-lock.json - npm version 6 2022-07-25 17:42:30 +02:00
Lorenz Hilpert
88c8885a81 Build Test 2022-07-25 17:26:14 +02:00
Andreas Schickinger
151760aef9 Merged PR 1346: #2303 WA AHF // Zusatz bei Gruppierung in Trefferliste beachten
#3203 WA AHF // Zusatz bei Gruppierung in Trefferliste beachten

Related work items: #3203
2022-07-25 15:15:02 +00:00
Nino Righi
6c89969b60 Merged PR 1345: #3214 Price Diff Modal Styling Adjustments
#3214 Price Diff Modal Styling Adjustments
2022-07-25 14:57:13 +00:00
Nino Righi
0fd5e66c33 Merged PR 1344: #3287 Notification Channel preselect E-Mail if E-Mail and SMS is available
#3287 Notification Channel preselect E-Mail if E-Mail and SMS is available
2022-07-25 14:55:08 +00:00
Lorenz Hilpert
c8aa526e4d Fix Toaster Component => onSlideFinished 2022-07-25 16:29:38 +02:00
Andreas Schickinger
f2c492c6ea Merged PR 1342: #3245 Zubuchen disabled wenn ein Zusatz ausgewählt ist
#3245 Zubuchen disabled wenn ein Zusatz ausgewählt ist

Related work items: #3245
2022-07-25 14:24:21 +00:00
Lorenz Hilpert
11cf845235 Update ToastComponent.timeoutRef to any 2022-07-25 15:25:00 +02:00
Lorenz Hilpert
ae6fbc7c64 update packages 2022-07-25 15:23:45 +02:00
Andreas Schickinger
71eda539f6 Merged PR 1341: Merged PR 1340: #3293 Filtereinstellungen bei Tabwechsel
Merged PR 1340: #3293 Filtereinstellungen bei Tabwechsel

#3293 Filtereinstellungen bei Tabwechsel

Related work items: #3293

Related work items: #3293
2022-07-21 13:51:09 +00:00
Andreas Schickinger
f43b948ac9 Merged PR 1340: #3293 Filtereinstellungen bei Tabwechsel
#3293 Filtereinstellungen bei Tabwechsel

Related work items: #3293
2022-07-21 09:58:55 +00:00
Andreas Schickinger
1b77020b6a Merge branch 'develop' into release/2.0 2022-07-20 13:38:46 +02:00
Andreas Schickinger
1f62040560 Merged PR 1339: #3265 Warenausgabe Scrolling und SilentReload Bugfix
#3265 Warenausgabe Scrolling und SilentReload Bugfix

Related work items: #3265
2022-07-19 15:17:11 +00:00
Nino Righi
cc5c3167b1 Merged PR 1338: #3292 Process Guards Breadcrumbs Fix
#3292 Process Guards Breadcrumbs Fix
2022-07-19 15:01:37 +00:00
Andreas Schickinger
b9b79b949f Merged PR 1337: #3291 Warenausgabe Caching Verhalten und Scrolling angepasst
#3291 Warenausgabe Caching Verhalten und Scrolling angepasst

Related work items: #3291
2022-07-19 13:06:05 +00:00
Nino Righi
a0d729fe6d Merged PR 1336: #3272 Revert WA Tab Naming changes back to current production version
#3272 Revert WA Tab Naming changes back to current production version
2022-07-19 12:31:10 +00:00
Andreas Schickinger
f618dd3865 Merged PR 1335: #3291 WA Listenansicht Statusänderung cleared Cache
#3291 WA Listenansicht Statusänderung cleared Cache

Related work items: #3291
2022-07-18 15:58:59 +00:00
Nino Righi
3fd3f972db Merged PR 1334: #3287 Notifications Deactivate Other Channels except SMS and EMail
#3287 Notifications Deactivate Other Channels except SMS and EMail
2022-07-18 15:32:07 +00:00
Andreas Schickinger
2311655e5e Merged PR 1333: #3285 TK Artikelbilder verzerrt Dashboard fix
#3285 TK Artikelbilder verzerrt Dashboard fix
2022-07-18 14:40:29 +00:00
Nino Righi
c589836097 Merged PR 1332: #3286 Remission Fix Filter Settings change to default after Source Changed
#3286 Remission Fix Filter Settings change to default after Source Changed
2022-07-18 14:34:52 +00:00
Andreas Schickinger
dbc641cfce Merged PR 1331: #3285 TK Bilder verzerrt
#3285 TK Bilder verzerrt

Related work items: #3285
2022-07-18 13:53:01 +00:00
Andreas Schickinger
f13bc58925 Merged PR 1330: #3276 Remission Filter Button nicht mehr disabled
#3276 Remission Filter Button nicht mehr disabled

Related work items: #3276
2022-07-18 13:08:07 +00:00
Andreas Schickinger
94d5892cf1 Merged PR 1328: #3272 Beim Wechsel zwischen WA und Artikelrecherche den Warenkorb nicht mehr...
#3272 Beim Wechsel zwischen WA und Artikelrecherche den Warenkorb nicht mehr verwerfen

Related work items: #3272
2022-07-18 10:18:10 +00:00
Andreas Schickinger
8e32b15f26 Merged PR 1329: #3265 Warenausgabe Tabwechsel fixes
#3265 Warenausgabe Tabwechsel fixes
- Fehlermeldung ScrollPosition
- Suche wird trotz Cache ausgeführt
- Scroll Top bei erneuter Suche im gleichen Tab oder über Filter
- Beim Vorgangswechsel wurde der Filter nicht korrekt resetted

Related work items: #3265
2022-07-18 09:37:27 +00:00
Andreas Schickinger
fe5f0ef2eb Merged PR 1327: #3283 Fallback URL für einen Vorgang von Dashboard auf Artikelsuche geändert
#3283 Fallback URL für einen Vorgang von Dashboard auf Artikelsuche geändert. Dadurch kommt es nicht mehr dazu, dass ein Tab nicht selektierbar "hängen bleibt" und zum Dashboard navigiert. Die Ursache wie es zu dem Problem kam ist noch unbekannt

Related work items: #3283
2022-07-18 09:18:13 +00:00
Andreas Schickinger
daa27d5f2d Merged PR 1326: #3282 Fehlerdialog und Logout bei zu langer Inaktivität
#3282 Fehlerdialog und Logout bei zu langer Inaktivität - PR für Test

Related work items: #3282
2022-07-18 09:14:05 +00:00
Nino Righi
bb7626609e Merged PR 1325: #3272 Prozess Tab Bugfixes
#3272 Prozess Tab Bugfixes
2022-07-14 15:59:36 +00:00
Nino Righi
9ed58b685b Merged PR 1323: #3272 #3275 Tab Process Management Updated, Cart, CheckoutCart, WA
#3272 #3275 Tab Process Management Updated, Cart, CheckoutCart, WA
2022-07-14 14:12:21 +00:00
Andreas Schickinger
4eb81ad30a Merged PR 1324: #3276 Remission Starten Button disabled, wenn Liste läd
#3276 Remission Starten Button disabled, wenn Liste läd

Related work items: #3276
2022-07-14 13:49:26 +00:00
Nino Righi
a1f2cb57b3 Merged PR 1322: #3275 Bestellbestätigungs Prozess Bugfixes
#3275 Bestellbestätigungs Prozess Bugfixes
2022-07-13 15:03:40 +00:00
Andreas Schickinger
62b8e387ca Merged PR 1321: #3270 Listenbestellung B2B Preis wird richtig übernommen
#3270 Listenbestellung B2B Preis wird richtig übernommen

Related work items: #3270
2022-07-13 14:59:36 +00:00
Andreas Schickinger
07498db711 Merged PR 1316: #3270 Listenbestellung Popup Preis wird bei Wechsel der Filteroption aktualis...
#3270 Listenbestellung Popup Preis wird bei Wechsel der Filteroption aktualisiert

Related work items: #3270
2022-07-12 15:33:29 +00:00
Nino Righi
2adc8c6f5d Merged PR 1320: #3275 Made Cart Checkout Process reusable
#3275 Made Cart Checkout Process reusable
2022-07-12 15:31:56 +00:00
Lorenz Hilpert
f15a43f303 Merged PR 1319: #3274 Warenausgabe - Filter
#3274 Warenausgabe - Filter
2022-07-12 13:12:59 +00:00
Nino Righi
e35aea5a7e Merged PR 1318: #3273 #3271 Notifications Cart Show Toggle and Bugfix
#3273 #3271 Notifications Cart Show Toggle and Bugfix
2022-07-12 09:02:00 +00:00
Lorenz Hilpert
0e1ed9d8cc Merge branch 'feature/#3265-Scroll-Position-Bug' into develop 2022-07-11 16:13:05 +02:00
Lorenz Hilpert
f62ef06e51 Remove Process Tabs in Goods Out 2022-07-11 16:06:47 +02:00
Andreas Schickinger
30f4d4588f Merged PR 1315: #3269 Bestellbestaetigung fehlerhafte Anzeige
#3269 Bestellbestaetigung fehlerhafte Anzeige

Related work items: #3269
2022-07-11 12:59:22 +00:00
Lorenz Hilpert
e102396dab #3265 Scroll Position Bug 2022-07-11 12:03:10 +02:00
Andreas Schickinger
f60815ef63 Merged PR 1314: #3268 Artikelformat wird überall auf undefined geprüft. Fehlendes FormatIcon...
#3268 Artikelformat wird überall auf undefined geprüft. Fehlendes FormatIcon in PurchasingOptions implementiert

Related work items: #3268
2022-07-08 13:59:37 +00:00
Andreas Schickinger
7b11b53774 Merged PR 1313: #3267 Warenkorb NotificationChannels nur speichern, wenn Haken und Value gese...
#3267 Warenkorb NotificationChannels nur speichern, wenn Haken und Value gesetzt wurden

Related work items: #3267
2022-07-08 08:11:35 +00:00
Nino Righi
abff7715ee Merged PR 1312: #3256 Fix Display Toast Notification
#3256 Fix Display Toast Notification
2022-07-08 08:09:07 +00:00
Andreas Schickinger
83c0a20d61 Warenausgabe Tabwechsel Bugfix 2022-07-06 17:47:39 +02:00
Andreas Schickinger
a4de2391e9 Merged PR 1310: #3262 WA Loader wird nicht angezeigt
#3262 WA Loader wird nicht angezeigt

Related work items: #3262
2022-07-06 14:58:22 +00:00
Andreas Schickinger
732566eacd Merged PR 1311: #3263 WA Details API calls angepasst
#3263 WA Details API calls angepasst

Related work items: #3263
2022-07-06 14:48:37 +00:00
Andreas Schickinger
2c98128531 Merged PR 1309: #3264 WA QueryParam aktualisieren mit debounce
#3264 WA QueryParam aktualisieren mit debounce

Related work items: #3264
2022-07-06 14:38:56 +00:00
Nino Righi
9a45823bdb Merged PR 1308: #3256 Abholfachremissionsvorschau Create Toas with Dialog Text or Fallback Me...
#3256 Abholfachremissionsvorschau Create Toas with Dialog Text or Fallback Message if Dialog is not available
2022-07-06 14:38:35 +00:00
Andreas Schickinger
fa43a08831 Merged PR 1305: Warenbegleitscheine Rechtschreibfehler fix
Warenbegleitscheine Rechtschreibfehler fix
2022-07-04 11:25:51 +00:00
Nino Righi
6fb8bdaff1 Merged PR 1304: Merge Develop -> Release/2.0
Merge Develop -> Release/2.0

Related work items: #905, #2737, #2790, #3040, #3150, #3157, #3158, #3175, #3179, #3189, #3212, #3234
2022-06-29 12:48:45 +00:00
Andreas Schickinger
35e6d60ac0 Merged PR 1301: #3234 Remission Artikel hinzufügen Teilmengen Bugfix
#3234 Remission Artikel hinzufügen Teilmengen Bugfix

Related work items: #3234
2022-06-29 08:37:58 +00:00
Nino Righi
ecfc241fb5 Merged PR 1303: #2953 Fix Zubuchen CompartmentCode
#2953 Fix Zubuchen CompartmentCode
2022-06-29 08:37:25 +00:00
Nino Righi
866a5100b9 Merged PR 1299: #2953 Updated QR Code Print Data
#2953 Updated QR Code Print Data
2022-06-28 13:31:47 +00:00
Andreas Schickinger
3d972fd740 Merged PR 1298: #3234 Bugfix Remission Artikel hinzufügen Menge falsch
#3234 Bugfix Remission Artikel hinzufügen Menge falsch

Related work items: #3234
2022-06-28 08:14:39 +00:00
Nino Righi
59c9275cc4 Merged PR 1297: #2953 Updated PRINT_PRICEDIFFQRCODELABEL compartmentCode and compartmentInfo
#2953 Updated PRINT_PRICEDIFFQRCODELABEL compartmentCode and compartmentInfo
2022-06-27 15:33:10 +00:00
Lorenz Hilpert
3ada83efff #3091 - Meldung bei nicht remittierbaren Artikel 2022-06-27 14:46:12 +02:00
Lorenz Hilpert
e263048a35 gen:swagger:remi - Regenerate remi api 2022-06-27 10:24:41 +02:00
Lorenz Hilpert
63980298b6 #3149 Query Filter Keep Changes Whitput Applying 2022-06-27 10:19:04 +02:00
Lorenz Hilpert
b904e94156 Merged PR 1296: Merge Release into Develop
Related work items: #905, #2737, #2790, #3040, #3150, #3157, #3158, #3175, #3179, #3189, #3212
2022-06-24 15:06:23 +00:00
Lorenz Hilpert
78f91b937f Merged PR 1295: Merge Develop into Release2.0
Related work items: #905, #3040, #3175, #3179, #3189, #3212
2022-06-24 14:54:23 +00:00
Andreas Schickinger
1ecd08d053 Merged PR 1293: #3212 Remission Stapel aktualisieren bugfix
#3212 Remission Stapel aktualisieren bugfix

Related work items: #3212
2022-06-24 14:06:41 +00:00
Andreas Schickinger
8c03accae7 Merged PR 1294: #3224 Remission Artikel hinzufügen Breadcrumb
#3224 Remission Artikel hinzufügen Breadcrumb
2022-06-24 14:05:51 +00:00
Andreas Schickinger
456d3f2f3a Merged PR 1290: #905 Remission WBS Leistung Dummy entfernt
#905 Remission WBS Leistung Dummy entfernt

Related work items: #905
2022-06-24 09:05:39 +00:00
Andreas Schickinger
fa2838ea5c Merged PR 1289: #3212 Remission Stapel aktualisieren fix
#3212 Remission Stapel aktualisieren fix
2022-06-24 09:04:58 +00:00
Andreas Schickinger
e228490812 Merged PR 1288: #3212 Remission Stapel Stock aktualisieren anhand EAN
#3212 Remission Stapel Stock aktualisieren anhand EAN

Related work items: #3212
2022-06-23 15:38:44 +00:00
Nino Righi
13d0ac0cbd Merged PR 1287: #3222 Remission Fix Trigger Init Search after Filter gets loaded, relocated u...
#3222 Remission Fix Trigger Init Search after Filter gets loaded, relocated update Cache function
2022-06-23 15:38:10 +00:00
Nino Righi
09bed1456e Merged PR 1284: #3216 Customer Order Details Display All Subset Items
#3216 Customer Order Details Display All Subset Items
2022-06-23 15:34:11 +00:00
Andreas Schickinger
c6827e499e Merged PR 1286: #3040 Logik zum Entfernen von Duplikaten angepasst
#3040 Logik zum Entfernen von Duplikaten angepasst

Related work items: #3040
2022-06-23 13:03:34 +00:00
Nino Righi
d54cc7a2fd Merged PR 1278: #3204 Platform Detection and Scrollbars
#3204 Platform Detection and Scrollbars
2022-06-23 07:28:14 +00:00
Andreas Schickinger
67dae94524 Merged PR 1283: #3040 Remission SilentReload entfernt und Ladelogik angepasst
#3040 Remission SilentReload entfernt und Ladelogik angepasst

Related work items: #3189
2022-06-22 14:05:11 +00:00
Nino Righi
3273a21246 Merged PR 1282: #3215 Config Produktbilder Changes
#3215 Config Produktbilder Changes
2022-06-22 13:18:40 +00:00
Nino Righi
b40b61a46c Merged PR 1281: #3215 Hotfix Update Configs Production Staging
#3215 Hotfix Update Configs Production Staging
2022-06-22 12:46:57 +00:00
Nino Righi
813f611843 Merged PR 1279: #3206 Hide Supplier if Supplier Count is <= 1 and Disable Logic if click on a...
#3206 Hide Supplier if Supplier Count is <= 1 and Disable Logic if click on already activated Supplier or Source
2022-06-21 13:12:16 +00:00
Lorenz Hilpert
1abacb75be debugger entfernt 2022-06-21 15:11:31 +02:00
Lorenz Hilpert
42028a2777 #3040 Remission - Fehler bei nicht gefunden stehen 2022-06-21 15:11:07 +02:00
Lorenz Hilpert
0a25eeadbe Merged PR 1277: #3149 Process Change and Caching
#3149 Process Change and Caching
2022-06-20 15:39:00 +00:00
Lorenz Hilpert
70a4451f90 #3189 Remission - Artikel entfernen von WBS landen nicht wieder auf Remi-Liste 2022-06-20 12:21:11 +02:00
Lorenz Hilpert
d859395f50 #3160 Spelling 2022-06-20 11:44:02 +02:00
Andreas Schickinger
97948df14e Merged PR 1276: #3175 Remission kein Refresh bei Wechsel von Warenbegleitschein
#3175 Remission kein Refresh bei Wechsel von Warenbegleitschein
#3189 Remission Artikel entfernen Refresh

Related work items: #3175
2022-06-17 09:20:16 +00:00
Lorenz Hilpert
dc84efb3ef #3160 Remission - im Abteilungremission Ladeanimation ohne Filter 2022-06-15 11:26:21 +02:00
Andreas Schickinger
12676a4314 Merged PR 1275: #3179 Refactoring Remission Warenbegleitschein eröffnen Rechtschreibfehler
#3179 Refactoring Remission Warenbegleitschein eröffnen Rechtschreibfehler

Related work items: #3179
2022-06-13 14:05:18 +00:00
Lorenz Hilpert
3aafb9f8e7 #3173 Remission - Single-Selection bei Abteilungsremission - Abteilungen 2022-06-13 16:04:32 +02:00
Lorenz Hilpert
4518db2bdd Merged PR 1274: Merge Dev => Release
Related work items: #2737, #2790, #3150, #3157, #3158
2022-06-10 13:16:11 +00:00
Andreas Schickinger
2cb161b62e Merged PR 1272: #3150 Scroll Arrows in UiSlider nur anzeigen wenn Scrollbar
#3150 Scroll Arrows in UiSlider nur anzeigen wenn Scrollbar

Related work items: #3150
2022-06-10 09:22:34 +00:00
Lorenz Hilpert
9d052284be #3040 - Remission - Fehler bei nicht gefunden stehen 2022-06-10 11:21:43 +02:00
Nico Hanus
98d9029dde remove single build for develop and enabled batch trigger 2022-06-09 12:41:42 +00:00
Lorenz Hilpert
040c67215e Merged PR 1271: #3040 Remission - Fehler bei nicht gefunden stehen
#3040 Remission - Fehler bei nicht gefunden stehen
2022-06-09 11:47:42 +00:00
Andreas Schickinger
d577312b24 Merged PR 1269: #3158 UiSpinner anstatt leerer Warenkorb Meldung beim Laden
#3158 UiSpinner anstatt leerer Warenkorb Meldung beim Laden

Related work items: #3158
2022-06-09 08:54:31 +00:00
Andreas Schickinger
fa10bc9c30 Merged PR 1268: #3157 AHF Routing und Item wählen anhand OrderItemSubsetId
#3157 AHF Routing und Item wählen anhand OrderItemSubsetId

Related work items: #3157
2022-06-09 08:53:45 +00:00
Andreas Schickinger
055339956a Merged PR 1270: #2790 Benachrichtigungskanäle bei Weiter zum Warenkorb nicht überschreiben
#2790 Benachrichtigungskanäle bei Weiter zum Warenkorb nicht überschreiben, wenn der Kunde bereits ausgewhält ist

Related work items: #2790
2022-06-09 08:34:04 +00:00
Lorenz Hilpert
6cffa53ea9 #3062 Remission Footer 2022-06-08 11:21:12 +02:00
Lorenz Hilpert
fe83ef56ea Fix core-application unit tests 2022-06-07 17:12:22 +02:00
Lorenz Hilpert
abd1cacdc0 #3149 Warenausgabe kann mehrmals in einem Vorgang geöffnet werden 2022-06-07 16:49:58 +02:00
Nino Righi
85b448ab85 Merged PR 1267: #3145 Checkout Summary Changes
#3145 Bugfix Artikellink hat keinen neuen Prozess eröffnet sondern einen bestehenden überschrieben,
Wenn neue Bestellung getätigt wird, wird die alte Bestellbestätigung removed
2022-06-07 08:31:07 +00:00
Nico Hanus
5749f0018c add batching in azurepipeline
https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/azure-repos-git?view=azure-devops&tabs=yaml#batching-ci-runs
2022-06-03 11:42:43 +00:00
Lorenz Hilpert
b65d2c5ff3 #3156 Login Test 2022-06-02 11:45:08 +02:00
Lorenz Hilpert
348b2c4aca #3156 manuelles Login mit Username und Passwort nicht möglich 2022-06-02 10:55:53 +02:00
Nino Righi
dfc3f32086 Merged PR 1266: #3147 Fix IPAD 6 Styling Issue inside Availabilities Modal
#3147 Fix IPAD 6 Styling Issue inside Availabilities Modal
2022-06-02 08:26:33 +00:00
Nino Righi
68d331864d Merged PR 1264: #3145 Updated Order Checkout Process
#3145 Updated Order Checkout Process
2022-06-01 08:19:14 +00:00
Lorenz Hilpert
24a008b20d Merged PR 1263: Cleanup 2022-05-31 12:56:41 +00:00
Nino Righi
0e5c35fae4 Merged PR 1262: #3146 Slide Current Active Process Tab Into View after Activating It
#3146 Slide Current Active Process Tab Into View after Activating It
2022-05-31 08:06:08 +00:00
Nino Righi
4d4b989dcd Merged PR 1261: #3082 Improved Error Handling on PDP
#3082 Improved Error Handling on PDP
2022-05-30 15:38:59 +00:00
Andreas Schickinger
2983d5a068 Merged PR 1260: #2737 Bei Zubuchen kein Abholfachzettel ausdrucken
#2737 Bei Zubuchen kein Abholfachzettel ausdrucken

Related work items: #2737
2022-05-30 08:37:19 +00:00
Nino Righi
efdc365b90 Merged PR 1259: #3022 ISA File Caching Improvements
#3022 ISA File Caching Improvements
2022-05-27 07:51:24 +00:00
Nino Righi
c4a8e3eb96 Merged PR 1258: Merge release into develop
Merge release into develop
2022-05-25 13:48:01 +00:00
Nino Righi
8cb3c98b8d Merged PR 1256: #3133 Changed Service Worker Update Implementation to Angular v12 and Fixed U...
#3133 Changed Service Worker Update Implementation to Angular v12 and Fixed Unit Tests
2022-05-24 16:19:25 +00:00
Nino Righi
9c12eda210 #3133 Changed Service Worker Update Implementation to Angular v12 and Fixed Unit Tests 2022-05-24 18:14:58 +02:00
Lorenz Hilpert
90afcc008d Disable check for update with interval 2022-05-24 17:44:19 +02:00
Lorenz Hilpert
7409d096fe Update Check Disabled 2022-05-24 17:43:24 +02:00
Lorenz Hilpert
a4c4a3c0c8 Merged PR 1255: Merge release 2.0 into develop 2022-05-24 14:29:50 +00:00
Lorenz Hilpert
2148af7b63 Merge branch 'develop' into release/2.0 2022-05-24 16:14:19 +02:00
Andreas Schickinger
d984bc04ec Merged PR 1250: #3109 Produktbilder in der Artikeltrefferliste mit ImageId laden
#3109 Produktbilder in der Artikeltrefferliste mit ImageId laden

Related work items: #3109
2022-05-23 14:46:51 +00:00
Andreas Schickinger
1b7dfcc3ac Merged PR 1249: #3127 iPad Zoom deaktivieren
#3127 iPad Zoom deaktivieren

Related work items: #3127
2022-05-23 14:46:26 +00:00
Andreas Schickinger
c762871cce Merged PR 1248: #2877 Preisunterschied Menge wird pro Item übergeben
#2877 Preisunterschied Menge wird pro Item übergeben

Related work items: #2877
2022-05-23 09:14:24 +00:00
Andreas Schickinger
007ea92bd5 Merged PR 1247: #3128 Wareneingang Warenausgang Breadcrumb wurde nicht immer richtig aktualis...
#3128 Wareneingang Warenausgang Breadcrumb wurde nicht immer richtig aktualisiert

Related work items: #3128
2022-05-23 09:13:44 +00:00
Nino Righi
688b758da2 Merged PR 1245: #3022 Unit Test Fix
#3022 Unit Test Fix
2022-05-19 08:38:09 +00:00
Nino Righi
39147d7afa Merged PR 1244: #3022 ISA File Caching
#3022 ISA File Caching
2022-05-19 08:29:30 +00:00
Andreas Schickinger
50cc17a44b Merged PR 1243: #2877 Preisunterschied Popup: Bei Close/Backdrop Click nicht mehr die Sekundä...
#2877 Preisunterschied Popup: Bei Close/Backdrop Click nicht mehr die Sekundär Action ausführen

Related work items: #2877
2022-05-19 08:29:00 +00:00
Andreas Schickinger
73f592df74 Merged PR 1241: #3116 Bei B2B Kunden war der Weiter Button immer disabled
#3116 Bei B2B Kunden war der Weiter Button immer disabled

Related work items: #3116
2022-05-17 14:18:49 +00:00
Lorenz Hilpert
821042d8b6 App Start - leistung verbessert und statusanzeige 2022-05-17 16:17:26 +02:00
Nino Righi
7123f6cc15 Merged PR 1242: #3124 Toast Message Adjusted Width and Height Values
#3124 Toast Message Adjusted Width and Height Values
2022-05-17 12:56:36 +00:00
Andreas Schickinger
6c2e9906b0 Merged PR 1240: #3117 Prozess Scrollpfeile sollte am iPad nicht sichtbar sein
#3117 Prozess Scrollpfeile sollte am iPad nicht sichtbar sein

Related work items: #3117
2022-05-16 13:59:18 +00:00
Andreas Schickinger
37648b79c3 Merged PR 1238: #3116 Weiter Button in den Kundendetails disabled, wenn noch keine Adressen geladen wurden
#3116 Weiter Button in den Kundendetails disabled, wenn noch keine Adressen geladen wurden

Related work items: #3116
2022-05-16 13:55:06 +00:00
Nino Righi
6b00c2c81a Merged PR 1239: #3093 ISA Offline Message Fix
#3093 ISA Offline Message Fix#3093 ISA Offline Message Fix
2022-05-16 13:24:22 +00:00
Andreas Schickinger
cd5599ff1c Merged PR 1237: #2981 Preisunterschied Action und Popup
#2981 Preisunterschied Action und Popup

Related work items: #2877, #2981
2022-05-16 09:51:40 +00:00
Andreas Schickinger
e870eb241b Merged PR 1236: #3122 Warenausgabe Endpoints angepasst
#3122 Warenausgabe Endpoints angepasst

Related work items: #3122
2022-05-16 09:32:52 +00:00
Andreas Schickinger
fbf8c282e8 Merged PR 1235: #3108 Remission Remi Grund nur anzeigen, wenn vor dem remittieren schon vorha...
#3108 Remission Remi Grund nur anzeigen, wenn vor dem remittieren schon vorhanden

Related work items: #3108
2022-05-16 08:19:23 +00:00
Nico Hanus
811e363dd4 increase cpu and ram limits for better performance 2022-05-13 10:55:36 +02:00
Nino Righi
d206ba1606 Merged PR 1234: #3118 Fixed Process nummeration
#3118 Fixed Process nummeration
2022-05-12 14:56:23 +00:00
Nino Righi
c529134cd2 Merged PR 1233: #3093 Improved Offline Handling
#3093 Improved Offline Handling
2022-05-12 14:21:43 +00:00
Andreas Schickinger
dafb1d335e Merged PR 1232: #3113 Bei Klick auf Abbrechen auf der Login Seite erneut zur Login Seite leiten
#3113 Bei Klick auf Abbrechen auf der Login Seite erneut zur Login Seite leiten

Related work items: #3113
2022-05-12 12:48:37 +00:00
Nino Righi
445899e731 Merged PR 1231: #3115 Fix WE WA Multi Search Request
#3115 Fix WE WA Multi Search Request
2022-05-12 08:18:05 +00:00
Andreas Schickinger
b21fbc974c Merged PR 1230: #2995 Kundensuche doppelter Request im Filter behoben
#2995 Kundensuche doppelter Request im Filter behoben

Related work items: #2995
2022-05-11 15:14:29 +00:00
Andreas Schickinger
4c98a73204 Merged PR 1229: #3108 Remission Restmenge nach dem remittieren angepasst
#3108 Remission Restmenge nach dem remittieren angepasst

Related work items: #3108
2022-05-11 14:41:08 +00:00
Nino Righi
e99d669086 Merged PR 1228: #3104 Improved Error Handling of Removing Items from Shipping Document
#3104 Improved Error Handling of Removing Items from Shipping Document
2022-05-10 15:12:34 +00:00
Andreas Schickinger
913ffbda97 Merged PR 1227: #1058 Remission abschließen Breadcrumb wird entfernt
#1058 Remission abschließen Breadcrumb wird entfernt

Related work items: #1058
2022-05-10 13:31:26 +00:00
Nino Righi
ec2bd0bd5d Merged PR 1226: #3061 Remission List Performance
#3061 Remission List Performance
2022-05-09 15:31:47 +00:00
Andreas Schickinger
08fefb1c4b Merged PR 1225: #3094 Remission mehrfach abschließen Fehlermeldung angepasst
#3094 Remission mehrfach abschließen Fehlermeldung angepasst

Related work items: #3094
2022-05-09 15:18:36 +00:00
Andreas Schickinger
e7f20bc553 Merged PR 1224: #3088 Remission Fehlermeldung bei gleichzeitigem entfernen und Liste Reload a...
#3088 Remission Fehlermeldung bei gleichzeitigem entfernen und Liste Reload auch bei Fehler

Related work items: #3088
2022-05-09 15:17:39 +00:00
Andreas Schickinger
f5993ca5c4 Merged PR 1223: #3087 Bei Error Status 409 Custom Fehlermeldung und Liste auch bei Fehler neu...
#3087 Bei Error Status 409 Custom Fehlermeldung und Liste auch bei Fehler neu laden

Related work items: #3087
2022-05-09 15:17:15 +00:00
Nino Righi
9f0c81c20f Merged PR 1215: #3062 #3051 Remission Breadcrumbs contain the correct queryParams, Remission Process Remembers Filters if Remission is started
#3062 #3051 Remission Breadcrumbs contain the correct queryParams, Remission Process Remembers Filters if Remission is started. Always load available Remission
2022-05-09 11:41:50 +00:00
Nino Righi
42199c8cda Merged PR 1222: #3097 #3089 Shell Process Tabs UI Changes
#3097 #3089 Shell Process Tabs UI Changes
2022-05-09 10:02:51 +00:00
Nino Righi
bc525fbffc Merged PR 1218: #3067 #3056 #2685 Toast Notifications
#3067 #3056 #2685 Toast Notifications
2022-05-09 09:45:54 +00:00
Nino Righi
dd6821642f Merged PR 1220: #3092 Remission Empty List Message
#3092 Remission Empty List Message
2022-05-09 09:43:20 +00:00
Nino Righi
b2dd96f044 Merged PR 1219: #3041 If no supplier selected inside create remission, Blank gets selected an...
#3041 If no supplier selected inside create remission, Blank gets selected and changed navigation inside goods in remission preview
2022-05-09 09:33:49 +00:00
Nino Righi
0ca8a1fabf Merged PR 1221: #3095 Remission Disable Remit Button if Quantity < 1
#3095 Remission Disable Remit Button if Quantity < 1
2022-05-09 09:23:14 +00:00
Nino Righi
d46643cf8c Merged PR 1217: #3084 Remission Added Loader to WBS on deleting Items
#3084 Remission Added Loader to WBS on deleting Items
2022-05-09 08:59:59 +00:00
Nino Righi
fc3bea28e8 Merged PR 1216: #2967 Remission Remit PopUp Wording Fix
#2967 Remission Remit PopUp Wording Fix
2022-05-09 08:57:05 +00:00
Nino Righi
b29323864e Updated Isa-App Config Files 2022-05-03 11:38:41 +02:00
Michael Auer
c4cad5d56f Merge branch 'develop' into release/2.0 2022-05-03 10:52:36 +02:00
Nino Righi
1d83c82919 Merged PR 1214: #3083 Adjusted File Memory Budgets for Production Build
#3083 Adjusted File Memory Budgets for Production Build
2022-05-02 12:05:51 +00:00
1900 changed files with 7607 additions and 76196 deletions

View File

@@ -3,256 +3,6 @@
"version": 1,
"newProjectRoot": "apps",
"projects": {
"ui": {
"root": "libs/ui",
"sourceRoot": "libs/ui",
"projectType": "library",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "libs/ui/tsconfig.lib.json",
"project": "libs/ui/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "libs/ui/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "libs/ui/src/test.ts",
"tsConfig": "libs/ui/tsconfig.spec.json",
"karmaConfig": "libs/ui/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"libs/ui/tsconfig.lib.json",
"libs/ui/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"sales": {
"root": "apps/sales/",
"sourceRoot": "apps/sales/src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"aot": true,
"outputPath": "dist/sales",
"index": "apps/sales/src/index.html",
"main": "apps/sales/src/main.ts",
"polyfills": "apps/sales/src/polyfills.ts",
"tsConfig": "apps/sales/tsconfig.app.json",
"assets": [
"apps/sales/src/favicon.ico",
"apps/sales/src/assets",
"apps/sales/src/manifest.webmanifest",
"apps/sales/src/browserconfig.xml",
"apps/sales/src/silent-refresh.html"
],
"styles": [
"apps/sales/src/styles.scss"
],
"stylePreprocessorOptions": {
"includePaths": [
"apps/sales/src/scss"
]
},
"scripts": [],
"customWebpackConfig": {
"path": "apps/sales/webpack.config.js"
}
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "apps/sales/src/environments/environment.ts",
"with": "apps/sales/src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "25kb"
}
],
"serviceWorker": true
},
"development": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "25kb"
}
]
}
}
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"options": {
"browserTarget": "sales:build"
},
"configurations": {
"test": {
"browserTarget": "sales:build:test"
},
"integration": {
"browserTarget": "sales:build:integration"
},
"staging": {
"browserTarget": "sales:build:staging"
},
"production": {
"browserTarget": "sales:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "sales:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/sales/src/test.ts",
"polyfills": "apps/sales/src/polyfills.ts",
"tsConfig": "apps/sales/tsconfig.spec.json",
"karmaConfig": "apps/sales/karma.conf.js",
"styles": [
"apps/sales/src/styles.scss"
],
"stylePreprocessorOptions": {
"includePaths": [
"apps/sales/src/scss"
]
},
"scripts": [],
"assets": [
"apps/sales/src/favicon.ico",
"apps/sales/src/assets",
"apps/sales/src/manifest.webmanifest"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/sales/tsconfig.app.json",
"apps/sales/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"sales-e2e": {
"root": "apps/sales-e2e/",
"projectType": "application",
"prefix": "",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "apps/sales-e2e/protractor.conf.js",
"devServerTarget": "sales:serve"
},
"configurations": {
"integration": {
"devServerTarget": "sales:serve:integration"
},
"production": {
"devServerTarget": "sales:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "apps/sales-e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"sso": {
"root": "libs/sso",
"sourceRoot": "libs/sso/src",
"projectType": "library",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "libs/sso/tsconfig.lib.json",
"project": "libs/sso/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "libs/sso/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "libs/sso/src/test.ts",
"tsConfig": "libs/sso/tsconfig.spec.json",
"karmaConfig": "libs/sso/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"libs/sso/tsconfig.lib.json",
"libs/sso/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"@swagger/availability": {
"root": "apps/swagger/availability",
"sourceRoot": "apps/swagger/availability/src",
@@ -3310,6 +3060,11 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"allowedCommonJsDependencies": [
"lodash",
"pdfjs-dist/es5/build/pdf",
"pdfjs-dist/es5/web/pdf_viewer"
],
"outputPath": "dist/isa-app",
"index": "apps/isa-app/src/index.html",
"main": "apps/isa-app/src/main.ts",
@@ -3335,13 +3090,12 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
"maximumWarning": "25kb"
}
],
"fileReplacements": [
@@ -3844,7 +3598,38 @@
}
}
}
},
"@core/toast": {
"projectType": "library",
"root": "apps/core/toast",
"sourceRoot": "apps/core/toast/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/core/toast/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/core/toast/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/core/toast/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/core/toast/src/test.ts",
"tsConfig": "apps/core/toast/tsconfig.spec.json",
"karmaConfig": "apps/core/toast/karma.conf.js"
}
}
}
}
},
},
"defaultProject": "isa-app"
}

View File

@@ -181,7 +181,8 @@ describe('ApplicationService', () => {
expect(store.dispatch).toHaveBeenCalledWith({
type: actions.patchProcess.type,
process: {
processId: process.id,
changes: {
...process,
},
});

View File

@@ -60,7 +60,7 @@ describe('applicationReducer', () => {
type: 'cart',
};
const action = actions.patchProcess({ process: { ...process, name: 'Test' } });
const action = actions.patchProcess({ processId: process.id, changes: { ...process, name: 'Test' } });
const state = applicationReducer(
{
...initialState,
@@ -81,7 +81,7 @@ describe('applicationReducer', () => {
type: 'cart',
};
const action = actions.patchProcess({ process: { ...process, id: 2 } });
const action = actions.patchProcess({ processId: process.id, changes: { ...process, id: 2 } });
const state = applicationReducer(
{
...initialState,

View File

@@ -23,7 +23,7 @@ const _applicationReducer = createReducer(
on(patchProcess, (state, { processId, changes }) => {
const processes = state.processes.map((process) => {
if (process.id === processId) {
return { ...process, ...changes };
return { ...process, ...changes, id: processId };
}
return process;
});

View File

@@ -1,7 +1,6 @@
import { Injectable } from '@angular/core';
import { CacheOptions } from './cache-options';
import { Cached } from './cached';
import { sha1 } from 'object-hash';
@Injectable({
providedIn: 'root',
@@ -60,7 +59,15 @@ export class CacheService {
}
private getKey(token: Object) {
return sha1(token);
return this.hash(JSON.stringify(token));
}
private hash(data: string): string {
let hash = 0;
for (let i = 0; i < data.length; i++) {
hash = data.charCodeAt(i) + ((hash << 5) - hash);
}
return hash.toString(16);
}
private serialize(data: Cached): string {

25
apps/core/toast/README.md Normal file
View File

@@ -0,0 +1,25 @@
# Toast
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.0.
## Code scaffolding
Run `ng generate component component-name --project toast` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project toast`.
> Note: Don't forget to add `--project toast` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build toast` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build toast`, go to the dist folder `cd dist/toast` and run `npm publish`.
## Running unit tests
Run `ng test toast` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View File

@@ -9,23 +9,32 @@ module.exports = function (config) {
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
jasmineHtmlReporter: {
suppressAll: true, // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, '../../../coverage/core/toast'),
subdir: '.',
reporters: [{ type: 'html' }, { type: 'text-summary' }],
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['ChromeHeadless'],
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/core/toast",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@core/toast",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^12.2.0",
"@angular/core": "^12.2.0"
},
"dependencies": {
"tslib": "^2.3.0"
}
}

View File

@@ -1,3 +1,3 @@
// start:ng42.barrel
export * from './focus.directive';
export * from './toast-animation';
// end:ng42.barrel

View File

@@ -0,0 +1,14 @@
import { AnimationTriggerMetadata, trigger, state, transition, style, animate } from '@angular/animations';
export const slideAnimationTime = 150;
export const toastAnimations: {
readonly slideToast: AnimationTriggerMetadata;
} = {
slideToast: trigger('slideAnimation', [
state('default', style({ transform: 'translateY(0%)' })),
transition('void => *', [style({ transform: 'translateY(-100%)' }), animate(`${slideAnimationTime}ms ease-in`)]),
transition('default => closing', animate(`${slideAnimationTime}ms ease-in`, style({ transform: 'translateY(-100%)' }))),
]),
};
export type ToastAnimationState = 'default' | 'closing';

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './toast';
export * from './toast-ref';
// end:ng42.barrel

View File

@@ -0,0 +1,17 @@
import { OverlayRef } from '@angular/cdk/overlay';
export class ToastRef {
constructor(private readonly _overlay: OverlayRef) {}
close() {
this._overlay.dispose();
}
isVisible() {
return this._overlay && this._overlay.overlayElement;
}
getPosition() {
return this._overlay.overlayElement.getBoundingClientRect();
}
}

View File

@@ -0,0 +1,11 @@
import { TemplateRef } from '@angular/core';
export interface Toast {
title?: string;
text?: string;
timer?: number;
position?: 'top-left' | 'top' | 'top-right' | 'bottom-right' | 'bottom' | 'bottom-left';
size?: 'width-full' | 'content';
template?: TemplateRef<any>; // For rendering dynamic content
templateContext?: {}; // For rendering dynamic content
}

View File

@@ -0,0 +1,8 @@
// start:ng42.barrel
export * from './toast.component';
export * from './toast.module';
export * from './toast.service';
export * from './defs';
export * from './animation';
export * from './tokens';
// end:ng42.barrel

View File

@@ -0,0 +1,15 @@
<div class="toast-main" [style.width]="width" [@slideAnimation]="{ value: animationState }" (@slideAnimation.done)="onSlideFinished()">
<button class="absolute top-2 right-2 p-6 border-none bg-transparent" (click)="close()">
<ui-icon icon="close" size="20px"></ui-icon>
</button>
<div class="toast-content flex flex-col justify-center items-center">
<h1 class="text-card-sub font-bold text-center py-3 whitespace-pre-wrap">{{ data.title }}</h1>
<ng-container *ngIf="data.text; else templateRef">
<p class="block text-base overflow-y-hidden pb-3 text-center overflow-x-hidden">{{ data.text }}</p>
</ng-container>
</div>
</div>
<ng-template #templateRef>
<ng-container *ngTemplateOutlet="data.template; context: data.templateContext"> </ng-container>
</ng-template>

View File

@@ -0,0 +1,12 @@
.toast-main {
@apply block relative mx-auto box-border text-white p-4;
background-color: var(--toast-background);
min-width: 18.75rem;
max-width: calc(100vw - 2rem);
min-height: 5rem;
border-radius: 25px;
}
.toast-content {
min-height: 3rem;
}

View File

@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ToastComponent } from './toast.component';
describe('ToastComponent', () => {
let component: ToastComponent;
let fixture: ComponentFixture<ToastComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ToastComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ToastComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,48 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { toastAnimations, ToastAnimationState, slideAnimationTime } from './animation';
import { Toast, ToastRef } from './defs';
import { TOAST_CONFIG_TOKEN } from './tokens';
@Component({
selector: 'lib-toast',
templateUrl: 'toast.component.html',
styleUrls: ['toast.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [toastAnimations.slideToast],
})
export class ToastComponent implements OnInit, OnDestroy {
timeoutRef?: any;
animationState: ToastAnimationState = 'default';
width = '55.25rem';
constructor(
@Inject(TOAST_CONFIG_TOKEN) public readonly data: Toast,
private readonly _ref: ToastRef,
private readonly _cdr: ChangeDetectorRef
) {}
ngOnInit(): void {
if (this.data?.size) {
this.width = this.data?.size === 'width-full' ? '100vw' : '55.25rem';
}
this.timeoutRef = setTimeout(() => {
this.close();
this._cdr.markForCheck();
}, slideAnimationTime + (this.data.timer ?? 5000));
}
ngOnDestroy() {
clearTimeout(this.timeoutRef);
}
close() {
this.animationState = 'closing';
}
onSlideFinished() {
if (this.animationState === 'closing') {
this._ref.close();
}
}
}

View File

@@ -0,0 +1,12 @@
import { OverlayModule } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { UiIconModule } from '@ui/icon';
import { ToastComponent } from './toast.component';
@NgModule({
declarations: [ToastComponent],
imports: [CommonModule, OverlayModule, UiIconModule],
exports: [ToastComponent],
})
export class ToastModule {}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ToastService } from './toast.service';
describe('ToastService', () => {
let service: ToastService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ToastService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,79 @@
import { Overlay } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { Injectable, Injector } from '@angular/core';
import { Toast, ToastRef } from './defs';
import { ToastComponent } from './toast.component';
import { TOAST_CONFIG_TOKEN } from './tokens';
@Injectable({
providedIn: 'root',
})
export class ToastService {
private _lastToastRef: ToastRef;
get lastToastRef() {
return this._lastToastRef;
}
set lastToastRef(toastRef: ToastRef) {
this._lastToastRef = toastRef;
}
constructor(private readonly _overlay: Overlay, private readonly _injector: Injector) {}
create(data: Toast) {
const positionStrategy = this.getPositionStrategy(data);
const overlayRef = this._overlay.create({ positionStrategy });
this.lastToastRef = new ToastRef(overlayRef);
const injector = this.getInjector(data, this.lastToastRef);
const toastPortal = new ComponentPortal(ToastComponent, null, injector);
overlayRef.attach(toastPortal);
return this.lastToastRef;
}
getInjector(data: Toast, ref: ToastRef) {
return Injector.create({
parent: this._injector,
providers: [
{ provide: TOAST_CONFIG_TOKEN, useValue: data },
{ provide: ToastRef, useValue: ref },
],
});
}
getPositionStrategy(data: Toast) {
switch (data?.position) {
case 'top':
return this._overlay.position().global().top(this.getNextPosition()).centerHorizontally();
case 'top-left':
return this._overlay.position().global().top(this.getNextPosition()).left('1rem');
case 'top-right':
return this._overlay.position().global().top(this.getNextPosition()).right('1rem');
case 'bottom':
return this._overlay.position().global().bottom(this.getNextPosition(true)).centerHorizontally();
case 'bottom-left':
return this._overlay.position().global().bottom(this.getNextPosition(true)).left('1rem');
case 'bottom-right':
return this._overlay.position().global().bottom(this.getNextPosition(true)).right('1rem');
default:
return this._overlay.position().global().top(this.getNextPosition()).centerHorizontally();
}
}
getNextPosition(fromBottom?: boolean) {
const lastToastIsVisible = this.lastToastRef && this.lastToastRef.isVisible();
let position = fromBottom ? 6 : 9;
if (lastToastIsVisible && fromBottom) {
position = (window.innerHeight - this.lastToastRef.getPosition().bottom + this.lastToastRef.getPosition().height + 16) / 16;
} else if (lastToastIsVisible) {
position = (this.lastToastRef.getPosition().bottom + 16) / 16;
}
return position + 'rem';
}
}

View File

@@ -0,0 +1,4 @@
import { InjectionToken } from '@angular/core';
import { Toast } from './defs';
export const TOAST_CONFIG_TOKEN = new InjectionToken<Toast>('TOAST_DATA');

View File

@@ -1,5 +1,5 @@
/*
* Public API Surface of sso
* Public API Surface of toast
*/
export * from './lib';

View File

@@ -1,15 +1,24 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'core-js/es7/reflect';
import 'zone.js';
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
declare const require: any;
declare const require: {
context(
path: string,
deep?: boolean,
filter?: RegExp
): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { teardown: { destroyAfterEach: true } });
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.

View File

@@ -0,0 +1,20 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -1,9 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"enableIvy": false
"compilationMode": "partial"
}
}
}

View File

@@ -0,0 +1,17 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { ItemDTO, SearchService } from '@swagger/cat';
import { ItemDTO } from '@swagger/cat';
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, StoreCheckoutService, SupplierDTO } from '@swagger/checkout';
import { combineLatest, Observable, of } from 'rxjs';
import {
@@ -37,7 +37,7 @@ export class DomainAvailabilityService {
@memorize()
getTakeAwaySupplier(): Observable<SupplierDTO> {
return this.storeCheckoutService.StoreCheckoutGetSuppliers({}).pipe(
map(({ result }) => result.find((supplier) => supplier?.supplierNumber === 'F')),
map(({ result }) => result?.find((supplier) => supplier?.supplierNumber === 'F')),
shareReplay()
);
}
@@ -125,8 +125,13 @@ export class DomainAvailabilityService {
getTakeAwayAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
return this.getCurrentStock().pipe(
switchMap((s) => this._stock.StockInStock({ articleIds: [item.itemId], stockId: s.id })),
withLatestFrom(this.getTakeAwaySupplier(), this.getCurrentBranch()),
switchMap((s) =>
combineLatest([
this._stock.StockInStock({ articleIds: [item.itemId], stockId: s.id }),
this.getTakeAwaySupplier(),
this.getCurrentBranch(),
])
),
map(([response, supplier, branch]) => {
const price = item?.price;
return this._mapToTakeAwayAvailability({ response, supplier, branch, quantity, price });

View File

@@ -877,6 +877,10 @@ export class DomainCheckoutService {
return this.store.select(DomainCheckoutSelectors.selectOrders);
}
removeAllOrders() {
this.store.dispatch(DomainCheckoutActions.removeAllOrders());
}
setSpecialComment({ processId, agentComment }: { processId: number; agentComment: string }) {
this.store.dispatch(DomainCheckoutActions.setSpecialComment({ processId, agentComment }));
}

View File

@@ -52,6 +52,8 @@ export const removeProcess = createAction(`${prefix} Remove Process`, props<{ pr
export const setOrders = createAction(`${prefix} Add Orders`, props<{ orders: DisplayOrderDTO[] }>());
export const removeAllOrders = createAction(`${prefix} Remove All Orders`);
export const setBuyer = createAction(`${prefix} Set Buyer`, props<{ processId: number; buyer: BuyerDTO }>());
export const setPayer = createAction(`${prefix} Set Payer`, props<{ processId: number; payer: PayerDTO }>());

View File

@@ -72,7 +72,11 @@ const _domainCheckoutReducer = createReducer(
return storeCheckoutAdapter.setOne(entity, s);
}),
on(DomainCheckoutActions.removeProcess, (s, { processId }) => storeCheckoutAdapter.removeOne(processId, s)),
on(DomainCheckoutActions.setOrders, (s, { orders }) => ({ ...s, orders })),
on(DomainCheckoutActions.setOrders, (s, { orders }) => ({ ...s, orders: [...s.orders, ...orders] })),
on(DomainCheckoutActions.removeAllOrders, (s) => ({
...s,
orders: [],
})),
on(DomainCheckoutActions.setOlaError, (s, { processId, olaErrorIds }) => {
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.olaErrorIds = olaErrorIds;

View File

@@ -1,4 +1,6 @@
// start:ng42.barrel
export * from './info-feed-item';
export * from './info-feed';
export * from './kpi-feed-item';
export * from './kpi-feed';
export * from './products-feed';

View File

@@ -0,0 +1,4 @@
export interface InfoFeedItem {
heading: string;
text: string;
}

View File

@@ -0,0 +1,7 @@
import { FeedDTO } from '@swagger/isa';
import { InfoFeedItem } from './info-feed-item';
export interface InfoFeed extends FeedDTO {
type: 'info';
items: InfoFeedItem[];
}

View File

@@ -36,4 +36,5 @@ export * from './shipping-note.action-handler';
export * from './supplier-temporarily-out-of-stock.action-handler copy';
export * from './collect-on-deliverynote.action-handler';
export * from './create-returnitem.action-handler';
export * from './print-pricediffqrcodelabel.action-handler';
// end:ng42.barrel

View File

@@ -0,0 +1,80 @@
import { Injectable } from '@angular/core';
import { ActionHandler } from '@core/command';
import { OrderItemsContext } from './order-items.context';
import { DomainPrinterService } from '@domain/printer';
import { ConfirmModalData, UiConfirmModalComponent, UiErrorModalComponent, UiModalService } from '@ui/modal';
import { PriceQRCodeDTO } from '@swagger/print';
import { PrintModalComponent } from '@modal/printer';
@Injectable()
export class PrintPriceDiffQrCodeLabelActionHandler extends ActionHandler<OrderItemsContext> {
constructor(private uiModal: UiModalService, private domainPrinterService: DomainPrinterService) {
super('PRINT_PRICEDIFFQRCODELABEL');
}
async handler(data: OrderItemsContext): Promise<OrderItemsContext> {
await this.print(data);
const result = await this.uiModal
.open({
content: UiConfirmModalComponent,
title: 'Bestellpreis abweichend zum Kassenpreis',
data: {
message:
'Der gedruckte QR Code enthält den richtigen\nBestellpreis. Bitte überkleben Sie mit dem Ausdruck\ndas Etikett auf dem Artikel.',
confirmLabel: 'In Ordnung',
rejectLabel: 'QR Code erneut drucken',
} as ConfirmModalData,
})
.afterClosed$.toPromise();
if (result?.data === false) {
await this.print(data);
}
return data;
}
private async print(data: OrderItemsContext) {
await this.uiModal
.open({
content: PrintModalComponent,
config: { showScrollbarY: false },
data: {
printImmediately: true,
printerType: 'Label',
print: async (printer) => {
return await this.printQrCode(data, printer);
},
},
})
.afterClosed$.toPromise();
}
private async printQrCode(data: OrderItemsContext, printer: string) {
try {
const payload = data.items.map((item) => {
return {
compartmentCode: data.compartmentCode,
compartmentInfo: data.compartmentInfo,
ean: item.product?.ean,
price: item.retailPrice?.value,
title: item.product?.name,
copies: data.itemQuantity?.get(item.orderItemSubsetId) || item?.quantity,
} as PriceQRCodeDTO;
});
const response = await this.domainPrinterService.printQrCode({ printer, data: payload }).toPromise();
if (!!response?.error) {
this.uiModal.open({
content: UiErrorModalComponent,
title: 'Fehler beim Drucken des QR Code',
data: { message: response?.message },
});
}
return response;
} catch (err) {
this.uiModal.open({ content: UiErrorModalComponent, title: 'Fehler beim Drucken des QR Code', data: err });
}
}
}

View File

@@ -5,10 +5,16 @@ import { UiModalService } from '@ui/modal';
import { ReorderModalComponent, ReorderResult } from '@modal/reorder';
import { DomainCheckoutService } from '@domain/checkout';
import { AvailabilityDTO2, OrderItemListItemDTO } from '@swagger/oms';
import { ToastService } from '@core/toast';
@Injectable()
export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
constructor(private _command: CommandService, private _domainCheckoutService: DomainCheckoutService, private _uiModal: UiModalService) {
constructor(
private _command: CommandService,
private _domainCheckoutService: DomainCheckoutService,
private _uiModal: UiModalService,
private _toastService: ToastService
) {
super('REORDER');
}
@@ -20,7 +26,7 @@ export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
content: ReorderModalComponent,
title: 'Artikel nachbestellen',
data: {
item: orderItem,
item: { ...orderItem, quantity: data.itemQuantity?.get(orderItem.orderItemSubsetId) ?? orderItem.quantity },
showReasons: true,
},
})
@@ -30,6 +36,8 @@ export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
const reorderResult = await this.reorder(result.data.item, result.data.availability, result.data.comment);
const resItem = reorderResult.item1;
this.createToast(result?.data?.comment);
updatedItems.push({
...result.data.item,
orderItemSubsetId: resItem.id,
@@ -57,6 +65,27 @@ export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
return { ...data, items: updatedItems };
}
createToast(comment?: string) {
let text: string = '';
switch (comment) {
case 'Artikel unverkäuflich, (und physisch in der Filiale vorhanden)':
text = 'Der Beschädigte Artikel wurde auf die Remi-Liste gesetzt.';
break;
case 'Falscher Titel geliefert (richtiges Etikett)':
text = 'Die Falschlieferung wurde auf die Remi-Liste gesetzt.';
break;
default:
text = '';
break;
}
this._toastService.create({
title: 'Artikel wurde nachbestellt',
text,
});
}
async reorder(orderItem: OrderItemListItemDTO, availability: AvailabilityDTO2, comment: string) {
return await this._domainCheckoutService
.reorder(orderItem.orderId, orderItem.orderItemId, orderItem.orderItemSubsetId, {

View File

@@ -25,24 +25,42 @@ describe('DomainGoodsInServie', () => {
expect(spectator.service).toBeDefined();
});
describe('search', () => {
describe('searchWareneingang', () => {
it('should call AbholfachService.AbholfachWareneingang', () => {
const queryTokenDto: QueryTokenDTO = {};
abholfachServiceMock.AbholfachWareneingang.and.returnValue(hot('-a|', { a: {} }));
expect(spectator.service.search(queryTokenDto)).toBeObservable(cold('-a|', { a: {} }));
expect(spectator.service.searchWareneingang(queryTokenDto)).toBeObservable(cold('-a|', { a: {} }));
expect(abholfachServiceMock.AbholfachWareneingang).toHaveBeenCalledWith(queryTokenDto);
});
});
describe('complete', () => {
describe('searchWarenausgabe', () => {
it('should call AbholfachService.AbholfachWarenausgabe', () => {
const queryTokenDto: QueryTokenDTO = {};
abholfachServiceMock.AbholfachWarenausgabe.and.returnValue(hot('-a|', { a: {} }));
expect(spectator.service.searchWarenausgabe(queryTokenDto)).toBeObservable(cold('-a|', { a: {} }));
expect(abholfachServiceMock.AbholfachWarenausgabe).toHaveBeenCalledWith(queryTokenDto);
});
});
describe('wareneingangComplete', () => {
it('should call AbholfachService.AbholfachWareneingangAutocomplete', () => {
const autocompleteToken: AutocompleteTokenDTO = {};
abholfachServiceMock.AbholfachWareneingangAutocomplete.and.returnValue(hot('-a|', { a: {} }));
expect(spectator.service.complete(autocompleteToken)).toBeObservable(cold('-a|', { a: {} }));
expect(spectator.service.wareneingangComplete(autocompleteToken)).toBeObservable(cold('-a|', { a: {} }));
expect(abholfachServiceMock.AbholfachWareneingangAutocomplete).toHaveBeenCalledWith(autocompleteToken);
});
});
describe('warenausgabeComplete', () => {
it('should call AbholfachService.AbholfachWarenausgabeAutocomplete', () => {
const autocompleteToken: AutocompleteTokenDTO = {};
abholfachServiceMock.AbholfachWarenausgabeAutocomplete.and.returnValue(hot('-a|', { a: {} }));
expect(spectator.service.warenausgabeComplete(autocompleteToken)).toBeObservable(cold('-a|', { a: {} }));
expect(abholfachServiceMock.AbholfachWarenausgabeAutocomplete).toHaveBeenCalledWith(autocompleteToken);
});
});
describe('list', () => {
it('should call search with queryToken', () => {
const queryToken: QueryTokenDTO = {
@@ -58,7 +76,7 @@ describe('DomainGoodsInServie', () => {
spyOn(dateAdapter, 'today').and.returnValue(new Date(2021, 5, 7));
spyOn(spectator.service, 'search').and.returnValue(hot('-a|', { a: {} }));
expect(spectator.service.list()).toBeObservable(cold('-a|', { a: {} }));
expect(spectator.service.search).toHaveBeenCalledWith(queryToken);
expect(spectator.service.searchWareneingang).toHaveBeenCalledWith(queryToken);
});
});

View File

@@ -7,15 +7,23 @@ import { shareReplay } from 'rxjs/operators';
export class DomainGoodsService {
constructor(private abholfachService: AbholfachService, private dateAdapter: DateAdapter) {}
search(queryToken: QueryTokenDTO) {
searchWareneingang(queryToken: QueryTokenDTO) {
return this.abholfachService.AbholfachWareneingang(queryToken);
}
complete(autocompleteToken: AutocompleteTokenDTO) {
searchWarenausgabe(queryToken: QueryTokenDTO) {
return this.abholfachService.AbholfachWarenausgabe(queryToken);
}
wareneingangComplete(autocompleteToken: AutocompleteTokenDTO) {
return this.abholfachService.AbholfachWareneingangAutocomplete(autocompleteToken);
}
getItemByOrderNumber(orderNumber: string) {
warenausgabeComplete(autocompleteToken: AutocompleteTokenDTO) {
return this.abholfachService.AbholfachWarenausgabeAutocomplete(autocompleteToken);
}
getWareneingangItemByOrderNumber(orderNumber: string) {
return this.abholfachService.AbholfachWareneingang({
filter: { all_branches: 'true', archive: 'true' },
input: {
@@ -24,8 +32,17 @@ export class DomainGoodsService {
});
}
getItemByCompartment(compartmentCode: string) {
return this.abholfachService.AbholfachWareneingang({
getWarenausgabeItemByOrderNumber(orderNumber: string) {
return this.abholfachService.AbholfachWarenausgabe({
filter: { all_branches: 'true', archive: 'true' },
input: {
qs: orderNumber,
},
});
}
getWarenausgabeItemByCompartment(compartmentCode: string) {
return this.abholfachService.AbholfachWarenausgabe({
filter: { all_branches: 'true', archive: 'true' },
input: {
qs: compartmentCode,
@@ -33,12 +50,12 @@ export class DomainGoodsService {
});
}
getItemByCustomerNumber(customerNumber: string) {
getWareneingangItemByCustomerNumber(customerNumber: string) {
// Suche anhand der Kundennummer mit Status Bestellt, nachbestellt, eingetroffen, weitergeleitet intern
return this.abholfachService.AbholfachWareneingang({
filter: { orderitemprocessingstatus: '16;128;8192;1048576' },
input: {
qs: customerNumber,
customer_name: customerNumber,
},
});
}
@@ -56,7 +73,7 @@ export class DomainGoodsService {
skip: 0,
take: 20,
};
return this.search(queryToken);
return this.searchWareneingang(queryToken);
}
@memorize()

View File

@@ -5,8 +5,10 @@ import {
CheckoutPrintService,
ItemDTO,
OMSPrintService,
PriceQRCodeDTO,
PrintRequestOfIEnumerableOfItemDTO,
PrintRequestOfIEnumerableOfLong,
PrintRequestOfIEnumerableOfPriceQRCodeDTO,
PrintService,
ResponseArgs,
} from '@swagger/print';
@@ -228,4 +230,8 @@ export class DomainPrinterService {
})
);
}
printQrCode(data: PrintRequestOfIEnumerableOfPriceQRCodeDTO) {
return this.oMSPrintService.OMSPrintPriceQRCode(data);
}
}

View File

@@ -12,6 +12,7 @@ import {
ReceiptDTO,
ReturnDTO,
ReturnQueryTokenDTO,
BatchResponseArgsOfReturnItemDTOAndReturnItemDTO,
} from '@swagger/remi';
import { memorize } from '@utils/common';
import { Observable, of, throwError } from 'rxjs';
@@ -140,17 +141,21 @@ export class DomainRemissionService {
)
);
} else if (arg.source === 'Abteilungsremission') {
result$ = this.getCurrentStock().pipe(
switchMap((stock) =>
this.getItemsForAbteilungsremission({
queryToken: {
stockId: stock.id,
supplierId: arg.supplierId,
...arg.queryToken,
},
})
)
);
if (!arg.queryToken.filter.abteilungen) {
result$ = of({ hits: 0, result: [] });
} else {
result$ = this.getCurrentStock().pipe(
switchMap((stock) =>
this.getItemsForAbteilungsremission({
queryToken: {
stockId: stock.id,
supplierId: arg.supplierId,
...arg.queryToken,
},
})
)
);
}
} else {
this._logger.error('Unbekannte Quelle', arg.source);
return throwError(new Error(`Unknown source: ${arg.source}`));
@@ -196,7 +201,7 @@ export class DomainRemissionService {
);
}
getStockInformation(items: RemissionListItem[]) {
getStockInformation(items: RemissionListItem[], recalculate: boolean = false) {
return this.getCurrentStock().pipe(
switchMap((stock) =>
this._stockService
@@ -226,14 +231,13 @@ export class DomainRemissionService {
let { remainingQuantity, remissionQuantity } = item;
if (!remissionQuantity) {
if (!remissionQuantity || recalculate) {
remissionQuantity = inStock - (remainingQuantity || 0);
if (remissionQuantity < 0) {
remissionQuantity = 0;
}
}
if (!remainingQuantity) {
if (!remainingQuantity || recalculate) {
remainingQuantity = inStock - (remissionQuantity || 0);
if (remainingQuantity < 0) {
remainingQuantity = 0;
@@ -295,6 +299,12 @@ export class DomainRemissionService {
);
}
canAddReturnItem(item: ReturnItemDTO): Observable<BatchResponseArgsOfReturnItemDTOAndReturnItemDTO> {
return this._remiService.RemiCanAddReturnItem({
data: [item],
});
}
async createReturn(supplierId: number, returnGroup?: string): Promise<ReturnDTO> {
const response = await this._returnService
.ReturnCreateReturn({

View File

@@ -1,13 +1,14 @@
import { Inject, Injectable, InjectionToken } from '@angular/core';
import { SignalrHub, SignalRHubOptions } from '@core/signalr';
import { merge, of } from 'rxjs';
import { filter, shareReplay, tap } from 'rxjs/operators';
import { BehaviorSubject, merge, of } from 'rxjs';
import { filter, map, shareReplay, tap, withLatestFrom } from 'rxjs/operators';
import { EnvelopeDTO, MessageBoardItemDTO } from './defs';
export const NOTIFICATIONS_HUB_OPTIONS = new InjectionToken<SignalRHubOptions>('hub.notifications.options');
@Injectable()
export class NotificationsHub extends SignalrHub {
updateNotification$ = new BehaviorSubject<MessageBoardItemDTO>(undefined);
constructor(@Inject(NOTIFICATIONS_HUB_OPTIONS) options: SignalRHubOptions) {
super(options);
}
@@ -16,6 +17,14 @@ export class NotificationsHub extends SignalrHub {
of(this._getNotifications()).pipe(filter((f) => !!f)),
this.listen<EnvelopeDTO<MessageBoardItemDTO[]>>('messageBoard')
).pipe(
withLatestFrom(this.updateNotification$),
map(([d, update]) => {
const data = d;
if (update && !!data && !data?.data?.find((message) => message?.category === 'ISA-Update')) {
data.data.push(update);
}
return data;
}),
tap((data) => this._storeNotifactions(data)),
shareReplay(1)
);
@@ -33,4 +42,13 @@ export class NotificationsHub extends SignalrHub {
}
return undefined;
}
updateNotification() {
this.updateNotification$.next({
category: 'ISA-Update',
type: 'update',
headline: 'Update Benachrichtigung',
text: 'Es steht eine aktuellere Version der ISA bereit. Bitte aktualisieren Sie die Anwendung.',
});
}
}

View File

@@ -7,11 +7,11 @@ import {
CanActivateCustomerWithProcessIdGuard,
CanActivateGoodsInGuard,
CanActivateGoodsOutGuard,
CanActivateGoodsOutWithProcessIdGuard,
CanActivateProductGuard,
CanActivateProductWithProcessIdGuard,
CanActivateRemissionGuard,
CanActivateTaskCalendarGuard,
InitStoreGuard,
IsAuthenticatedGuard,
} from './guards';
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
@@ -21,7 +21,10 @@ import { TokenLoginComponent, TokenLoginModule } from './token-login';
const routes: Routes = [
{
path: 'login',
children: [{ path: ':token', component: TokenLoginComponent }],
children: [
{ path: ':token', component: TokenLoginComponent },
{ path: '**', redirectTo: 'kunde', pathMatch: 'full' },
],
},
{
path: '',
@@ -29,12 +32,11 @@ const routes: Routes = [
children: [
{
path: '',
canActivate: [InitStoreGuard],
canActivate: [],
children: [
{
path: 'kunde',
component: ShellComponent,
canActivate: [InitStoreGuard],
children: [
{
path: 'dashboard',
@@ -77,6 +79,12 @@ const routes: Routes = [
loadChildren: () => import('@page/goods-out').then((m) => m.GoodsOutModule),
canActivate: [CanActivateGoodsOutGuard],
},
{
path: ':processId/goods/out',
loadChildren: () => import('@page/goods-out').then((m) => m.GoodsOutModule),
canActivate: [CanActivateGoodsOutWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{ path: '**', redirectTo: 'dashboard', pathMatch: 'full' },
],
resolve: { section: CustomerSectionResolver },

View File

@@ -1,6 +1,6 @@
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { ActionReducer, INIT, MetaReducer, StoreModule } from '@ngrx/store';
import { ActionReducer, MetaReducer, StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { storeFreeze } from 'ngrx-store-freeze';
import packageInfo from 'package';
@@ -11,7 +11,7 @@ import { RootState } from './store/root.state';
export function storeInLocalStorage(reducer: ActionReducer<any>): ActionReducer<any> {
return function (state, action) {
if (action.type === INIT) {
if (action.type === 'HYDRATE') {
const initialState = RootStateService.LoadFromLocalStorage();
if (initialState?.version === packageInfo.version) {

View File

@@ -6,17 +6,22 @@ import { ApplicationService } from '@core/application';
import { of } from 'rxjs';
import { Renderer2 } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ServiceWorkerModule, SwUpdate } from '@angular/service-worker';
import { NotificationsHub } from '@hub/notifications';
import { discardPeriodicTasks, fakeAsync, flush, tick } from '@angular/core/testing';
describe('AppComponent', () => {
let spectator: Spectator<AppComponent>;
let config: SpyObject<Config>;
let renderer: SpyObject<Renderer2>;
let applicationServiceMock: SpyObject<ApplicationService>;
let notificationsHubMock: SpyObject<NotificationsHub>;
let swUpdateMock: SpyObject<SwUpdate>;
const createComponent = createComponentFactory({
component: AppComponent,
imports: [CommonModule, RouterTestingModule],
providers: [],
mocks: [Config],
mocks: [Config, SwUpdate],
});
beforeEach(() => {
@@ -25,6 +30,10 @@ describe('AppComponent', () => {
applicationServiceMock.getActivatedProcessId$.and.returnValue(of(undefined));
renderer = jasmine.createSpyObj('Renderer2', ['addClass', 'removeClass']);
notificationsHubMock = createSpyObject(NotificationsHub);
notificationsHubMock.notifications$ = of({});
swUpdateMock = createSpyObject(SwUpdate);
spectator = createComponent({
providers: [
{ provide: ApplicationService, useValue: applicationServiceMock },
@@ -32,6 +41,8 @@ describe('AppComponent', () => {
provide: Renderer2,
useValue: renderer,
},
{ provide: NotificationsHub, useValue: notificationsHubMock },
{ provide: SwUpdate, useValue: swUpdateMock },
],
});
config = spectator.inject(Config);
@@ -77,13 +88,48 @@ describe('AppComponent', () => {
});
});
// describe('sectionChangeHandler', () => {
// fit('should add class customer and remove class branch from body when section is customer', () => {
// spectator.component.sectionChangeHandler('customer');
// console.log(renderer);
// console.log('expect');
// expect(renderer.removeClass).toHaveBeenCalled();
// expect(renderer.addClass).toHaveBeenCalled();
// --------------------------------------------------------
// Unit Tests Implementation for Angular Version 13.x.x
// describe('updateClient()', () => {
// it('should call checkForUpdate() if SwUpdate.isEnabled is True', () => {
// spyOn(spectator.component, 'checkForUpdate');
// spyOn(spectator.component, 'initialCheckForUpdate');
// (swUpdateMock as any).isEnabled = true;
// spectator.component.updateClient();
// expect(spectator.component.initialCheckForUpdate).toHaveBeenCalled();
// expect(spectator.component.checkForUpdate).toHaveBeenCalled();
// });
// it('should not call checkForUpdate() if SwUpdate.isEnabled is False', () => {
// spyOn(spectator.component, 'checkForUpdate');
// spyOn(spectator.component, 'initialCheckForUpdate');
// (swUpdateMock as any).isEnabled = false;
// spectator.component.updateClient();
// expect(spectator.component.initialCheckForUpdate).not.toHaveBeenCalled();
// expect(spectator.component.checkForUpdate).not.toHaveBeenCalled();
// });
// });
// describe('checkForUpdate', () => {
// it('should call swUpdate.checkForUpdate() and notifications.updateNotification() every second', fakeAsync(() => {
// swUpdateMock.checkForUpdate.and.returnValue(Promise.resolve());
// spectator.component.checkForUpdates = 1000;
// spectator.component.checkForUpdate();
// spectator.detectChanges();
// tick(1100);
// expect(notificationsHubMock.updateNotification).toHaveBeenCalled();
// discardPeriodicTasks();
// }));
// });
// describe('initialCheckForUpdate', () => {
// it('should call swUpdate.checkForUpdate()', () => {
// swUpdateMock.checkForUpdate.and.returnValue(new Promise(undefined));
// spectator.component.initialCheckForUpdate();
// expect(swUpdateMock.checkForUpdate).toHaveBeenCalled();
// });
// });
});

View File

@@ -1,9 +1,14 @@
import { DOCUMENT } from '@angular/common';
import { Component, Inject, OnInit, Renderer2 } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { SwUpdate, UpdateAvailableEvent } from '@angular/service-worker';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { NotificationsHub } from '@hub/notifications';
import packageInfo from 'package';
import { interval, Observable, Subscription } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Platform } from '@angular/cdk/platform';
@Component({
selector: 'app-root',
@@ -11,17 +16,37 @@ import packageInfo from 'package';
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
private _checkForUpdates: number = this._config.get('checkForUpdates');
updateAvailableObs: Observable<UpdateAvailableEvent>;
get checkForUpdates(): number {
return this._checkForUpdates;
}
// For Unit Testing
set checkForUpdates(time: number) {
this._checkForUpdates = time;
}
subscriptions = new Subscription();
constructor(
private readonly _config: Config,
private readonly _title: Title,
private readonly _appService: ApplicationService,
@Inject(DOCUMENT) private readonly _document: Document,
private readonly _renderer: Renderer2
) {}
private readonly _renderer: Renderer2,
private readonly _swUpdate: SwUpdate,
private readonly _notifications: NotificationsHub,
private readonly _platform: Platform
) {
this.updateClient();
}
ngOnInit() {
this.setTitle();
this.logVersion();
this.determinePlatform();
this._appService.getSection$().subscribe(this.sectionChangeHandler.bind(this));
}
@@ -34,6 +59,18 @@ export class AppComponent implements OnInit {
console.log(`%c${this._config.get('title')}\r\nVersion: ${packageInfo.version}`, 'font-weight: bold; font-size: 20px;');
}
determinePlatform() {
if (this._platform.IOS && !this._platform.SAFARI) {
this._renderer.addClass(this._document.body, 'tablet');
this._renderer.addClass(this._document.body, 'tablet-native');
} else if (this._platform.IOS && this._platform.SAFARI) {
this._renderer.addClass(this._document.body, 'tablet');
this._renderer.addClass(this._document.body, 'tablet-browser');
} else if (this._platform.isBrowser) {
this._renderer.addClass(this._document.body, 'desktop');
}
}
sectionChangeHandler(section: string) {
if (section === 'customer') {
this._renderer.removeClass(this._document.body, 'branch');
@@ -43,4 +80,74 @@ export class AppComponent implements OnInit {
this._renderer.addClass(this._document.body, 'branch');
}
}
// --------------------------------------------------------
// Implementation before Angular Version 13.x.x
async updateClient() {
if (!this._swUpdate.isEnabled) {
return;
}
await this.initialCheckForUpdate();
this.checkForUpdateInterval();
}
checkForUpdateInterval() {
this.updateAvailableObs = this._swUpdate.available.pipe(
tap((availableEvent) => {
if (availableEvent?.current?.hash !== availableEvent?.available?.hash) {
this._notifications.updateNotification();
this.subscriptions.unsubscribe();
}
})
);
this.subscriptions.add(
interval(this._checkForUpdates).subscribe(async () => {
await this._swUpdate.checkForUpdate();
})
);
}
async initialCheckForUpdate() {
this.updateAvailableObs = this._swUpdate.available.pipe(
tap((availableEvent) => {
if (availableEvent?.current?.hash !== availableEvent?.available?.hash) {
location.reload();
}
})
);
this.subscriptions.add(this.updateAvailableObs.subscribe());
await this._swUpdate.checkForUpdate();
}
// --------------------------------------------------------
// Implementation for Angular Version 13.x.x
// updateClient() {
// if (!this._swUpdate.isEnabled) {
// return;
// }
// this.initialCheckForUpdate();
// this.checkForUpdate();
// }
// checkForUpdate() {
// interval(this._checkForUpdates).subscribe(() => {
// this._swUpdate.checkForUpdate().then((value) => {
// if (value) {
// this._notifications.updateNotification();
// }
// });
// });
// }
// initialCheckForUpdate() {
// this._swUpdate.checkForUpdate().then((value) => {
// if (value) {
// location.reload();
// }
// });
// }
}

View File

@@ -1,7 +1,8 @@
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { APP_INITIALIZER, ErrorHandler, LOCALE_ID, NgModule } from '@angular/core';
import { APP_INITIALIZER, ErrorHandler, Injector, LOCALE_ID, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { PlatformModule } from '@angular/cdk/platform';
import { Config, ConfigModule, JsonConfigLoader } from '@core/config';
import { AuthModule, AuthService } from '@core/auth';
@@ -28,14 +29,24 @@ import { CoreLoggerModule, LOG_PROVIDER } from '@core/logger';
import { IsaLogProvider } from './providers';
import { IsaErrorHandler } from './providers/isa.error-handler';
import { ScanAdapterModule } from '@adapter/scan';
import { RootStateService } from './store/root-state.service';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
export function _appInitializerFactory(config: Config, auth: AuthService) {
export function _appInitializerFactory(config: Config, auth: AuthService, injector: Injector) {
return async () => {
const statusElement = document.querySelector('#init-status');
statusElement.innerHTML = 'Konfigurationen werden geladen...';
await config.init();
statusElement.innerHTML = 'Authentifizierung wird geprüft...';
await auth.init();
if (auth.isAuthenticated()) {
statusElement.innerHTML = 'App wird initialisiert...';
const state = injector.get(RootStateService);
await state.init();
}
};
}
@@ -71,13 +82,14 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
registrationStrategy: 'registerWhenStable:30000',
}),
ScanAdapterModule.forRoot(!environment.production),
PlatformModule,
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: _appInitializerFactory,
multi: true,
deps: [Config, AuthService],
deps: [Config, AuthService, Injector],
},
{
provide: NOTIFICATIONS_HUB_OPTIONS,

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
@@ -25,11 +25,16 @@ export class CanActivateCartWithProcessIdGuard implements CanActivate {
id: +route.params.processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${processes.length + 1}`,
name: `Vorgang ${this.processNumber(processes)}`,
});
}
this._applicationService.activateProcess(+route.params.processId);
return true;
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers?.length > 0 ? Math.max(...processNumbers) + 1 : 1;
}
}

View File

@@ -1,11 +1,12 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateCustomerWithProcessIdGuard implements CanActivate {
constructor(private readonly _applicationService: ApplicationService) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService
@@ -25,11 +26,42 @@ export class CanActivateCustomerWithProcessIdGuard implements CanActivate {
id: +route.params.processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${processes.length + 1}`,
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
}
await this.removeBreadcrumbWithSameProcessId(route);
this._applicationService.activateProcess(+route.params.processId);
return true;
}
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
const crumbs = await this._breadcrumbService
.getBreadcrumbByKey$(+route.params.processId)
.pipe(first())
.toPromise();
// Entferne alle Crumbs die nichts mit der Kundensuche zu tun haben
if (crumbs.length > 1) {
const crumbsToRemove = crumbs.filter((crumb) => crumb.tags.find((tag) => tag === 'customer') === undefined);
for (const crumb of crumbsToRemove) {
await this._breadcrumbService.removeBreadcrumb(crumb.id);
}
}
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
if (!processNumbers.find((number) => number === missingNumber)) {
return missingNumber;
}
}
return Math.max(...processNumbers) + 1;
}
}

View File

@@ -1,27 +1,117 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateCustomerGuard implements CanActivate {
constructor(private readonly _applicationService: ApplicationService, private readonly _router: Router) {}
constructor(
private readonly _applicationService: ApplicationService,
private readonly _checkoutService: DomainCheckoutService,
private readonly _router: Router
) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
let lastActivatedProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
)?.id;
if (!lastActivatedProcessId) {
lastActivatedProcessId = Date.now();
await this._applicationService.createProcess({
id: lastActivatedProcessId,
type: 'cart',
section: 'customer',
name: `Vorgang ${processes.length + 1}`,
});
const lastActivatedCartCheckoutProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
)?.id;
const lastActivatedGoodsOutProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
)?.id;
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
// Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
await this.fromCartCheckoutProcess(processes, lastActivatedCartCheckoutProcessId);
return false;
} else if (!!lastActivatedGoodsOutProcessId && lastActivatedGoodsOutProcessId === activatedProcessId) {
await this.fromGoodsOutProcess(processes, lastActivatedGoodsOutProcessId);
return false;
}
if (!lastActivatedProcessId) {
await this.fromCartProcess(processes);
return false;
} else {
await this._router.navigate(['/kunde', String(lastActivatedProcessId), 'customer']);
}
await this._router.navigate(['/kunde', lastActivatedProcessId, 'customer']);
return false;
}
// Bei offener Artikelsuche/Kundensuche und Klick auf Footer Kundensuche
async fromCartProcess(processes: ApplicationProcess[]) {
const newProcessId = Date.now();
await this._applicationService.createProcess({
id: newProcessId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
await this._router.navigate(['/kunde', String(newProcessId), 'customer']);
}
// Bei offener Bestellbestätigung und Klick auf Footer Kundensuche
async fromCartCheckoutProcess(processes: ApplicationProcess[], processId: number) {
// Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
this._checkoutService.removeProcess({ processId });
// Ändere type cart-checkout zu cart
this._applicationService.patchProcess(processId, {
id: processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
data: {},
});
// Navigation
await this._router.navigate(['/kunde', String(processId), 'customer']);
}
// Bei offener Warenausgabe und Klick auf Footer Kundensuche
async fromGoodsOutProcess(processes: ApplicationProcess[], processId: number) {
const buyer = await this._checkoutService.getBuyer({ processId }).pipe(first()).toPromise();
const customerFeatures = await this._checkoutService.getCustomerFeatures({ processId }).pipe(first()).toPromise();
const name = buyer
? customerFeatures?.b2b
? buyer.organisation?.name
? buyer.organisation?.name
: buyer.lastName
: buyer.lastName
: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`;
// Ändere type goods-out zu cart
this._applicationService.patchProcess(processId, {
id: processId,
type: 'cart',
section: 'customer',
name,
});
// Navigation
await this._router.navigate(['/kunde', String(processId), 'customer']);
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
if (!processNumbers.find((number) => number === missingNumber)) {
return missingNumber;
}
}
return Math.max(...processNumbers) + 1;
}
}

View File

@@ -0,0 +1,52 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateGoodsOutWithProcessIdGuard implements CanActivate {
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService
.getProcessById$(+route.params.processId)
.pipe(first())
.toPromise();
if (!process) {
// const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
await this._applicationService.createProcess({
id: +route.params.processId,
type: 'goods-out',
section: 'customer',
name: `Warenausgabe`,
});
}
await this.removeBreadcrumbWithSameProcessId(route);
this._applicationService.activateProcess(+route.params.processId);
return true;
}
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
const crumbs = await this._breadcrumbService
.getBreadcrumbByKey$(+route.params.processId)
.pipe(first())
.toPromise();
// Entferne alle Crumbs die nichts mit der Warenausgabe zu tun haben
if (crumbs.length > 1) {
const crumbsToRemove = crumbs.filter((crumb) => crumb.tags.find((tag) => tag === 'goods-out') === undefined);
for (const crumb of crumbsToRemove) {
await this._breadcrumbService.removeBreadcrumb(crumb.id);
}
}
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers?.length > 0 ? Math.max(...processNumbers) + 1 : 1;
}
}

View File

@@ -1,24 +1,183 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateGoodsOutGuard implements CanActivate {
constructor(private readonly _applicationService: ApplicationService, private readonly _config: Config) {}
constructor(
private readonly _applicationService: ApplicationService,
private readonly _checkoutService: DomainCheckoutService,
private readonly _router: Router
) {}
// !!! Ticket #3272 Code soll vorerst bestehen bleiben. Prozess Warenausgabe soll wieder Vorgang heißen (wie aktuell im Produktiv), bis zum neuen Navigationskonzept
// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
// async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
// const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
// let lastActivatedProcessId = (
// await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
// )?.id;
// const lastActivatedCartProcessId = (
// await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
// )?.id;
// const lastActivatedCartCheckoutProcessId = (
// await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
// )?.id;
// const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
// // Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
// if (!!lastActivatedCartProcessId && lastActivatedCartProcessId === activatedProcessId) {
// await this.fromCartProcess(processes, route, lastActivatedCartProcessId);
// return false;
// } else if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
// await this.fromCartCheckoutProcess(processes, route, lastActivatedCartCheckoutProcessId);
// return false;
// }
// if (!lastActivatedProcessId) {
// await this.fromGoodsOutProcess(processes, route);
// return false;
// } else {
// await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
// }
// return false;
// }
// // Bei offener Warenausgabe und Klick auf Footer Warenausgabe
// async fromGoodsOutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
// const newProcessId = Date.now();
// await this._applicationService.createProcess({
// id: newProcessId,
// type: 'goods-out',
// section: 'customer',
// name: `Warenausgabe ${this.processNumber(processes.filter((process) => process.type === 'goods-out'))}`,
// });
// await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(newProcessId)]));
// }
// // Bei offener Artikelsuche/Kundensuche und Klick auf Footer Warenausgabe
// async fromCartProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
// // Ändere type cart zu goods-out
// this._applicationService.patchProcess(processId, {
// id: processId,
// type: 'goods-out',
// section: 'customer',
// name: `Warenausgabe ${this.processNumber(processes.filter((process) => process.type === 'goods-out'))}`,
// });
// // Navigation
// await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
// }
// // Bei offener Bestellbestätigung, Artikelsuche/Kundensuche und Klick auf Footer Warenausgabe
// async fromCartCheckoutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
// // Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
// this._checkoutService.removeProcess({ processId });
// // Ändere type cart-checkout zu goods-out
// this._applicationService.patchProcess(processId, {
// id: processId,
// type: 'goods-out',
// section: 'customer',
// name: `Warenausgabe ${this.processNumber(processes.filter((process) => process.type === 'goods-out'))}`,
// data: {},
// });
// // Navigation
// await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
// }
// !!! Ticket #3272 Code soll vorerst bestehen bleiben. Prozess Warenausgabe soll wieder Vorgang heißen (wie aktuell im Produktiv), bis zum neuen Navigationskonzept
// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService.getProcessById$(this._config.get('process.ids.goodsOut')).pipe(first()).toPromise();
if (!process) {
await this._applicationService.createProcess({
id: this._config.get('process.ids.goodsOut'),
type: 'goods-out',
section: 'customer',
name: 'Warenausgabe',
});
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
let lastActivatedProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
)?.id;
const lastActivatedCartCheckoutProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
)?.id;
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
// Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
await this.fromCartCheckoutProcess(processes, route, lastActivatedCartCheckoutProcessId);
return false;
}
this._applicationService.activateProcess(this._config.get('process.ids.goodsOut'));
return true;
if (!lastActivatedProcessId) {
await this.fromGoodsOutProcess(processes, route);
return false;
} else {
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
}
return false;
}
// Bei offener Warenausgabe und Klick auf Footer Warenausgabe
async fromGoodsOutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
const newProcessId = Date.now();
await this._applicationService.createProcess({
id: newProcessId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(newProcessId)]));
}
// Bei offener Bestellbestätigung und Klick auf Footer Warenausgabe
async fromCartCheckoutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
// Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
this._checkoutService.removeProcess({ processId });
// Ändere type cart-checkout zu goods-out
this._applicationService.patchProcess(processId, {
id: processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
data: {},
});
// Navigation
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
}
getUrlFromSnapshot(route: ActivatedRouteSnapshot, url: string[] = []): string[] {
url.push(...route.url.map((segment) => segment.path));
if (route.firstChild) {
return this.getUrlFromSnapshot(route.firstChild, url);
}
return url.filter((segment) => !!segment);
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
if (!processNumbers.find((number) => number === missingNumber)) {
return missingNumber;
}
}
return Math.max(...processNumbers) + 1;
}
}

View File

@@ -1,11 +1,12 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateProductWithProcessIdGuard implements CanActivate {
constructor(private readonly _applicationService: ApplicationService) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService
@@ -25,11 +26,45 @@ export class CanActivateProductWithProcessIdGuard implements CanActivate {
id: +route.params.processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${processes.length + 1}`,
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
}
await this.removeBreadcrumbWithSameProcessId(route);
this._applicationService.activateProcess(+route.params.processId);
return true;
}
// Fix #3292: Alle Breadcrumbs die nichts mit dem aktuellen Prozess zu tun haben, müssen removed werden
async removeBreadcrumbWithSameProcessId(route: ActivatedRouteSnapshot) {
const crumbs = await this._breadcrumbService
.getBreadcrumbByKey$(+route.params.processId)
.pipe(first())
.toPromise();
// Entferne alle Crumbs die nichts mit der Artikelsuche zu tun haben
if (crumbs.length > 1) {
const crumbsToRemove = crumbs.filter((crumb) => crumb.tags.find((tag) => tag === 'catalog') === undefined);
for (const crumb of crumbsToRemove) {
await this._breadcrumbService.removeBreadcrumb(crumb.id);
}
}
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
// Ticket #3272 Bei Klick auf "+" bzw. neuen Prozess hinzufügen soll der neue Tab immer die höchste Nummer haben (wie aktuell im Produktiv)
// ----------------------------------------------------------------------------------------------------------------------------------------
// for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
// if (!processNumbers.find((number) => number === missingNumber)) {
// return missingNumber;
// }
// }
return Math.max(...processNumbers) + 1;
}
}

View File

@@ -1,11 +1,16 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CanActivateProductGuard implements CanActivate {
constructor(private readonly _applicationService: ApplicationService, private readonly _router: Router) {}
constructor(
private readonly _applicationService: ApplicationService,
private readonly _checkoutService: DomainCheckoutService,
private readonly _router: Router
) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
@@ -13,20 +18,90 @@ export class CanActivateProductGuard implements CanActivate {
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
)?.id;
if (!lastActivatedProcessId) {
lastActivatedProcessId = Date.now();
await this._applicationService.createProcess({
id: lastActivatedProcessId,
type: 'cart',
section: 'customer',
name: `Vorgang ${processes.length + 1}`,
});
const lastActivatedCartCheckoutProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
)?.id;
const lastActivatedGoodsOutProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
)?.id;
const activatedProcessId = await this._applicationService.getActivatedProcessId$().pipe(first()).toPromise();
// Darf nur reinkommen wenn der aktuell aktive Tab ein Bestellabschluss Tab ist
if (!!lastActivatedCartCheckoutProcessId && lastActivatedCartCheckoutProcessId === activatedProcessId) {
await this.fromCartCheckoutProcess(processes, route, lastActivatedCartCheckoutProcessId);
return false;
} else if (!!lastActivatedGoodsOutProcessId && lastActivatedGoodsOutProcessId === activatedProcessId) {
await this.fromGoodsOutProcess(processes, route, lastActivatedGoodsOutProcessId);
return false;
}
if (!lastActivatedProcessId) {
await this.fromCartProcess(processes, route);
return false;
} else {
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
}
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(lastActivatedProcessId)]));
return false;
}
// Bei offener Artikelsuche/Kundensuche und Klick auf Footer Artikelsuche
async fromCartProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot) {
const newProcessId = Date.now();
await this._applicationService.createProcess({
id: newProcessId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(newProcessId)]));
}
// Bei offener Warenausgabe und Klick auf Footer Artikelsuche
async fromGoodsOutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
const buyer = await this._checkoutService.getBuyer({ processId }).pipe(first()).toPromise();
const customerFeatures = await this._checkoutService.getCustomerFeatures({ processId }).pipe(first()).toPromise();
const name = buyer
? customerFeatures?.b2b
? buyer.organisation?.name
? buyer.organisation?.name
: buyer.lastName
: buyer.lastName
: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`;
// Ändere type goods-out zu cart
this._applicationService.patchProcess(processId, {
id: processId,
type: 'cart',
section: 'customer',
name,
});
// Navigation
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
}
// Bei offener Bestellbestätigung und Klick auf Footer Artikelsuche
async fromCartCheckoutProcess(processes: ApplicationProcess[], route: ActivatedRouteSnapshot, processId: number) {
// Um alle Checkout Daten zu resetten die mit dem Prozess assoziiert sind
this._checkoutService.removeProcess({ processId });
// Ändere type cart-checkout zu cart
this._applicationService.patchProcess(processId, {
id: processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
data: {},
});
// Navigation
await this._router.navigate(this.getUrlFromSnapshot(route, ['/kunde', String(processId)]));
}
getUrlFromSnapshot(route: ActivatedRouteSnapshot, url: string[] = []): string[] {
url.push(...route.url.map((segment) => segment.path));
if (route.firstChild) {
@@ -34,4 +109,18 @@ export class CanActivateProductGuard implements CanActivate {
}
return url.filter((segment) => !!segment);
}
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
if (!processNumbers.find((number) => number === missingNumber)) {
return missingNumber;
}
}
return Math.max(...processNumbers) + 1;
}
}

View File

@@ -1,7 +1,6 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { first } from 'rxjs/operators';
@@ -27,7 +26,8 @@ export class CanActivateRemissionGuard implements CanActivate {
this._applicationService.activateProcess(this._config.get('process.ids.remission'));
if (!!process?.data?.active && !state.url.includes(`/filiale/remission/${process?.data?.active}`)) {
await this._router.navigate(['/filiale', 'remission', process?.data?.active, 'list']);
const queryParams = process?.data?.queryParams ?? {};
await this._router.navigate(['/filiale', 'remission', process?.data?.active, 'list'], { queryParams });
return false;
} else {
return true;

View File

@@ -3,10 +3,10 @@ export * from './can-activate-cart.guard';
export * from './can-activate-customer-with-process-id.guard';
export * from './can-activate-customer.guard';
export * from './can-activate-goods-in.guard';
export * from './can-activate-goods-out-with-process-id.guard';
export * from './can-activate-goods-out.guard';
export * from './can-activate-product-with-process-id.guard';
export * from './can-activate-product.guard';
export * from './can-activate-remission.guard';
export * from './can-activate-task-calendar.guard';
export * from './init-store.guard';
export * from './is-authenticated.guard';

View File

@@ -1,15 +0,0 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { RootStateService } from '../store/root-state.service';
@Injectable({ providedIn: 'root' })
export class InitStoreGuard implements CanActivate {
constructor(private readonly _rootStateService: RootStateService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (await this._rootStateService.load()) {
window.location.reload();
}
return true;
}
}

View File

@@ -52,8 +52,8 @@ describe('HttpErrorInterceptor', () => {
httpErrorInterceptor.handleError(error as any);
expect(modalMock.open).toHaveBeenCalledWith({
content: UiMessageModalComponent,
title: 'Die Netzwerkverbindung wurde unterbrochen',
data: { message: 'Bitte überprüfen Sie Ihre Netzwerkverbindung.' },
title: 'Sie sind offline, keine Verbindung zum Netzwerk',
data: { message: 'Bereits geladene Inhalte werden angezeigt. Interaktionen sind aktuell nicht möglich.' },
});
});
});

View File

@@ -16,8 +16,8 @@ export class HttpErrorInterceptor implements HttpInterceptor {
if (error.status === 0) {
this._modal.open({
content: UiMessageModalComponent,
title: 'Die Netzwerkverbindung wurde unterbrochen',
data: { message: 'Bitte überprüfen Sie Ihre Netzwerkverbindung.' },
title: 'Sie sind offline, keine Verbindung zum Netzwerk',
data: { message: 'Bereits geladene Inhalte werden angezeigt. Interaktionen sind aktuell nicht möglich.' },
});
}

View File

@@ -1,12 +1,44 @@
import { HttpErrorResponse } from '@angular/common/http';
import { ErrorHandler, Injectable } from '@angular/core';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { AuthService } from '@core/auth';
import { UiDialogModalComponent, UiErrorModalComponent, UiModalService } from '@ui/modal';
@Injectable()
export class IsaErrorHandler implements ErrorHandler {
constructor(private _modal: UiModalService) {}
constructor(private _modal: UiModalService, private _authService: AuthService) {}
handleError(error: any): void {
async handleError(error: any): Promise<void> {
console.error(error);
this._modal.open({ content: UiErrorModalComponent, title: 'Unbekannter Fehler', data: error });
// Bei Klick auf Abbrechen auf der Login Seite erneut zur Login Seite weiterleiten
if (error?.type === 'token_error') {
this._authService.login();
return;
}
if (error instanceof HttpErrorResponse && error?.status === 401) {
await this._modal
.open({
content: UiDialogModalComponent,
title: 'Sitzung abgelaufen',
data: {
content: 'Sie waren zu lange nicht in der ISA aktiv. Bitte melden Sie sich erneut an',
actions: [{ command: 'CLOSE', selected: true, label: 'Erneut anmelden' }],
},
})
.afterClosed$.toPromise();
this._authService.logout();
return;
}
this._modal.open({
content: UiErrorModalComponent,
title:
!navigator.onLine || (error instanceof HttpErrorResponse && error?.status === 0)
? 'Sie sind offline, keine Verbindung zum Netzwerk'
: 'Unbekannter Fehler',
data: error,
});
}
}

View File

@@ -61,7 +61,7 @@
<ui-icon icon="box_return" size="24px"></ui-icon>
Abholfach
</a>
<a [routerLink]="['/filiale/remission']" routerLinkActive="active">
<a [routerLink]="[remissionUrl$ | async]" [queryParams]="remissionQueryParams$ | async" routerLinkActive="active">
<ui-icon icon="documents_refresh" size="24px"></ui-icon>
Remission
</a>

View File

@@ -5,6 +5,7 @@ import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { AuthService } from '@core/auth';
import { Config } from '@core/config';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainDashboardService } from '@domain/isa';
@@ -56,13 +57,14 @@ describe('ShellComponent', () => {
MockComponent(ShellProcessTabComponent),
MockComponent(UiIconComponent),
],
mocks: [BreadcrumbService, DomainAvailabilityService, AuthService, DomainDashboardService],
mocks: [BreadcrumbService, DomainAvailabilityService, AuthService, DomainDashboardService, Config],
});
beforeEach(() => {
applicationServiceMock = createSpyObject(ApplicationService);
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of([]));
applicationServiceMock.getProcessById$.and.returnValue(of({ id: 4000 }));
applicationServiceMock.getActivatedProcessId$.and.returnValue(of(undefined));
applicationServiceMock.getLastActivatedProcessWithSectionAndType$.and.returnValue(of({}));
applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({}));
@@ -225,6 +227,63 @@ describe('ShellComponent', () => {
});
});
describe('remissionProcess$', () => {
it('should call _appService.getProcessById$() with Remission Id and return its value', async () => {
applicationServiceMock.getProcessById$.and.returnValue(of({ id: 4000 }));
await spectator.component.remissionProcess$.pipe(first()).toPromise();
expect(applicationServiceMock.getProcessById$).toHaveBeenCalled();
});
});
describe('remissionUrl$', () => {
it('should return the correct url if process.data.active is available', async () => {
const process = {
id: 4000,
data: {
active: 9999,
},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const url = await spectator.component.remissionUrl$.pipe(first()).toPromise();
expect(url).toBe('/filiale/remission/9999/list');
});
it('should return the correct url if process.data.active is not available', async () => {
const process = {
id: 4000,
data: {},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const url = await spectator.component.remissionUrl$.pipe(first()).toPromise();
expect(url).toBe('/filiale/remission');
});
});
describe('remissionQueryParams$', () => {
it('should return the correct queryParams if process.data.active and process.data.queryParams are available', async () => {
const process = {
id: 4000,
data: {
active: 9999,
queryParams: { filter: 'test' },
},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const queryParams = await spectator.component.remissionQueryParams$.pipe(first()).toPromise();
expect(queryParams).toEqual(process.data.queryParams);
});
it('should return the correct queryParams if process.data.active and process.data.queryParams are not available', async () => {
const process = {
id: 4000,
data: {},
};
applicationServiceMock.getProcessById$.and.returnValue(of(process));
const queryParams = await spectator.component.remissionQueryParams$.pipe(first()).toPromise();
expect(queryParams).toEqual({});
});
});
describe('setSection()', () => {
it('should call _appService.setSection() with the argument section', async () => {
await spectator.component.setSection('customer');
@@ -284,7 +343,7 @@ describe('ShellComponent', () => {
applicationServiceMock.getSection$.and.returnValue(of('customer'));
applicationServiceMock.getProcesses$.and.returnValue(of(processes));
await spectator.component.closeProcess(1);
expect(router.navigate).not.toHaveBeenCalled();
expect(router.navigate).not.toHaveBeenCalledWith(['/kunde', 'dashboard']);
});
it('should activate the next process when it was not the last process', async () => {
@@ -326,7 +385,7 @@ describe('ShellComponent', () => {
spyOn(router, 'navigate');
await spectator.component.activateProcess(1);
expect(router.navigate).toHaveBeenCalledWith(['/kunde']);
expect(router.navigate).toHaveBeenCalledWith(['/kunde', 1, 'product']);
});
});

View File

@@ -1,15 +1,16 @@
import { Component, ChangeDetectionStrategy, ViewChildren, QueryList } from '@angular/core';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { first, map, shareReplay, switchMap } from 'rxjs/operators';
import { first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { NotificationsHub } from '@hub/notifications';
import { ModalNotificationsComponent } from '@modal/notifications';
import { UiModalService } from '@ui/modal';
import { ConfirmModalData, UiConfirmModalComponent, UiModalService } from '@ui/modal';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { combineLatest } from 'rxjs';
import { AuthService } from '@core/auth';
import { DomainAvailabilityService } from '@domain/availability';
import { ShellProcessTabComponent } from '@shell/process';
import { Config } from '@core/config';
@Component({
selector: 'app-shell',
@@ -26,7 +27,11 @@ export class ShellComponent {
notificationCount$ = this.notifications$.pipe(map((message) => message?.data?.length));
get activatedProcessId$() {
return this._appService.getActivatedProcessId$().pipe(shareReplay());
return this._appService.getActivatedProcessId$().pipe(
tap((activatedProcessId) => {
this.processTabs?.find((process) => process?.process?.id === activatedProcessId && !process?.isActive)?.slideIntoView();
})
);
}
get section$() {
@@ -37,6 +42,22 @@ export class ShellComponent {
return this.section$.pipe(switchMap((section) => this._appService.getProcesses$(section)));
}
get remissionProcess$() {
return this._appService.getProcessById$(this._config.get('process.ids.remission'));
}
get remissionUrl$() {
return this.remissionProcess$.pipe(
map((process) => (process?.data?.active ? `/filiale/remission/${process.data.active}/list` : '/filiale/remission'))
);
}
get remissionQueryParams$() {
return this.remissionProcess$.pipe(
map((process) => (process?.data?.active && process?.data?.queryParams ? process.data.queryParams : {}))
);
}
get addProcessLabel$() {
return combineLatest([this.section$, this.processes$]).pipe(
map(([section, processes]) => (section === 'customer' && processes.length === 0 ? 'VORGANG STARTEN' : ''))
@@ -53,6 +74,7 @@ export class ShellComponent {
constructor(
private readonly _appService: ApplicationService,
private readonly _config: Config,
private readonly _notificationsHub: NotificationsHub,
private readonly _modal: UiModalService,
private readonly _router: Router,
@@ -79,12 +101,11 @@ export class ShellComponent {
}
async activateProcess(activatedProcessId: number) {
const latestCrumb = await this._breadcrumbService.getLastActivatedBreadcrumbByKey$(activatedProcessId).pipe(first()).toPromise();
const latestCrumb = await this._breadcrumbService?.getLastActivatedBreadcrumbByKey$(activatedProcessId)?.pipe(first()).toPromise();
if (latestCrumb) {
await this._router.navigate([latestCrumb.path], { queryParams: latestCrumb.params });
} else {
await this._router.navigate(['/kunde']);
await this._router.navigate(['/kunde', activatedProcessId, 'product']);
}
}

View File

@@ -17,10 +17,13 @@ export class RootStateService {
window['clearUserState'] = () => {
this.clear();
console.log('UserState wurde geleert. Bitte Seite neu laden.');
};
}
}
async init() {
await this.load();
this._store.dispatch({ type: 'HYDRATE', payload: RootStateService.LoadFromLocalStorage() });
this.initSave();
}
@@ -67,6 +70,7 @@ export class RootStateService {
.toPromise()
.catch((error) => this._logger.log(LogLevel.ERROR, error));
RootStateService.RemoveFromLocalStorage();
window.location.reload();
}
static SaveToLocalStorage(state: RootState) {

View File

@@ -15,7 +15,9 @@ export class TokenLoginComponent implements OnInit {
if (this._route.snapshot.params.token && !this._authService.isAuthenticated()) {
this._authService.setKeyCardToken(this._route.snapshot.params.token);
this._authService.login();
} else {
} else if (!this._authService.isAuthenticated()) {
this._authService.login();
} else if (this._authService.isAuthenticated()) {
this._router.navigate(['/']);
}
}

View File

@@ -1,62 +1,63 @@
{
"title": "ISA - Local",
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
"@core/auth": {
"issuer": "https://sso-test.paragon-data.de",
"clientId": "hug-isa",
"responseType": "id_token token",
"oidc": true,
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
},
"@core/logger": {
"logLevel": "debug"
},
"@swagger/isa": {
"rootUrl": "https://isa-integration.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v5"
},
"@swagger/av": {
"rootUrl": "https://isa-test.paragon-data.net/ava/v4"
},
"@swagger/checkout": {
"rootUrl": "https://isa-test.paragon-data.net/checkout/v3"
},
"@swagger/crm": {
"rootUrl": "https://isa-test.paragon-data.net/crm/v3"
},
"@swagger/oms": {
"rootUrl": "https://isa-test.paragon-data.net/oms/v4"
},
"@swagger/print": {
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
},
"@swagger/eis": {
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-test.paragon-data.net/inv/v1"
},
"hubs": {
"notifications": {
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
"enableAutomaticReconnect": false,
"httpOptions": {
"transport": 1,
"logMessageContent": true,
"skipNegotiation": true
}
}
},
"process": {
"ids": {
"goodsOut": 1000,
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000
}
}
"title": "ISA - Feature",
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
"@core/auth": {
"issuer": "https://sso-test.paragon-data.de",
"clientId": "hug-isa",
"responseType": "id_token token",
"oidc": true,
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
},
"@core/logger": {
"logLevel": "debug"
},
"@swagger/isa": {
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v5"
},
"@swagger/av": {
"rootUrl": "https://isa-test.paragon-data.net/ava/v4"
},
"@swagger/checkout": {
"rootUrl": "https://isa-test.paragon-data.net/checkout/v3"
},
"@swagger/crm": {
"rootUrl": "https://isa-test.paragon-data.net/crm/v3"
},
"@swagger/oms": {
"rootUrl": "https://isa-test.paragon-data.net/oms/v4"
},
"@swagger/print": {
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
},
"@swagger/eis": {
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-test.paragon-data.net/inv/v1"
},
"hubs": {
"notifications": {
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
"enableAutomaticReconnect": false,
"httpOptions": {
"transport": 1,
"logMessageContent": true,
"skipNegotiation": true
}
}
},
"process": {
"ids": {
"goodsOut": 1000,
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000
}
},
"checkForUpdates": 3600000
}

View File

@@ -1,5 +1,8 @@
{
"title": "ISA - Integration",
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
"@core/auth": {
"issuer": "https://sso-test.paragon-data.de",
"clientId": "hug-isa",
@@ -7,6 +10,36 @@
"oidc": true,
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
},
"@core/logger": {
"logLevel": "debug"
},
"@swagger/isa": {
"rootUrl": "https://isa-integration.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-integration.paragon-data.net/catsearch/v5"
},
"@swagger/av": {
"rootUrl": "https://isa-integration.paragon-data.net/ava/v4"
},
"@swagger/checkout": {
"rootUrl": "https://isa-integration.paragon-data.net/checkout/v3"
},
"@swagger/crm": {
"rootUrl": "https://isa-integration.paragon-data.net/crm/v3"
},
"@swagger/oms": {
"rootUrl": "https://isa-integration.paragon-data.net/oms/v4"
},
"@swagger/print": {
"rootUrl": "https://isa-integration.paragon-data.net/print/v1"
},
"@swagger/eis": {
"rootUrl": "https://filialinformationsystem-integration.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-integration.paragon-data.net/inv/v1"
},
"hubs": {
"notifications": {
"url": "https://isa-integration.paragon-data.net/isa/v1/rt",
@@ -17,5 +50,14 @@
"skipNegotiation": true
}
}
}
},
"process": {
"ids": {
"goodsOut": 1000,
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000
}
},
"checkForUpdates": 3600000
}

View File

@@ -58,5 +58,6 @@
"taskCalendar": 3000,
"remission": 4000
}
}
}
},
"checkForUpdates": 3600000
}

View File

@@ -1,5 +1,8 @@
{
"title": "ISA - Production",
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
"@core/auth": {
"issuer": "https://sso.paragon-data.de",
"clientId": "hug-isa",
@@ -7,6 +10,36 @@
"oidc": true,
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
},
"@core/logger": {
"logLevel": "debug"
},
"@swagger/isa": {
"rootUrl": "https://isa.paragon-systems.de/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa.paragon-systems.de/catsearch/v5"
},
"@swagger/av": {
"rootUrl": "https://isa.paragon-systems.de/ava/v4"
},
"@swagger/checkout": {
"rootUrl": "https://isa.paragon-systems.de/checkout/v3"
},
"@swagger/crm": {
"rootUrl": "https://isa.paragon-systems.de/crm/v3"
},
"@swagger/oms": {
"rootUrl": "https://isa.paragon-systems.de/oms/v4"
},
"@swagger/print": {
"rootUrl": "https://isa.paragon-systems.de/print/v1"
},
"@swagger/eis": {
"rootUrl": "https://filialinformationsystem.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa.paragon-systems.de/inv/v1"
},
"hubs": {
"notifications": {
"url": "https://isa.paragon-systems.de/isa/v1/rt",
@@ -17,5 +50,14 @@
"skipNegotiation": true
}
}
}
},
"process": {
"ids": {
"goodsOut": 1000,
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000
}
},
"checkForUpdates": 3600000
}

View File

@@ -1,5 +1,8 @@
{
"title": "ISA - Staging",
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
"@core/auth": {
"issuer": "https://sso.paragon-data.de",
"clientId": "hug-isa",
@@ -7,6 +10,36 @@
"oidc": true,
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
},
"@core/logger": {
"logLevel": "debug"
},
"@swagger/isa": {
"rootUrl": "https://isa-staging.paragon-systems.de/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-staging.paragon-systems.de/catsearch/v5"
},
"@swagger/av": {
"rootUrl": "https://isa-staging.paragon-systems.de/ava/v4"
},
"@swagger/checkout": {
"rootUrl": "https://isa-staging.paragon-systems.de/checkout/v3"
},
"@swagger/crm": {
"rootUrl": "https://isa-staging.paragon-systems.de/crm/v3"
},
"@swagger/oms": {
"rootUrl": "https://isa-staging.paragon-systems.de/oms/v4"
},
"@swagger/print": {
"rootUrl": "https://isa-staging.paragon-systems.de/print/v1"
},
"@swagger/eis": {
"rootUrl": "https://filialinformationsystem-staging.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-staging.paragon-systems.de/inv/v1"
},
"hubs": {
"notifications": {
"url": "https://isa-staging.paragon-systems.de/isa/v1/rt",
@@ -17,5 +50,14 @@
"skipNegotiation": true
}
}
}
},
"process": {
"ids": {
"goodsOut": 1000,
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000
}
},
"checkForUpdates": 3600000
}

View File

@@ -1,62 +1,63 @@
{
"title": "ISA - Test",
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
"@core/auth": {
"issuer": "https://sso-test.paragon-data.de",
"clientId": "hug-isa",
"responseType": "id_token token",
"oidc": true,
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
},
"@core/logger": {
"logLevel": "debug"
},
"@swagger/isa": {
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v5"
},
"@swagger/av": {
"rootUrl": "https://isa-test.paragon-data.net/ava/v4"
},
"@swagger/checkout": {
"rootUrl": "https://isa-test.paragon-data.net/checkout/v3"
},
"@swagger/crm": {
"rootUrl": "https://isa-test.paragon-data.net/crm/v3"
},
"@swagger/oms": {
"rootUrl": "https://isa-test.paragon-data.net/oms/v4"
},
"@swagger/print": {
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
},
"@swagger/eis": {
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-test.paragon-data.net/inv/v1"
},
"hubs": {
"notifications": {
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
"enableAutomaticReconnect": false,
"httpOptions": {
"transport": 1,
"logMessageContent": true,
"skipNegotiation": true
}
}
},
"process": {
"ids": {
"goodsOut": 1000,
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000
}
}
"title": "ISA - Test",
"@cdn/product-image": {
"url": "https://produktbilder.paragon-data.net"
},
"@core/auth": {
"issuer": "https://sso-test.paragon-data.de",
"clientId": "hug-isa",
"responseType": "id_token token",
"oidc": true,
"scope": "openid profile cmf_user isa-isa-webapi isa-checkout-webapi isa-cat-webapi isa-ava-webapi isa-crm-webapi isa-review-webapi isa-kpi-webapi isa-oms-webapi isa-nbo-webapi isa-print-webapi eis-service isa-inv-webapi"
},
"@core/logger": {
"logLevel": "debug"
},
"@swagger/isa": {
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v5"
},
"@swagger/av": {
"rootUrl": "https://isa-test.paragon-data.net/ava/v4"
},
"@swagger/checkout": {
"rootUrl": "https://isa-test.paragon-data.net/checkout/v3"
},
"@swagger/crm": {
"rootUrl": "https://isa-test.paragon-data.net/crm/v3"
},
"@swagger/oms": {
"rootUrl": "https://isa-test.paragon-data.net/oms/v4"
},
"@swagger/print": {
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
},
"@swagger/eis": {
"rootUrl": "https://filialinformationsystem-test.paragon-systems.de/eiswebapi/v1"
},
"@swagger/remi": {
"rootUrl": "https://isa-test.paragon-data.net/inv/v1"
},
"hubs": {
"notifications": {
"url": "https://isa-test.paragon-data.net/isa/v1/rt",
"enableAutomaticReconnect": false,
"httpOptions": {
"transport": 1,
"logMessageContent": true,
"skipNegotiation": true
}
}
},
"process": {
"ids": {
"goodsOut": 1000,
"goodsIn": 2000,
"taskCalendar": 3000,
"remission": 4000
}
},
"checkForUpdates": 3600000
}

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<title>IsaApp</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600,700" rel="stylesheet" />
<link rel="manifest" href="manifest.webmanifest" />
@@ -13,9 +13,15 @@
<body>
<app-root>
<div class="grid place-items-center h-screen">
<img class="animate-spin" src="/assets/images/spinner.svg" alt="spinner animation" />
<div class="grid grid-flow-row gap-4 items-center justify-center">
<div class="flex flex-col items-center">
<img class="animate-spin" src="/assets/images/spinner.svg" alt="spinner animation" />
</div>
<div id="init-status" class="text-center">App wird geladen</div>
</div>
</div>
</app-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
</body>
</html>

View File

@@ -28,7 +28,7 @@
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
@@ -62,3 +62,4 @@ import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
import 'hammerjs';

View File

@@ -1,6 +1,9 @@
body.branch {
--bg-color: #edeff0;
// @isa-app/scrollbar
--scrollbar-color: #596470;
// @shell/header
--shell-header-button-color: #89949e;
--shell-header-button-color-active: #586470;
@@ -23,4 +26,7 @@ body.branch {
--shell-process-text-inactive: #9ca5b0;
--shell-process-badge-background: #edeff0;
--shell-process-badge-active: #596470;
// @core/toast
--toast-background: #596470;
}

View File

@@ -1,6 +1,9 @@
body.customer {
--bg-color: #e6eff9;
// @isa-app/scrollbar
--scrollbar-color: #1f466c;
// @shell/header
--shell-header-button-color: #9db2c6;
--shell-header-button-color-active: #557596;
@@ -26,4 +29,7 @@ body.customer {
// @page/dashboard
--page-dashboard-card-title-color: #1f466c;
// @core/toast
--toast-background: #1f466c;
}

View File

@@ -15,4 +15,7 @@
--shell-process-add-label: #f70400;
--shell-process-border-active: #f70400;
--shell-process-background: #fff;
// @core/toast
--toast-background: #1f466c;
}

View File

@@ -21,3 +21,23 @@ body {
width: 0; // remove scrollbar space
background: transparent; // optional: just make scrollbar invisible */
}
.desktop .scroll-bar::-webkit-scrollbar,
.desktop pdf-viewer ::-webkit-scrollbar {
width: 12px;
background-color: transparent;
}
.desktop .scroll-bar::-webkit-scrollbar-thumb,
.desktop pdf-viewer ::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgb(0 0 0 / 10%);
background-color: var(--scrollbar-color);
}
.desktop .scroll-bar::-webkit-scrollbar-track,
.desktop pdf-viewer ::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 4px rgb(0 0 0 / 10%);
border-radius: 10px;
background-color: white;
}

View File

@@ -48,7 +48,7 @@ hr {
.branch-info {
@apply flex flex-row;
max-width: 505px;
max-width: 485px;
.branch-name {
@apply font-bold whitespace-nowrap;

View File

@@ -0,0 +1,25 @@
<div class="header">
<div class="notification-icon">
<span class="notification-counter">{{ notifications.length }}</span>
<ui-icon icon="notification" size="26px"></ui-icon>
</div>
<h2>ISA-Update</h2>
</div>
<hr />
<div class="notification-list scroll-bar">
<ng-container *ngFor="let notification of notifications">
<div class="notification-headline">
<h1>{{ notification.headline }}</h1>
</div>
<div class="notification-text">{{ notification.text }}</div>
<hr />
</ng-container>
</div>
<div class="actions">
<button class="cta-primary" (click)="reload()">
Aktualisieren
</button>
</div>

View File

@@ -0,0 +1,41 @@
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { CommonModule } from '@angular/common';
import { UiIconModule } from '@ui/icon';
import { ModalNotificationsUpdateGroupComponent } from './notifications-update-group.component';
describe('ModalNotificationsUpdateGroupComponent', () => {
let spectator: Spectator<ModalNotificationsUpdateGroupComponent>;
const createComponent = createComponentFactory({
component: ModalNotificationsUpdateGroupComponent,
imports: [CommonModule, UiIconModule],
});
beforeEach(() => {
spectator = createComponent({ props: { notifications: [] } });
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
describe('notifications input', () => {
it('should display the right notification-counter value based on the length of the input array', () => {
spectator.setInput({ notifications: [{}, {}, {}] });
expect(spectator.query('.notification-counter')).toHaveText('3');
});
it('should not display notification-counter if input array has length 0', () => {
spectator.setInput({ notifications: [] });
expect(spectator.query('.notification-counter')).toHaveText('');
});
it('should render notification-headline and notification-text based on the input array', () => {
const notifications = [{}, {}];
spectator.setInput({ notifications });
spectator.detectComponentChanges();
expect(spectator.queryAll('.notification-headline')).toHaveLength(notifications.length);
expect(spectator.queryAll('.notification-text')).toHaveLength(notifications.length);
});
});
});

View File

@@ -0,0 +1,18 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
@Component({
selector: 'modal-notifications-update-group',
templateUrl: 'notifications-update-group.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ModalNotificationsUpdateGroupComponent {
@Input()
notifications: MessageBoardItemDTO[];
constructor() {}
reload() {
location.reload();
}
}

View File

@@ -12,6 +12,10 @@
</ng-container>
<ng-container [ngSwitch]="activeCard$ | async">
<modal-notifications-update-group
*ngSwitchCase="'ISA-Update'"
[notifications]="activeNotifications$ | async"
></modal-notifications-update-group>
<modal-notifications-reservation-group
*ngSwitchCase="'Reservierungsanfragen'"
[notifications]="activeNotifications$ | async"

View File

@@ -12,7 +12,8 @@ modal-notifications {
modal-notifications-remission-group,
modal-notifications-reservation-group,
modal-notifications-task-calendar-group {
modal-notifications-task-calendar-group,
modal-notifications-update-group {
@apply flex flex-col relative pb-2;
.header {
@@ -57,7 +58,8 @@ modal-notifications {
}
}
modal-notifications-list-item {
modal-notifications-list-item,
modal-notifications-update-group {
@apply flex flex-col relative py-1 px-4;
.notification-headline {

View File

@@ -7,6 +7,7 @@ import { ModalNotificationsListItemComponent } from './notifications-list-item/n
import { ModalNotificationsRemissionGroupComponent } from './notifications-remission-group/notifications-remission-group.component';
import { ModalNotificationsReservationGroupComponent } from './notifications-reservation-group/notifications-reservation-group.component';
import { ModalNotificationsTaskCalendarGroupComponent } from './notifications-task-calendar-group/notifications-task-calendar-group.component';
import { ModalNotificationsUpdateGroupComponent } from './notifications-update-group/notifications-update-group.component';
import { ModalNotificationsComponent } from './notifications.component';
@NgModule({
@@ -16,6 +17,7 @@ import { ModalNotificationsComponent } from './notifications.component';
ModalNotificationsReservationGroupComponent,
ModalNotificationsRemissionGroupComponent,
ModalNotificationsTaskCalendarGroupComponent,
ModalNotificationsUpdateGroupComponent,
ModalNotificationsListItemComponent,
],
exports: [
@@ -23,6 +25,7 @@ import { ModalNotificationsComponent } from './notifications.component';
ModalNotificationsReservationGroupComponent,
ModalNotificationsRemissionGroupComponent,
ModalNotificationsTaskCalendarGroupComponent,
ModalNotificationsUpdateGroupComponent,
ModalNotificationsListItemComponent,
],
})

View File

@@ -53,7 +53,7 @@ hr {
}
span.number {
@apply text-right;
@apply text-center;
}
span {

View File

@@ -4,8 +4,8 @@
<div class="product-details">
<div class="product-image">
<button class="image-button" (click)="showImages()">
<img [src]="item.imageId | productImage: 195:315:true" alt="product image" />
<ui-icon icon="search_add" size="22px"></ui-icon>
<img (load)="loadImage()" [src]="item.imageId | productImage: 195:315:true" alt="product image" />
<ui-icon *ngIf="imageLoaded$ | async" icon="search_add" size="22px"></ui-icon>
</button>
<button (click)="showReviews()" class="recessions" *ngIf="item.reviews?.length > 0">
@@ -36,7 +36,7 @@
<div class="row">
<div>
<div class="format">
<div class="format" *ngIf="item?.product?.format && item?.product?.formatDetail">
<img
*ngIf="item?.product?.format !== '--'"
class="format-icon"
@@ -84,18 +84,33 @@
<div data-name="product-ean">{{ item.product?.ean }}</div>
<div class="right">
<div class="availability-icons">
<div class="fetching medium" *ngIf="fetchingAvailabilities$ | async"></div>
<ng-container *ngIf="!(fetchingAvailabilities$ | async)">
<div class="fetching xsmall" *ngIf="store.fetchingTakeAwayAvailability$ | async; else showAvailabilityTakeAwayIcon"></div>
<ng-template #showAvailabilityTakeAwayIcon>
<ui-icon *ngIf="store.isTakeAwayAvailabilityAvailable$ | async" icon="shopping_bag" size="18px"></ui-icon>
<ui-icon *ngIf="store.isPickUpAvailabilityAvailable$ | async" icon="box_out" size="18px"></ui-icon>
<ui-icon class="truck" *ngIf="showDeliveryTruck$ | async" icon="truck" size="30px"></ui-icon>
<ui-icon class="truck_b2b" *ngIf="showDeliveryB2BTruck$ | async" icon="truck_b2b" size="40px"></ui-icon>
</ng-template>
<span *ngIf="store.isDownload$ | async" class="download-icon">
<ui-icon icon="download" size="18px"></ui-icon>
<span class="label">Download</span>
</span>
</ng-container>
<div class="fetching xsmall" *ngIf="store.fetchingPickUpAvailability$ | async; else showAvailabilityPickUpIcon"></div>
<ng-template #showAvailabilityPickUpIcon>
<ui-icon *ngIf="store.isPickUpAvailabilityAvailable$ | async" icon="box_out" size="18px"></ui-icon>
</ng-template>
<div class="fetching xsmall" *ngIf="store.fetchingDeliveryAvailability$ | async; else showAvailabilityDeliveryIcon"></div>
<ng-template #showAvailabilityDeliveryIcon>
<ui-icon *ngIf="showDeliveryTruck$ | async" class="truck" icon="truck" size="30px"></ui-icon>
</ng-template>
<div
class="fetching xsmall"
*ngIf="store.fetchingDeliveryB2BAvailability$ | async; else showAvailabilityDeliveryB2BIcon"
></div>
<ng-template #showAvailabilityDeliveryB2BIcon>
<ui-icon *ngIf="showDeliveryB2BTruck$ | async" class="truck_b2b" icon="truck_b2b" size="40px"></ui-icon>
</ng-template>
<span *ngIf="store.isDownload$ | async" class="download-icon">
<ui-icon icon="download" size="18px"></ui-icon>
<span class="label">Download</span>
</span>
</div>
</div>
</div>

View File

@@ -108,6 +108,10 @@
animation: load 0.75s linear infinite;
}
.xsmall {
@apply w-6;
}
.small {
@apply w-16;
}
@@ -117,7 +121,7 @@
}
.availability-icons {
@apply flex flex-row justify-end text-dark-cerulean mt-4;
@apply flex flex-row items-center justify-end text-dark-cerulean mt-4;
ui-icon {
@apply mx-1;
@@ -168,7 +172,7 @@
}
.product-text {
@apply flex flex-col whitespace-pre-line mb-px-100;
@apply flex flex-col whitespace-pre-line mb-px-100 break-words;
h3 {
@apply my-4;

View File

@@ -8,7 +8,7 @@ import { UiModalService } from '@ui/modal';
import { ModalReviewsComponent } from '@modal/reviews';
import { PurchasingOptionsModalComponent, PurchasingOptionsModalData } from 'apps/page/checkout/src/lib/modals/purchasing-options-modal';
import { PurchasingOptions } from 'apps/page/checkout/src/lib/modals/purchasing-options-modal/purchasing-options-modal.store';
import { combineLatest, Subscription } from 'rxjs';
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
import { filter, first, map, shareReplay } from 'rxjs/operators';
import { ArticleDetailsStore } from './article-details.store';
import { ModalImagesComponent } from 'apps/modal/images/src/public-api';
@@ -32,6 +32,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
private readonly subscriptions = new Subscription();
showRecommendations: boolean;
imageLoaded$ = new BehaviorSubject<boolean>(false);
fetchingAvailabilities$ = combineLatest([
this.store.fetchingDeliveryAvailability$,
this.store.fetchingDeliveryB2BAvailability$,
@@ -268,4 +270,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
const element = this.elementRef.nativeElement.closest('.main-wrapper');
element?.scrollTo({ top: 0, behavior: 'smooth' });
}
loadImage() {
this.imageLoaded$.next(true);
}
}

View File

@@ -1,10 +1,14 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainAvailabilityService, ItemData } from '@domain/availability';
import { DomainCatalogService } from '@domain/catalog';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { ItemDTO, ResponseArgsOfItemDTO } from '@swagger/cat';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { combineLatest, Observable, of } from 'rxjs';
import { catchError, filter, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { catchError, filter, first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
export interface ArticleDetailsState {
fetchingItem?: boolean;
@@ -241,7 +245,11 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
constructor(
private readonly domainCatalogService: DomainCatalogService,
private readonly domainAvailabilityService: DomainAvailabilityService
private readonly domainAvailabilityService: DomainAvailabilityService,
private readonly _appService: ApplicationService,
private readonly _router: Router,
private readonly _breadcrumb: BreadcrumbService,
private readonly _modal: UiModalService
) {
super({});
}
@@ -253,9 +261,15 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
switchMap((id) => this.domainCatalogService.getDetailsById({ id })),
tapResponse<ResponseArgsOfItemDTO>(
(response) => this.patchState({ item: response.result, fetchingItem: false }),
(err) => {
console.error('loadItemById failed', err);
async (err) => {
this.patchState({ item: undefined, fetchingItem: false });
const errorModalRef = this._modal.open({
content: UiErrorModalComponent,
title: 'Fehler beim Laden des Artikels über die ID',
data: { message: 'Sie kehren nun auf die ursprüngliche Artikeldetailseite zurück' },
});
await errorModalRef.afterClosed$.toPromise();
await this.navigateBack();
}
)
)
@@ -268,11 +282,26 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
switchMap((ean) => this.domainCatalogService.getDetailsByEan({ ean })),
tapResponse<ResponseArgsOfItemDTO>(
(response) => this.patchState({ item: response.result, fetchingItem: false }),
(err) => {
console.error('loadItemByEan failed', err);
async (err) => {
this.patchState({ item: undefined, fetchingItem: false });
const errorModalRef = this._modal.open({
content: UiErrorModalComponent,
title: 'Fehler beim Laden des Artikels über die EAN',
data: { message: 'Sie kehren nun auf die ursprüngliche Artikeldetailseite zurück' },
});
await errorModalRef.afterClosed$.toPromise();
await this.navigateBack();
}
)
)
);
async navigateBack() {
const crumb = await this._breadcrumb.getLastActivatedBreadcrumbByKey$(this._appService.activatedProcessId).pipe(first()).toPromise();
if (crumb) {
await this._router.navigate([crumb.path]);
} else {
await this._router.navigate(['/kunde', this._appService.activatedProcessId, 'product', 'search']);
}
}
}

View File

@@ -17,12 +17,14 @@
</div>
</div>
<div class="cta-wrapper">
<button class="cta-reset-filter" (click)="resetFilter(filter)">
<button class="cta-reset-filter" (click)="resetFilter(filter)" [disabled]="fetching$ | async">
Filter zurücksetzen
</button>
<button class="cta-apply-filter" (click)="applyFilter(filter)">
Filter anwenden
<button class="cta-apply-filter" (click)="applyFilter(filter)" [disabled]="fetching$ | async">
<ui-spinner [show]="fetching$ | async">
Filter anwenden
</ui-spinner>
</button>
</div>
</ng-container>

View File

@@ -26,6 +26,10 @@
.cta-reset-filter,
.cta-apply-filter {
@apply text-lg font-bold px-6 py-3 rounded-full border-solid border-2 border-brand outline-none mx-2;
&:disabled {
@apply bg-inactive-branch cursor-not-allowed border-none text-white;
}
}
.cta-reset-filter {

View File

@@ -2,11 +2,12 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { UiFilterNextModule } from '@ui/filter';
import { UiIconModule } from '@ui/icon';
import { UiSpinnerModule } from '@ui/spinner';
import { ArticleSearchFilterComponent } from './search-filter.component';
@NgModule({
imports: [CommonModule, UiFilterNextModule, UiIconModule],
imports: [CommonModule, UiFilterNextModule, UiIconModule, UiSpinnerModule],
exports: [ArticleSearchFilterComponent],
declarations: [ArticleSearchFilterComponent],
providers: [],

View File

@@ -1,6 +1,6 @@
<a class="product-list-result-content" [routerLink]="['/kunde', applicationService.activatedProcessId, 'product', 'details', item?.id]">
<div class="item-thumbnail">
<img loading="lazy" *ngIf="item?.product?.ean | thumbnailUrl; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.title" />
<img loading="lazy" *ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl" [src]="thumbnailUrl" [alt]="item?.product?.title" />
</div>
<div class="item-contributors">
@@ -39,7 +39,7 @@
{{ item?.catalogAvailability?.ssc }} - {{ item?.catalogAvailability?.sscText }}
</div>
<div class="item-format">
<div class="item-format" *ngIf="item?.product?.format && item?.product?.formatDetail">
<img
*ngIf="item?.product?.format !== '--'"
loading="lazy"

View File

@@ -5,7 +5,7 @@
<cdk-virtual-scroll-viewport
#scrollContainer
class="product-list scroll-bar"
class="product-list scroll-bar scroll-bar-margin"
[itemSize]="187"
minBufferPx="1200"
maxBufferPx="1200"

View File

@@ -44,3 +44,9 @@
@apply bg-brand text-white;
}
}
::ng-deep .desktop page-search-results {
.scroll-bar-margin::-webkit-scrollbar-track {
margin-bottom: 0.5rem;
}
}

View File

@@ -7,7 +7,7 @@ import { DomainCheckoutService } from '@domain/checkout';
import { ItemDTO } from '@swagger/cat';
import { AddToShoppingCartDTO } from '@swagger/checkout';
import { UiFilter } from '@ui/filter';
import { UiErrorModalComponent, UiModalRef, UiModalService } from '@ui/modal';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { CacheService } from 'apps/core/cache/src/public-api';
import { isEqual } from 'lodash';
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';

View File

@@ -1,4 +1,4 @@
<ng-container *ngIf="(groupedItems$ | async)?.length <= 0">
<ng-container *ngIf="(groupedItems$ | async)?.length <= 0 && !(fetching$ | async); else shoppingCart">
<div class="card stretch card-empty">
<div class="empty-message">
<span class="cart-icon">
@@ -20,7 +20,11 @@
</div>
</ng-container>
<ng-container *ngIf="(groupedItems$ | async)?.length > 0">
<div class="flex items-center justify-center card stretch card-empty" *ngIf="fetching$ | async">
<ui-spinner show="true"> </ui-spinner>
</div>
<ng-template #shoppingCart>
<ng-container *ngIf="shoppingCart$ | async; let shoppingCart">
<div class="card stretch">
<div class="cta-print-wrapper">
@@ -172,7 +176,15 @@
</strong>
<span class="shipping-cost-info">ohne Versandkosten</span>
</div>
<button class="cta-primary" (click)="order()" [disabled]="showOrderButtonSpinner">
<button
class="cta-primary"
(click)="order()"
[disabled]="
showOrderButtonSpinner ||
((primaryCtaLabel$ | async) === 'Bestellen' && !(checkNotificationChannelControl$ | async)) ||
control.invalid
"
>
<ui-spinner [show]="showOrderButtonSpinner">
{{ primaryCtaLabel$ | async }}
</ui-spinner>
@@ -180,4 +192,4 @@
</div>
</div>
</ng-container>
</ng-container>
</ng-template>

View File

@@ -70,7 +70,7 @@ button {
@apply bg-brand text-white font-bold text-lg outline-none border-brand border-solid border-2 rounded-full px-6 py-3;
&:disabled {
@apply bg-inactive-customer border-none;
@apply bg-inactive-customer border-solid border-inactive-customer cursor-not-allowed;
}
}

View File

@@ -9,7 +9,7 @@ import { PrintModalData, PrintModalComponent } from '@modal/printer';
import { PurchasingOptionsModalComponent, PurchasingOptionsModalData } from '../modals/purchasing-options-modal';
import { PurchasingOptions } from '../modals/purchasing-options-modal/purchasing-options-modal.store';
import { AuthService } from '@core/auth';
import { first, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
import { first, map, shareReplay, switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
import { Subject, NEVER, combineLatest, BehaviorSubject } from 'rxjs';
import { DomainCatalogService } from '@domain/catalog';
import { BreadcrumbService } from '@core/breadcrumb';
@@ -25,6 +25,7 @@ import { ComponentStore, tapResponse } from '@ngrx/component-store';
export interface CheckoutReviewComponentState {
shoppingCart: ShoppingCartDTO;
shoppingCartItems: ShoppingCartItemDTO[];
fetching: boolean;
}
@Component({
@@ -36,6 +37,8 @@ export interface CheckoutReviewComponentState {
export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewComponentState> implements OnInit {
private _orderCompleted = new Subject<void>();
checkNotificationChannelControl$ = new BehaviorSubject<boolean>(true);
get shoppingCart() {
return this.get((s) => s.shoppingCart);
}
@@ -52,6 +55,14 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
}
readonly shoppingCartItems$ = this.select((s) => s.shoppingCartItems);
get fetching() {
return this.get((s) => s.fetching);
}
set fetching(fetching: boolean) {
this.patchState({ fetching });
}
readonly fetching$ = this.select((s) => s.fetching);
payer$ = this.applicationService.activatedProcessId$.pipe(
takeUntil(this._orderCompleted),
switchMap((processId) => this.domainCheckoutService.getPayer({ processId })),
@@ -183,7 +194,8 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
communicationDetails$ = this.applicationService.activatedProcessId$.pipe(
takeUntil(this._orderCompleted),
switchMap((processId) => this.domainCheckoutService.getBuyerCommunicationDetails({ processId }))
switchMap((processId) => this.domainCheckoutService.getBuyerCommunicationDetails({ processId })),
map((communicationDetails) => communicationDetails ?? { email: undefined, mobile: undefined })
);
notificationChannelLoading$ = new Subject<boolean>();
@@ -234,6 +246,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
super({
shoppingCart: undefined,
shoppingCartItems: [],
fetching: false,
});
}
@@ -249,6 +262,7 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
loadShoppingCart = this.effect(($) =>
$.pipe(
tap(() => (this.fetching = true)),
withLatestFrom(this.applicationService.activatedProcessId$),
switchMap(([_, processId]) => {
return this.domainCheckoutService.getShoppingCart({ processId, latest: true }).pipe(
@@ -265,7 +279,8 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
() => {}
)
);
})
}),
tap(() => (this.fetching = false))
)
);
@@ -305,26 +320,51 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
const communicationDetails = await this.communicationDetails$.pipe(first()).toPromise();
this.control = fb.group({
notificationChannel: new FormGroup({
selected: new FormControl(notificationChannel),
selected: new FormControl((notificationChannel & 3) === 3 || communicationDetails.email ? 1 : notificationChannel),
email: new FormControl(communicationDetails ? communicationDetails.email : '', emailNotificationValidator),
mobile: new FormControl(communicationDetails ? communicationDetails.mobile : '', mobileNotificationValidator),
}),
});
}
async onNotificationChange(notificationChannels: NotificationChannel[]) {
async onNotificationChange(notificationChannels?: NotificationChannel[]) {
this.notificationChannelLoading$.next(true);
try {
const control = this.control?.getRawValue();
const notificationChannel = notificationChannels.reduce((val, current) => val | current, 0) as NotificationChannel;
const notificationChannel = notificationChannels
? (notificationChannels.reduce((val, current) => val | current, 0) as NotificationChannel)
: control?.notificationChannel?.selected || 0;
const processId = await this.applicationService.activatedProcessId$.pipe(first()).toPromise();
const email = control?.notificationChannel?.email;
const mobile = control?.notificationChannel?.mobile;
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, email, mobile });
// Check if E-Mail and Mobilnumber is available if E-Mail or SMS checkbox is active
if (notificationChannel === 3 && (!email || !mobile)) {
this.checkNotificationChannelControl$.next(false);
} else if (notificationChannel === 2 && !mobile) {
this.checkNotificationChannelControl$.next(false);
} else if (notificationChannel === 1 && !email) {
this.checkNotificationChannelControl$.next(false);
} else {
this.checkNotificationChannelControl$.next(true);
}
// NotificationChannel nur speichern, wenn Haken und Value gesetzt
let setNotificationChannel = 0;
if ((notificationChannel & 1) === 1 && email) {
setNotificationChannel += 1;
}
if ((notificationChannel & 2) === 2 && mobile) {
setNotificationChannel += 2;
}
if (notificationChannel > 0) {
this.setCommunicationDetails({ processId, notificationChannel, email, mobile });
}
this.domainCheckoutService.setNotificationChannels({
processId,
notificationChannels: (notificationChannel as NotificationChannel) || 0,
notificationChannels: (setNotificationChannel as NotificationChannel) || 0,
});
} catch (error) {
this.uiModal.open({ content: UiErrorModalComponent, data: error, title: 'Fehler beim setzen des Benachrichtigungskanals' });
@@ -333,6 +373,29 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
this.notificationChannelLoading$.next(false);
}
setCommunicationDetails({
processId,
notificationChannel,
email,
mobile,
}: {
processId: number;
notificationChannel: number;
email: string;
mobile: string;
}) {
const emailValid = this.control?.get('notificationChannel')?.get('email')?.valid;
const mobileValid = this.control?.get('notificationChannel')?.get('mobile')?.valid;
if (notificationChannel === 3 && emailValid && mobileValid) {
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, email, mobile });
} else if (notificationChannel === 1 && emailValid) {
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, email });
} else if (notificationChannel === 2 && mobileValid) {
this.domainCheckoutService.setBuyerCommunicationDetails({ processId, mobile });
}
}
openDummyModal(data?: any) {
this.uiModal.open({
content: CheckoutDummyComponent,
@@ -749,10 +812,13 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
} else {
try {
this.showOrderButtonSpinner = true;
await this.domainCheckoutService.completeCheckout({ processId }).toPromise();
// Ticket #3287 Um nur E-Mail und SMS Benachrichtigungen zu setzen und um alle anderen Benachrichtigungskanäle wie z.B. Brief zu deaktivieren
await this.onNotificationChange();
const orders = await this.domainCheckoutService.completeCheckout({ processId }).toPromise();
const orderIds = orders.map((order) => order.id).join(',');
this._orderCompleted.next();
await this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'cart', 'summary']);
this.applicationService.removeProcess(this.applicationService.activatedProcessId);
await this.patchProcess(processId);
await this.router.navigate(['/kunde', processId, 'cart', 'summary', orderIds]);
} catch (error) {
const response = error?.error;
let message: string = response?.message ?? '';
@@ -771,8 +837,8 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
if (error.status === 409) {
this._orderCompleted.next();
await this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'cart', 'summary']);
this.applicationService.removeProcess(this.applicationService.activatedProcessId);
await this.patchProcess(processId);
await this.router.navigate(['/kunde', processId, 'cart', 'summary']);
}
} finally {
this.showOrderButtonSpinner = false;
@@ -780,4 +846,10 @@ export class CheckoutReviewComponent extends ComponentStore<CheckoutReviewCompon
}
}
}
async patchProcess(processId: number) {
this.applicationService.patchProcess(processId, {
type: 'cart-checkout',
});
}
}

Some files were not shown because too many files have changed in this diff Show More