Compare commits

...

220 Commits

Author SHA1 Message Date
Michael Auer
a9479db5be Merge branch 'hotfix/post-2.1-hotfix' 2023-01-12 15:54:53 +01:00
Lorenz Hilpert
262e0c5e11 #3719 Benachrichtigungskanal wird nicht angezeigt 2022-12-19 16:10:53 +01:00
Lorenz Hilpert
8718dd8231 Merged PR 1459: Fix NotificationChannel Display in AHF
Fix NotificationChannel Display in AHF

Related work items: #3719
2022-12-16 14:26:32 +00:00
Lorenz Hilpert
0274b2af3b Merged PR 1458: #3718 Eingabe bei Enter bewirkt löschen und Eingabe Enter
#3718 Eingabe bei Enter bewirkt löschen und Eingabe Enter
2022-12-16 13:20:20 +00:00
Andreas Schickinger
2e286b2de3 Merged PR 1455: #3691 WA Kunden mit unterschiedlichen Kundennummern und gleicher Abholfachnummer
#3691 WA Kunden mit unterschiedlichen Kundennummern und gleicher Abholfachnummer

Related work items: #3691
2022-12-07 14:31:54 +00:00
Andreas Schickinger
168847b24a Merged PR 1454: #3661 AHF Feedbackmeldung beim Nachbestellen entfernt
#3661 AHF Feedbackmeldung beim Nachbestellen entfernt

Related work items: #3661
2022-11-29 14:22:04 +00:00
Andreas Schickinger
69c23a55f7 Merged PR 1453: #3682 AHF // Reservierungsliste - RadionButton ein- und ausblenden bei Paid/Unpaid
#3682 AHF // Reservierungsliste - Radion-Button ein- und ausblenden bei Bezahlt vs Nicht Bezahlt

Related work items: #3682
2022-11-29 14:17:52 +00:00
Andreas Schickinger
f344c2b0e8 Merged PR 1451: #3656 TK Loader Styling Anpassung
#3656 TK Loader Styling Anpassung

Related work items: #3656
2022-11-25 12:49:24 +00:00
Lorenz Hilpert
11819e59c6 #3654 Zubuchen zu Pay und Collect Artikeln verhindern 2022-11-25 13:47:30 +01:00
Lorenz Hilpert
caf7331d28 Fix Unit Test 2022-11-24 11:38:42 +01:00
Lorenz Hilpert
68e5926568 Isa Error Logging 2022-11-24 10:45:53 +01:00
Andreas Schickinger
9d46f49634 Merged PR 1450: #3656 TK Kalenderansicht Loading Feedback
#3656 TK Kalenderansicht Loading Feedback

Related work items: #3656
2022-11-22 17:18:44 +00:00
Lorenz Hilpert
daae9323e8 Merged PR 1447: #3643 API Calls in der WA und WE reduziert
#3643 API Calls in der WA und WE reduziert
2022-11-21 16:46:19 +00:00
Andreas Schickinger
80c425aa6f Merged PR 1449: #3645 TK Bugfix Initiales Loading
#3645 TK Bugfix Initiales Loading

Related work items: #3645
2022-11-21 15:37:00 +00:00
Andreas Schickinger
1cbe309778 Merged PR 1448: #3646 Hotfix TK Fehlender Abstand bei keinen Suchergebnissen
#3646 Hotfix TK Fehlender Abstand bei keinen Suchergebnissen

Related work items: #3646
2022-11-21 15:36:33 +00:00
Michael Auer
189dc64a0f Merge branch 'release/2.1' 2022-11-21 11:35:17 +01:00
Lorenz Hilpert
2f0ede7170 Merge branch 'develop' into release/2.1 2022-11-11 14:49:43 +01:00
Lorenz Hilpert
c7e444d446 #3617 CompartmentLabel für Kunden zwischenhspeichern 2022-11-11 10:56:18 +01:00
Lorenz Hilpert
f5ac916663 #3617 QR-Code Reservierungen 2022-11-10 15:02:27 +01:00
Lorenz Hilpert
b3b9f0223e #3629 - Scpeichern doppelclick abfangen 2022-11-10 14:08:50 +01:00
Lorenz Hilpert
70455df6d3 Merge branch 'develop' into release/2.1 2022-11-08 17:19:29 +01:00
Andreas Schickinger
b89889a3e9 Merged PR 1440: #3602 TK Kalenderansicht lädt nur noch die angezeigten Tage
#3602 TK Kalenderansicht lädt nur noch die angezeigten Tage

Related work items: #3602
2022-11-08 15:50:29 +00:00
Lorenz Hilpert
7ec00925ed Merged PR 1441: #3626 Fehlerbehebung bei der Dummy Anlage Änderung
#3626 Fehlerbehebung bei der Dummy Anlage Änderung
2022-11-08 15:40:07 +00:00
Andreas Schickinger
d3e3d127b3 Merged PR 1439: #3602 TK Performance
- Kein Reload mehr bei Wechsel zwischen Trefferliste/Kalenderansicht
- Suche startet ca. 150ms früher bei Page Load
- In der Wochenansicht wird nur noch die aktuelle Woche und 7 Tage in die Vergangenheit geladen (zuvor: aktuelle Woche + 7 Tage Vergangenheit/ 7 Tage Zukunft)
- In der Monatsansicht werden nur noch die Tage des Monats geladen (zuvor: aktueller Monat + 7 Tage Vergangenheit/ 7 Tage Zukunft
- Bugfix: doppelte Suche wurde ausgelöst, wenn man sucht -> zurück zur Übersicht klickt -> nochmal das gleiche sucht

Related work items: #3602
2022-11-08 09:13:21 +00:00
Nino Righi
11c4d8fb72 Merged PR 1438: #3608 Fix typo
#3608 Fix typo
2022-11-07 16:23:23 +00:00
Andreas Schickinger
9b02a19b9c Merged PR 1437: #3609 Remission Schriftfarbe bei Auswahl weiß
#3609 Remission Schriftfarbe bei Auswahl weiß

Related work items: #3609
2022-11-07 15:44:11 +00:00
Michael Auer
57262919b8 Merge tag '2.1.10' into develop 2022-11-07 15:07:34 +01:00
Michael Auer
f787a15347 Merge branch 'release/2.1' 2022-11-07 15:06:31 +01:00
Michael Auer
bc74eac86d Merge tag '2.1.2' into develop 2022-11-07 14:58:16 +01:00
Michael Auer
0a1df250cb Merge commit '9e58e8aad9ae6a6928235dca55b30a0925c668f6' 2022-11-07 14:55:46 +01:00
Lorenz Hilpert
8a7b9de29d Merged PR 1436: #3615 Übernahme der Suchfeld eingabe in die Kundenanlage
#3615 Übernahme der Suchfeld eingabe in die Kundenanlage
2022-11-07 13:31:14 +00:00
Nino Righi
8efb87a1f7 Merged PR 1434: #3610 Fix Added Async Validator Function to p4m number form block
#3610 Fix Added Async Validator Function to p4m number form block
2022-11-03 18:21:07 +00:00
Andreas Schickinger
27ee667bfb Merged PR 1435: #3604 Kubi Checkmarks bei Kundentypanlage angepasst
#3604 Kubi Checkmarks bei Kundentypanlage angepasst

Related work items: #3604
2022-11-03 14:34:30 +00:00
Lorenz Hilpert
c64ff772e7 Merged PR 1433: Kundenanlage: Scanbutton für das scannen der Kundenkarte
Kundenanlage: Scanbutton für das scannen der Kundenkarte
2022-11-03 14:13:58 +00:00
Lorenz Hilpert
313efebb8b #3611 Dialog Anpassung handleAction 2022-11-02 16:47:58 +01:00
Lorenz Hilpert
9eee4fff6c Merged PR 1432: #3613 Wannennummer Scannen - Dialog erweitert
#3613 Wannennummer Scannen - Dialog erweitert
2022-11-02 15:15:48 +00:00
Lorenz Hilpert
b5a7c96181 Merge branch 'develop' into release/2.1 2022-11-02 15:00:54 +01:00
Lorenz Hilpert
5bea71e19e Merged PR 1431: #3611 Dummy Command angelegt
#3611 Dummy Command angelegt
2022-11-02 13:55:01 +00:00
Lorenz Hilpert
751e533dce Merge branch 'release/2.1' into develop 2022-10-28 11:20:42 +02:00
Lorenz Hilpert
9e58e8aad9 Merge branch 'develop' into release/2.1 2022-10-26 18:52:17 +02:00
Lorenz Hilpert
d62a7d704a #3587 Fix rueckgaenging gemacht, da fix im BE 2022-10-26 18:21:16 +02:00
Andreas Schickinger
f8d7d12d61 Merged PR 1430: #3586 WK Hindernis Popup Text angepasst
#3586 WK Hindernis Popup Text angepasst

Related work items: #3586
2022-10-26 16:07:07 +00:00
Lorenz Hilpert
2ad71cad78 #3587 Filter wurde nicht richtig übernommen 2022-10-26 18:04:38 +02:00
Andreas Schickinger
689d8ead24 Merged PR 1427: #3592 TK Tasks Wochenansicht reload wenn sich das DisplayedDate ändert
#3592 TK Tasks Wochenansicht reload wenn sich das DisplayedDate ändert

Related work items: #3592
2022-10-26 14:33:52 +00:00
Andreas Schickinger
b6a7df76f6 Merged PR 1428: #3593 TK Footer Menü Klick setzt den Filter zurück
#3593 TK Footer Menü Klick setzt den Filter zurück

Related work items: #3593
2022-10-26 14:30:27 +00:00
Andreas Schickinger
41e8145858 Merged PR 1429: #3594 TK Zurück zur Übersicht Button
#3594 TK Zurück zur Übersicht Button

Related work items: #3594
2022-10-26 14:29:27 +00:00
Lorenz Hilpert
3a5b80657c Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2022-10-26 15:06:23 +02:00
Lorenz Hilpert
41c6897224 uiDateInput - datum mit momentjs parsen 2022-10-26 15:06:16 +02:00
Lorenz Hilpert
8d644cdd65 Merged PR 1426: 3581 Handlungsanweisung
Related work items: #3581
2022-10-25 12:34:39 +00:00
Lorenz Hilpert
7a68229432 Merged PR 1424: #3580 Text in der Kundensuche angepasst
#3580 Text in der Kundensuche angepasst
2022-10-25 11:57:35 +00:00
Lorenz Hilpert
4dab0ef6f6 Merged PR 1425: #3581 Textanpassung für Kundenanlage
#3581 Textanpassung für Kundenanlage
2022-10-25 11:50:22 +00:00
Andreas Schickinger
75cad811bc Merged PR 1423: #3419 TK Suche / Wochenansicht Heute
#3419 TK Suche / Wochenansicht Heute

Related work items: #3419
2022-10-25 08:43:59 +00:00
Lorenz Hilpert
0709f1dd6a Merged PR 1422: #3576 Dialog angepasst und Weiterleitung auf Kundendetails
#3576 Dialog angepasst und Weiterleitung auf Kundendetails

Related work items: #3576
2022-10-24 16:06:00 +00:00
Lorenz Hilpert
478950c446 Merged PR 1421: #3577 Fallback walls Preis undefined ist
#3577 Fallback walls Preis undefined ist
2022-10-24 15:10:13 +00:00
Andreas Schickinger
b5e5601671 Merged PR 1420: #3420 TK Routing
#3420 TK Routing

Related work items: #3420
2022-10-24 14:50:15 +00:00
Lorenz Hilpert
0ee86faa00 Merged PR 1418: Suchbutton werden nicht immer angezeigt
Related work items: #3546
2022-10-24 14:10:30 +00:00
Andreas Schickinger
fa7b204f89 Merged PR 1419: #3547 TK iPad Drucken Button
#3547 TK iPad Drucken Button

Related work items: #3547
2022-10-24 14:06:55 +00:00
Lorenz Hilpert
22f9ba80fe removed unused icons 2022-10-21 16:15:11 +02:00
Michael Auer
065878a6e9 ~ Version Bump: 2.1 2022-10-21 15:35:52 +02:00
Lorenz Hilpert
5df433d603 #3559 PDP falsche Farbe von Reiter-Hintergrund 2022-10-21 14:41:14 +02:00
Lorenz Hilpert
21375855dd #3556 HFI Gutschein Kaufoption im Nachhinein ändern 2022-10-21 14:36:48 +02:00
Lorenz Hilpert
b847ca6b3e #3554 AGB ist Akzeptiert wenn Dialog mit Ja beantwortet wird 2022-10-21 14:26:53 +02:00
Andreas Schickinger
50caf9811d Merged PR 1416: #3520 HFI Geschenkkarte Mengenänderung übergibt Preis
#3520 HFI Geschenkkarte Mengenänderung übergibt Preis

Related work items: #3520
2022-10-21 10:58:21 +00:00
Andreas Schickinger
f0df9e1157 Merged PR 1417: #3571 TK Kalender Klicks navigieren wieder zur Liste
#3571 TK Kalender Klicks navigieren wieder zur Liste

Related work items: #3571
2022-10-21 10:57:53 +00:00
Lorenz Hilpert
3e1347f17e #3573 Breadcrumb Fix 2022-10-20 16:06:10 +02:00
Lorenz Hilpert
86082bbfda #3572 Chckbox Plazierung 2022-10-20 15:21:10 +02:00
Lorenz Hilpert
6e6551ceae #3567 B2B Meldenummer auf der Artikeldetailseite 2022-10-20 14:13:11 +02:00
Lorenz Hilpert
08ef5f0853 #3568 Typo Rechnungsadresse 2022-10-20 13:53:48 +02:00
Lorenz Hilpert
92131453e8 #3422 Focus Searchbar nach Seitenwechsel TK 2022-10-20 13:33:44 +02:00
Lorenz Hilpert
888a95d2a0 #3233 CRM API Anpassung für Kundenkartencheck 2022-10-20 13:12:52 +02:00
Lorenz Hilpert
ab745cba18 #2748 Icons Kundentrefferliste und Details 2022-10-20 11:44:12 +02:00
Lorenz Hilpert
dfe6b3977f #3554 Dialog angepasst 2022-10-19 16:51:06 +02:00
Lorenz Hilpert
e7e8b71a70 Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2022-10-19 13:45:12 +02:00
Lorenz Hilpert
c158e16bd3 #3553 Plattierung AGB akzeptieren 2022-10-19 13:45:04 +02:00
Andreas Schickinger
464fac660b Merged PR 1415: #3543 TK iPad Scrolling
#3543 TK iPad Scrolling

Related work items: #3543
2022-10-19 11:21:18 +00:00
Lorenz Hilpert
79b1920b15 #3542 WA Keine Suchanfrage wenn Daten bereits vorhanden sind 2022-10-18 11:33:52 +02:00
Andreas Schickinger
f071e7b2d5 Merged PR 1411: #3527 Kundenkarten Validator
#3527 Kundenkarten Validator

Related work items: #3527
2022-10-17 15:30:52 +00:00
Andreas Schickinger
3afb8d6ed1 Merged PR 1412: #3422 TK Suchfeld Autofocus directive und Laufende Aufgaben auch ausblenden,...
#3422 TK Suchfeld Autofocus directive und Laufende Aufgaben auch ausblenden, wenn keine vorhanden

Related work items: #3422
2022-10-17 15:03:52 +00:00
Andreas Schickinger
87a2e94dd6 Merged PR 1406: #3496 HFI Geschenkkarte max Preis fix
#3496 HFI Geschenkkarte max Preis fix

Related work items: #3496
2022-10-17 14:01:42 +00:00
Andreas Schickinger
d711d4a816 Merged PR 1409: #3419 TK Überfällige Aufgaben nur anzeigen, wenn vorhanden
#3419 TK Überfällige Aufgaben nur anzeigen, wenn vorhanden

Related work items: #3419
2022-10-17 13:55:47 +00:00
Lorenz Hilpert
a4b4aeed64 #3471 Email entfernen bei klick auf nein 2022-10-17 15:51:08 +02:00
Lorenz Hilpert
3707572bd8 Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2022-10-17 15:48:37 +02:00
Lorenz Hilpert
aef5c06f0a #3531 CSS Kundenanlage Speichern Button Position 2022-10-17 15:48:26 +02:00
Lorenz Hilpert
a021ac0da3 Merged PR 1410: #3503 IPad Sortierung im TK falsh
#3503 IPad Sortierung im TK falsh
2022-10-17 13:40:33 +00:00
Lorenz Hilpert
1e4e6da44e Merged PR 1408: #3545 Fix Icon Positions IPad mini 2
#3545 Fix Icon Positions IPad mini 2
2022-10-17 12:46:45 +00:00
Andreas Schickinger
6b0d9774c5 Merged PR 1407: #3423 TK Kalendar klicks navigieren nicht mehr zur Listenansicht
#3423 TK Kalendar klicks navigieren nicht mehr zur Listenansicht

Related work items: #3423
2022-10-17 11:55:20 +00:00
Lorenz Hilpert
e1720e6023 Merged PR 1405: icon scss 2022-10-14 14:24:12 +00:00
Lorenz Hilpert
3078724ced #3540 Text Änderung Interessen 2022-10-14 15:20:51 +02:00
Lorenz Hilpert
f3eb0a67f6 #3539 Typo Interessen 2022-10-14 15:19:53 +02:00
Lorenz Hilpert
65d7a6f5a4 Merged PR 1404: #3471 Zusätzliche addressen werden nun gespeichert
#3471 Zusätzliche addressen werden nun gespeichert

Related work items: #3471
2022-10-14 11:36:44 +00:00
Lorenz Hilpert
b2b5456400 Merged PR 1403: #3518 Checkbox ist nicht abwählbar bei einer checkbox
#3518 Checkbox ist nicht abwählbar bei einer checkbox
2022-10-14 10:08:29 +00:00
Lorenz Hilpert
a598be069d Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2022-10-14 10:55:59 +02:00
Andreas Schickinger
bf61f2c982 Merged PR 1402: #3507 Listenbestellung getPickUpAvailability hack
#3507 Listenbestellung getPickUpAvailability hack

Related work items: #3507
2022-10-14 08:49:41 +00:00
Lorenz Hilpert
63225491f1 FIX CUSTOMER CREATE NAVIGATION 2022-10-13 18:37:18 +02:00
Andreas Schickinger
0dbc773775 Merged PR 1400: #3496 HFI Geschenkkarte max Preis
#3496 HFI Geschenkkarte max Preis

Related work items: #3496
2022-10-13 13:10:34 +00:00
Lorenz Hilpert
47b7d42dd3 Merged PR 1399: #3227 Anpassung der Handlungsanweisung
#3227 Anpassung der Handlungsanweisung
2022-10-13 13:06:36 +00:00
Lorenz Hilpert
d713c787e6 Merged PR 1398: #3515 Added Newsletter Text to webshop and fix navigation
#3515 Added Newsletter Text to webshop and fix navigation
2022-10-13 13:05:56 +00:00
Lorenz Hilpert
68a2eab425 Merged PR 1396: #3502 Guard bei navigation auf P4M Anlage angepasst
#3502 Guard bei navigation auf P4M Anlage angepasst
2022-10-12 15:02:17 +00:00
Andreas Schickinger
886f063d1b Fix für Anzeigefehler im SectionToggle 2022-10-12 16:40:29 +02:00
Andreas Schickinger
ed6ee36509 Merged PR 1395: #3481 Remission Filter zwischenspeichern bei Wechsel zwischen Pflicht- und Ab...
#3481 Remission Filter zwischenspeichern bei Wechsel zwischen Pflicht- und Abteilungsremission

Related work items: #3481
2022-10-12 13:57:28 +00:00
Andreas Schickinger
ab345dae0d Merged PR 1393: #3514 TK Fehlermeldung fix
#3514 TK Fehlermeldung fix

Related work items: #3514
2022-10-12 13:39:29 +00:00
Lorenz Hilpert
fbb1e6c4a2 Merged PR 1394: #3511 Addressvalidierung für Onlinekonto aktiviert
#3511 Addressvalidierung für Onlinekonto aktiviert
2022-10-12 10:38:35 +00:00
Lorenz Hilpert
4ede9226b4 Merged PR 1392: #3484 Keep Userstates on Tab Changes
#3484 Keep Userstates on Tab Changes
2022-10-11 15:05:04 +00:00
Andreas Schickinger
fbfecbd8ae Merged PR 1391: TK Suche Timespan Fallback angepasst
TK Suche Timespan von 6 Monate Vergangenheit/Zukunft auf 6 Wochen Vergangenheit, 2 Wochen Zukunft geändert. Wird verwendet, wenn kein Timespan Filter gesetzt ist

Related work items: #3422
2022-10-11 14:32:30 +00:00
Lorenz Hilpert
3c4612d15c Merged PR 1389: #3307 Filter Wird Nicht Gerendert
#3307 Filter Wird Nicht Gerendert
2022-10-11 13:33:10 +00:00
Andreas Schickinger
7fa2e7862d Merged PR 1388: #3508 Bestellbestätigung "zur Warenausgabe": Prüfung auf enabled bei CustomerFeature B2B entfernt
#3508 Prüfung auf enabled bei CustomerFeature B2B entfernt

Related work items: #3508
2022-10-11 12:16:07 +00:00
Andreas Schickinger
114267362c Merged PR 1384: #3503 TK Sortierung umgedreht, #3504 Scroll Fix
#3503 TK Sortierung umgedreht, #3504 Scroll Fix

Related work items: #3503, #3504
2022-10-11 11:06:01 +00:00
Lorenz Hilpert
4050e9605d FIX Routing Kundenalage 2022-10-11 10:41:25 +02:00
Andreas Schickinger
fdd5373aaf Merged PR 1385: #3493 TK Modal Header Abstand erhöht
#3493 TK Modal Header Abstand erhöht

Related work items: #3493
2022-10-10 15:59:04 +00:00
Andreas Schickinger
2dfe7ec05b Merged PR 1386: #3492 TK Drucken in der Trefferliste fix
#3492 TK Drucken in der Trefferliste fix

Related work items: #3492
2022-10-10 15:58:42 +00:00
Lorenz Hilpert
82513b5dde Merged PR 1387: Kubi Kundenanlage und Kundenkarte
Related work items: #3230, #3233
2022-10-10 15:57:50 +00:00
Lorenz Hilpert
14d1bb6ac8 Merged PR 1383: HFI Geschnakkarte
Related work items: #3496
2022-10-07 14:20:05 +00:00
Lorenz Hilpert
d589c94681 Fix Kundendatenerfassen - added modifier add-loyality-card 2022-10-06 12:42:45 +02:00
Lorenz Hilpert
eca19bb507 Updateing Customer for P4M 2022-10-06 10:47:10 +02:00
Lorenz Hilpert
282ff30b3e #3494 Upgrade Ava API auf v6 2022-10-05 13:58:02 +02:00
Lorenz Hilpert
5d0b810674 Upgrade catsearch API auf V6 2022-10-05 13:54:12 +02:00
Lorenz Hilpert
e32482c634 Type Kundendaten erfassen speichern aufruf 2022-10-05 13:25:43 +02:00
Lorenz Hilpert
650026b0c0 Es existiert bereits ein Onlinekonto 2022-10-04 17:48:15 +02:00
Lorenz Hilpert
cd25d6da38 #3489 fonts 2022-10-04 12:10:17 +02:00
Andreas Schickinger
9dd0954967 Merged PR 1381: #3326 Tätigkeitskalender Suchfunktion und neues Design
Tätigkeitskalender Suchfunktion und neues Design

Zusätzliche Änderungen im PR:
- Anpassungen für GitHub package Zugriff
- UiModalRef um ein afterChanged$ erweitert, um nach dem schließen zu erkennen ob ein reload notwendig ist
- ui-loader funktionierte nicht bei verwendung von ui-scroll-container mit useLoadAnimation false
- ui-skeleton-loader um Template für TK Listenitem erweitert

Related work items: #3419, #3420, #3421, #3422, #3423
2022-10-04 09:42:49 +00:00
Lorenz Hilpert
fdaceb9bf8 Merged PR 1382: Kubi
Related work items: #3228, #3230, #3289, #3467, #3471, #3478
2022-09-30 13:48:19 +00:00
Lorenz Hilpert
4ab3a3b3cf #3307 Fixed With Filter 2022-09-30 11:19:17 +02:00
Lorenz Hilpert
eb8b54dc63 Fix Unit Test 2022-09-29 18:05:43 +02:00
Lorenz Hilpert
4703aee60c Interveptor Unit Test Fix 2022-09-29 16:26:43 +02:00
Lorenz Hilpert
93b0d43bd7 #3307 Anzeige des backdrops bei Filtern 2022-09-29 14:20:41 +02:00
Lorenz Hilpert
1029310e0d #3483 Neuanmeldung bei 401 Antworten 2022-09-29 14:11:59 +02:00
Michael Auer
c083684db2 Merge tag '3452-Autocomplete-Abbrechen-Bei-Suche' into develop 2022-09-28 22:19:03 +02:00
Michael Auer
d6775aad69 Merge branch 'hotfix/3452-Autocomplete-Abbrechen-Bei-Suche' 2022-09-28 22:19:02 +02:00
Lorenz Hilpert
3eff10bbb4 Merged PR 1380: Upgrade der API auf V6
Upgrade der API auf V6

Related work items: #3466
2022-09-20 16:14:50 +00:00
Lorenz Hilpert
e4cbab8365 Merged PR 1379: #3428 Aktivierung der Buttons ohne Raio button aktivieren wenn nur ein artikel
#3428 Aktivierung der Buttons ohne Raio button aktivieren wenn nur ein artikel
2022-09-15 16:05:12 +00:00
Lorenz Hilpert
18212e7a4c Merged PR 1378: Update CRM API V6
Update CRM API V6
2022-09-14 15:13:14 +00:00
Lorenz Hilpert
0cd0b1abfd Merged PR 1377: Update CRM API
Related work items: #3464
2022-09-14 13:48:25 +00:00
Lorenz Hilpert
a66137873c Merged PR 1376: #3448 Anzeige Sonderinfo
#3448 Anzeige Sonderinfo
2022-09-14 08:26:29 +00:00
Andreas Schickinger
469110eabf Merged PR 1375: #3455 AHF Frist immer für gesamten Warenkorb festlegen
#3455 AHF Frist immer für gesamten Warenkorb festlegen

Related work items: #3455
2022-09-13 13:08:59 +00:00
Lorenz Hilpert
55474fa4e3 Merged PR 1374: #3451 nach löschen des Browser-Verlauf kommt Fehler
#3451 nach löschen des Browser-Verlauf kommt Fehler
2022-09-13 12:56:17 +00:00
Lorenz Hilpert
3bdcdee031 #3452 Autocomplete wird nicht abgebrochen, wenn Suche ausgelöst wurde 2022-09-12 14:34:24 +02:00
Lorenz Hilpert
246c5a61dd Merged PR 1373: #3428 bei Teilabholung ohne ausgewählten Radio-Button wirf Fehler
#3428 bei Teilabholung ohne ausgewählten Radio-Button wirf Fehler
2022-09-08 13:20:45 +00:00
Andreas Schickinger
0c8bfba515 Merged PR 1372: #3340 Quantity Dropdown um Suffix erweitert
#3340 Quantity Dropdown um Suffix erweitert

Related work items: #3340
2022-09-01 08:23:20 +00:00
Michael Auer
3c8d9bb1e5 Merge tag '2.0' into develop 2022-08-24 10:33:19 +02:00
Michael Auer
da2c1c8316 Merge branch 'release/2.0' 2022-08-24 10:33:18 +02:00
Lorenz Hilpert
0334b2dd33 Merge branch 'release/2.0' into develop 2022-08-23 14:24:21 +02:00
Lorenz Hilpert
96356042af Remission - Anpassung HTML fuer E2E Tests 2022-08-23 14:02:50 +02:00
Lorenz Hilpert
21adff8d0c #3140 Button "Nicht-Clickbar"-Icon nur wenn disabled 2022-08-22 16:11:51 +02:00
Lorenz Hilpert
35def2a7c7 #3405 Kalender -Speichern-Button verschwindet 2022-08-19 17:07:30 +02:00
Lorenz Hilpert
e3d82794a3 Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2022-08-19 15:55:10 +02:00
Lorenz Hilpert
20cbac8f17 #3140 Ausgrauen des Auswaehlen Buttons fur
Zuruecklegen bis
2022-08-19 15:55:01 +02:00
Lorenz Hilpert
6067e02729 #3400 Uebergabe 0 Eur wenn kein Preis existiert 2022-08-18 16:57:06 +02:00
Lorenz Hilpert
eb77664ea1 #3387 - Auswertung der Kapazitaeten angepasst 2022-08-18 15:38:24 +02:00
Michael Auer
8b1baf9ebd Merge tag '2.0.531' into develop 2022-08-18 13:47:13 +02:00
Michael Auer
12fb774b73 Merge branch 'release/2.0' 2022-08-18 13:44:03 +02:00
Lorenz Hilpert
d79dbb11fe #2162 - Warnung eingebaut und Anzeigefehler behoben 2022-08-17 17:08:09 +02:00
Lorenz Hilpert
19ccb29248 #2162 - WA - Auf der Details Seite werden nur items mit dem gleichen Kunden angezeigt. 2022-08-16 15:29:33 +02:00
Lorenz Hilpert
199c4f30e7 #3139 Loder angepasst 2022-08-11 17:23:20 +02:00
Lorenz Hilpert
732c0d4e35 #3139 Load Spinner für Abholfrist 2022-08-10 16:50:29 +02:00
Lorenz Hilpert
fa1769da9f 2022-08-08 12:10:19 +02:00
Lorenz Hilpert
029997d624 #3338 - Uebergabe Filter angepasst 2022-08-08 11:56:46 +02:00
Andreas Schickinger
d2546409cb Merged PR 1370: #3139 Abholfachfrist: Für alle festlegen Button in Bestellbestätigung
#3139 Abholfachfrist: Für alle festlegen Button in Bestellbestätigung

Related work items: #3139
2022-08-08 08:46:00 +00:00
Andreas Schickinger
cb2bc8d65b Merged PR 1369: #3332 AHFFrist DisplayOrderItemSubsetDTO verwendet
#3332 AHFFrist DisplayOrderItemSubsetDTO verwendet

Related work items: #3332
2022-08-04 13:38:39 +00:00
Andreas Schickinger
57bd8d4dd4 Merged PR 1368: #3337 Hotfix E-Mail Adresse wird nur noch angehakt, wenn auch eine hinterlegt ist
#3337 E-Mail Adresse wird nur noch angehakt, wenn auch eine hinterlegt ist

Related work items: #3337
2022-08-04 09:45:59 +00:00
Andreas Schickinger
cc1e210799 Merged PR 1367: #3139, #3140, #3328 AHFFrist auf Bestellbestätigung- und Bestellpostenseite
Related work items: #3139, #3140, #3328
2022-08-03 13:51:25 +00:00
Lorenz Hilpert
4bee08d483 Merged PR 1366: Merge release => develop
Related work items: #3180, #3203, #3245, #3293, #3299, #3312, #3320, #3322
2022-08-02 12:30:08 +00: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
854 changed files with 17999 additions and 15060 deletions

4
.npmrc
View File

@@ -1,3 +1 @@
@isa:registry=https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/
@cmf:registry=https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/
always-auth=true
@paragondata:registry=https://npm.pkg.github.com

View File

@@ -3629,6 +3629,37 @@
}
}
}
},
"@ui/form-field": {
"projectType": "library",
"root": "apps/ui/form-field",
"sourceRoot": "apps/ui/form-field/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/ui/form-field/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/ui/form-field/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/ui/form-field/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/ui/form-field/src/test.ts",
"tsConfig": "apps/ui/form-field/tsconfig.spec.json",
"karmaConfig": "apps/ui/form-field/karma.conf.js"
}
}
}
}
},
"defaultProject": "isa-app"

View File

@@ -31,7 +31,11 @@ export class AuthService {
this._oAuthService.tokenValidationHandler = new JwksValidationHandler();
this._oAuthService.setupAutomaticSilentRefresh();
await this._oAuthService.loadDiscoveryDocumentAndTryLogin();
try {
await this._oAuthService.loadDiscoveryDocumentAndTryLogin();
} catch (error) {
this.login();
}
this._initialized.next(true);
}

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 {

View File

@@ -1,10 +1,10 @@
import { Injectable, Injector } from '@angular/core';
import { Injectable, Injector, Optional, SkipSelf } from '@angular/core';
import { ActionHandler } from './action-handler.interface';
import { FEATURE_ACTION_HANDLERS, ROOT_ACTION_HANDLERS } from './tokens';
@Injectable()
export class CommandService {
constructor(private injector: Injector) {}
constructor(private injector: Injector, @Optional() @SkipSelf() private _parent: CommandService) {}
async handleCommand<T>(command: string, data?: T): Promise<T> {
const actions = this.getActions(command);
@@ -15,7 +15,7 @@ export class CommandService {
console.error('CommandService.handleCommand', 'Action Handler does not exist', { action });
throw new Error('Action Handler does not exist');
}
console.log('handle command', handler, data);
data = await handler.handler(data);
}
return data;
@@ -25,10 +25,16 @@ export class CommandService {
return command?.split('|') || [];
}
getActionHandler(action: string): ActionHandler {
getActionHandler(action: string): ActionHandler | undefined {
const featureActionHandlers: ActionHandler[] = this.injector.get(FEATURE_ACTION_HANDLERS, []);
const rootActionHandlers: ActionHandler[] = this.injector.get(ROOT_ACTION_HANDLERS, []);
return [...featureActionHandlers, ...rootActionHandlers].find((handler) => handler.action === action);
let handler = [...featureActionHandlers, ...rootActionHandlers].find((handler) => handler.action === action);
if (this._parent && !handler) {
handler = this._parent.getActionHandler(action);
}
return handler;
}
}

View File

@@ -1,9 +1,4 @@
<div
class="toast-main"
[style.width]="width"
[@slideAnimation]="{ value: animationState }"
(@slideAnimation.done)="onSlideFinished($event)"
>
<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>

View File

@@ -11,9 +11,9 @@ import { TOAST_CONFIG_TOKEN } from './tokens';
animations: [toastAnimations.slideToast],
})
export class ToastComponent implements OnInit, OnDestroy {
timeoutRef: NodeJS.Timeout;
timeoutRef?: any;
animationState: ToastAnimationState = 'default';
width: string = '55.25rem';
width = '55.25rem';
constructor(
@Inject(TOAST_CONFIG_TOKEN) public readonly data: Toast,
@@ -40,7 +40,7 @@ export class ToastComponent implements OnInit, OnDestroy {
this.animationState = 'closing';
}
onSlideFinished(event: AnimationEvent) {
onSlideFinished() {
if (this.animationState === 'closing') {
this._ref.close();
}

View File

@@ -1,34 +1,42 @@
import { Injectable } from '@angular/core';
import { ItemDTO, SearchService } from '@swagger/cat';
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, StoreCheckoutService, SupplierDTO } from '@swagger/checkout';
import { ItemDTO } from '@swagger/cat';
import {
AvailabilityDTO,
BranchDTO,
OLAAvailabilityDTO,
StoreCheckoutBranchService,
StoreCheckoutSupplierService,
SupplierDTO,
} from '@swagger/checkout';
import { combineLatest, Observable, of } from 'rxjs';
import {
AvailabilityRequestDTO,
AvailabilityService as SwaggerAvailabilityService,
AvailabilityService,
AvailabilityDTO as SwaggerAvailabilityDTO,
AvailabilityType,
} from '@swagger/availability';
import { AvailabilityDTO as CatAvailabilityDTO } from '@swagger/cat';
import { map, shareReplay, switchMap, withLatestFrom, mergeMap, timeout } from 'rxjs/operators';
import { isArray, memorize } from '@utils/common';
import { OrderService } from '@swagger/oms';
import { LogisticianDTO, LogisticianService } from '@swagger/oms';
import { ResponseArgsOfIEnumerableOfStockInfoDTO, StockDTO, StockInfoDTO, StockService } from '@swagger/remi';
import { ItemData } from './defs/item-data.model';
import { PriceDTO } from '@swagger/availability';
import { AvailabilityByBranchDTO } from './defs/availability-by-branch-dto.model';
import { AvailabilityByBranchDTO, ItemData } from './defs';
import { Availability } from './defs/availability';
@Injectable()
export class DomainAvailabilityService {
constructor(
private swaggerAvailabilityService: SwaggerAvailabilityService,
private storeCheckoutService: StoreCheckoutService,
private orderService: OrderService,
private _stock: StockService
private _availabilityService: AvailabilityService,
private _logisticanService: LogisticianService,
private _stockService: StockService,
private _supplierService: StoreCheckoutSupplierService,
private _branchService: StoreCheckoutBranchService
) {}
@memorize()
getSuppliers(): Observable<SupplierDTO[]> {
return this.storeCheckoutService.StoreCheckoutGetSuppliers({}).pipe(
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
map((response) => response.result),
shareReplay()
);
@@ -36,15 +44,15 @@ export class DomainAvailabilityService {
@memorize()
getTakeAwaySupplier(): Observable<SupplierDTO> {
return this.storeCheckoutService.StoreCheckoutGetSuppliers({}).pipe(
map(({ result }) => result.find((supplier) => supplier?.supplierNumber === 'F')),
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
map(({ result }) => result?.find((supplier) => supplier?.supplierNumber === 'F')),
shareReplay()
);
}
@memorize()
getBranches(): Observable<BranchDTO[]> {
return this.storeCheckoutService.StoreCheckoutGetBranches({}).pipe(
return this._branchService.StoreCheckoutBranchGetBranches({}).pipe(
map((response) => response.result),
shareReplay()
);
@@ -52,7 +60,7 @@ export class DomainAvailabilityService {
@memorize()
getCurrentStock(): Observable<StockDTO> {
return this._stock.StockCurrentStock().pipe(
return this._stockService.StockCurrentStock().pipe(
map((response) => response.result),
shareReplay()
);
@@ -60,7 +68,7 @@ export class DomainAvailabilityService {
@memorize()
getCurrentBranch(): Observable<BranchDTO> {
return this._stock.StockCurrentBranch().pipe(
return this._stockService.StockCurrentBranch().pipe(
map((response) => ({
id: response.result.id,
name: response.result.name,
@@ -83,8 +91,8 @@ export class DomainAvailabilityService {
}
@memorize({})
getLogisticians() {
return this.orderService.OrderGetLogisticians({}).pipe(
getLogisticians(): Observable<LogisticianDTO> {
return this._logisticanService.LogisticianGetLogisticians({}).pipe(
map((response) => response.result?.find((l) => l.logisticianNumber === '2470')),
shareReplay()
);
@@ -101,7 +109,7 @@ export class DomainAvailabilityService {
price: PriceDTO;
quantity: number;
}): Observable<AvailabilityByBranchDTO[]> {
return this._stock.StockStockRequest({ stockRequest: { branchIds, itemId } }).pipe(
return this._stockService.StockStockRequest({ stockRequest: { branchIds, itemId } }).pipe(
map((response) => response.result),
withLatestFrom(this.getTakeAwaySupplier()),
map(([result, supplier]) => {
@@ -125,8 +133,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._stockService.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 });
@@ -148,7 +161,7 @@ export class DomainAvailabilityService {
quantity: number;
}): Observable<AvailabilityDTO> {
return combineLatest([
this._stock.StockStockRequest({ stockRequest: { branchIds: [branch.id], itemId } }),
this._stockService.StockStockRequest({ stockRequest: { branchIds: [branch.id], itemId } }),
this.getTakeAwaySupplier(),
]).pipe(
map(([response, supplier]) => {
@@ -168,7 +181,7 @@ export class DomainAvailabilityService {
quantity: number;
}): Observable<AvailabilityDTO> {
return this.getCurrentStock().pipe(
switchMap((s) => this._stock.StockInStockByEAN({ eans, stockId: s.id })),
switchMap((s) => this._stockService.StockInStockByEAN({ eans, stockId: s.id })),
withLatestFrom(this.getTakeAwaySupplier(), this.getCurrentBranch()),
map(([response, supplier, branch]) => {
return this._mapToTakeAwayAvailability({ response, supplier, branch, quantity, price });
@@ -180,7 +193,7 @@ export class DomainAvailabilityService {
getTakeAwayAvailabilitiesByEans({ eans }: { eans: string[] }): Observable<StockInfoDTO[]> {
const eansFiltered = Array.from(new Set(eans));
return this.getCurrentStock().pipe(
switchMap((s) => this._stock.StockInStockByEAN({ eans: eansFiltered, stockId: s.id })),
switchMap((s) => this._stockService.StockInStockByEAN({ eans: eansFiltered, stockId: s.id })),
withLatestFrom(this.getTakeAwaySupplier(), this.getCurrentBranch()),
map((response) => response[0].result),
shareReplay()
@@ -188,8 +201,16 @@ export class DomainAvailabilityService {
}
@memorize({ ttl: 10000 })
getPickUpAvailability({ item, branch, quantity }: { item: ItemData; quantity: number; branch: BranchDTO }): Observable<AvailabilityDTO> {
return this.swaggerAvailabilityService
getPickUpAvailability({
item,
branch,
quantity,
}: {
item: ItemData;
quantity: number;
branch: BranchDTO;
}): Observable<Availability<AvailabilityDTO, SwaggerAvailabilityDTO>> {
return this._availabilityService
.AvailabilityStoreAvailability([
{
qty: quantity,
@@ -207,7 +228,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
return this.swaggerAvailabilityService
return this._availabilityService
.AvailabilityShippingAvailability([
{
ean: item?.ean,
@@ -225,7 +246,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDigDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
return this.swaggerAvailabilityService
return this._availabilityService
.AvailabilityShippingAvailability([
{
qty: quantity,
@@ -270,7 +291,11 @@ export class DomainAvailabilityService {
timeout(5000),
mergeMap((branch) =>
this.getPickUpAvailability({ item, quantity, branch }).pipe(
mergeMap((availability) => logistician$.pipe(map((logistician) => ({ ...availability, logistician: { id: logistician.id } })))),
mergeMap((availability) =>
logistician$.pipe(
map((logistician) => ({ ...(availability?.length > 0 ? availability[0] : []), logistician: { id: logistician.id } }))
)
),
shareReplay()
)
)
@@ -279,7 +304,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDownloadAvailability({ item }: { item: ItemData }): Observable<AvailabilityDTO> {
return this.swaggerAvailabilityService
return this._availabilityService
.AvailabilityShippingAvailability([
{
ean: item?.ean,
@@ -314,11 +339,11 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getTakeAwayAvailabilities(items: { id: number; price: PriceDTO }[], branchId: number) {
return this._stock.StockGetStocksByBranch({ branchId }).pipe(
return this._stockService.StockGetStocksByBranch({ branchId }).pipe(
map((req) => req.result?.find((_) => true)?.id),
switchMap((stockId) =>
stockId
? this._stock.StockInStock({ articleIds: items.map((i) => i.id), stockId })
? this._stockService.StockInStock({ articleIds: items.map((i) => i.id), stockId })
: of({ result: [] } as ResponseArgsOfIEnumerableOfStockInfoDTO)
),
timeout(20000),
@@ -339,7 +364,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getPickUpAvailabilities(payload: AvailabilityRequestDTO[], preferred?: boolean) {
return this.swaggerAvailabilityService.AvailabilityStoreAvailability(payload).pipe(
return this._availabilityService.AvailabilityStoreAvailability(payload).pipe(
timeout(20000),
map((response) => (preferred ? this._mapToPickUpAvailability(response.result) : response.result))
);
@@ -347,7 +372,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
return this.swaggerAvailabilityService.AvailabilityShippingAvailability(payload).pipe(
return this._availabilityService.AvailabilityShippingAvailability(payload).pipe(
timeout(20000),
map((response) => this._mapToShippingAvailability(response.result))
);
@@ -355,7 +380,7 @@ export class DomainAvailabilityService {
@memorize({ ttl: 10000 })
getDigDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
return this.swaggerAvailabilityService.AvailabilityShippingAvailability(payload).pipe(
return this._availabilityService.AvailabilityShippingAvailability(payload).pipe(
timeout(20000),
map((response) => this._mapToShippingAvailability(response.result))
);
@@ -381,7 +406,7 @@ export class DomainAvailabilityService {
): PriceDTO {
switch (purchasingOption) {
case 'take-away':
return availability?.price || availability?.retailPrice;
return availability?.price || catalogAvailability?.price;
case 'delivery':
case 'dig-delivery':
if (catalogAvailability?.price?.value?.value < availability?.price?.value?.value) {
@@ -442,9 +467,11 @@ export class DomainAvailabilityService {
inStock: inStock,
supplierSSC: quantity <= inStock ? '999' : '',
supplierSSCText: quantity <= inStock ? 'Filialentnahme' : '',
price,
price: price ?? stockInfo?.retailPrice,
supplier: { id: supplier?.id },
retailPrice: (stockInfo as any)?.retailPrice, // TODO: Change after API Update
// TODO: Change after API Update
// LH: 2021-03-09 preis Property hat nun ein Fallback auf retailPrice
// retailPrice: (stockInfo as any)?.retailPrice,
};
return availability;
}
@@ -474,26 +501,29 @@ export class DomainAvailabilityService {
return availability;
}
private _mapToPickUpAvailability(availabilities: SwaggerAvailabilityDTO[]) {
private _mapToPickUpAvailability(availabilities: SwaggerAvailabilityDTO[]): Availability<AvailabilityDTO, SwaggerAvailabilityDTO>[] {
if (isArray(availabilities)) {
const preferred = availabilities.filter((f) => f.preferred === 1);
const totalAvailable = availabilities.reduce((sum, av) => sum + (av?.qty || 0), 0);
return preferred.map((p) => {
return {
availabilityType: p?.status,
ssc: p?.ssc,
sscText: p?.sscText,
supplier: { id: p?.supplierId },
isPrebooked: p?.isPrebooked,
estimatedShippingDate: p?.requestStatusCode === '32' ? p?.altAt : p?.at,
price: p?.price,
inStock: totalAvailable,
supplierProductNumber: p?.supplierProductNumber,
supplierInfo: p?.requestStatusCode,
lastRequest: p?.requested,
itemId: p.itemId,
};
return [
{
availabilityType: p?.status,
ssc: p?.ssc,
sscText: p?.sscText,
supplier: { id: p?.supplierId },
isPrebooked: p?.isPrebooked,
estimatedShippingDate: p?.requestStatusCode === '32' ? p?.altAt : p?.at,
price: p?.price,
inStock: totalAvailable,
supplierProductNumber: p?.supplierProductNumber,
supplierInfo: p?.requestStatusCode,
lastRequest: p?.requested,
itemId: p.itemId,
},
p,
];
});
}
}

View File

@@ -0,0 +1 @@
export type Availability<T, S> = [T, S];

View File

@@ -1,3 +1,3 @@
// start:ng42.barrel
export * from './item-data.model';
// end:ng42.barrel
export * from './availability-by-branch-dto';
export * from './availability';
export * from './item-data';

View File

@@ -21,8 +21,13 @@ import {
UpdateShoppingCartItemDTO,
InputDTO,
ItemPayload,
StoreCheckoutShoppingCartService,
StoreCheckoutPaymentService,
StoreCheckoutBuyerService,
StoreCheckoutPayerService,
StoreCheckoutBranchService,
} from '@swagger/checkout';
import { DisplayOrderDTO, OrderCheckoutService, ReorderValues } from '@swagger/oms';
import { DisplayOrderDTO, DisplayOrderItemDTO, OrderCheckoutService, ReorderValues } from '@swagger/oms';
import { isNullOrUndefined, memorize } from '@utils/common';
import { combineLatest, Observable, of, concat, isObservable, throwError } from 'rxjs';
import { bufferCount, catchError, filter, first, map, mergeMap, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
@@ -32,6 +37,7 @@ import * as DomainCheckoutActions from './store/domain-checkout.actions';
import { DomainAvailabilityService } from '@domain/availability';
import { HttpErrorResponse } from '@angular/common/http';
import { ApplicationService } from '@core/application';
import { CustomerDTO, EntityDTOContainerOfAttributeDTO } from '@swagger/crm';
@Injectable()
export class DomainCheckoutService {
@@ -40,7 +46,12 @@ export class DomainCheckoutService {
private applicationService: ApplicationService,
private storeCheckoutService: StoreCheckoutService,
private orderCheckoutService: OrderCheckoutService,
private availabilityService: DomainAvailabilityService
private availabilityService: DomainAvailabilityService,
private _shoppingCartService: StoreCheckoutShoppingCartService,
private _paymentService: StoreCheckoutPaymentService,
private _buyerService: StoreCheckoutBuyerService,
private _payerService: StoreCheckoutPayerService,
private _branchService: StoreCheckoutBranchService
) {}
//#region shoppingcart
@@ -53,8 +64,8 @@ export class DomainCheckoutService {
return false;
} else if (cart && _latest) {
_latest = false;
this.storeCheckoutService
.StoreCheckoutGetShoppingCart({
this._shoppingCartService
.StoreCheckoutShoppingCartGetShoppingCart({
shoppingCartId: cart.id,
})
.pipe(
@@ -77,7 +88,7 @@ export class DomainCheckoutService {
}
createShoppingCart({ processId }: { processId: number }): Observable<ShoppingCartDTO> {
return this.storeCheckoutService.StoreCheckoutCreateShoppingCart().pipe(
return this._shoppingCartService.StoreCheckoutShoppingCartCreateShoppingCart().pipe(
map((response) => response.result),
tap((shoppingCart) =>
this.store.dispatch(
@@ -94,8 +105,8 @@ export class DomainCheckoutService {
return this.getShoppingCart({ processId }).pipe(
first(),
mergeMap((cart) =>
this.storeCheckoutService
.StoreCheckoutAddItemToShoppingCart({
this._shoppingCartService
.StoreCheckoutShoppingCartAddItemToShoppingCart({
items,
shoppingCartId: cart.id,
})
@@ -125,8 +136,8 @@ export class DomainCheckoutService {
return this.getShoppingCart({ processId }).pipe(
first(),
mergeMap((shoppingCart) =>
this.storeCheckoutService
.StoreCheckoutSetLogisticianOnDestinationsByBuyer({
this._shoppingCartService
.StoreCheckoutShoppingCartSetLogisticianOnDestinationsByBuyer({
shoppingCartId: shoppingCart?.id,
payload: { customerFeatures },
})
@@ -139,7 +150,7 @@ export class DomainCheckoutService {
return this.getShoppingCart({ processId }).pipe(
first(),
mergeMap((cart) =>
this.storeCheckoutService.StoreCheckoutCanAddDestination({
this._shoppingCartService.StoreCheckoutShoppingCartCanAddDestination({
shoppingCartId: cart.id,
payload: destinationDTO,
})
@@ -166,8 +177,8 @@ export class DomainCheckoutService {
first(),
withLatestFrom(this.store.select(DomainCheckoutSelectors.selectCustomerFeaturesByProcessId, { processId })),
mergeMap(([shoppingCart, customerFeatures]) =>
this.storeCheckoutService
.StoreCheckoutCanAddItem({
this._shoppingCartService
.StoreCheckoutShoppingCartCanAddItem({
shoppingCartId: shoppingCart?.id,
payload: {
customerFeatures,
@@ -199,8 +210,8 @@ export class DomainCheckoutService {
orderType,
};
});
return this.storeCheckoutService
.StoreCheckoutCanAddItems({
return this._shoppingCartService
.StoreCheckoutShoppingCartCanAddItems({
shoppingCartId: shoppingCart.id,
payload,
})
@@ -222,7 +233,7 @@ export class DomainCheckoutService {
shoppingCartItemId: number;
availability: AvailabilityDTO;
}) {
return this.storeCheckoutService.StoreCheckoutUpdateShoppingCartItemAvailability({
return this._shoppingCartService.StoreCheckoutShoppingCartUpdateShoppingCartItemAvailability({
shoppingCartId,
shoppingCartItemId,
availability,
@@ -241,8 +252,8 @@ export class DomainCheckoutService {
return this.getShoppingCart({ processId }).pipe(
first(),
mergeMap((shoppingCart) =>
this.storeCheckoutService
.StoreCheckoutUpdateShoppingCartItem({
this._shoppingCartService
.StoreCheckoutShoppingCartUpdateShoppingCartItem({
shoppingCartId: shoppingCart.id,
shoppingCartItemId,
values: update,
@@ -318,8 +329,8 @@ export class DomainCheckoutService {
return this.getCheckout({ processId }).pipe(
first(),
mergeMap((checkout) =>
this.storeCheckoutService
.StoreCheckoutGetCheckoutPayment({
this._paymentService
.StoreCheckoutPaymentGetCheckoutPayment({
checkoutId: checkout.id,
})
.pipe(map((response) => response.result))
@@ -335,8 +346,8 @@ export class DomainCheckoutService {
return this.getCheckout({ processId }).pipe(
first(),
mergeMap((checkout) =>
this.storeCheckoutService
.StoreCheckoutSetPaymentType({
this._paymentService
.StoreCheckoutPaymentSetPaymentType({
checkoutId: checkout?.id,
paymentType,
})
@@ -352,8 +363,8 @@ export class DomainCheckoutService {
return this.getCheckout({ processId }).pipe(
first(),
mergeMap((checkout) =>
this.storeCheckoutService
.StoreCheckoutSetBuyer({
this._buyerService
.StoreCheckoutBuyerSetBuyerPOST({
checkoutId: checkout?.id,
buyerDTO: buyer,
})
@@ -369,8 +380,8 @@ export class DomainCheckoutService {
return this.getCheckout({ processId }).pipe(
first(),
mergeMap((checkout) =>
this.storeCheckoutService
.StoreCheckoutSetPayer({
this._payerService
.StoreCheckoutPayerSetPayerPOST({
checkoutId: checkout?.id,
payerDTO: payer,
})
@@ -389,7 +400,7 @@ export class DomainCheckoutService {
mergeMap((cart) =>
concat(
...cart.items.map((item) =>
this.storeCheckoutService.StoreCheckoutUpdateShoppingCartItem({
this._shoppingCartService.StoreCheckoutShoppingCartUpdateShoppingCartItem({
shoppingCartId: cart.id,
shoppingCartItemId: item.id,
values: { specialComment },
@@ -650,7 +661,7 @@ export class DomainCheckoutService {
first(),
mergeMap((checkout) =>
this.orderCheckoutService
.OrderCheckoutCreateOrder({
.OrderCheckoutCreateOrderPOST({
checkoutId: checkout.id,
})
.pipe(
@@ -734,8 +745,8 @@ export class DomainCheckoutService {
return this.getShoppingCart({ processId }).pipe(
first(),
mergeMap((shoppingCart) =>
this.storeCheckoutService
.StoreCheckoutCanAddBuyer({
this._shoppingCartService
.StoreCheckoutShoppingCartCanAddBuyer({
shoppingCartId: shoppingCart.id,
payload: { customerFeatures },
})
@@ -771,35 +782,39 @@ export class DomainCheckoutService {
return this.canSetCustomer({ processId, customerFeatures: undefined }).pipe(
map((res) => {
let setableTypes: { [key: string]: boolean } = {
store: true,
guest: true,
webshop: true,
b2b: true,
store: false,
guest: false,
webshop: false,
b2b: false,
};
if (Object.keys(res.filter).length === 0) {
return setableTypes;
}
res.create?.options?.values?.forEach((option) => {
setableTypes[option.value] = option.enabled !== false;
});
const customerTypes = res.filter?.customertype?.split(';') || [];
const customerAttributes = res.filter?.customerattributes?.split(';') || [];
// if (Object.keys(res.filter).length === 0) {
// return setableTypes;
// }
const typesAndAttributes = [...customerTypes, ...customerAttributes];
if (typesAndAttributes.includes('webshop') && !typesAndAttributes.includes('!guest')) {
typesAndAttributes.push('guest');
}
// const customerTypes = res.filter?.customertype?.split(';') || [];
// const customerAttributes = res.filter?.customerattributes?.split(';') || [];
for (const key in setableTypes) {
if (Object.prototype.hasOwnProperty.call(setableTypes, key)) {
if (typesAndAttributes.includes(key)) {
setableTypes[key] = true;
} else if (typesAndAttributes.includes(`!${key}`)) {
setableTypes[key] = false;
} else {
setableTypes[key] = false;
}
}
}
// const typesAndAttributes = [...customerTypes, ...customerAttributes];
// if (typesAndAttributes.includes('webshop') && !typesAndAttributes.includes('!guest')) {
// typesAndAttributes.push('guest');
// }
// for (const key in setableTypes) {
// if (Object.prototype.hasOwnProperty.call(setableTypes, key)) {
// if (typesAndAttributes.includes(key)) {
// setableTypes[key] = true;
// } else if (typesAndAttributes.includes(`!${key}`)) {
// setableTypes[key] = false;
// } else {
// setableTypes[key] = false;
// }
// }
// }
return setableTypes;
})
@@ -808,8 +823,8 @@ export class DomainCheckoutService {
@memorize()
getBranches(): Observable<BranchDTO[]> {
return this.storeCheckoutService
.StoreCheckoutGetBranches({
return this._branchService
.StoreCheckoutBranchGetBranches({
take: 999,
})
.pipe(
@@ -828,10 +843,6 @@ export class DomainCheckoutService {
.pipe(map((response) => response.result));
}
setCustomerFeatures({ processId, customerFeatures }: { processId: number; customerFeatures: { [key: string]: string } }) {
this.store.dispatch(DomainCheckoutActions.setCustomerFeatures({ processId, customerFeatures }));
}
setOlaErrors({ processId, errorIds }: { processId: number; errorIds: number[] }) {
this.store.dispatch(
DomainCheckoutActions.setOlaError({
@@ -857,6 +868,14 @@ export class DomainCheckoutService {
this.store.dispatch(DomainCheckoutActions.removeProcess({ processId }));
}
setCustomer({ processId, customerDto }: { processId: number; customerDto: CustomerDTO }) {
this.store.dispatch(DomainCheckoutActions.setCustomer({ processId, customer: customerDto }));
}
getCustomer({ processId }: { processId: number }): Observable<CustomerDTO> {
return this.store.select(DomainCheckoutSelectors.selectCustomerByProcessId, { processId });
}
setPayer({ processId, payer }: { processId: number; payer: PayerDTO }) {
this.store.dispatch(DomainCheckoutActions.setPayer({ processId, payer }));
}
@@ -877,6 +896,10 @@ export class DomainCheckoutService {
return this.store.select(DomainCheckoutSelectors.selectOrders);
}
updateOrderItem(item: DisplayOrderItemDTO) {
this.store.dispatch(DomainCheckoutActions.updateOrderItem({ item }));
}
removeAllOrders() {
this.store.dispatch(DomainCheckoutActions.removeAllOrders());
}

View File

@@ -1,11 +1,12 @@
import { BuyerDTO, CheckoutDTO, NotificationChannel, PayerDTO, ShippingAddressDTO, ShoppingCartDTO } from '@swagger/checkout';
import { CustomerDTO } from '@swagger/crm';
import { DisplayOrderDTO } from '@swagger/oms';
export interface CheckoutEntity {
processId: number;
checkout: CheckoutDTO;
shoppingCart: ShoppingCartDTO;
customerFeatures: { [key: string]: string };
customer: CustomerDTO;
payer: PayerDTO;
buyer: BuyerDTO;
shippingAddress: ShippingAddressDTO;

View File

@@ -8,7 +8,8 @@ import {
BuyerDTO,
PayerDTO,
} from '@swagger/checkout';
import { DisplayOrderDTO } from '@swagger/oms';
import { CustomerDTO } from '@swagger/crm';
import { DisplayOrderDTO, DisplayOrderItemDTO } from '@swagger/oms';
const prefix = '[DOMAIN-CHECKOUT]';
@@ -38,11 +39,6 @@ export const setCheckoutDestination = createAction(
props<{ processId: number; destination: DestinationDTO }>()
);
export const setCustomerFeatures = createAction(
`${prefix} Set Customer Features`,
props<{ processId: number; customerFeatures: { [key: string]: string } }>()
);
export const setShippingAddress = createAction(
`${prefix} Set Shipping Address`,
props<{ processId: number; shippingAddress: ShippingAddressDTO }>()
@@ -52,6 +48,8 @@ export const removeProcess = createAction(`${prefix} Remove Process`, props<{ pr
export const setOrders = createAction(`${prefix} Add Orders`, props<{ orders: DisplayOrderDTO[] }>());
export const updateOrderItem = createAction(`${prefix} Update Orders`, props<{ item: DisplayOrderItemDTO }>());
export const removeAllOrders = createAction(`${prefix} Remove All Orders`);
export const setBuyer = createAction(`${prefix} Set Buyer`, props<{ processId: number; buyer: BuyerDTO }>());
@@ -61,3 +59,5 @@ export const setPayer = createAction(`${prefix} Set Payer`, props<{ processId: n
export const setSpecialComment = createAction(`${prefix} Set Agent Comment`, props<{ processId: number; agentComment: string }>());
export const setOlaError = createAction(`${prefix} Set Ola Error`, props<{ processId: number; olaErrorIds: number[] }>());
export const setCustomer = createAction(`${prefix} Set Customer`, props<{ processId: number; customer: CustomerDTO }>());

View File

@@ -46,11 +46,6 @@ const _domainCheckoutReducer = createReducer(
};
return storeCheckoutAdapter.setOne(entity, s);
}),
on(DomainCheckoutActions.setCustomerFeatures, (s, { processId, customerFeatures }) => {
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.customerFeatures = customerFeatures;
return storeCheckoutAdapter.setOne(entity, s);
}),
on(DomainCheckoutActions.setShippingAddress, (s, { processId, shippingAddress }) => {
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.shippingAddress = shippingAddress;
@@ -73,6 +68,25 @@ const _domainCheckoutReducer = createReducer(
}),
on(DomainCheckoutActions.removeProcess, (s, { processId }) => storeCheckoutAdapter.removeOne(processId, s)),
on(DomainCheckoutActions.setOrders, (s, { orders }) => ({ ...s, orders: [...s.orders, ...orders] })),
on(DomainCheckoutActions.updateOrderItem, (s, { item }) => {
const orders = [...s.orders];
const orderToUpdate = orders?.find((order) => order.items?.find((i) => i.id === item?.id));
const orderToUpdateIndex = orders?.indexOf(orderToUpdate);
const orderItemToUpdate = orderToUpdate?.items?.find((i) => i.id === item?.id);
const orderItemToUpdateIndex = orderToUpdate?.items?.indexOf(orderItemToUpdate);
const items = [...orderToUpdate?.items];
items[orderItemToUpdateIndex] = item;
orders[orderToUpdateIndex] = {
...orderToUpdate,
items: [...items],
};
return { ...s, orders: [...orders] };
}),
on(DomainCheckoutActions.removeAllOrders, (s) => ({
...s,
orders: [],
@@ -81,6 +95,11 @@ const _domainCheckoutReducer = createReducer(
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.olaErrorIds = olaErrorIds;
return storeCheckoutAdapter.setOne(entity, s);
}),
on(DomainCheckoutActions.setCustomer, (s, { processId, customer }) => {
const entity = getOrCreateCheckoutEntity({ processId, entities: s.entities });
entity.customer = customer;
return storeCheckoutAdapter.setOne(entity, s);
})
);
@@ -96,7 +115,6 @@ function getOrCreateCheckoutEntity({ entities, processId }: { entities: Dictiona
processId,
checkout: undefined,
shoppingCart: undefined,
customerFeatures: undefined,
shippingAddress: undefined,
orders: [],
payer: undefined,
@@ -104,6 +122,7 @@ function getOrCreateCheckoutEntity({ entities, processId }: { entities: Dictiona
specialComment: '',
notificationChannels: 0,
olaErrorIds: [],
customer: undefined,
};
}

View File

@@ -1,5 +1,6 @@
import { Dictionary } from '@ngrx/entity';
import { createSelector } from '@ngrx/store';
import { CustomerDTO } from '@swagger/crm';
import { CheckoutEntity } from './defs/checkout.entity';
import { storeCheckoutAdapter, storeFeatureSelector } from './domain-checkout.state';
@@ -22,7 +23,7 @@ export const selectCheckoutByProcessId = createSelector(
export const selectCustomerFeaturesByProcessId = createSelector(
selectEntities,
(entities: Dictionary<CheckoutEntity>, { processId }: { processId: number }) => entities[processId]?.customerFeatures
(entities: Dictionary<CheckoutEntity>, { processId }: { processId: number }) => getCusomterFeatures(entities[processId]?.customer)
);
export const selectShippingAddressByProcessId = createSelector(
@@ -61,3 +62,19 @@ export const selectOlaErrorsByProcessId = createSelector(
selectEntities,
(entities: Dictionary<CheckoutEntity>, { processId }: { processId: number }) => entities[processId]?.olaErrorIds
);
export const selectCustomerByProcessId = createSelector(
selectEntities,
(entities: Dictionary<CheckoutEntity>, { processId }: { processId: number }) => entities[processId]?.customer
);
function getCusomterFeatures(custoemr: CustomerDTO): { [key: string]: string } {
const customerFeatures = custoemr?.features ?? [];
const features: { [key: string]: string } = {};
for (const feature of customerFeatures) {
features[feature.key] = feature.key;
}
return features;
}

View File

@@ -1,21 +1,26 @@
import { Injectable } from '@angular/core';
import {
AddressDTO,
AddressService,
AssignedPayerDTO,
AutocompleteDTO,
CommunicationDetailsDTO,
CountryDTO,
CountryService,
CustomerDTO,
CustomerInfoDTO,
CustomerService,
InputDTO,
KeyValueDTOOfStringAndString,
ListResponseArgsOfCustomerInfoDTO,
LoyaltyCardService,
NotificationChannel,
PayerDTO,
PayerService,
ResponseArgsOfHistoryDTO,
ResponseArgsOfIEnumerableOfBonusCardInfoDTO,
ShippingAddressDTO,
ShippingAddressService,
} from '@swagger/crm';
import { isArray } from '@utils/common';
import { PagedResult, Result } from 'apps/domain/defs/src/public-api';
@@ -24,7 +29,14 @@ import { catchError, map, mergeMap, retry } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class CrmCustomerService {
constructor(private customerService: CustomerService, private payerService: PayerService) {}
constructor(
private customerService: CustomerService,
private payerService: PayerService,
private addressService: AddressService,
private countryService: CountryService,
private shippingAddressService: ShippingAddressService,
private loyaltyCardService: LoyaltyCardService
) {}
complete(queryString: string, filter?: { [key: string]: string }): Observable<Result<AutocompleteDTO[]>> {
return this.customerService.CustomerCustomerAutocomplete({
@@ -66,10 +78,6 @@ export class CrmCustomerService {
return this.customerService.CustomerGetAssignedPayersByCustomerId(params);
}
getFilters(): Observable<Result<InputDTO[]>> {
return this.customerService.CustomerQueryCustomerFilter();
}
/* @internal */
getNotificationChannelForCommunicationDetails({
communicationDetails,
@@ -96,12 +104,23 @@ export class CrmCustomerService {
return this.customerService.CustomerPatchCustomer({ customerId, customer: { ...customer, notificationChannels } });
}
createB2BCustomer(customer: CustomerDTO) {
createB2BCustomer(customer: CustomerDTO): Promise<Result<CustomerDTO>> {
const notificationChannels = this.getNotificationChannelForCommunicationDetails({
communicationDetails: customer?.communicationDetails,
});
return this.customerService.CustomerCreateCustomer({ ...customer, customerType: 16, notificationChannels });
const payload: CustomerDTO = { ...customer, customerType: 16, notificationChannels };
payload.shippingAddresses = payload.shippingAddresses ?? [];
payload.payers = payload.payers ?? [];
return this.customerService
.CustomerCreateCustomer({
customer: payload,
modifiers: [{ key: 'b2b', group: 'customertype' }],
})
.toPromise();
}
createOnlineCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
@@ -148,63 +167,253 @@ export class CrmCustomerService {
];
}
return this.customerService.CustomerCreateOnlineCustomer({ ...payload, notificationChannels });
const p4mUser = customer.features.find((f) => f.key === 'p4mUser')?.value;
const modifiers: KeyValueDTOOfStringAndString[] = [{ key: 'webshop', group: 'customertype' }];
if (p4mUser) {
modifiers.push({ key: 'add-loyalty-card', value: p4mUser });
}
return this.customerService.CustomerCreateCustomer({
customer: { ...payload, notificationChannels },
modifiers,
});
}
createGuestCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
mapCustomerToPayer(customer: CustomerDTO): PayerDTO {
return {
address: customer.address,
communicationDetails: customer.communicationDetails,
firstName: customer.firstName,
lastName: customer.lastName,
organisation: customer.organisation,
title: customer.title,
payerType: 1,
gender: customer.gender,
};
}
mapCustomerToShippingAddress(customer: CustomerDTO): ShippingAddressDTO {
return {
address: customer.address,
communicationDetails: customer.communicationDetails,
firstName: customer.firstName,
gender: customer.gender,
lastName: customer.lastName,
organisation: customer.organisation,
title: customer.title,
type: 1,
};
}
async updateToOnlineCustomer(customer: CustomerDTO): Promise<Result<CustomerDTO>> {
const payload: CustomerDTO = { shippingAddresses: [], payers: [], ...customer, customerType: 8, hasOnlineAccount: true };
const notificationChannels = this.getNotificationChannelForCommunicationDetails({
communicationDetails: payload?.communicationDetails,
});
const shippingAddressesToAdd = payload.shippingAddresses?.filter((sa) => !sa.id)?.map((m) => m.data) ?? [];
payload.shippingAddresses = payload.shippingAddresses?.filter((sa) => !!sa.id) ?? [];
if (payload.shippingAddresses.length === 0) {
shippingAddressesToAdd.unshift(this.mapCustomerToShippingAddress(payload));
}
const payersToAdd = payload.payers?.filter((p) => !p.assignedToCustomer)?.map((p) => p.payer?.data) ?? [];
payload.payers = payload.payers?.filter((p) => !!p.assignedToCustomer) ?? [];
if (payload.payers.length === 0) {
payersToAdd.unshift({
...this.mapCustomerToPayer(payload),
payerType: payload.customerType,
});
}
const modifiers: KeyValueDTOOfStringAndString[] = [{ key: 'webshop', group: 'customertype' }];
const res = await this.customerService
.CustomerUpdateCustomer({
customerId: customer.id,
payload: {
modifiers,
customer: { ...payload, notificationChannels },
},
})
.toPromise();
for (let shippingAddress of shippingAddressesToAdd) {
await this.createShippingAddress(res.result.id, shippingAddress, true);
}
for (let payer of payersToAdd) {
await this.createPayer(res.result.id, payer, true);
}
return res;
}
async updateToP4MOnlineCustomer(customer: CustomerDTO): Promise<Result<CustomerDTO>> {
const shippingAddressesToAdd = customer.shippingAddresses?.filter((sa) => !sa.id)?.map((m) => m.data) ?? [];
customer.shippingAddresses = customer.shippingAddresses?.filter((sa) => !!sa.id) ?? [];
if (customer.shippingAddresses.length === 0) {
shippingAddressesToAdd.unshift(this.mapCustomerToShippingAddress(customer));
}
const payersToAdd = customer.payers?.filter((p) => !p.assignedToCustomer)?.map((p) => p.payer?.data) ?? [];
customer.payers = customer.payers?.filter((p) => !!p.assignedToCustomer) ?? [];
if (customer.payers.length === 0) {
payersToAdd.unshift({
...this.mapCustomerToPayer(customer),
payerType: customer.customerType,
});
}
const p4mUser = customer.features.find((f) => f.key === 'p4mUser')?.value;
const modifiers: KeyValueDTOOfStringAndString[] = [{ key: 'webshop', group: 'customertype' }];
if (p4mUser) {
modifiers.push({ key: 'add-loyalty-card', value: p4mUser });
}
const res = await this.customerService
.CustomerUpdateCustomer({
customerId: customer.id,
payload: {
customer,
modifiers,
},
})
.toPromise();
for (let shippingAddress of shippingAddressesToAdd) {
await this.createShippingAddress(res.result.id, shippingAddress, true);
}
for (let payer of payersToAdd) {
await this.createPayer(res.result.id, payer, true);
}
return res;
}
async updateStoreP4MToWebshopP4M(customer: CustomerDTO): Promise<Result<CustomerDTO>> {
const shippingAddressesToAdd = customer.shippingAddresses?.filter((sa) => !sa.id)?.map((m) => m.data) ?? [];
customer.shippingAddresses = customer.shippingAddresses?.filter((sa) => !!sa.id) ?? [];
if (customer.shippingAddresses.length === 0) {
shippingAddressesToAdd.unshift(this.mapCustomerToShippingAddress(customer));
}
const payersToAdd = customer.payers?.filter((p) => !p.assignedToCustomer)?.map((p) => p.payer?.data) ?? [];
customer.payers = customer.payers?.filter((p) => !!p.assignedToCustomer) ?? [];
if (customer.payers.length === 0) {
payersToAdd.unshift({
...this.mapCustomerToPayer(customer),
payerType: customer.customerType,
});
}
const modifiers: KeyValueDTOOfStringAndString[] = [{ key: 'webshop', group: 'customertype' }];
const res = await this.customerService
.CustomerUpdateCustomer({
customerId: customer.id,
payload: {
customer,
modifiers,
},
})
.toPromise();
for (let shippingAddress of shippingAddressesToAdd) {
await this.createShippingAddress(res.result.id, shippingAddress, true);
}
for (let payer of payersToAdd) {
await this.createPayer(res.result.id, payer, true);
}
return res;
}
async createGuestCustomer(customer: CustomerDTO): Promise<Result<CustomerDTO>> {
const notificationChannels = this.getNotificationChannelForCommunicationDetails({
communicationDetails: customer?.communicationDetails,
});
const payload: CustomerDTO = { ...customer, customerType: 8, isGuestAccount: true, notificationChannels };
if (!(isArray(payload.shippingAddresses) && payload.shippingAddresses.length > 0)) {
payload.shippingAddresses = [
{
data: {
address: payload.address,
communicationDetails: payload.communicationDetails,
firstName: payload.firstName,
gender: payload.gender,
lastName: payload.lastName,
organisation: payload.organisation,
title: payload.title,
type: 1,
},
},
];
}
payload.shippingAddresses = customer.shippingAddresses ?? [];
if (!(isArray(payload.payers) && payload.payers.length > 0)) {
payload.payers = [
{
payer: {
data: {
address: payload.address,
communicationDetails: payload.communicationDetails,
firstName: payload.firstName,
gender: payload.gender,
lastName: payload.lastName,
organisation: payload.organisation,
title: payload.title,
payerType: payload.customerType,
},
},
},
];
}
payload.shippingAddresses.push({
data: this.mapCustomerToShippingAddress(customer),
});
return this.customerService.CustomerCreateOnlineCustomer(payload);
payload.payers = customer.payers ?? [];
payload.payers.push({
payer: {
data: {
...this.mapCustomerToPayer(customer),
payerType: payload.customerType,
},
},
});
const res = await this.customerService
.CustomerCreateCustomer({
customer: payload,
modifiers: [{ key: 'webshop', group: 'customertype' }],
})
.toPromise();
return res;
}
createBranchCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
createStoreCustomer(customer: CustomerDTO): Observable<Result<CustomerDTO>> {
const notificationChannels = this.getNotificationChannelForCommunicationDetails({
communicationDetails: customer?.communicationDetails,
});
return this.customerService.CustomerCreateCustomer({ ...customer, customerType: 8, notificationChannels });
const p4mUser = customer.features.find((f) => f.key === 'p4mUser')?.value;
const modifiers: KeyValueDTOOfStringAndString[] = [{ key: 'store', group: 'customertype' }];
if (p4mUser) {
modifiers.push({ key: 'add-loyalty-card', value: p4mUser });
}
return this.customerService.CustomerCreateCustomer({
customer: { ...customer, customerType: 8, notificationChannels },
modifiers,
});
}
validateAddress(address: AddressDTO): Observable<Result<AddressDTO[]>> {
return this.customerService.CustomerValidateAddress(address);
return this.addressService.AddressValidateAddress(address);
}
getOnlineCustomerByEmail(email: string): Observable<CustomerInfoDTO | null> {
return this.getCustomers(email, {
take: 1,
filter: {
customertype: 'webshop',
},
}).pipe(
map((r) => {
if (r.hits === 1) {
return r.result[0];
} else {
return null;
}
}),
catchError((err) => [null])
);
}
private cachedCountriesFailed = false;
@@ -213,8 +422,8 @@ export class CrmCustomerService {
if (!this.cachedCountries || this.cachedCountriesFailed) {
this.cachedCountriesFailed = false;
this.cachedCountries = new ReplaySubject();
this.customerService
.CustomerGetCountries({})
this.countryService
.CountryGetCountries({})
.pipe(
retry(3),
catchError((err) => {
@@ -235,22 +444,28 @@ export class CrmCustomerService {
return this.customerService.CustomerEmailExists(email);
}
createPayer(customerId: number, payer: PayerDTO, isDefault?: boolean): Observable<[Result<PayerDTO>, Result<AssignedPayerDTO>]> {
return this.getCustomer(customerId).pipe(
mergeMap((customerResponse) =>
this.payerService
.PayerCreatePayer({ ...payer, payerType: customerResponse.result.customerType })
.pipe(
mergeMap((payerResponse) =>
this.customerService
.CustomerAddPayerReference({ customerId: customerId, payerId: payerResponse.result.id, isDefault: isDefault })
.pipe(
map((assigendPayerResponse) => [payerResponse, assigendPayerResponse] as [Result<PayerDTO>, Result<AssignedPayerDTO>])
)
checkLoyaltyCard({ loyaltyCardNumber, customerId }: { loyaltyCardNumber: string; customerId?: number }) {
return this.loyaltyCardService.LoyaltyCardCheckLoyaltyCard({ loyaltyCardNumber, customerId });
}
createPayer(customerId: number, payer: PayerDTO, isDefault?: boolean): Promise<[Result<PayerDTO>, Result<AssignedPayerDTO>]> {
return this.getCustomer(customerId)
.pipe(
mergeMap((customerResponse) =>
this.payerService
.PayerCreatePayer({ ...payer, payerType: customerResponse.result.customerType })
.pipe(
mergeMap((payerResponse) =>
this.customerService
.CustomerAddPayerReference({ customerId: customerId, payerId: payerResponse.result.id, isDefault: isDefault })
.pipe(
map((assigendPayerResponse) => [payerResponse, assigendPayerResponse] as [Result<PayerDTO>, Result<AssignedPayerDTO>])
)
)
)
)
)
)
);
.toPromise();
}
updatePayer(customerId: number, payerId: number, payer: PayerDTO, isDefault?: boolean): Observable<Result<PayerDTO>> {
@@ -263,11 +478,7 @@ export class CrmCustomerService {
return this.customerService.CustomerModifyPayerReference({ payerId, customerId, isDefault });
}
createShippingAddress(
customerId: number,
shippingAddress: ShippingAddressDTO,
isDefault?: boolean
): Observable<Result<ShippingAddressDTO>> {
createShippingAddress(customerId: number, shippingAddress: ShippingAddressDTO, isDefault?: boolean): Promise<Result<ShippingAddressDTO>> {
const data: ShippingAddressDTO = { ...shippingAddress };
if (isDefault) {
data.isDefault = new Date().toJSON();
@@ -275,7 +486,7 @@ export class CrmCustomerService {
delete data.isDefault;
}
return this.customerService.CustomerCreateShippingAddress({ customerId, shippingAddress: data });
return this.shippingAddressService.ShippingAddressCreateShippingAddress({ customerId, shippingAddress: data }).toPromise();
}
updateShippingAddress(
@@ -283,7 +494,7 @@ export class CrmCustomerService {
shippingAddressId: number,
shippingAddress: ShippingAddressDTO,
isDefault?: boolean
): Observable<Result<ShippingAddressDTO>> {
): Promise<Result<ShippingAddressDTO>> {
const data: ShippingAddressDTO = { ...shippingAddress };
if (isDefault) {
@@ -292,15 +503,17 @@ export class CrmCustomerService {
delete data.isDefault;
}
return this.customerService.CustomerUpdateShippingAddress({ shippingAddressId, shippingAddress: data, customerId });
return this.shippingAddressService
.ShippingAddressUpdateShippingAddress({ shippingAddressId, shippingAddress: data, customerId })
.toPromise();
}
getShippingAddress(shippingAddressId: number): Observable<Result<ShippingAddressDTO>> {
return this.customerService.CustomerGetShippingaddress(shippingAddressId);
return this.shippingAddressService.ShippingAddressGetShippingaddress(shippingAddressId);
}
getShippingAddresses(params: CustomerService.CustomerGetShippingAddressesParams): Observable<Result<ShippingAddressDTO[]>> {
return this.customerService.CustomerGetShippingAddresses(params);
getShippingAddresses(params: ShippingAddressService.ShippingAddressGetShippingAddressesParams): Observable<Result<ShippingAddressDTO[]>> {
return this.shippingAddressService.ShippingAddressGetShippingAddresses(params);
}
getPayer(payerId: number): Observable<Result<PayerDTO>> {

View File

@@ -1,3 +1,5 @@
import { DialogOfString } from '@swagger/crm';
export interface Result<T> {
/** Ergebnis */
result?: T;
@@ -13,4 +15,6 @@ export interface Result<T> {
/** Fehlerhafte Daten */
invalidProperties?: { [key: string]: string };
dialog?: DialogOfString;
}

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

@@ -26,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,
},
})
@@ -66,24 +66,15 @@ export class ReOrderActionHandler extends ActionHandler<OrderItemsContext> {
}
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',
});
}
this._toastService.create({
title: 'Artikel wurde nachbestellt',
text,
});
}
async reorder(orderItem: OrderItemListItemDTO, availability: AvailabilityDTO2, comment: string) {

View File

@@ -32,18 +32,18 @@ export class DomainGoodsService {
});
}
getWarenausgabeItemByOrderNumber(orderNumber: string) {
getWarenausgabeItemByOrderNumber(orderNumber: string, archive: boolean) {
return this.abholfachService.AbholfachWarenausgabe({
filter: { all_branches: 'true', archive: 'true' },
filter: { all_branches: 'true', archive: `${archive}` },
input: {
qs: orderNumber,
},
});
}
getWarenausgabeItemByCompartment(compartmentCode: string) {
getWarenausgabeItemByCompartment(compartmentCode: string, archive: boolean) {
return this.abholfachService.AbholfachWarenausgabe({
filter: { all_branches: 'true', archive: 'true' },
filter: { all_branches: 'true', archive: `${archive}` },
input: {
qs: compartmentCode,
},
@@ -55,7 +55,7 @@ export class DomainGoodsService {
return this.abholfachService.AbholfachWareneingang({
filter: { orderitemprocessingstatus: '16;128;8192;1048576' },
input: {
qs: customerNumber,
customer_name: customerNumber,
},
});
}

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import {
BranchService,
ChangeStockStatusCodeValues,
HistoryDTO,
NotificationChannel,
@@ -11,8 +12,10 @@ import {
OrderService,
ReceiptService,
StatusValues,
StockStatusCodeService,
ValueTupleOfLongAndReceiptTypeAndEntityDTOContainerOfReceiptDTO,
ValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO,
VATService,
} from '@swagger/oms';
import { memorize } from '@utils/common';
import { Observable } from 'rxjs';
@@ -23,6 +26,9 @@ export class DomainOmsService {
constructor(
private orderService: OrderService,
private receiptService: ReceiptService,
private branchService: BranchService,
private vatService: VATService,
private stockStatusCodeService: StockStatusCodeService,
private _orderCheckoutService: OrderCheckoutService
) {}
@@ -37,7 +43,7 @@ export class DomainOmsService {
}
getBranches() {
return this.orderService.OrderGetBranches({});
return this.branchService.BranchGetBranches({});
}
getHistory(orderItemSubsetId: number): Observable<HistoryDTO> {
@@ -62,13 +68,13 @@ export class DomainOmsService {
@memorize()
getVATs() {
return this.orderService.OrderGetVATs({}).pipe(map((response) => response.result));
return this.vatService.VATGetVATs({}).pipe(map((response) => response.result));
}
// ttl 4 Stunden
@memorize({ ttl: 14400000 })
getStockStatusCodes({ supplierId, eagerLoading = 0 }: { supplierId: number; eagerLoading?: number }) {
return this.orderService.OrderGetStockStatusCodes({ supplierId, eagerLoading }).pipe(
return this.stockStatusCodeService.StockStatusCodeGetStockStatusCodes({ supplierId, eagerLoading }).pipe(
map((response) => response.result),
shareReplay()
);
@@ -152,6 +158,10 @@ export class DomainOmsService {
.pipe(map((response) => response.result));
}
setPreferredPickUpDate({ data }: { data: { [key: string]: string } }) {
return this.orderService.OrderSetPreferredPickUpDate({ data });
}
changeOrderItemStatus(data: OrderService.OrderChangeStatusParams) {
return this.orderService.OrderChangeStatus(data);
}

View File

@@ -11,6 +11,7 @@ import {
PrintRequestOfIEnumerableOfPriceQRCodeDTO,
PrintService,
ResponseArgs,
LoyaltyCardPrintService,
} from '@swagger/print';
import { Observable, of } from 'rxjs';
import { catchError, filter, map, switchMap, timeout } from 'rxjs/operators';
@@ -25,7 +26,8 @@ export class DomainPrinterService {
private oMSPrintService: OMSPrintService,
private catalogPrintService: CatalogPrintService,
private checkoutPrintService: CheckoutPrintService,
private eisPublicDocumentService: EISPublicDocumentService
private eisPublicDocumentService: EISPublicDocumentService,
private _loyaltyCardPrintService: LoyaltyCardPrintService
) {}
getAvailablePrinters(): Observable<Printer[] | { error: string }> {
@@ -143,6 +145,13 @@ export class DomainPrinterService {
});
}
printKubiAgb({ p4mCode, printer }: { p4mCode: string; printer: string }) {
return this._loyaltyCardPrintService.LoyaltyCardPrintPrintLoyaltyCardAGB({
printer,
data: p4mCode,
});
}
printProduct({ item, printer }: { item: ItemDTO; printer: string }): Observable<ResponseArgs> {
const params = <PrintRequestOfIEnumerableOfItemDTO>{
printer: printer,

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { EISPublicService, FileDTO, ProcessingStatus, QueryTokenDTO } from '@swagger/eis';
import { DisplayInfoDTO, EISPublicService, FileDTO, ProcessingStatus, QueryTokenDTO } from '@swagger/eis';
import { DateAdapter } from '@ui/common';
import { memorize } from '@utils/common';
import { map, shareReplay, switchMap } from 'rxjs/operators';
@@ -279,4 +279,80 @@ export class DomainTaskCalendarService {
)
);
}
moveRemovedToEnd(a: DisplayInfoDTO, b: DisplayInfoDTO) {
const statusA = this.getProcessingStatusList(a)?.includes('Removed');
const statusB = this.getProcessingStatusList(b)?.includes('Removed');
if (statusA && statusB) {
return 0;
} else if (statusA && !statusB) {
return 1;
} else if (!statusA && statusB) {
return -1;
}
return 0;
}
/**
* Returns an Array of DisplayInfoDTO, sorted by ProcessingStatus
* Ignores Overdue if Task is already Completed
* Compared DisploayInfoDTO is of Type Task and Info Or PreInfo then sort by Type
* @param items DisplayInfoDTO Array to sort
* @param order Processing Status Order
* @returns DisplayInfoDTO Array ordered by Processing Status anf Type
*/
sort(items: DisplayInfoDTO[], order: ProcessingStatusList) {
let result = [...items];
const reversedOrder = [...order].reverse();
for (const status of reversedOrder) {
result = result?.sort((a, b) => {
const statusA = this.getProcessingStatusList(a);
const statusB = this.getProcessingStatusList(b);
// Ignore Overdue when it is already Completed
if (status === 'Overdue' && statusA.includes('Completed')) {
return 0;
}
const aHasStatus = statusA.includes(status);
const bHasStatus = statusB.includes(status);
if (aHasStatus && bHasStatus) {
// If it has the same ProcessingStatus then Sort by Type
const aType = this.getInfoType(a);
const bType = this.getInfoType(b);
if (aType !== bType) {
if (aType === 'Info' || aType === 'PreInfo') {
return -1;
} else {
return 1;
}
}
if (statusB.includes('Completed')) {
return -1;
}
return 0;
} else if (aHasStatus && !bHasStatus) {
return -1;
} else if (!aHasStatus && bHasStatus) {
return 1;
}
return 0;
});
}
return result;
}
getDateGroupKey(d: string) {
// Get Date as string key to ignore time for grouping
const date = new Date(d);
return date.toISOString().split('T')[0];
}
}

View File

@@ -6,9 +6,9 @@ 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 { SwUpdate } from '@angular/service-worker';
import { NotificationsHub } from '@hub/notifications';
import { discardPeriodicTasks, fakeAsync, flush, tick } from '@angular/core/testing';
import { UserStateService } from '@swagger/isa';
describe('AppComponent', () => {
let spectator: Spectator<AppComponent>;
@@ -21,7 +21,7 @@ describe('AppComponent', () => {
component: AppComponent,
imports: [CommonModule, RouterTestingModule],
providers: [],
mocks: [Config, SwUpdate],
mocks: [Config, SwUpdate, UserStateService],
});
beforeEach(() => {

View File

@@ -9,6 +9,8 @@ import packageInfo from 'package';
import { interval, Observable, Subscription } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Platform } from '@angular/cdk/platform';
import { UserStateService } from '@swagger/isa';
import { IsaLogProvider } from './providers';
@Component({
selector: 'app-root',
@@ -38,16 +40,17 @@ export class AppComponent implements OnInit {
private readonly _renderer: Renderer2,
private readonly _swUpdate: SwUpdate,
private readonly _notifications: NotificationsHub,
private readonly _platform: Platform
private readonly _platform: Platform,
private infoService: UserStateService
) {
this.updateClient();
IsaLogProvider.InfoService = infoService;
}
ngOnInit() {
this.setTitle();
this.logVersion();
this.determinePlatform();
this._appService.getSection$().subscribe(this.sectionChangeHandler.bind(this));
}

View File

@@ -6,6 +6,7 @@ import { PlatformModule } from '@angular/cdk/platform';
import { Config, ConfigModule, JsonConfigLoader } from '@core/config';
import { AuthModule, AuthService } from '@core/auth';
import { CoreCommandModule } from '@core/command';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@@ -30,6 +31,9 @@ import { IsaLogProvider } from './providers';
import { IsaErrorHandler } from './providers/isa.error-handler';
import { ScanAdapterModule } from '@adapter/scan';
import { RootStateService } from './store/root-state.service';
import * as Commands from './commands';
import { UiIconModule } from '@ui/icon';
import { UserStateService } from '@swagger/isa';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
@@ -70,6 +74,7 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
useConfigLoader: JsonConfigLoader,
jsonConfigLoaderUrl: '/config/config.json',
}),
CoreCommandModule.forRoot(Object.values(Commands)),
CoreLoggerModule.forRoot(),
AppStoreModule,
AuthModule.forRoot(),
@@ -83,6 +88,12 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
}),
ScanAdapterModule.forRoot(!environment.production),
PlatformModule,
UiIconModule.forRoot({
aliases: [
{ alias: 'd-account', name: 'account' },
{ alias: 'd-no-account', name: 'package-variant-closed' },
],
}),
],
providers: [
{

View File

@@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { ActionHandler } from '@core/command';
/** Dummy Command um Fehlermeldungen aus dem Diloag zu verhinden */
@Injectable()
export class CloseCommand extends ActionHandler<any> {
constructor() {
super('CLOSE');
}
handler(ctx: any): any {
return ctx;
}
}

View File

@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { ActionHandler } from '@core/command';
import { Result } from '@domain/defs';
import { CustomerInfoDTO } from '@swagger/crm';
@Injectable()
export class CreateCustomerCommand extends ActionHandler<Result<CustomerInfoDTO[]>> {
constructor(private _router: Router, private _application: ApplicationService) {
super('CREATE_CUSTOMER');
}
async handler(data: Result<CustomerInfoDTO[]>): Promise<Result<CustomerInfoDTO[]>> {
let customerType: string;
if (data.result.length > 0) {
const customerInfo = data.result[0];
if (customerInfo.features) {
if (customerInfo.features.some((f) => f.key === 'store')) {
customerType = 'store';
}
if (customerInfo.features.some((f) => f.key === 'webshop')) {
customerType = 'webshop';
}
}
}
if (!customerType) {
customerType = 'store';
}
await this._router.navigate(['/kunde', this._application.activatedProcessId, 'customer', 'create', customerType]);
return data;
}
}

View File

@@ -0,0 +1,45 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { ActionHandler } from '@core/command';
import { Result } from '@domain/defs';
import { CustomerInfoDTO } from '@swagger/crm';
import { encodeFormData, mapCustomerInfoDtoToCustomerCreateFormData } from 'apps/page/customer/src/lib/create-customer';
@Injectable()
export class CreateKubiCustomerCommand extends ActionHandler<Result<CustomerInfoDTO[]>> {
constructor(private _router: Router, private _application: ApplicationService) {
super('CREATE_KUBI_CUSTOMER');
}
async handler(data: Result<CustomerInfoDTO[]>): Promise<Result<CustomerInfoDTO[]>> {
let customerType: string;
let formData: string;
if (data.result.length > 0) {
const customerInfo = data.result[0];
const fd = mapCustomerInfoDtoToCustomerCreateFormData(customerInfo);
formData = encodeFormData({
...fd,
agb: true,
});
if (customerInfo.features) {
if (customerInfo.features.some((f) => f.key === 'store')) {
customerType = 'store';
}
if (customerInfo.features.some((f) => f.key === 'webshop')) {
customerType = 'webshop';
}
}
}
if (!customerType) {
customerType = 'store';
}
await this._router.navigate(['/kunde', this._application.activatedProcessId, 'customer', 'create', `${customerType}-p4m`], {
queryParams: { formData },
});
return data;
}
}

View File

@@ -0,0 +1,5 @@
export * from './close.command';
export * from './create-customer.command';
export * from './create-kubi-customer.command';
export * from './print-kubi-agb.command';
export * from './remit.command';

View File

@@ -0,0 +1,34 @@
import { Injectable } from '@angular/core';
import { ActionHandler } from '@core/command';
import { Result } from '@domain/defs';
import { DomainPrinterService } from '@domain/printer';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { CustomerInfoDTO } from '@swagger/crm';
import { UiModalService } from '@ui/modal';
@Injectable()
export class PrintKubiCustomerCommand extends ActionHandler<Result<CustomerInfoDTO[]>> {
constructor(private _uiModal: UiModalService, private _printerService: DomainPrinterService) {
super('PRINT_KUBI_AGB');
}
async handler(data: Result<CustomerInfoDTO[]>): Promise<Result<CustomerInfoDTO[]>> {
const customerInfo = data.result ? data.result[0] : undefined;
let p4mCode: string;
if (customerInfo) {
p4mCode = customerInfo.features.find((f) => f.key === 'p4mUser').value;
}
await this._uiModal
.open({
content: PrintModalComponent,
config: { showScrollbarY: false },
data: {
printerType: 'Label',
print: (printer) => this._printerService.printKubiAgb({ printer, p4mCode }).toPromise(),
} as PrintModalData,
})
.afterClosed$.toPromise();
return data;
}
}

View File

@@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { ActionHandler } from '@core/command';
/** Dummy Command um Fehlermeldungen aus dem Diloag zu verhinden */
@Injectable()
export class RemitCommand extends ActionHandler<any> {
constructor() {
super('remit');
}
handler(ctx: any): any {
return ctx;
}
}

View File

@@ -1,11 +1,12 @@
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 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,16 +26,42 @@ export class CanActivateCustomerWithProcessIdGuard implements CanActivate {
id: +route.params.processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes)}`,
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 ? Math.max(...processNumbers) + 1 : 1;
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

@@ -1,11 +1,12 @@
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) {}
constructor(private readonly _applicationService: ApplicationService, private readonly _breadcrumbService: BreadcrumbService) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const process = await this._applicationService
@@ -14,19 +15,36 @@ export class CanActivateGoodsOutWithProcessIdGuard implements CanActivate {
.toPromise();
if (!process) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
// 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 ${this.processNumber(processes)}`,
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,44 +1,164 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { first, map } from 'rxjs/operators';
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 _router: Router) {}
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 processesIds = await this._applicationService
.getProcesses$('customer')
.pipe(
first(),
map((p) => {
return p.filter((process) => process.type === 'goods-out').map((process) => +process.name.replace('Warenausgabe', '').trim());
})
)
.toPromise();
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
let lastActivatedProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'goods-out').pipe(first()).toPromise()
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()
)?.id;
console.log(processesIds);
const lastActivatedCartCheckoutProcessId = (
await this._applicationService.getLastActivatedProcessWithSectionAndType$('customer', 'cart-checkout').pipe(first()).toPromise()
)?.id;
// if (!lastActivatedProcessId) {
lastActivatedProcessId = Date.now();
await this._applicationService.createProcess({
id: lastActivatedProcessId,
type: 'goods-out',
section: 'customer',
name: `Warenausgabe ${Math.max(...processesIds, 0) + 1}`,
});
// }
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;
}
if (!lastActivatedProcessId) {
await this.fromGoodsOutProcess(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 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) {
@@ -46,4 +166,18 @@ export class CanActivateGoodsOutGuard 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,11 +1,12 @@
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 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,16 +26,45 @@ export class CanActivateProductWithProcessIdGuard implements CanActivate {
id: +route.params.processId,
type: 'cart',
section: 'customer',
name: `Vorgang ${this.processNumber(processes)}`,
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 ? Math.max(...processNumbers) + 1 : 1;
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,8 +1,9 @@
import { HttpErrorInterceptor } from './http-error.interceptor';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator';
import { UiErrorModalComponent, UiMessageModalComponent, UiModalService } from '@ui/modal';
import { throwError } from 'rxjs';
import { UiMessageModalComponent, UiModalResult, UiModalService } from '@ui/modal';
import { of, Subject, throwError } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
import { AuthService } from '@core/auth';
describe('HttpErrorInterceptor', () => {
let spectator: SpectatorService<HttpErrorInterceptor>;
@@ -11,13 +12,17 @@ describe('HttpErrorInterceptor', () => {
const createService = createServiceFactory({
service: HttpErrorInterceptor,
mocks: [UiModalService],
mocks: [UiModalService, AuthService],
});
beforeEach(() => {
spectator = createService();
httpErrorInterceptor = spectator.service;
modalMock = spectator.inject(UiModalService);
modalMock.open.and.returnValue({
afterClosed$: of({} as UiModalResult<any>),
} as any);
});
it('should be created', () => {
@@ -31,6 +36,7 @@ describe('HttpErrorInterceptor', () => {
statusText: '',
url: '',
});
const handleErrorSpy = spyOn(httpErrorInterceptor, 'handleError').and.callThrough();
httpErrorInterceptor.intercept(null, { handle: () => throwError(error) }).subscribe({
error: () => {

View File

@@ -2,11 +2,14 @@ import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { catchError } from 'rxjs/operators';
import { catchError, mergeMap, tap } from 'rxjs/operators';
import { AuthService } from '@core/auth';
import { IsaLogProvider } from '../providers';
import { LogLevel } from '@core/logger';
@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
constructor(private _modal: UiModalService) {}
constructor(private _modal: UiModalService, private _auth: AuthService, private _isaLogProvider: IsaLogProvider) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(catchError((error: HttpErrorResponse, caught: any) => this.handleError(error)));
@@ -14,13 +17,30 @@ export class HttpErrorInterceptor implements HttpInterceptor {
handleError(error: HttpErrorResponse): Observable<any> {
if (error.status === 0) {
this._modal.open({
content: UiMessageModalComponent,
title: 'Sie sind offline, keine Verbindung zum Netzwerk',
data: { message: 'Bereits geladene Inhalte werden angezeigt. Interaktionen sind aktuell nicht möglich.' },
});
return this._modal
.open({
content: UiMessageModalComponent,
title: 'Sie sind offline, keine Verbindung zum Netzwerk',
data: { message: 'Bereits geladene Inhalte werden angezeigt. Interaktionen sind aktuell nicht möglich.' },
})
.afterClosed$.pipe(mergeMap(() => throwError(error)));
} else if (error.status === 401) {
return this._modal
.open({
content: UiMessageModalComponent,
title: 'Sie sind nicht mehr angemeldet',
data: { message: 'Sie werden neu angemeldet' },
})
.afterClosed$.pipe(
tap(() => {
this._auth.login();
}),
mergeMap(() => throwError(error))
);
}
this._isaLogProvider.log(LogLevel.ERROR, 'Http Error', error);
return throwError(error);
}
}

View File

@@ -1,13 +1,15 @@
import { HttpErrorResponse } from '@angular/common/http';
import { ErrorHandler, Injectable } from '@angular/core';
import { AuthService } from '@core/auth';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { DialogModel, UiDialogModalComponent, UiErrorModalComponent, UiModalService } from '@ui/modal';
import { IsaLogProvider } from './isa.log-provider';
import { LogLevel } from '@core/logger';
@Injectable()
@Injectable({ providedIn: 'root' })
export class IsaErrorHandler implements ErrorHandler {
constructor(private _modal: UiModalService, private _authService: AuthService) {}
constructor(private _modal: UiModalService, private _authService: AuthService, private _isaLogProvider: IsaLogProvider) {}
handleError(error: any): void {
async handleError(error: any): Promise<void> {
console.error(error);
// Bei Klick auf Abbrechen auf der Login Seite erneut zur Login Seite weiterleiten
@@ -16,6 +18,25 @@ export class IsaErrorHandler implements ErrorHandler {
return;
}
if (error instanceof HttpErrorResponse && error?.status === 401) {
await this._modal
.open({
content: UiDialogModalComponent,
title: 'Sitzung abgelaufen',
data: {
handleCommand: false,
content: 'Sie waren zu lange nicht in der ISA aktiv. Bitte melden Sie sich erneut an',
actions: [{ command: 'CLOSE', selected: true, label: 'Erneut anmelden' }],
} as DialogModel,
})
.afterClosed$.toPromise();
this._authService.logout();
return;
}
this._isaLogProvider.log(LogLevel.ERROR, 'Client Error', error);
this._modal.open({
content: UiErrorModalComponent,
title:

View File

@@ -1,20 +1,26 @@
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { LogLevel, LogProvider } from '@core/logger';
import { UserStateService } from '@swagger/isa';
import { environment } from '../../environments/environment';
@Injectable()
@Injectable({ providedIn: 'root' })
export class IsaLogProvider implements LogProvider {
constructor(private readonly _infoService: UserStateService) {}
static InfoService: UserStateService | undefined;
log(logLevel: LogLevel, message: string, ...optionalParams: any[]): void {
constructor() {}
log(logLevel: LogLevel, message: string, error: Error, ...optionalParams: any[]): void {
if (!environment.production && (logLevel === LogLevel.WARN || logLevel === LogLevel.ERROR)) {
this._infoService
.UserStateSaveLog({
logType: logLevel,
message: message,
content: JSON.stringify(optionalParams),
})
IsaLogProvider.InfoService?.UserStateSaveLog({
logType: logLevel,
message: message,
content: JSON.stringify({
error: error?.name,
message: error?.message,
stack: error?.stack,
data: optionalParams,
}),
})
.toPromise()
.catch(() => {});
}

View File

@@ -385,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

@@ -3,7 +3,7 @@ import { ApplicationProcess, ApplicationService } from '@core/application';
import { first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { NotificationsHub } from '@hub/notifications';
import { ModalNotificationsComponent } from '@modal/notifications';
import { ConfirmModalData, UiConfirmModalComponent, UiModalService } from '@ui/modal';
import { UiModalService } from '@ui/modal';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { combineLatest } from 'rxjs';
@@ -105,7 +105,7 @@ export class ShellComponent {
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

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -0,0 +1,219 @@
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url('./Open_Sans-400-cyrillic-ext1.woff2') format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url('./Open_Sans-400-cyrillic2.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url('./Open_Sans-400-greek-ext3.woff2') format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url('./Open_Sans-400-greek4.woff2') format('woff2');
unicode-range: U+0370-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url('./Open_Sans-400-hebrew5.woff2') format('woff2');
unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url('./Open_Sans-400-vietnamese6.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url('./Open_Sans-400-latin-ext7.woff2') format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-stretch: normal;
src: url('./Open_Sans-400-latin8.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url('./Open_Sans-600-cyrillic-ext9.woff2') format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url('./Open_Sans-600-cyrillic10.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url('./Open_Sans-600-greek-ext11.woff2') format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url('./Open_Sans-600-greek12.woff2') format('woff2');
unicode-range: U+0370-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url('./Open_Sans-600-hebrew13.woff2') format('woff2');
unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url('./Open_Sans-600-vietnamese14.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url('./Open_Sans-600-latin-ext15.woff2') format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 600;
font-stretch: normal;
src: url('./Open_Sans-600-latin16.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url('./Open_Sans-700-cyrillic-ext17.woff2') format('woff2');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url('./Open_Sans-700-cyrillic18.woff2') format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url('./Open_Sans-700-greek-ext19.woff2') format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url('./Open_Sans-700-greek20.woff2') format('woff2');
unicode-range: U+0370-03FF;
}
/* hebrew */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url('./Open_Sans-700-hebrew21.woff2') format('woff2');
unicode-range: U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
}
/* vietnamese */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url('./Open_Sans-700-vietnamese22.woff2') format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url('./Open_Sans-700-latin-ext23.woff2') format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-stretch: normal;
src: url('./Open_Sans-700-latin24.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}

View File

@@ -212,4 +212,7 @@
<g id="shiiping_document" transform="matrix(1.11057,0,0,1.11057,2.2921,-1.02614)">
<path d="M20.713,0.924L2.151,0.924C1.824,0.924 1.517,1.048 1.288,1.283C1.06,1.512 0.936,1.819 0.936,2.139L0.936,25.634C0.936,26.307 1.478,26.849 2.151,26.849C2.151,26.849 2.758,26.849 2.758,26.849C2.758,26.849 2.758,28.523 2.758,28.523C2.758,29.196 3.3,29.738 3.973,29.738L22.535,29.738C23.208,29.738 23.75,29.196 23.75,28.523L23.75,5.028C23.75,4.361 23.202,3.819 22.535,3.819C22.535,3.819 21.928,3.819 21.928,3.819C21.928,3.819 21.928,2.139 21.928,2.139C21.928,1.466 21.386,0.924 20.713,0.924ZM22.471,5.098L22.471,28.459L4.037,28.459L4.037,26.849C4.037,26.849 20.713,26.849 20.713,26.849C21.341,26.849 21.855,26.371 21.921,25.766L21.928,25.64C21.928,25.64 21.828,25.734 21.828,25.734L21.928,25.634L21.928,5.098L22.471,5.098ZM2.215,2.197L2.215,25.57L20.649,25.57L20.649,2.197L2.215,2.197ZM9.403,22.1L3.397,22.1C3.042,22.1 2.758,22.384 2.758,22.739C2.758,23.094 3.042,23.379 3.397,23.379C3.397,23.379 9.403,23.379 9.403,23.379C9.758,23.379 10.043,23.094 10.043,22.739C10.043,22.384 9.758,22.1 9.403,22.1ZM14.522,19.469L3.277,19.469C2.996,19.469 2.758,19.747 2.758,20.108C2.758,20.469 2.996,20.747 3.277,20.747L14.522,20.747C14.803,20.747 15.041,20.469 15.041,20.108C15.041,19.747 14.803,19.469 14.522,19.469ZM12.352,16.843L3.397,16.843C3.042,16.843 2.758,17.127 2.758,17.483C2.758,17.838 3.042,18.122 3.397,18.122C3.397,18.122 12.352,18.122 12.352,18.122C12.707,18.122 12.992,17.838 12.992,17.483C12.992,17.127 12.707,16.843 12.352,16.843ZM14.478,14.212L3.322,14.212C3.013,14.212 2.758,14.492 2.758,14.851C2.758,15.211 3.013,15.491 3.322,15.491C3.322,15.491 14.478,15.491 14.478,15.491C14.786,15.491 15.041,15.211 15.041,14.851C15.041,14.492 14.786,14.212 14.478,14.212ZM9.593,11.587L3.298,11.587C3.004,11.587 2.758,11.866 2.758,12.226C2.758,12.587 3.004,12.866 3.298,12.866C3.298,12.866 9.593,12.866 9.593,12.866C9.887,12.866 10.133,12.587 10.133,12.226C10.133,11.866 9.887,11.587 9.593,11.587ZM15.163,3.657C12.86,3.657 10.987,5.53 10.987,7.833C10.987,10.136 12.86,12.009 15.163,12.009C17.466,12.009 19.345,10.136 19.345,7.833C19.345,5.53 17.472,3.657 15.163,3.657ZM15.163,4.936C16.762,4.936 18.066,6.234 18.066,7.833C18.066,9.432 16.768,10.73 15.163,10.73C13.564,10.73 12.266,9.432 12.266,7.833C12.266,6.234 13.564,4.936 15.163,4.936ZM9.525,8.956L3.366,8.956C3.03,8.956 2.758,9.238 2.758,9.595C2.758,9.952 3.03,10.234 3.366,10.234L9.525,10.234C9.861,10.234 10.133,9.952 10.133,9.595C10.133,9.238 9.861,8.956 9.525,8.956ZM14.821,7.954L14.61,7.634C14.459,7.403 14.127,7.319 13.867,7.453C13.596,7.593 13.507,7.902 13.664,8.142C13.664,8.142 14.228,9.011 14.228,9.011C14.313,9.141 14.457,9.233 14.626,9.251C14.651,9.256 14.676,9.256 14.701,9.256C14.846,9.256 14.984,9.207 15.087,9.116C15.087,9.116 17.248,7.196 17.248,7.196C17.469,6.999 17.469,6.68 17.248,6.483C17.037,6.291 16.693,6.291 16.481,6.479L14.821,7.954ZM2.164,2.197C2.16,2.197 2.157,2.197 2.154,2.197C2.152,2.197 2.151,2.197 2.151,2.197L2.164,2.197ZM2.151,2.097L2.151,2.097L2.151,2.197L2.151,2.097Z" style="fill:rgb(0,4,0);"/>
</g>
<g id="filter_new" transform="matrix(1.77778,0,0,2,0.000431998,3.99944)">
<path d="M7,12L11,12L11,10L7,10L7,12ZM0,0L0,2L18,2L18,0L0,0ZM3,7L15,7L15,5L3,5L3,7Z" style="fill-rule:nonzero;"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -0,0 +1,20 @@
@font-face {
font-family: 'Material Symbols Outlined';
font-style: normal;
font-weight: 100 700;
src: url(./materials-icons-outlined.woff2) format('woff2');
}
@font-face {
font-family: 'Material Symbols Rounded';
font-style: normal;
font-weight: 100 700;
src: url(./materials-icons-rounded.woff2) format('woff2');
}
@font-face {
font-family: 'Material Symbols Sharp';
font-style: normal;
font-weight: 100 700;
src: url(./materials-icons-sharp.woff2) format('woff2');
}

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -17,19 +17,19 @@
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v5"
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v6"
},
"@swagger/av": {
"rootUrl": "https://isa-test.paragon-data.net/ava/v4"
"rootUrl": "https://isa-test.paragon-data.net/ava/v6"
},
"@swagger/checkout": {
"rootUrl": "https://isa-test.paragon-data.net/checkout/v3"
"rootUrl": "https://isa-test.paragon-data.net/checkout/v6"
},
"@swagger/crm": {
"rootUrl": "https://isa-test.paragon-data.net/crm/v3"
"rootUrl": "https://isa-test.paragon-data.net/crm/v6"
},
"@swagger/oms": {
"rootUrl": "https://isa-test.paragon-data.net/oms/v4"
"rootUrl": "https://isa-test.paragon-data.net/oms/v6"
},
"@swagger/print": {
"rootUrl": "https://isa-test.paragon-data.net/print/v1"

View File

@@ -17,19 +17,19 @@
"rootUrl": "https://isa-integration.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-integration.paragon-data.net/catsearch/v5"
"rootUrl": "https://isa-integration.paragon-data.net/catsearch/v6"
},
"@swagger/av": {
"rootUrl": "https://isa-integration.paragon-data.net/ava/v4"
"rootUrl": "https://isa-integration.paragon-data.net/ava/v6"
},
"@swagger/checkout": {
"rootUrl": "https://isa-integration.paragon-data.net/checkout/v3"
"rootUrl": "https://isa-integration.paragon-data.net/checkout/v6"
},
"@swagger/crm": {
"rootUrl": "https://isa-integration.paragon-data.net/crm/v3"
"rootUrl": "https://isa-integration.paragon-data.net/crm/v6"
},
"@swagger/oms": {
"rootUrl": "https://isa-integration.paragon-data.net/oms/v4"
"rootUrl": "https://isa-integration.paragon-data.net/oms/v6"
},
"@swagger/print": {
"rootUrl": "https://isa-integration.paragon-data.net/print/v1"

View File

@@ -17,19 +17,19 @@
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v5"
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v6"
},
"@swagger/av": {
"rootUrl": "https://isa-test.paragon-data.net/ava/v4"
"rootUrl": "https://isa-test.paragon-data.net/ava/v6"
},
"@swagger/checkout": {
"rootUrl": "https://isa-test.paragon-data.net/checkout/v3"
"rootUrl": "https://isa-test.paragon-data.net/checkout/v6"
},
"@swagger/crm": {
"rootUrl": "https://isa-test.paragon-data.net/crm/v3"
"rootUrl": "https://isa-test.paragon-data.net/crm/v6"
},
"@swagger/oms": {
"rootUrl": "https://isa-test.paragon-data.net/oms/v4"
"rootUrl": "https://isa-test.paragon-data.net/oms/v6"
},
"@swagger/print": {
"rootUrl": "https://isa-test.paragon-data.net/print/v1"
@@ -60,4 +60,4 @@
}
},
"checkForUpdates": 3600000
}
}

View File

@@ -17,19 +17,19 @@
"rootUrl": "https://isa.paragon-systems.de/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa.paragon-systems.de/catsearch/v5"
"rootUrl": "https://isa.paragon-systems.de/catsearch/v6"
},
"@swagger/av": {
"rootUrl": "https://isa.paragon-systems.de/ava/v4"
"rootUrl": "https://isa.paragon-systems.de/ava/v6"
},
"@swagger/checkout": {
"rootUrl": "https://isa.paragon-systems.de/checkout/v3"
"rootUrl": "https://isa.paragon-systems.de/checkout/v6"
},
"@swagger/crm": {
"rootUrl": "https://isa.paragon-systems.de/crm/v3"
"rootUrl": "https://isa.paragon-systems.de/crm/v6"
},
"@swagger/oms": {
"rootUrl": "https://isa.paragon-systems.de/oms/v4"
"rootUrl": "https://isa.paragon-systems.de/oms/v6"
},
"@swagger/print": {
"rootUrl": "https://isa.paragon-systems.de/print/v1"

View File

@@ -17,19 +17,19 @@
"rootUrl": "https://isa-staging.paragon-systems.de/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-staging.paragon-systems.de/catsearch/v5"
"rootUrl": "https://isa-staging.paragon-systems.de/catsearch/v6"
},
"@swagger/av": {
"rootUrl": "https://isa-staging.paragon-systems.de/ava/v4"
"rootUrl": "https://isa-staging.paragon-systems.de/ava/v6"
},
"@swagger/checkout": {
"rootUrl": "https://isa-staging.paragon-systems.de/checkout/v3"
"rootUrl": "https://isa-staging.paragon-systems.de/checkout/v6"
},
"@swagger/crm": {
"rootUrl": "https://isa-staging.paragon-systems.de/crm/v3"
"rootUrl": "https://isa-staging.paragon-systems.de/crm/v6"
},
"@swagger/oms": {
"rootUrl": "https://isa-staging.paragon-systems.de/oms/v4"
"rootUrl": "https://isa-staging.paragon-systems.de/oms/v6"
},
"@swagger/print": {
"rootUrl": "https://isa-staging.paragon-systems.de/print/v1"

View File

@@ -17,19 +17,19 @@
"rootUrl": "https://isa-test.paragon-data.net/isa/v1"
},
"@swagger/cat": {
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v5"
"rootUrl": "https://isa-test.paragon-data.net/catsearch/v6"
},
"@swagger/av": {
"rootUrl": "https://isa-test.paragon-data.net/ava/v4"
"rootUrl": "https://isa-test.paragon-data.net/ava/v6"
},
"@swagger/checkout": {
"rootUrl": "https://isa-test.paragon-data.net/checkout/v3"
"rootUrl": "https://isa-test.paragon-data.net/checkout/v6"
},
"@swagger/crm": {
"rootUrl": "https://isa-test.paragon-data.net/crm/v3"
"rootUrl": "https://isa-test.paragon-data.net/crm/v6"
},
"@swagger/oms": {
"rootUrl": "https://isa-test.paragon-data.net/oms/v4"
"rootUrl": "https://isa-test.paragon-data.net/oms/v6"
},
"@swagger/print": {
"rootUrl": "https://isa-test.paragon-data.net/print/v1"

View File

@@ -6,10 +6,12 @@
<base href="/" />
<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 href="/assets/fonts/fonts.css" rel="stylesheet" />
<link href="/assets/icons/icons.css" rel="stylesheet" />
<link rel="manifest" href="manifest.webmanifest" />
<meta name="theme-color" content="#1976d2" />
</head>
<body>
<app-root>
<div class="grid place-items-center h-screen">

View File

@@ -1,5 +1,8 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import * as moment from 'moment';
moment.locale('de');
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

View File

@@ -0,0 +1 @@
@import './components/icon';

View File

@@ -0,0 +1,121 @@
@layer components {
.icon {
--icon-fill: 0;
--icon-weight: 400;
--icon-grade: 0;
--icon-optical-size: 48;
}
.icon,
.icon-outlined {
font-family: 'Material Symbols Outlined';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
.icon-rounded {
font-family: 'Material Symbols Rounded';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
.icon-sharp {
font-family: 'Material Symbols Sharp';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
.icon-fill {
--icon-fill: 1;
}
.icon-weight-100 {
--icon-weight: 100;
}
.icon-weight-200 {
--icon-weight: 200;
}
.icon-weight-300 {
--icon-weight: 300;
}
.icon-weight-400 {
--icon-weight: 400;
}
.icon-weight-500 {
--icon-weight: 500;
}
.icon-weight-600 {
--icon-weight: 600;
}
.icon-weight-700 {
--icon-weight: 700;
}
.icon-grade {
--icon-grade: 0;
}
.icon-grade-200 {
--icon-grade: 200;
}
.icon-grade--25 {
--icon-grade: -25;
}
.icon-optical-size-20 {
--icon-optical-size: 20;
}
.icon-optical-size-24 {
--icon-optical-size: 24;
}
.icon-optical-size-40 {
--icon-optical-size: 40;
}
.icon-optical-size-48 {
--icon-optical-size: 48;
}
.icon {
font-variation-settings: 'FILL' var(--icon-fill), 'wght' var(--icon-weight), 'GRAD' var(--icon-grade), 'opsz' var(--icon-optical-size);
}
}

View File

@@ -1,9 +1,11 @@
/* You can add global styles to this file, and also import other style files */
@import '~@angular/cdk/overlay-prebuilt.css';
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
@tailwind base;
@tailwind components;
@tailwind utilities;
@import './scss/components';
@import './scss/root';
@import './scss/customer';

View File

@@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core';
import { AvailabilityByBranchDTO } from 'apps/domain/availability/src/lib/defs/availability-by-branch-dto.model';
import { AvailabilityByBranchDTO } from '@domain/availability';
@Pipe({
name: 'inStock',

View File

@@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core';
import { AvailabilityByBranchDTO } from 'apps/domain/availability/src/lib/defs/availability-by-branch-dto.model';
import { AvailabilityByBranchDTO } from '@domain/availability';
@Pipe({
name: 'stockInfo',

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">
@@ -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)">
<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>
<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>
</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;
@@ -255,7 +259,7 @@
max-width: 916px;
.product-button {
@apply flex flex-row justify-center items-center w-full text-xl bg-white text-ucla-blue font-bold border-none outline-none bg-transparent rounded-t-card;
@apply flex flex-row justify-center items-center w-full text-xl bg-white text-ucla-blue font-bold border-none outline-none rounded-t-card;
box-shadow: 0 -2px 24px 0 #dce2e9;
height: 60px;
}

View File

@@ -2,13 +2,14 @@ import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ElementRef } fro
import { ActivatedRoute } from '@angular/router';
import { ApplicationService } from '@core/application';
import { DomainPrinterService } from '@domain/printer';
import { ItemDTO as PrinterItemDTO } from '@swagger/print';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { AvailabilityDTO, BranchDTO } from '@swagger/checkout';
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 +33,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$,
@@ -146,7 +149,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
content: PrintModalComponent,
data: {
printerType: 'Label',
print: (printer) => this.domainPrinterService.printProduct({ item, printer }).toPromise(),
// TODO: remove item: item as PrinterItemDTO when the backend is fixed
print: (printer) => this.domainPrinterService.printProduct({ item: item as PrinterItemDTO, printer }).toPromise(),
} as PrintModalData,
config: {
panelClass: [],
@@ -268,4 +272,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

@@ -6,7 +6,9 @@ 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 { AvailabilityDTO } from '@swagger/checkout';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { isEmpty } from 'lodash';
import { combineLatest, Observable, of } from 'rxjs';
import { catchError, filter, first, map, shareReplay, switchMap, tap } from 'rxjs/operators';
@@ -113,11 +115,24 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
//#region Abholung
readonly fetchingPickUpAvailability$ = this.select((s) => s.fetchingPickUpAvailability);
readonly pickUpAvailability$ = combineLatest([this.itemData$, this.branch$, this.isDownload$]).pipe(
readonly pickUpAvailability$: Observable<AvailabilityDTO> = combineLatest([this.itemData$, this.branch$, this.isDownload$]).pipe(
tap(() => this.patchState({ fetchingPickUpAvailability: true, fetchingPickUpAvailabilityError: undefined })),
switchMap(([item, branch, isDownload]) =>
!!item && !!branch && !isDownload
? this.domainAvailabilityService.getPickUpAvailability({ item, branch, quantity: 1 }).pipe(
map((av) => {
if (av?.length > 0) {
if (av[1].availableFor) {
if ((av[1].availableFor & 2) === 2) {
return av[0];
} else {
return undefined;
}
} else {
return av[0];
}
}
}),
catchError((err) => {
console.error('getPickUpAvailability failed.', err);
this.patchState({ fetchingPickUpAvailabilityError: 'Fehler beim laden der Abholung.' });
@@ -224,22 +239,42 @@ export class ArticleDetailsStore extends ComponentStore<ArticleDetailsState> {
readonly isDeliveryB2BAvailabilityAvailable$ = this.select(this.deliveryB2BAvailability$, (availability) =>
this.domainAvailabilityService.isAvailable({ availability })
);
//#endregion
//#endregion#
readonly sscText$ = combineLatest([
this.item$,
this.isDownload$,
this.pickUpAvailability$,
this.deliveryDigAvailability$,
this.deliveryB2BAvailability$,
this.downloadAvailability$,
]).pipe(
map(([item, isDownload, pickupAvailability, deliveryDigAvailability, downloadAvailability]) => {
const availability = isDownload ? downloadAvailability : pickupAvailability || deliveryDigAvailability;
return item?.catalogAvailability?.supplier === 'S' && !isDownload
? `${item?.catalogAvailability?.ssc} - ${item?.catalogAvailability?.sscText}`
: availability?.ssc || availability?.sscText
? `${availability?.ssc} - ${availability?.sscText}`
: 'Keine Lieferanten vorhanden';
map(([item, isDownload, pickupAvailability, deliveryDigAvailability, deliveryB2BAvailability, downloadAvailability]) => {
// const availability = isDownload ? downloadAvailability : pickupAvailability || deliveryDigAvailability || deliveryB2BAvailability;
let availability: AvailabilityDTO;
if (isDownload) {
availability = downloadAvailability;
} else {
if (pickupAvailability?.sscText) {
availability = pickupAvailability;
} else if (deliveryDigAvailability?.sscText) {
availability = deliveryDigAvailability;
} else if (deliveryB2BAvailability?.sscText) {
availability = deliveryB2BAvailability;
}
}
if (item?.catalogAvailability?.supplier === 'S' && !isDownload) {
return [item?.catalogAvailability?.ssc, item?.catalogAvailability?.sscText].filter((f) => !!f).join(' - ');
}
if (availability?.ssc || availability?.sscText) {
return [availability?.ssc, availability?.sscText].filter((f) => !!f).join(' - ');
}
return 'Keine Lieferanten vorhanden';
})
);

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, EventEmitter, OnInit, Output } from '@angular/core';
import { UiFilter } from '@ui/filter';
import { ChangeDetectionStrategy, Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
import { UiFilter, UiFilterComponent } from '@ui/filter';
import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { ArticleSearchService } from '../article-search.store';
@@ -20,6 +20,9 @@ export class ArticleSearchFilterComponent implements OnInit {
searchboxHint$ = this.articleSearch.searchboxHint$;
@ViewChild(UiFilterComponent, { static: false })
uiFilterComponent: UiFilterComponent;
constructor(private articleSearch: ArticleSearchService) {}
ngOnInit() {
@@ -41,6 +44,7 @@ export class ArticleSearchFilterComponent implements OnInit {
}
applyFilter(value: UiFilter) {
this.uiFilterComponent?.cancelAutocomplete();
this.articleSearch.setFilter(value);
this.articleSearch.search({ clear: true });
this.articleSearch.searchCompleted.pipe(take(1)).subscribe((s) => {

View File

@@ -1,14 +1,13 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { DomainCatalogService } from '@domain/catalog';
import { UiFilter } from '@ui/filter';
import { UiFilter, UiFilterInputGroupMainComponent } from '@ui/filter';
import { combineLatest, NEVER, Subscription } from 'rxjs';
import { catchError, debounceTime, first } from 'rxjs/operators';
import { FocusSearchboxEvent } from '../focus-searchbox.event';
import { ArticleSearchService } from '../article-search.store';
import { isEmpty, isEqual } from 'lodash';
import { isEqual } from 'lodash';
@Component({
selector: 'page-article-search-main',
@@ -27,6 +26,9 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
subscriptions = new Subscription();
@ViewChild(UiFilterInputGroupMainComponent, { static: false })
uiInputGroupMain: UiFilterInputGroupMainComponent;
constructor(
private searchService: ArticleSearchService,
private catalog: DomainCatalogService,
@@ -69,6 +71,7 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
}
search(filter: UiFilter) {
this.uiInputGroupMain.cancelAutocomplete();
this.searchService.setFilter(filter);
this.searchService.search({ clear: true });
}

View File

@@ -251,6 +251,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
catalogProductNumber: String(item?.id),
...item?.product,
},
itemType: item.type,
promotion: { points: item?.promoPoints },
};

View File

@@ -0,0 +1,3 @@
import { ShoppingCartItemDTO } from '@swagger/checkout';
export interface CheckoutDummyData extends ShoppingCartItemDTO {}

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