Compare commits

...

155 Commits

Author SHA1 Message Date
Lorenz Hilpert
c4134e7f99 Merge branch 'hotfix/online-offline-network' 2024-09-16 16:46:28 +02:00
Lorenz Hilpert
b7a16f5d30 IPad 6 Fehlermeldung word wrap 2024-09-16 14:39:00 +02:00
Lorenz Hilpert
4105709286 Added Styles instead of classes for word wrapping 2024-09-16 14:23:11 +02:00
Lorenz Hilpert
0c3b322fbd Ipad 4 Error Anzeige 2024-09-16 13:31:00 +02:00
Lorenz Hilpert
12096754c7 Set Max Screen Width Error 2024-09-16 12:28:25 +02:00
Lorenz Hilpert
453d921a99 Nachricht angepasst 2024-09-16 11:54:52 +02:00
Lorenz Hilpert
bad05fd098 Offline und Online Banner
Initialisierung Wartet auf Netzwerk
2024-09-13 17:30:08 +02:00
Lorenz Hilpert
363daf1e35 console.log entfernt 2024-09-13 16:08:21 +02:00
Lorenz Hilpert
e0cb0974cf Initialisierung gibt ein Feedback an den Benutzer aus. Feedback wenn Benutzer offline ist. 2024-09-13 16:05:54 +02:00
Lorenz Hilpert
c3d9274766 Merge branch 'hotfix/pwa-camera-dialog-size' 2024-09-13 11:03:04 +02:00
Lorenz Hilpert
bc16b841fb Kamera Overlay Größe
(cherry picked from commit d5dc4e053d)
2024-09-13 11:02:30 +02:00
Lorenz Hilpert
2118bd996a Merge branch 'release/3.1' 2024-09-04 17:52:55 +02:00
Lorenz Hilpert
8a6448cc17 Merge branch 'develop' into release/3.1 2024-08-19 15:01:59 +02:00
Lorenz Hilpert
f2c7d57ad6 Fix Remi Filter 2024-08-16 14:17:11 +02:00
Lorenz Hilpert
9c9ddfaeec #4774 Remi // Wannennummer nur einmal am Tag benutzbar 2024-08-14 15:45:39 +02:00
Lorenz Hilpert
6eaa347de5 Merge branch 'master' into develop 2024-08-13 11:04:36 +02:00
Lorenz Hilpert
a16f355396 Merge branch 'hotfix/scandit-lizenz-erneuern' 2024-08-13 11:02:16 +02:00
Lorenz Hilpert
8b8db6e335 Update Scandit Lizenz 2024-08-12 13:39:32 +02:00
Lorenz Hilpert
06e248d615 Kundensuche abbrechen 2024-06-27 15:47:41 +02:00
Lorenz Hilpert
c68706b54f Merged PR 1786: #4760 Fehler bei Abholpreisberechnung in Filiale Darmstadt Ernst-Ludwig-Straße
#4760 Fehler bei Abholpreisberechnung in Filiale Darmstadt Ernst-Ludwig-Straße
2024-06-10 12:22:36 +00:00
Lorenz Hilpert
b271ce9711 Merged PR 1785: Angular 17 Update + Cleanup Packages 2024-06-06 13:31:02 +00:00
Nino Righi
94888213b1 Merged PR 1784: #4758 Loading Indicator if Date gets Patched on OrderItemSubset
#4758 Loading Indicator if Date gets Patched on OrderItemSubset
2024-06-05 17:04:41 +00:00
Nino Righi
1041d92486 Merged PR 1783: #4750 Code improvements, Check if Icon gets loaded
#4750 Code improvements, Check if Icon gets loaded
2024-06-04 16:02:53 +00:00
Nino Righi
43d8d220c9 Merged PR 1782: #4750 Check if Icon File exists
#4750 Check if Icon File exists
2024-05-28 16:03:15 +00:00
Nino Righi
e0993d9c46 Merged PR 1781: #4599 Fix Routing After Click on Continue Shopping CTA - On Tablet Size or Sm...
#4599 Fix Routing After Click on Continue Shopping CTA - On Tablet Size or Smaller
2024-05-28 15:57:54 +00:00
Nino Righi
82656d9b27 Merged PR 1780: #4314 Navigation on Product Cover Click
#4314 Navigation on Product Cover Click
2024-05-28 15:57:31 +00:00
Lorenz Hilpert
df36d0934d Merged PR 1779: #4752 Performance in der WA verbessert -> Details Seite
#4752 Performance in der WA verbessert -> Details Seite
2024-05-28 13:48:00 +00:00
Nino Righi
920b8eb8e3 Merged PR 1778: #4004 Changed Minimum Birth Date for Kubi Case
#4004 Changed Minimum Birth Date for Kubi Case
2024-05-06 12:00:58 +00:00
Nino Righi
4db28b1aa7 Merged PR 1777: Merge Develop -> Release/3.1
Merge Develop -> Release/3.1
2024-05-03 13:00:46 +00:00
Nino Righi
3a9820aa54 Merged PR 1776: #4721 Removed unnecessary check for scrolling to item
#4721 Removed unnecessary check for scrolling to item
2024-05-02 19:21:52 +00:00
Nino Righi
30ad99332e Merged PR 1775: #3751 ProductGroup and ProductGroupDetails Update
#3751 ProductGroup and ProductGroupDetails Update
2024-04-22 13:31:20 +00:00
Lorenz Hilpert
4b48275910 Merged PR 1774: AHF Performance 2024-04-19 09:54:54 +00:00
Lorenz Hilpert
d3e3316459 Merge branch 'master' into develop 2024-04-17 16:25:02 +02:00
Nino Righi
4ef1bd4df6 Merged PR 1771: #4720 Hotfix Navigation Pickup Shelf Out
#4720 Hotfix Navigation Pickup Shelf Out
2024-04-17 14:21:30 +00:00
Nino Righi
0c2a23e5d2 Merged PR 1773: #3751 Reservation List Show Compartment and ProductGroup from InStock
#3751 Reservation List Show Compartment and ProductGroup from InStock
2024-04-17 08:46:32 +00:00
Nino Righi
36bd2c1eba Merged PR 1772: #4004 Kubi Check If Customer is 18 Years old
#4004 Kubi Check If Customer is 18 Years old
2024-04-15 12:34:14 +00:00
Nino Righi
a38d2eede6 Merged PR 1770: #4709 Removed Save Special Comment CTA on Checkout Cart Review
#4709 Removed Save Special Comment CTA on Checkout Cart Review
2024-04-12 10:37:43 +00:00
Lorenz Hilpert
ed7dc10246 Bump Version 2024-04-09 10:43:39 +02:00
Lorenz Hilpert
f5251d9069 Merge tag '3.0' into develop
3.0
2024-04-09 09:35:54 +02:00
Lorenz Hilpert
2bd21e168a Merge branch 'release/3.0' 2024-04-09 09:34:33 +02:00
Lorenz Hilpert
3661bf7580 Merge branch 'develop' into release/3.0 2024-04-03 11:59:12 +02:00
Lorenz Hilpert
9f2a6633f7 Anpasssung Selector Für E2E - Lieferadresse, Rechnugsadresse 2024-04-03 11:30:07 +02:00
Nino
3c4d0ea56c Merge branch 'release/3.0' into develop 2024-03-20 12:10:17 +01:00
Nino
56bb784c83 Added Classes and Data Attributes to package-inspection for e2e testing 2024-03-20 12:06:05 +01:00
Nino Righi
c687570b1f Merged PR 1769: #4712 Removed isShippingEnabled check from availabilities modal
#4712 Removed isShippingEnabled check from availabilities modal
2024-03-20 10:42:59 +00:00
Nino Righi
afe5d3468a Merged PR 1768: Merge Develop to Release 3.0
Merge Develop to Release 3.0
2024-03-15 10:21:12 +00:00
Nino Righi
65f43d22ee Merged PR 1767: #4706 AHF Fix History Navigation after Switching Tabs
#4706 AHF Fix History Navigation after Switching Tabs
2024-03-13 14:17:15 +00:00
Nino Righi
67203a8506 Merged PR 1766: #4696 Bugfix Cover Items
#4696 Bugfix Cover Items
2024-03-13 14:12:08 +00:00
Nino Righi
92e522dedf Merged PR 1765: #4696 PickupShelfIn Details Page Clear Previous Selected OrderItemSubsetId fr...
#4696 PickupShelfIn Details Page Clear Previous Selected OrderItemSubsetId from Store after Leaving Page to avoid side effects
2024-03-12 15:31:21 +00:00
Nino Righi
fb46d329dc Merged PR 1764: #4547 WE Updated Annotation Implementation
#4547 WE Updated Annotation Implementation
2024-03-12 14:43:00 +00:00
Lorenz Hilpert
64d0a9fdb9 Merged PR 1763: #4547 Wareneingang // Kontrolle der Service Packstücke
#4547 Wareneingang // Kontrolle der Service Packstücke
2024-03-11 14:51:46 +00:00
Nino Righi
8f47163627 Merged PR 1762: #4692 Hotfix Undefined Values in Route Url
#4692 Hotfix Undefined Values in Route Url
2024-03-07 14:33:36 +00:00
Nino Righi
49f2a44461 Merged PR 1761: #4691 Small Fixes Customer Create
#4691 Small Fixes Customer Create
2024-03-06 16:01:27 +00:00
Nino Righi
a209d59ea9 Merged PR 1760: #4532 Fallback Route if Url contains Undefined or Null values
#4532 Fallback Route if Url contains Undefined or Null values
2024-03-06 12:13:13 +00:00
Nino Righi
03124d8736 Merged PR 1759: #4688 Change PickUpShelf Navigation based on Area In or Out
#4688 Change PickUpShelf Navigation based on Area In or Out
2024-03-06 11:55:11 +00:00
Nino Righi
a3330263f8 Merged PR 1758: #4689 Pickup Shelf Out Update Quantity After FETCHED_PARTIAL in Result List
#4689 Pickup Shelf Out Update Quantity After FETCHED_PARTIAL in Result List
2024-03-04 16:45:50 +00:00
Nino Righi
89092a5f6e Merged PR 1757: #4690 PickupShelfOut Details Display FETCHED_PARTIAL Action Correctly
#4690 PickupShelfOut Details Display FETCHED_PARTIAL Action Correctly
2024-03-04 15:54:43 +00:00
Nino
42fa108bb6 Pickup Shelf Out Details with Supplier Id Filter 2024-03-01 10:43:32 +01:00
Nino
2692588357 Changed Checkout Summary Navigation To Pickup Shelf Out Filter to supplier id 16 2024-02-29 12:47:45 +01:00
Nino Righi
ec26b5f4c0 Merged PR 1756: #4684 Routing to Pickup Shelf Out Update
#4684 Routing to Pickup Shelf Out Update
2024-02-28 11:28:26 +00:00
Nino Righi
ff985bda64 Merged PR 1755: #4684 Checkout Summary link to pickup shelf out updated
#4684 Checkout Summary link to pickup shelf out updated
2024-02-27 13:18:41 +00:00
Nino Righi
ca255cb592 Merged PR 1754: #4615 Tracking Link Update Customer Orders
#4615 Tracking Link Update Customer Orders
2024-02-26 16:19:56 +00:00
Nino Righi
8df5052c76 Merged PR 1753: #4684 Fix Navigation from Checkout Summary to Pickup Shelf Out
#4684 Fix Navigation from Checkout Summary to Pickup Shelf Out
2024-02-26 13:29:48 +00:00
Nino Righi
c78ddb5c8c Merged PR 1752: #4615 Tracking Link Implementation Customer Orders
#4615 Tracking Link Implementation Customer Orders
2024-02-23 08:49:54 +00:00
Lorenz Hilpert
5d84b4a55a #4617 Drucken // Pop Up entfernen wenn Drucker bereits ausgewählt ist (Desktop) 2024-02-21 18:52:28 +01:00
Nino Righi
a6142a5d86 Merged PR 1751: #4617 Fix Always Show Modal on Ipad, Fixed Loading Spinner Bug after Modal Close
#4617 Fix Always Show Modal on Ipad, Fixed Loading Spinner Bug after Modal Close
2024-02-21 17:02:38 +00:00
Nino Righi
fdf50fe11e Merged PR 1748: #4676 Removed updateBreadcrumb Function to prevent adding the breadcrumb by c...
#4676 Removed updateBreadcrumb Function to prevent adding the breadcrumb by closing the process
2024-02-21 12:48:04 +00:00
Nino Righi
e8bf922a67 Merged PR 1750: #4617 Added ipad check, Added implementation to checkout summary and added lo...
#4617 Added ipad check, Added implementation to checkout summary and added loading spinner
2024-02-21 10:38:46 +00:00
Nino Righi
f202ff5291 Merged PR 1749: #4679 Fix Catalog Navigation Splitscreen if hits is 1, fixed minor scroll pos...
#4679 Fix Catalog Navigation Splitscreen if hits is 1, fixed minor scroll position bug
2024-02-20 14:08:46 +00:00
Nino Righi
0c25859b6b Merged PR 1746: #4665 Added new Icon and Implemented Shared Icon Badge component, check if promotionPoints > 0
#4665 Added new Icon and Implemented Shared Icon Badge component, check if promotionPoints > 0
2024-02-20 08:44:07 +00:00
Nino Righi
215cb89aff Merged PR 1747: #4675 Show Fetching Spinner even if data is in cache - for user feedback
#4675 Show Fetching Spinner even if data is in cache - for user feedback
2024-02-20 08:43:11 +00:00
Nino Righi
9256a79087 Merged PR 1745: #4665 Catalog Details RedemptionPoints Badge
#4665 Catalog Details RedemptionPoints Badge
2024-02-16 15:25:51 +00:00
Nino Righi
f1ff9c6c55 Merged PR 1744: #4659 PickupShelf, CustomerOrders - Changed Navigation if Action Command incl...
#4659 PickupShelf, CustomerOrders - Changed Navigation if Action Command includes BACKTOSTOCK
2024-02-16 15:25:03 +00:00
Nino Righi
3f05e57554 Merged PR 1743: #4619 Fix removed memorize decorator on canSetCustomer function
#4619 Fix removed memorize decorator on canSetCustomer function
2024-02-16 15:24:04 +00:00
Anastasiia Chetverykova
2062bf3bab Merged PR 1742: #4617 - Drucken-Pop-Up nicht anzeigen, wenn ein Default Drucker bereits ausge...
#4617 - Drucken-Pop-Up nicht anzeigen, wenn ein Default Drucker bereits ausgewählt ist
2024-02-16 15:23:36 +00:00
Nino Righi
2d71a567ff Merged PR 1740: #4634 Remission Filter Reset Filter
#4634 Remission Filter Reset Filter
2024-02-13 17:20:42 +00:00
Lorenz Hilpert
547e615522 #4664 AHF // Bearbeiten - "Abbrechen" führt zu leerer Seite 2024-02-13 17:33:34 +01:00
Lorenz Hilpert
5d904e9d88 Merged PR 1739: #4662 AHF // "vsl. Lieferdatum" ändern ändert Datum für alle Bestellposten
#4662 AHF // "vsl. Lieferdatum" ändern ändert Datum für alle Bestellposten
2024-02-13 15:34:38 +00:00
Lorenz Hilpert
b7ccde4d44 #4663 AHF // Bearbeiten - Mobilnummer eingeben -> kein Refresh 2024-02-13 15:35:26 +01:00
Lorenz Hilpert
b838f4c475 Merge branch 'develop' into release/3.0 2024-02-01 18:33:25 +01:00
Lorenz Hilpert
2bc97ee574 #4633 WK // nach klick auf "Ändern" ("Name, Vorname" oder Adressen) im Warenkorb -> Suche nicht korrekt 2024-01-31 19:29:21 +01:00
Lorenz Hilpert
f054614cfe clearUserState() ist immer zugänglich. 2024-01-31 10:58:00 +01:00
Lorenz Hilpert
0aa1cddf72 Merge branch 'release/3.0' into develop 2024-01-29 17:37:07 +01:00
Lorenz Hilpert
d39521b9f2 Merge branch 'develop' into release/3.0 2024-01-29 17:36:25 +01:00
Lorenz Hilpert
f5468d7f8e #4624 #4623 Fallback wenn keine Versandoption für den Kundentypen vorhanden ist 2024-01-29 16:01:42 +01:00
Lorenz Hilpert
4ad99270bd #4622 AHF // Filter schließt sich nicht 2024-01-29 15:49:45 +01:00
Lorenz Hilpert
4e098ae962 #4620 Suche nach EAN wirft Fehlermeldung 2024-01-29 14:25:08 +01:00
Lorenz Hilpert
8e00e646fb #4621 Preis von Preisgebundenen Artikel wird nach Kundenauswahl nicht richtig übernommen 2024-01-29 14:19:31 +01:00
Lorenz Hilpert
4fad5a7c2f Merge branch 'develop' into release/3.0 2024-01-26 15:39:53 +01:00
Lorenz Hilpert
5ece030ec8 #2062 Fachbodenbeschriftung 2024-01-25 19:56:33 +01:00
Lorenz Hilpert
54d7c525a9 Null Check PriceMaintained und fallback für delivery kaufoptionen 2024-01-24 14:48:51 +01:00
Lorenz Hilpert
41d4dc4663 #4611 Preisunterschiede im Warenkorb 2024-01-24 13:48:52 +01:00
Lorenz Hilpert
c266c51572 #4611 Deadcode entfernt 2024-01-24 11:09:59 +01:00
Lorenz Hilpert
4ea50f68d1 #4611 Preisunterschiede im Warenkorb 2024-01-24 11:09:02 +01:00
Lorenz Hilpert
e5d61c8622 #Preis // Preisunterschiede im Warenkorb 2024-01-23 17:44:28 +01:00
Lorenz Hilpert
d06c64c08a #4613 IPad 6 Breite angepasst - wegen overflow-x 2024-01-22 10:11:12 +01:00
Lorenz Hilpert
91ebc3e27f #2062 text-right hinzugefügt 2024-01-19 16:19:08 +01:00
Lorenz Hilpert
d643c19642 Merged PR 1737: #4611 Preisunterschiede im Warenkorb
#4611 Preisunterschiede im Warenkorb
2024-01-19 13:57:36 +00:00
Nino Righi
afd1f5e302 Merged PR 1736: #2527 Improved Code, fixed navigation error when adding a new item
#2527 Improved Code, fixed navigation error when adding a new item
2024-01-19 13:09:11 +00:00
Lorenz Hilpert
4099aa0a57 #2062 #4613 Remi Liste Anpassungen 2024-01-19 13:51:49 +01:00
Nino Righi
ebe11b75d1 Merged PR 1735: #2527 Navigation Improvements Dummy Modal
#2527 Navigation Improvements Dummy Modal
2024-01-18 15:06:49 +00:00
Lorenz Hilpert
81f7270cf7 #4611 Preisunterschiede im Warenkorb 2024-01-18 15:50:46 +01:00
Nino Righi
570a8800a0 Merged PR 1734: #4010 Removed Cheaper Price Tooltip From Purchasing Options Modal
#4010 Removed Cheaper Price Tooltip From Purchasing Options Modal
2024-01-18 12:47:31 +00:00
Lorenz Hilpert
25aecffafc Merge branch 'release/3.0' into develop 2024-01-17 17:31:21 +01:00
Lorenz Hilpert
7c48c63584 Merge branch 'develop' into release/3.0 2024-01-17 17:30:50 +01:00
Nino Righi
5bf32b2e72 Merged PR 1733: #4603 Select no default gender
#4603 Select no default gender
2024-01-17 16:12:31 +00:00
Lorenz Hilpert
f44fbe3fdb #4598 ngIf angepasst Kundendetails 2024-01-17 13:21:07 +01:00
Lorenz Hilpert
5df075f448 Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2024-01-17 13:18:52 +01:00
Lorenz Hilpert
9cee33e286 #4598 Firma soll angezeigt werden wenn eingetragn 2024-01-17 13:18:33 +01:00
Nino Righi
42bf7e4120 Merged PR 1731: #4601 WA, AHF Navigation Dropdown Fix
#4601 WA, AHF Navigation Dropdown Fix
2024-01-17 12:15:25 +00:00
Nino Righi
77ff7ca1a8 Merged PR 1732: #4603 Changed Genders Order
#4603 Changed Genders Order
2024-01-17 12:14:59 +00:00
Lorenz Hilpert
7f195ee627 Merged PR 1730: #4598 Kundendetails // "Geb.-tags"-Feld wird nicht angezeigt, "USt-ID" und "A...
#4598 Kundendetails // "Geb.-tags"-Feld wird nicht angezeigt, "USt-ID" und "Abteilung" dafür immer
2024-01-16 15:12:53 +00:00
Nino Righi
79bec55818 Merged PR 1729: #4600 Fix Product Images Height
#4600 Fix Product Images Height
2024-01-16 14:05:48 +00:00
Nino Righi
35093afaff Merged PR 1728: #4597 Only Print Order Confirmation If Not On Tablet
#4597 Only Print Order Confirmation If Not On Tablet
2024-01-15 16:07:13 +00:00
Nino Righi
358ba3963c Merged PR 1727: #3462 Fix Checkout Review Details Display Name Section if Customer has no shi...
#3462 Fix Checkout Review Details Display Name Section if Customer has no shipping or billing addresses but orderType is shipping
2024-01-15 16:06:49 +00:00
Anastasiia Chetverykova
d47e617f8c Merged PR 1726: #4419 - Pop Up - Wording Änderung
#4419 - Pop Up - Wording Änderung

Related work items: #4419
2024-01-15 12:38:18 +00:00
Nino Righi
55bd001146 Merged PR 1725: #4597 Fix Print Order Confirmation Immediately
#4597 Fix Print Order Confirmation Immediately
2024-01-12 19:50:00 +00:00
Lorenz Hilpert
a9f11426a7 Merge branch 'develop' into release/3.0 2024-01-11 18:47:50 +01:00
Nino Righi
10b86756d2 Merged PR 1724: #4564 Fix customer page empty details view
#4564 Fix customer page empty details view
2024-01-11 16:19:24 +00:00
Lorenz Hilpert
262dd084c1 Merged PR 1723: #4571 - Kundendaten erfassen // Bei Kundenkarten Formular mit Onlinekonto feh...
#4571 - Kundendaten erfassen // Bei Kundenkarten Formular mit Onlinekonto fehlt die Aufklärung über die Konditionen
2024-01-11 10:27:02 +00:00
Nino Righi
abc58c8a78 Merged PR 1722: #4569 Fix Pickup Shelf display searchboxHint correctly
#4569 Fix Pickup Shelf display searchboxHint correctly
2024-01-10 15:39:08 +00:00
Nino Righi
866cd23e41 Merged PR 1721: #4564 Always keep customer updated and check name properties correctly
#4564 Always keep customer updated and check name properties correctly
2024-01-09 19:12:45 +00:00
Lorenz Hilpert
fdcf12c022 Element naming - Vorgangs-ID 2024-01-09 13:54:31 +01:00
Nino Righi
432f1161af Merged PR 1720: #4553 Always take retailPrice from instock request for take away availability
#4553 Always take retailPrice from instock request for take away availability
2024-01-08 12:59:14 +00:00
Nino Righi
82dbce5744 Merged PR 1713: #4291 Gender Refactoring
#4291 Gender Refactoring
2024-01-08 11:46:24 +00:00
Nino
a4b9f5fcf1 Merge branch 'develop' into release/3.0 2024-01-05 14:17:29 +01:00
Nino Righi
7ea9359c30 Merged PR 1719: #4514 Added regex pattern to compartmentCode field inside goods in out order...
#4514 Added regex pattern to compartmentCode field inside goods in out order edit page
2024-01-05 12:09:43 +00:00
Nino Righi
b9a4b0d315 Merged PR 1718: #4552 Added whitespace
#4552 Added whitespace
2024-01-05 09:19:08 +00:00
Anastasiia Chetverykova
7809e7a2b5 Merged PR 1717: #4552 - Packstückprüfung Anzahl Exemplare
#4552 - Packstückprüfung Anzahl Exemplare
2024-01-04 13:35:57 +00:00
Nino Righi
9a8c74b148 Merged PR 1716: #4534 Goods In Out Order Edit Always Show Price with Two Decimal Places
#4534 Goods In Out Order Edit Always Show Price with Two Decimal Places
2024-01-04 10:36:37 +00:00
Nino Righi
ad62e67771 Merged PR 1715: #4525 Improved Scroll Position Handling in Goods In List and Remission Previe...
#4525 Improved Scroll Position Handling in Goods In List and Remission Preview Page
2024-01-04 09:23:28 +00:00
Nino Righi
6feb8079b7 Merged PR 1714: #4550 Fix Multiple Processes Bug
#4550 Fix Multiple Processes Bug
2024-01-04 08:23:31 +00:00
Nino
7f8f48f393 Updated tsconfig 2024-01-03 11:54:03 +01:00
Nino
0fe0c5242d Merge branch 'release/3.0' into develop 2024-01-03 11:52:25 +01:00
Nino
d208bdaf97 Added Package Inspection Class Name in Details Page for Page Objects Targetting 2024-01-02 15:08:19 +01:00
Nino Righi
dc80df4ad4 Merged PR 1712: #4524 Page Article Search Improved Order by Filter Handling
#4524 Page Article Search Improved Order by Filter Handling
2023-12-29 16:12:53 +00:00
Nino Righi
3020609682 Merged PR 1711: #4545 #4548 Fix HSC Breadcrumb and Multiple Processes bug
#4545 #4548 Fix HSC Breadcrumb and Multiple Processes bug
2023-12-29 15:22:26 +00:00
Nino Righi
5c3e1ed2ad Merged PR 1710: #4544 Fix Proveded Store correctly, removed take from filter and cancleSearch...
#4544 Fix Proveded Store correctly, removed take from filter and cancleSearchRequests on process change
2023-12-29 15:12:51 +00:00
Nino Righi
e832feebc5 Merged PR 1709: #4545 Fix Missing Main Breadcrumb
#4545 Fix Missing Main Breadcrumb
2023-12-28 15:10:44 +00:00
Nino Righi
08580d782d Merged PR 1708: #4546 HSC Customer Orders Fixed Navigation Link Active Styling
#4546 HSC Customer Orders Fixed Navigation Link Active Styling
2023-12-28 15:01:40 +00:00
Nino Righi
2d07556341 Merged PR 1707: #4533 Fixed Breadcrumb and Searchbox after Scan
#4533 Fixed Breadcrumb and Searchbox after Scan
2023-12-28 12:23:48 +00:00
Nino Righi
9ad1256019 Merged PR 1706: #4543 Styling Fix Checkout Review for multiple Destinations per OrderType
#4543 Styling Fix Checkout Review for multiple Destinations per OrderType
2023-12-27 16:00:23 +00:00
Nino Righi
250002f057 Merged PR 1705: #4516 Fix Also Show Details Items Group in Pick Up Shelf In
#4516 Fix Also Show Details Items Group in Pick Up Shelf In
2023-12-27 15:59:11 +00:00
Nino Righi
0973b01bf0 Merged PR 1704: #4539 Fix Skip Location Change Filter
#4539 Fix Skip Location Change Filter
2023-12-27 14:39:15 +00:00
Nino Righi
d5254cc150 Merged PR 1703: #4541 Added Errorhandling to Customer Search Store
#4541 Added Errorhandling to Customer Search Store
2023-12-27 08:09:29 +00:00
Nino Righi
adc5a5a280 Merged PR 1702: #4516 WA Implementation of orderType Groups
#4516 WA Implementation of orderType Groups
2023-12-22 16:13:52 +00:00
Nino Righi
2ff033ea55 Merged PR 1701: #4539 Hotfix AHF Open Filter Page does not trigger search request
#4539 Hotfix AHF Open Filter Page does not trigger search request
2023-12-21 17:23:08 +00:00
Nino Righi
cbaac8ed9a Merged PR 1700: #4537 Relocated Component Store provision
#4537 Relocated Component Store provision
2023-12-21 17:19:35 +00:00
Lorenz Hilpert
803a8e316c Skip UnitTest for some libs 2023-12-19 17:33:19 +01:00
Nino Righi
83cab7796e Merged PR 1697: #4530 Notification Batch Messages for each type
#4530 Notification Batch Messages for each type

(cherry picked from commit 5073693fc2)
2023-12-19 17:14:37 +01:00
Nino Righi
c70dd30830 Merged PR 1695: #4523 AHF, WA Added other notification types to Email Notification Badge
#4523 AHF, WA Added other notification types to Email Notification Badge

(cherry picked from commit f3cb6236a5)
2023-12-19 17:14:03 +01:00
Lorenz Hilpert
b28bb165d4 Merged PR 1696: #4532 Mitarbeiter klickt auf Einbuchen und erreicht den Tätigkeitskalender
#4532 Mitarbeiter klickt auf Einbuchen und erreicht den Tätigkeitskalender
2023-12-19 13:35:58 +00:00
Nino Righi
32d8d81f53 Merged PR 1694: #4526 Hotfix Pickup Shelf Edit show Erneut Senden CTA
#4526 Hotfix Pickup Shelf Edit show Erneut Senden CTA
2023-12-15 10:12:51 +00:00
Nino
0361aa63ff Merge Develop into Release/3.0, corrected itemSize in page catalog list 2023-12-12 16:52:03 +01:00
Nino
2f95c23910 Merge branch 'develop' into release/3.0 2023-12-12 16:35:35 +01:00
271 changed files with 9702 additions and 11808 deletions

1
.husky/pre-commit Normal file
View File

@@ -0,0 +1 @@
npm run pretty-quick

View File

@@ -1,4 +0,0 @@
- Neue Icon Module (z.B. mit SVG sprites)
- Breadcrumb Navigation (Neu)
- Remissions Produkt Liste (Refactoring / Neu)
- Angular Version (Upgrade)

View File

@@ -959,10 +959,10 @@
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "isa-app:build:production"
"buildTarget": "isa-app:build:production"
},
"development": {
"browserTarget": "isa-app:build:development"
"buildTarget": "isa-app:build:development"
}
},
"defaultConfiguration": "development"
@@ -970,7 +970,7 @@
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "isa-app:build"
"buildTarget": "isa-app:build"
}
},
"test": {
@@ -1470,39 +1470,6 @@
}
}
}
},
"shell": {
"projectType": "library",
"root": "apps/shell",
"sourceRoot": "apps/shell/src",
"prefix": "shell",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "apps/shell/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/shell/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "apps/shell/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"tsConfig": "apps/shell/tsconfig.spec.json",
"polyfills": [
"zone.js",
"zone.js/testing"
]
}
}
}
}
},
"cli": {

View File

@@ -4,12 +4,11 @@
.scanner-container {
width: 100vw;
max-width: 95vw;
max-height: calc(95vh - 120px);
height: 100vh;
}
.close-scanner {
@apply block px-6 py-4 bg-white text-brand border-2 border-solid border-brand rounded-full text-lg font-bold mx-auto mt-4;
@apply absolute bottom-12 left-[50%] -translate-x-[50%] block px-6 py-4 bg-white text-brand border-2 border-solid border-brand rounded-full text-lg font-bold mx-auto mt-4;
}
@screen desktop {

View File

@@ -0,0 +1,31 @@
import { Directive, HostListener, Input } from '@angular/core';
import { ProductCatalogNavigationService } from '@shared/services';
@Directive({
selector: '[productImageNavigation]',
standalone: true,
})
export class NavigateOnClickDirective {
@Input('productImageNavigation') ean: string;
constructor(private readonly _productCatalogNavigation: ProductCatalogNavigationService) {}
@HostListener('click', ['$event'])
async onClick(event: MouseEvent) {
event.preventDefault();
event.stopPropagation();
if (this.ean) {
await this._navigateToProductSearchDetails();
}
}
private async _navigateToProductSearchDetails() {
await this._productCatalogNavigation
.getArticleDetailsPathByEan({
processId: Date.now(),
ean: this.ean,
extras: { queryParams: { main_qs: this.ean } },
})
.navigate();
}
}

View File

@@ -5,4 +5,5 @@
export * from './lib/product-image.service';
export * from './lib/product-image.module';
export * from './lib/product-image.pipe';
export * from './lib/product-image-navigation.directive';
export * from './lib/tokens';

View File

@@ -36,7 +36,7 @@ export class DomainAvailabilityService {
private _logisticanService: LogisticianService,
private _stockService: StockService,
private _supplierService: StoreCheckoutSupplierService,
private _branchService: StoreCheckoutBranchService
private _branchService: StoreCheckoutBranchService,
) {}
@memorize({ ttl: 10000 })
@@ -48,7 +48,7 @@ export class DomainAvailabilityService {
getSuppliers(): Observable<SupplierDTO[]> {
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
map((response) => response.result),
shareReplay(1)
shareReplay(1),
);
}
@@ -56,7 +56,7 @@ export class DomainAvailabilityService {
getTakeAwaySupplier(): Observable<SupplierDTO> {
return this._supplierService.StoreCheckoutSupplierGetSuppliers({}).pipe(
map(({ result }) => result?.find((supplier) => supplier?.supplierNumber === 'F')),
shareReplay(1)
shareReplay(1),
);
}
@@ -64,7 +64,7 @@ export class DomainAvailabilityService {
getBranches(): Observable<BranchDTO[]> {
return this._branchService.StoreCheckoutBranchGetBranches({}).pipe(
map((response) => response.result),
shareReplay(1)
shareReplay(1),
);
}
@@ -73,7 +73,7 @@ export class DomainAvailabilityService {
return this._stockService.StockGetStocksByBranch({ branchId }).pipe(
map((response) => response.result),
map((result) => result?.find((_) => true)),
shareReplay(1)
shareReplay(1),
);
}
@@ -81,7 +81,7 @@ export class DomainAvailabilityService {
getDefaultStock(): Observable<StockDTO> {
return this._stockService.StockCurrentStock().pipe(
map((response) => response.result),
shareReplay(1)
shareReplay(1),
);
}
@@ -105,7 +105,7 @@ export class DomainAvailabilityService {
status: response.result.status,
version: response.result.version,
})),
shareReplay(1)
shareReplay(1),
);
}
@@ -113,7 +113,7 @@ export class DomainAvailabilityService {
getLogisticians(): Observable<LogisticianDTO> {
return this._logisticanService.LogisticianGetLogisticians({}).pipe(
map((response) => response.result?.find((l) => l.logisticianNumber === '2470')),
shareReplay(1)
shareReplay(1),
);
}
@@ -146,7 +146,7 @@ export class DomainAvailabilityService {
});
return availabilities;
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -167,13 +167,13 @@ export class DomainAvailabilityService {
this._stockService.StockInStock({ articleIds: [item.itemId], stockId: s.id }),
this.getTakeAwaySupplier(),
this.getDefaultBranch(),
])
]),
),
map(([response, supplier, defaultBranch]) => {
const price = item?.price;
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branch?.id ?? defaultBranch?.id, quantity, price });
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -196,7 +196,7 @@ export class DomainAvailabilityService {
map(([response, supplier]) => {
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branch.id, quantity, price });
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -218,7 +218,7 @@ export class DomainAvailabilityService {
map(([response, supplier, defaultBranch]) => {
return this._mapToTakeAwayAvailability({ response, supplier, branchId: branchId ?? defaultBranch.id, quantity, price });
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -228,7 +228,7 @@ export class DomainAvailabilityService {
switchMap((s) => this._stockService.StockInStockByEAN({ eans: eansFiltered, stockId: s.id })),
withLatestFrom(this.getTakeAwaySupplier(), this.getDefaultBranch()),
map((response) => response[0].result),
shareReplay(1)
shareReplay(1),
);
}
@@ -254,7 +254,7 @@ export class DomainAvailabilityService {
])
.pipe(
map((r) => this._mapToPickUpAvailability(r.result)?.find((_) => true)),
shareReplay(1)
shareReplay(1),
);
}
@@ -270,7 +270,7 @@ export class DomainAvailabilityService {
]).pipe(
timeout(5000),
map((r) => this._mapToShippingAvailability(r.result)?.find((_) => true)),
shareReplay(1)
shareReplay(1),
);
}
@@ -305,7 +305,7 @@ export class DomainAvailabilityService {
priceMaintained: preferred?.priceMaintained,
};
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -329,12 +329,12 @@ export class DomainAvailabilityService {
this.getPickUpAvailability({ item, quantity, branch: branch ?? defaultBranch }).pipe(
mergeMap((availability) =>
logistician$.pipe(
map((logistician) => ({ ...(availability?.length > 0 ? availability[0] : []), logistician: { id: logistician.id } }))
)
map((logistician) => ({ ...(availability?.length > 0 ? availability[0] : []), logistician: { id: logistician.id } })),
),
),
shareReplay(1)
)
)
shareReplay(1),
),
),
);
}
@@ -367,7 +367,7 @@ export class DomainAvailabilityService {
priceMaintained: preferred?.priceMaintained,
};
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -378,7 +378,7 @@ export class DomainAvailabilityService {
switchMap((stockId) =>
stockId
? this._stockService.StockInStock({ articleIds: items.map((i) => i.id), stockId })
: of({ result: [] } as ResponseArgsOfIEnumerableOfStockInfoDTO)
: of({ result: [] } as ResponseArgsOfIEnumerableOfStockInfoDTO),
),
timeout(20000),
withLatestFrom(this.getTakeAwaySupplier()),
@@ -389,10 +389,10 @@ export class DomainAvailabilityService {
supplier,
quantity: 1,
price: items?.find((i) => i.id === stockInfo.itemId)?.price,
})
}),
);
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -400,7 +400,7 @@ export class DomainAvailabilityService {
getPickUpAvailabilities(payload: AvailabilityRequestDTO[], preferred?: boolean) {
return this._availabilityService.AvailabilityStoreAvailability(payload).pipe(
timeout(20000),
map((response) => (preferred ? this._mapToPickUpAvailability(response.result) : response.result))
map((response) => (preferred ? this._mapToPickUpAvailability(response.result) : response.result)),
);
}
@@ -408,7 +408,7 @@ export class DomainAvailabilityService {
getDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
return this.memorizedAvailabilityShippingAvailability(payload).pipe(
timeout(20000),
map((response) => this._mapToShippingAvailability(response.result))
map((response) => this._mapToShippingAvailability(response.result)),
);
}
@@ -416,7 +416,7 @@ export class DomainAvailabilityService {
getDigDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
return this.memorizedAvailabilityShippingAvailability(payload).pipe(
timeout(20000),
map((response) => this._mapToShippingAvailability(response.result))
map((response) => this._mapToShippingAvailability(response.result)),
);
}
@@ -427,16 +427,16 @@ export class DomainAvailabilityService {
return this.getPickUpAvailabilities(payload, true).pipe(
timeout(20000),
switchMap((availability) =>
logistician$.pipe(map((logistician) => ({ availability: [...availability], logistician: { id: logistician.id } })))
logistician$.pipe(map((logistician) => ({ availability: [...availability], logistician: { id: logistician.id } }))),
),
shareReplay(1)
shareReplay(1),
);
}
getPriceForAvailability(
purchasingOption: string,
catalogAvailability: CatAvailabilityDTO | AvailabilityDTO,
availability: AvailabilityDTO
availability: AvailabilityDTO,
): PriceDTO {
switch (purchasingOption) {
case 'take-away':
@@ -458,10 +458,6 @@ export class DomainAvailabilityService {
return [2, 32, 256, 1024, 2048, 4096].some((code) => availability?.availabilityType === code);
}
private _priceIsEmpty(price: PriceDTO) {
return isEmpty(price?.value) || isEmpty(price?.vat);
}
private _mapToTakeAwayAvailability({
response,
supplier,
@@ -482,7 +478,7 @@ export class DomainAvailabilityService {
inStock: inStock,
supplierSSC: quantity <= inStock ? '999' : '',
supplierSSCText: quantity <= inStock ? 'Filialentnahme' : '',
price: this._priceIsEmpty(price) ? stockInfo?.retailPrice : price,
price: stockInfo?.retailPrice ?? price, // #4553 Es soll nun immer der retailPrice aus der InStock Abfrage verwendet werden, egal ob "price" empty ist oder nicht
supplier: { id: supplier?.id },
// TODO: Change after API Update
// LH: 2021-03-09 preis Property hat nun ein Fallback auf retailPrice
@@ -571,12 +567,12 @@ export class DomainAvailabilityService {
if (!params.branchId) {
branchId$ = this.getDefaultBranch().pipe(
first(),
map((b) => b.id)
map((b) => b.id),
);
}
const stock$ = branchId$.pipe(
mergeMap((branchId) => this._stockService.StockGetStocksByBranch({ branchId }).pipe(map((response) => response.result?.[0])))
mergeMap((branchId) => this._stockService.StockGetStocksByBranch({ branchId }).pipe(map((response) => response.result?.[0]))),
);
return stock$.pipe(
@@ -593,17 +589,17 @@ export class DomainAvailabilityService {
acc[stockInfo.ean] = stockInfo;
return acc;
}, {});
})
)
)
}),
),
),
);
}
getInStock({ itemIds, branchId }: { itemIds: number[]; branchId: number }): Observable<StockInfoDTO[]> {
return this.getStockByBranch(branchId).pipe(
mergeMap((stock) =>
this._stockService.StockInStock({ articleIds: itemIds, stockId: stock.id }).pipe(map((response) => response.result))
)
this._stockService.StockInStock({ articleIds: itemIds, stockId: stock.id }).pipe(map((response) => response.result)),
),
);
}
}

View File

@@ -11,9 +11,12 @@ export class ThumbnailUrlPipe implements PipeTransform, OnDestroy {
private input$ = new BehaviorSubject<{ width?: number; height?: number; ean?: string }>(undefined);
private result: string;
private onDestroy$ = new Subject();
private onDestroy$ = new Subject<void>();
constructor(private domainCatalogThumbnailService: DomainCatalogThumbnailService, private cdr: ChangeDetectorRef) {}
constructor(
private domainCatalogThumbnailService: DomainCatalogThumbnailService,
private cdr: ChangeDetectorRef,
) {}
ngOnDestroy(): void {
this.onDestroy$.next();
@@ -27,7 +30,7 @@ export class ThumbnailUrlPipe implements PipeTransform, OnDestroy {
this.input$
.pipe(
takeUntil(this.onDestroy$),
switchMap((input) => this.domainCatalogThumbnailService.getThumnaulUrl(input))
switchMap((input) => this.domainCatalogThumbnailService.getThumnaulUrl(input)),
)
.subscribe((result) => {
this.result = result;

View File

@@ -1021,7 +1021,11 @@ export class DomainCheckoutService {
//#region Common
@memorize()
// Fix für Ticket #4619 Versand Artikel im Warenkob -> keine Änderung bei Kundendaten erfassen
// Auskommentiert, da dieser Aufruf oftmals mit gleichen Parametern aufgerufen wird (ohne ausgewählten Kunden nur ein leeres Objekt bei customerFeatures)
// memorize macht keinen deepCompare von Objekten und denkt hier, dass immer der gleiche Return Wert zurückkommt, allerdings ist das hier oft nicht der Fall
// und der Decorator memorized dann fälschlicherweise
// @memorize()
canSetCustomer({
processId,
customerFeatures,

View File

@@ -1,37 +1,60 @@
import { Injectable } from '@angular/core';
import { ActionHandler } from '@core/command';
import { DomainPrinterService } from '@domain/printer';
import { DomainPrinterService, Printer } from '@domain/printer';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { UiModalService } from '@ui/modal';
import { NativeContainerService } from 'native-container';
import { OrderItemsContext } from './order-items.context';
import { EnvironmentService } from '@core/environment';
@Injectable()
export class PrintCompartmentLabelActionHandler extends ActionHandler<OrderItemsContext> {
constructor(
private uiModal: UiModalService,
private domainPrinterService: DomainPrinterService,
private nativeContainerService: NativeContainerService
private nativeContainerService: NativeContainerService,
private _environmentSerivce: EnvironmentService
) {
super('PRINT_COMPARTMENTLABEL');
}
printCompartmentLabelHelper(printer: string, orderItemSubsetIds: number[]) {
return this.domainPrinterService
.printCompartmentLabel({
printer,
orderItemSubsetIds,
})
.toPromise();
}
async handler(data: OrderItemsContext): Promise<OrderItemsContext> {
await this.uiModal
.open({
content: PrintModalComponent,
config: { showScrollbarY: false },
data: {
printImmediately: !this.nativeContainerService.isNative,
printerType: 'Label',
print: (printer) =>
this.domainPrinterService
.printCompartmentLabel({ printer, orderItemSubsetIds: data.items.map((item) => item.orderItemSubsetId) })
.toPromise(),
} as PrintModalData,
})
.afterClosed$.toPromise();
const printerList = await this.domainPrinterService.getAvailableLabelPrinters().toPromise();
let printer: Printer;
if (Array.isArray(printerList)) {
printer = printerList.find((printer) => printer.selected === true);
}
if (!printer || this._environmentSerivce.matchTablet()) {
await this.uiModal
.open({
content: PrintModalComponent,
config: { showScrollbarY: false },
data: {
printImmediately: !this._environmentSerivce.matchTablet(),
printerType: 'Label',
print: (printer) =>
this.printCompartmentLabelHelper(
printer,
data.items.map((item) => item.orderItemSubsetId)
),
} as PrintModalData,
})
.afterClosed$.toPromise();
} else {
await this.printCompartmentLabelHelper(
printer.key,
data.items.map((item) => item.orderItemSubsetId)
);
}
return data;
}
}

View File

@@ -6,45 +6,60 @@ import { UiModalService } from '@ui/modal';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { groupBy } from '@ui/common';
import { NativeContainerService } from 'native-container';
import { ReceiptDTO } from '@swagger/oms';
import { EnvironmentService } from '@core/environment';
@Injectable()
export class PrintShippingNoteActionHandler extends ActionHandler<OrderItemsContext> {
constructor(
private uiModal: UiModalService,
private domainPrinterService: DomainPrinterService,
private nativeContainerService: NativeContainerService
private nativeContainerService: NativeContainerService,
private _environmentSerivce: EnvironmentService
) {
super('PRINT_SHIPPINGNOTE');
}
async printShippingNoteHelper(printer: string, receipts: ReceiptDTO[]) {
try {
for (const group of groupBy(receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
await this.domainPrinterService.printShippingNote({ printer, receipts: group?.items?.map((r) => r?.id) }).toPromise();
}
return {
error: false,
};
} catch (error) {
console.error(error);
return {
error: true,
message: error?.message || error,
};
}
}
async handler(data: OrderItemsContext): Promise<OrderItemsContext> {
await this.uiModal
.open({
content: PrintModalComponent,
config: { showScrollbarY: false },
data: {
printImmediately: !this.nativeContainerService.isNative,
printerType: 'Label',
print: async (printer) => {
try {
const receipts = data?.receipts?.filter((r) => r?.receiptType & 1);
for (const group of groupBy(receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
await this.domainPrinterService.printShippingNote({ printer, receipts: group?.items?.map((r) => r?.id) }).toPromise();
}
return {
error: false,
};
} catch (error) {
console.error(error);
return {
error: true,
message: error?.message || error,
};
}
},
} as PrintModalData,
})
.afterClosed$.toPromise();
const printerList = await this.domainPrinterService.getAvailableLabelPrinters().toPromise();
const receipts = data?.receipts?.filter((r) => r?.receiptType & 1);
let printer: Printer;
if (Array.isArray(printerList)) {
printer = printerList.find((printer) => printer.selected === true);
}
if (!printer || this._environmentSerivce.matchTablet()) {
await this.uiModal
.open({
content: PrintModalComponent,
config: { showScrollbarY: false },
data: {
printImmediately: !this.nativeContainerService.isNative,
printerType: 'Label',
print: async (printer) => await this.printShippingNoteHelper(printer, receipts),
} as PrintModalData,
})
.afterClosed$.toPromise();
} else {
await this.printShippingNoteHelper(printer.key, receipts);
}
return data;
}

View File

@@ -35,7 +35,7 @@ export class PickupShelfOutService extends PickupShelfIOService {
);
}
const { orderdate } = args.filter?.getQueryToken()?.filter ?? {};
const { orderdate, supplier_id } = args.filter?.getQueryToken()?.filter ?? {};
return this._abholfachService.AbholfachWarenausgabe({
input: {
@@ -45,6 +45,7 @@ export class PickupShelfOutService extends PickupShelfIOService {
archive: String(true),
all_branches: String(true),
orderdate,
supplier_id,
},
});
}

View File

@@ -1,4 +1,4 @@
import { inject, isDevMode, NgModule } from '@angular/core';
import { isDevMode, NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {
CanActivateCartGuard,
@@ -8,8 +8,6 @@ import {
CanActivateCustomerOrdersWithProcessIdGuard,
CanActivateCustomerWithProcessIdGuard,
CanActivateGoodsInGuard,
CanActivateGoodsOutGuard,
CanActivateGoodsOutWithProcessIdGuard,
CanActivateProductGuard,
CanActivateProductWithProcessIdGuard,
CanActivateRemissionGuard,
@@ -22,7 +20,6 @@ import { MainComponent } from './main.component';
import { PreviewComponent } from './preview';
import { BranchSectionResolver, CustomerSectionResolver, ProcessIdResolver } from './resolvers';
import { TokenLoginComponent, TokenLoginModule } from './token-login';
import { ApplicationService } from '@core/application';
import { ProcessIdGuard } from './guards/process-id.guard';
import { ActivateProcessIdGuard, ActivateProcessIdWithConfigKeyGuard } from './guards/activate-process-id.guard';

View File

@@ -28,7 +28,7 @@ export const metaReducers: MetaReducer<RootState>[] = !environment.production ?
imports: [
StoreModule.forRoot(rootReducer, { metaReducers }),
EffectsModule.forRoot([]),
StoreDevtoolsModule.instrument({ name: 'ISA Ngrx Application Store' }),
StoreDevtoolsModule.instrument({ name: 'ISA Ngrx Application Store', connectInZone: true }),
],
})
export class AppStoreModule {}

View File

@@ -1 +1,28 @@
@if ($offlineBannerVisible()) {
<div [@fadeInOut] class="bg-brand text-white text-center fixed inset-x-0 top-0 z-tooltip p-4">
<h3 class="font-bold grid grid-flow-col items-center justify-center text-xl gap-4">
<div>
<ng-icon name="matWifiOff"></ng-icon>
</div>
<div>Sie sind offline, keine Verbindung zum Netzwerk.</div>
</h3>
<p>Bereits geladene Ihnalte werden angezeigt, Interaktionen sind aktuell nicht möglich.</p>
</div>
}
@if ($onlineBannerVisible()) {
<div [@fadeInOut] class="bg-green-500 text-white text-center fixed inset-x-0 top-0 z-tooltip p-4">
<h3 class="font-bold grid grid-flow-col items-center justify-center text-xl gap-4">
<div>
<ng-icon name="matWifi"></ng-icon>
</div>
<div>Sie sind wieder online.</div>
</h3>
<button class="fixed top-2 right-4 text-3xl w-12 h-12" type="button" (click)="$onlineBannerVisible.set(false)">
<ng-icon name="matClose"></ng-icon>
</button>
</div>
}
<router-outlet></router-outlet>

View File

@@ -1,30 +1,71 @@
import { DOCUMENT } from '@angular/common';
import { Component, HostListener, Inject, OnInit, Renderer2 } from '@angular/core';
import { Component, effect, HostListener, Inject, OnInit, Renderer2, signal, untracked } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { SwUpdate, UpdateAvailableEvent } from '@angular/service-worker';
import { SwUpdate } from '@angular/service-worker';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { NotificationsHub } from '@hub/notifications';
import packageInfo from 'package';
import { asapScheduler, interval, Observable, Subscription } from 'rxjs';
import { asapScheduler, interval, Subscription } from 'rxjs';
import { UserStateService } from '@swagger/isa';
import { IsaLogProvider } from './providers';
import { EnvironmentService } from '@core/environment';
import { AuthService } from '@core/auth';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { tap } from 'rxjs/operators';
import { injectOnline$ } from './services/network-status.service';
import { toSignal } from '@angular/core/rxjs-interop';
import { animate, style, transition, trigger } from '@angular/animations';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
animations: [
trigger('fadeInOut', [
transition(':enter', [
// :enter wird ausgelöst, wenn das Element zum DOM hinzugefügt wird
style({ opacity: 0, transform: 'translateY(-100%)' }),
animate('300ms', style({ opacity: 1, transform: 'translateY(0)' })),
]),
transition(':leave', [
// :leave wird ausgelöst, wenn das Element aus dem DOM entfernt wird
animate('300ms', style({ opacity: 0, transform: 'translateY(-100%)' })),
]),
]),
],
})
export class AppComponent implements OnInit {
$online = toSignal(injectOnline$());
$offlineBannerVisible = signal(false);
$onlineBannerVisible = signal(false);
private onlineBannerDismissTimeout: any;
onlineEffects = effect(() => {
const online = this.$online();
const offlineBannerVisible = this.$offlineBannerVisible();
untracked(() => {
this.$offlineBannerVisible.set(!online);
if (!online) {
this.$onlineBannerVisible.set(false);
clearTimeout(this.onlineBannerDismissTimeout);
}
if (offlineBannerVisible && online) {
this.$onlineBannerVisible.set(true);
this.onlineBannerDismissTimeout = setTimeout(() => this.$onlineBannerVisible.set(false), 5000);
}
});
});
private _checkForUpdates: number = this._config.get('checkForUpdates');
updateAvailableObs: Observable<UpdateAvailableEvent>;
get checkForUpdates(): number {
return this._checkForUpdates;
return this._checkForUpdates ?? 60 * 60 * 1000; // default 1 hour
}
// For Unit Testing
@@ -45,7 +86,7 @@ export class AppComponent implements OnInit {
private infoService: UserStateService,
private readonly _environment: EnvironmentService,
private readonly _authService: AuthService,
private readonly _modal: UiModalService
private readonly _modal: UiModalService,
) {
this.updateClient();
IsaLogProvider.InfoService = this.infoService;

View File

@@ -37,6 +37,10 @@ import { NativeContainerService } from 'native-container';
import { ShellModule } from '@shared/shell';
import { MainComponent } from './main.component';
import { IconModule } from '@shared/components/icon';
import { NgIconsModule } from '@ng-icons/core';
import { matClose, matWifi, matWifiOff } from '@ng-icons/material-icons/baseline';
import { NetworkStatusService } from './services/network-status.service';
import { firstValueFrom } from 'rxjs';
registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);
@@ -46,26 +50,68 @@ export function _appInitializerFactory(
auth: AuthService,
injector: Injector,
scanAdapter: ScanAdapterService,
nativeContainer: NativeContainerService
nativeContainer: NativeContainerService,
networkStatus: NetworkStatusService,
) {
return async () => {
const statusElement = document.querySelector('#init-status');
statusElement.innerHTML = 'Konfigurationen werden geladen...';
await config.init();
statusElement.innerHTML = 'Authentifizierung wird geprüft...';
await auth.init();
const laoderElement = document.querySelector('#init-loader');
if (auth.isAuthenticated()) {
statusElement.innerHTML = 'App wird initialisiert...';
const state = injector.get(RootStateService);
await state.init();
try {
let online = false;
while (!online) {
online = await firstValueFrom(networkStatus.online$);
if (!online) {
statusElement.innerHTML =
'<b>Warte auf Netzwerkverbindung (WLAN)</b><br><br>Bitte prüfen Sie die Netzwerkverbindung (WLAN).<br>Sobald eine Netzwerkverbindung besteht, wird die App automatisch neu geladen.';
await new Promise((resolve) => setTimeout(resolve, 250));
}
}
statusElement.innerHTML = 'Konfigurationen werden geladen...';
await config.init();
statusElement.innerHTML = 'Authentifizierung wird geprüft...';
await auth.init();
if (auth.isAuthenticated()) {
statusElement.innerHTML = 'App wird initialisiert...';
const state = injector.get(RootStateService);
await state.init();
}
statusElement.innerHTML = 'Native Container wird initialisiert...';
await nativeContainer.init();
statusElement.innerHTML = 'Scanner wird initialisiert...';
await scanAdapter.init();
} catch (error) {
laoderElement.remove();
statusElement.classList.add('text-xl');
statusElement.innerHTML = '<b>Fehler bei der Initialisierung</b><br><br>Bitte prüfen Sie die Netzwerkverbindung (WLAN).<br><br>';
const reload = document.createElement('button');
reload.classList.add('bg-brand', 'text-white', 'p-2', 'rounded', 'cursor-pointer');
reload.innerHTML = 'App neu laden';
reload.onclick = () => window.location.reload();
statusElement.appendChild(reload);
const preLabel = document.createElement('div');
preLabel.classList.add('mt-12');
preLabel.innerHTML = 'Fehlermeldung:';
statusElement.appendChild(preLabel);
const pre = document.createElement('pre');
pre.classList.add('mt-4', 'text-wrap');
pre.innerHTML = error.message;
statusElement.appendChild(pre);
console.error('Error during app initialization', error);
throw error;
}
statusElement.innerHTML = 'Native Container wird initialisiert...';
await nativeContainer.init();
statusElement.innerHTML = 'Scanner wird initialisiert...';
await scanAdapter.init();
};
}
@@ -107,13 +153,14 @@ export function _notificationsHubOptionsFactory(config: Config, auth: AuthServic
ScanditScanAdapterModule.forRoot(),
PlatformModule,
IconModule.forRoot(),
NgIconsModule.withIcons({ matWifiOff, matClose, matWifi }),
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: _appInitializerFactory,
multi: true,
deps: [Config, AuthService, Injector, ScanAdapterService, NativeContainerService],
deps: [Config, AuthService, Injector, ScanAdapterService, NativeContainerService, NetworkStatusService],
},
{
provide: NOTIFICATIONS_HUB_OPTIONS,

View File

@@ -2,30 +2,31 @@ import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { NEVER, Observable, throwError } from 'rxjs';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { catchError, mergeMap, tap } from 'rxjs/operators';
import { catchError, filter, mergeMap, takeUntil, tap } from 'rxjs/operators';
import { AuthService } from '@core/auth';
import { IsaLogProvider } from '../providers';
import { LogLevel } from '@core/logger';
import { injectOnline$ } from '../services/network-status.service';
@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
constructor(private _modal: UiModalService, private _auth: AuthService, private _isaLogProvider: IsaLogProvider) {}
readonly offline$ = injectOnline$().pipe(filter((online) => !online));
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)));
return next.handle(req).pipe(
takeUntil(this.offline$),
catchError((error: HttpErrorResponse, caught: any) => this.handleError(error)),
);
}
handleError(error: HttpErrorResponse): Observable<any> {
if (error.status === 0) {
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) {
console.log('401', error);
if (error.status === 401) {
return this._modal
.open({
content: UiMessageModalComponent,
@@ -36,11 +37,13 @@ export class HttpErrorInterceptor implements HttpInterceptor {
tap(() => {
this._auth.login();
}),
mergeMap(() => NEVER)
mergeMap(() => NEVER),
);
}
this._isaLogProvider.log(LogLevel.ERROR, 'Http Error', error);
if (!error.url.endsWith('/isa/logging')) {
this._isaLogProvider.log(LogLevel.ERROR, 'Http Error', error);
}
return throwError(error);
}

View File

@@ -7,7 +7,11 @@ import { LogLevel } from '@core/logger';
@Injectable({ providedIn: 'root' })
export class IsaErrorHandler implements ErrorHandler {
constructor(private _modal: UiModalService, private _authService: AuthService, private _isaLogProvider: IsaLogProvider) {}
constructor(
private _modal: UiModalService,
private _authService: AuthService,
private _isaLogProvider: IsaLogProvider,
) {}
async handleError(error: any): Promise<void> {
console.error(error);
@@ -37,13 +41,13 @@ export class IsaErrorHandler implements ErrorHandler {
this._isaLogProvider.log(LogLevel.ERROR, 'Client Error', error);
this._modal.open({
content: UiErrorModalComponent,
title:
!navigator.onLine || (error instanceof HttpErrorResponse && error?.status === 0)
? 'Sie sind offline, keine Verbindung zum Netzwerk'
: 'Unbekannter Fehler',
data: error,
});
// this._modal.open({
// content: UiErrorModalComponent,
// title:
// !navigator.onLine || (error instanceof HttpErrorResponse && error?.status === 0)
// ? 'Sie sind offline, keine Verbindung zum Netzwerk'
// : 'Unbekannter Fehler',
// data: error,
// });
}
}

View File

@@ -0,0 +1,25 @@
import { inject, Injectable } from '@angular/core';
import { map, Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class NetworkStatusService {
online$ = new Observable<boolean>((subscriber) => {
const handler = () => subscriber.next(navigator.onLine);
window.addEventListener('online', handler);
window.addEventListener('offline', handler);
handler();
return () => {
window.removeEventListener('online', handler);
window.removeEventListener('offline', handler);
};
});
status$ = this.online$.pipe(map((online) => (online ? 'online' : 'offline')));
}
export const injectNetworkStatus$ = () => inject(NetworkStatusService).status$;
export const injectOnline$ = () => inject(NetworkStatusService).online$;

View File

@@ -2,23 +2,26 @@ import { Injectable } from '@angular/core';
import { Logger, LogLevel } from '@core/logger';
import { Store } from '@ngrx/store';
import { UserStateService } from '@swagger/isa';
import { debounceTime, switchMap } from 'rxjs/operators';
import { debounceTime, switchMap, takeUntil } from 'rxjs/operators';
import { RootState } from './root.state';
import packageInfo from 'package';
import { environment } from '../../environments/environment';
import { Subject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class RootStateService {
static LOCAL_STORAGE_KEY = 'ISA_APP_INITIALSTATE';
private _cancelSave = new Subject<void>();
constructor(private readonly _userStateService: UserStateService, private _logger: Logger, private _store: Store) {
if (!environment.production) {
console.log('Die UserState kann in der Konsole mit der Funktion "clearUserState()" geleert werden.');
window['clearUserState'] = () => {
this.clear();
};
}
window['clearUserState'] = () => {
this.clear();
};
}
async init() {
@@ -31,7 +34,8 @@ export class RootStateService {
this._store
.select((state) => state)
.pipe(
debounceTime(500),
takeUntil(this._cancelSave),
debounceTime(1000),
switchMap((state) => {
const raw = JSON.stringify({ ...state, version: packageInfo.version });
RootStateService.SaveToLocalStorageRaw(raw);
@@ -64,13 +68,17 @@ export class RootStateService {
return false;
}
clear() {
this._userStateService
.UserStateResetUserState()
.toPromise()
.catch((error) => this._logger.log(LogLevel.ERROR, error));
RootStateService.RemoveFromLocalStorage();
window.location.reload();
async clear() {
try {
this._cancelSave.next();
await this._userStateService.UserStateResetUserState().toPromise();
await new Promise((resolve) => setTimeout(resolve, 100));
RootStateService.RemoveFromLocalStorage();
await new Promise((resolve) => setTimeout(resolve, 100));
window.location.reload();
} catch (error) {
this._logger.log(LogLevel.ERROR, error);
}
}
static SaveToLocalStorage(state: RootState) {

View File

@@ -280,6 +280,11 @@
"name": "apps",
"data": "M226-160q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19ZM226-414q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19ZM226-668q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Zm254 0q-28 0-47-19t-19-47q0-28 19-47t47-19q28 0 47 19t19 47q0 28-19 47t-47 19Z",
"viewBox": "0 -960 960 960"
},
{
"name": "gift",
"data": "M2 21V10H0V4H5.2C5.11667 3.85 5.0625 3.69167 5.0375 3.525C5.0125 3.35833 5 3.18333 5 3C5 2.16667 5.29167 1.45833 5.875 0.875C6.45833 0.291667 7.16667 0 8 0C8.38333 0 8.74167 0.0708333 9.075 0.2125C9.40833 0.354167 9.71667 0.55 10 0.8C10.2833 0.533333 10.5917 0.333333 10.925 0.2C11.2583 0.0666667 11.6167 0 12 0C12.8333 0 13.5417 0.291667 14.125 0.875C14.7083 1.45833 15 2.16667 15 3C15 3.18333 14.9833 3.35417 14.95 3.5125C14.9167 3.67083 14.8667 3.83333 14.8 4H20V10H18V21H2ZM12 2C11.7167 2 11.4792 2.09583 11.2875 2.2875C11.0958 2.47917 11 2.71667 11 3C11 3.28333 11.0958 3.52083 11.2875 3.7125C11.4792 3.90417 11.7167 4 12 4C12.2833 4 12.5208 3.90417 12.7125 3.7125C12.9042 3.52083 13 3.28333 13 3C13 2.71667 12.9042 2.47917 12.7125 2.2875C12.5208 2.09583 12.2833 2 12 2ZM7 3C7 3.28333 7.09583 3.52083 7.2875 3.7125C7.47917 3.90417 7.71667 4 8 4C8.28333 4 8.52083 3.90417 8.7125 3.7125C8.90417 3.52083 9 3.28333 9 3C9 2.71667 8.90417 2.47917 8.7125 2.2875C8.52083 2.09583 8.28333 2 8 2C7.71667 2 7.47917 2.09583 7.2875 2.2875C7.09583 2.47917 7 2.71667 7 3ZM2 6V8H9V6H2ZM9 19V10H4V19H9ZM11 19H16V10H11V19ZM18 8V6H11V8H18Z",
"viewBox": "0 0 20 21"
}
],

View File

@@ -0,0 +1,5 @@
<svg width="48" height="51" viewBox="0 0 48 51" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 4.47368C8 2.01878 9.99009 0 12.445 0L43.555 0C46.0099 0 48 2.01878 48 4.47368H8Z" fill="#172062"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 4.445C0 1.99009 1.99009 0 4.445 0L42.7807 0V43.4808C42.7807 46.7878 39.2981 48.9368 36.3423 47.4537L23.376 40.948C22.1212 40.3183 20.6426 40.3186 19.3879 40.9486L6.4397 47.4505C3.48377 48.9348 0 46.7859 0 43.4782L0 4.445Z" fill="#0556B4"/>
<rect x="19" y="19" width="18" height="17" fill="#0556B4"/>
</svg>

After

Width:  |  Height:  |  Size: 606 B

View File

@@ -73,7 +73,13 @@
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
"scandit": "Ac2kvx5ZOzjvFl/LuAd6wds3C30YJ4g8Cm6PX4sgUnKPePVMuH+rFQIyVNn1YdS3myORojEOBsIZWhMw2nRUGBtOnQ5FO+cRHgQu0pkP+VG6OYvt8ETUTn8Aa2f9bmfqclO3LI8WN8psWr+adkZEtqNTvCgyDLZaICh8S7RfmwJVVWaOPX4LDagGhLDPS2YQdg+ibpR0l0ZlX2h/3GttofE64HOlBN3QtDB8yihHJNgVcUVy2UQVS+BXOyvIfZEFbFgPYVt5HZ0aQgcISlYVAmJvRsHyeKYRUnIi3ZN73EQmHzWcOV4/HWVoTs1MTW0mxV074vNwqExYW9LYmz9zgLUD7FMnKMFClkzRBHRN1CC7brosCnFkZWQp3CV4Ua48Fn9GW0Zpn/4MIZgzSU26inl5ZnT3dCc2+3BH3Us1uugTUrOPwFuwsPpr4NZYXWJMOmcm4kBBVJd0Uwk28GyZM7x1hXADcQgcc23+gDtbbUZWVmr3TE7GBcV3j+XUeOk8nHZw3DsYJ46MT2sSfks3QXRl4tBkBad6M+UxW4tb2IQC/4K9IXKhDN8VYXpzrrRHJNvOiH5+NrixthDZHHd/MGNuBDrJFOuXq8L7O6PxBoVZ0NPXmCO5vKyYdhBz5gJ5u3vNkSke7p+cDcvFicsVyNWRaaoFL0UN9gxFVMY8hkJKBZyGxZL2LEDXD44PxPsDnygpDC1Jyc7pkhEA0fKSc9aXZb1HDpwe1hqyyRVBtHkgPiN78GmJImbaUlj8XsK3yVyAjs2XvPR0/3ASHS53ViwzWKL3Oi8I515IJDtDhlvrkeh7MrWMaEu9k6ZQXJi1uJsh3JGfyS+yDlPjePVdsVqnZ4uw9pKMXvjlJeNcIiGM2Cf5S89nk2Qe/56MrKB9Frm6Q5wQHai8TNdCZYBi67dmAX8KHPXvEU4K6KyyW89YGcabZQ3eOJDr4oqW9ZcYPBttREdH3WI/HxvpEq6bqoDhT9AxpIWEMVb6y/DcDHSEresepibug4qOr9xOPq0yk2uiWYhPubFnCk7thQCXOGv9crWnQoOrt9c1qoaWXM4YmKqfcaQ67Tn+uFQmYTZyqw4jlJU4GgKe5/GNCVQM5aNUg1J0Px1NlFCS+rrIDyMQp0byFcgTd/E9sA5d1+YZKHKmJiQwEAz6oU9yyoUlxntSI42GHB/UttPc7Hj14V5+oJ+Yz+CZodmkXFg57Vx4NuxveNtO"
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"

View File

@@ -72,7 +72,13 @@
},
"checkForUpdates": 900000,
"licence": {
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
"scandit": "Ac2kvx5ZOzjvFl/LuAd6wds3C30YJ4g8Cm6PX4sgUnKPePVMuH+rFQIyVNn1YdS3myORojEOBsIZWhMw2nRUGBtOnQ5FO+cRHgQu0pkP+VG6OYvt8ETUTn8Aa2f9bmfqclO3LI8WN8psWr+adkZEtqNTvCgyDLZaICh8S7RfmwJVVWaOPX4LDagGhLDPS2YQdg+ibpR0l0ZlX2h/3GttofE64HOlBN3QtDB8yihHJNgVcUVy2UQVS+BXOyvIfZEFbFgPYVt5HZ0aQgcISlYVAmJvRsHyeKYRUnIi3ZN73EQmHzWcOV4/HWVoTs1MTW0mxV074vNwqExYW9LYmz9zgLUD7FMnKMFClkzRBHRN1CC7brosCnFkZWQp3CV4Ua48Fn9GW0Zpn/4MIZgzSU26inl5ZnT3dCc2+3BH3Us1uugTUrOPwFuwsPpr4NZYXWJMOmcm4kBBVJd0Uwk28GyZM7x1hXADcQgcc23+gDtbbUZWVmr3TE7GBcV3j+XUeOk8nHZw3DsYJ46MT2sSfks3QXRl4tBkBad6M+UxW4tb2IQC/4K9IXKhDN8VYXpzrrRHJNvOiH5+NrixthDZHHd/MGNuBDrJFOuXq8L7O6PxBoVZ0NPXmCO5vKyYdhBz5gJ5u3vNkSke7p+cDcvFicsVyNWRaaoFL0UN9gxFVMY8hkJKBZyGxZL2LEDXD44PxPsDnygpDC1Jyc7pkhEA0fKSc9aXZb1HDpwe1hqyyRVBtHkgPiN78GmJImbaUlj8XsK3yVyAjs2XvPR0/3ASHS53ViwzWKL3Oi8I515IJDtDhlvrkeh7MrWMaEu9k6ZQXJi1uJsh3JGfyS+yDlPjePVdsVqnZ4uw9pKMXvjlJeNcIiGM2Cf5S89nk2Qe/56MrKB9Frm6Q5wQHai8TNdCZYBi67dmAX8KHPXvEU4K6KyyW89YGcabZQ3eOJDr4oqW9ZcYPBttREdH3WI/HxvpEq6bqoDhT9AxpIWEMVb6y/DcDHSEresepibug4qOr9xOPq0yk2uiWYhPubFnCk7thQCXOGv9crWnQoOrt9c1qoaWXM4YmKqfcaQ67Tn+uFQmYTZyqw4jlJU4GgKe5/GNCVQM5aNUg1J0Px1NlFCS+rrIDyMQp0byFcgTd/E9sA5d1+YZKHKmJiQwEAz6oU9yyoUlxntSI42GHB/UttPc7Hj14V5+oJ+Yz+CZodmkXFg57Vx4NuxveNtO"
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -74,7 +74,13 @@
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
"scandit": "Ac2kvx5ZOzjvFl/LuAd6wds3C30YJ4g8Cm6PX4sgUnKPePVMuH+rFQIyVNn1YdS3myORojEOBsIZWhMw2nRUGBtOnQ5FO+cRHgQu0pkP+VG6OYvt8ETUTn8Aa2f9bmfqclO3LI8WN8psWr+adkZEtqNTvCgyDLZaICh8S7RfmwJVVWaOPX4LDagGhLDPS2YQdg+ibpR0l0ZlX2h/3GttofE64HOlBN3QtDB8yihHJNgVcUVy2UQVS+BXOyvIfZEFbFgPYVt5HZ0aQgcISlYVAmJvRsHyeKYRUnIi3ZN73EQmHzWcOV4/HWVoTs1MTW0mxV074vNwqExYW9LYmz9zgLUD7FMnKMFClkzRBHRN1CC7brosCnFkZWQp3CV4Ua48Fn9GW0Zpn/4MIZgzSU26inl5ZnT3dCc2+3BH3Us1uugTUrOPwFuwsPpr4NZYXWJMOmcm4kBBVJd0Uwk28GyZM7x1hXADcQgcc23+gDtbbUZWVmr3TE7GBcV3j+XUeOk8nHZw3DsYJ46MT2sSfks3QXRl4tBkBad6M+UxW4tb2IQC/4K9IXKhDN8VYXpzrrRHJNvOiH5+NrixthDZHHd/MGNuBDrJFOuXq8L7O6PxBoVZ0NPXmCO5vKyYdhBz5gJ5u3vNkSke7p+cDcvFicsVyNWRaaoFL0UN9gxFVMY8hkJKBZyGxZL2LEDXD44PxPsDnygpDC1Jyc7pkhEA0fKSc9aXZb1HDpwe1hqyyRVBtHkgPiN78GmJImbaUlj8XsK3yVyAjs2XvPR0/3ASHS53ViwzWKL3Oi8I515IJDtDhlvrkeh7MrWMaEu9k6ZQXJi1uJsh3JGfyS+yDlPjePVdsVqnZ4uw9pKMXvjlJeNcIiGM2Cf5S89nk2Qe/56MrKB9Frm6Q5wQHai8TNdCZYBi67dmAX8KHPXvEU4K6KyyW89YGcabZQ3eOJDr4oqW9ZcYPBttREdH3WI/HxvpEq6bqoDhT9AxpIWEMVb6y/DcDHSEresepibug4qOr9xOPq0yk2uiWYhPubFnCk7thQCXOGv9crWnQoOrt9c1qoaWXM4YmKqfcaQ67Tn+uFQmYTZyqw4jlJU4GgKe5/GNCVQM5aNUg1J0Px1NlFCS+rrIDyMQp0byFcgTd/E9sA5d1+YZKHKmJiQwEAz6oU9yyoUlxntSI42GHB/UttPc7Hj14V5+oJ+Yz+CZodmkXFg57Vx4NuxveNtO"
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -73,7 +73,13 @@
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "AVljxT/dG+TAIDDL2jTxm843juR2OtZ6lHLxRpYR7x9uYiSvY2IAHdRx8tjsf9KU7wK0F5cAeb/nLMHF6Vor9ps79wvuBQw6G3N0IW978b78ZUgPOFzxHUAMuD8dbkDZlX8r9y1cOd9sT3UNEwGrQ4siUt2oCkigyTxJAgYs1ijnjQid7q42hHk3tMXywrAYeu5MhF0TV1H77DRDMxPHD/xiR0zhFQRB2Dtnm1+e3LHKCyQjZ/zknEpQB6HS7UbCBoEDj4tohb83E6oqmQFWwt85/Jk9f49gxXakIcNODnQI5H63kSqpEmV9Al1a5L+WGZ6Bq1gwBbnD8FBXlVqxoooiFXW7jzzBa9LNmQiQ5J8yEkIsPeyOHec7F4ERvVONSMYwWyH39ZweSiRsZRM1UsFPhN96bCT5MEwkjPFn4gji6TPGEceJZvV3HwsiCT5Bgjla4bvDsZ2jYvAr9tSij8kIii9dHvsWlrimt+szHJLSz+8uNI6jAvXyr2f3oRxZD/F9osZHVWkgtAc+vVWqkxVJCqmpmoHOXI6TFSqSjYHddhZyU5r2lgQt0+NI6k/bV3iN7Le1RJCP/wuSDCTZjzsU1igB7UnIN2Y70CqCjIeVH9qlxaI1YAC9lwFv1FZvsiueYeJP1n39mmXCSELVtzxgIBEX5yaIHNbbGXd+e8JUgcO8vJ2JA2kJudaU+xfYR5SY//+J1kPsNSbnBnM25LL+LjeRB3QTfqV5sFq8ORWcIMITvkEaRfP3PVcOzb+hO4Ren4ezhJuyADulmvG8a9Kxxk6ymzBbE7a93SGVbxp7OQNEmvTn5+B9wJ7/l1mtvZL2TilrDZBQVMYWrGuUGpA="
"scandit": "AZ70hgtZLmFWHbYP+BDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YfzKaR2xbfjsImjXJN7C/TE1CYXsv3DeqIzgDRA/kII4LzqoICWm9mnwejER1kMu1Vf+1NWbwUuYJjsQVQXYkwkWgnv53obevBZtjpgZqIzlStufIU+sPNEWSd9BlrLmiSEu6nXC5lbstyPJVaqihsFoBPNJ0Q+IgJUjqaxhTf6hGtNB3Rqcpv0ZT349NdK9mQ+9lAQYCwXdmYbELdlbZVSja0aJQlkf0TaAjixDbNcR32/VMIw98F2YxVRVWPnADUaJ9Bn8oa7NkOJIUfB7VAg1KtUl8IIwSXvRk436UtPZGxg+PQM7UXX/TgiBuqCQWdUChoH/QAoY2fyKRKgXJHnGdt+pBRQ2OYs7TZWCJLZsLKgnHV+eWh3Y3EB1hN9cmcszdnmYiyD1gCY8pSETFbWV01nVPvIuUTc+jMipv3NN81VQDS5/eU1VJNpRuDiufdBKJv0jjHQk+hTdqe+2GSmMD24ZG8FQXJZZPBCodWR1A81T8toCaIAGHnOfZeO7nxZu9Uo+6ohrVxc5F4szSizra+M3zfuFnm5FGgxCKt5uibLYgRyNcmR8/9vqTfMZgalTklbKHEFw1i+xOwrFrqRMh7FoovDRoI3QuaHLpV+ZUpZo3zTOQofdXPthKAmSLc11cjn509oTnnXUBBoBn/hErrwC/v8dZep1YEKM3wjfuIFsHRrsECswqNjwN/yOZCirm2VvFJEPMuKIwy+8jdNGncm8v4R9Br3c+wYJ8696L7Dg0iFShoyoP4OqIvBNY+dYyy935yGkIF2KI6l01xQZcWoHSVKPd6/78Iwy4lTKhtOqtu7ETJqOE53gQpR9jaAY0RiBB1SLm+Jbwt3ipSJiCDgKmkVmk5AU9HC0XYR/erjg13HF4hIcpLPW1ZWMKSxTqZ8z8FMJilInBgfcBwvjNE5seWvdFKltvlItnGhSh7BLUZ5UInDhl00NJBE2PdstDvRvQjLKvYUoFly3jONVsUfuQpzrcjT0g6gEgL8ZtlmaF11owcCvqhNDxWuTAJTX/xXf6WehxEB0qy3xCdxahbuneS3DPI0z2kAQwrx19i5r+RKQ8bWDaQ/OGY47sLPJgDgijEz16CvAMBSN0PvOso6FstsT/ynR3LLTPcY0QDw21Dv3wNHNa9HbanJcb3/MuhZGlrKp7"
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -73,7 +73,13 @@
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "AVljxT/dG+TAIDDL2jTxm843juR2OtZ6lHLxRpYR7x9uYiSvY2IAHdRx8tjsf9KU7wK0F5cAeb/nLMHF6Vor9ps79wvuBQw6G3N0IW978b78ZUgPOFzxHUAMuD8dbkDZlX8r9y1cOd9sT3UNEwGrQ4siUt2oCkigyTxJAgYs1ijnjQid7q42hHk3tMXywrAYeu5MhF0TV1H77DRDMxPHD/xiR0zhFQRB2Dtnm1+e3LHKCyQjZ/zknEpQB6HS7UbCBoEDj4tohb83E6oqmQFWwt85/Jk9f49gxXakIcNODnQI5H63kSqpEmV9Al1a5L+WGZ6Bq1gwBbnD8FBXlVqxoooiFXW7jzzBa9LNmQiQ5J8yEkIsPeyOHec7F4ERvVONSMYwWyH39ZweSiRsZRM1UsFPhN96bCT5MEwkjPFn4gji6TPGEceJZvV3HwsiCT5Bgjla4bvDsZ2jYvAr9tSij8kIii9dHvsWlrimt+szHJLSz+8uNI6jAvXyr2f3oRxZD/F9osZHVWkgtAc+vVWqkxVJCqmpmoHOXI6TFSqSjYHddhZyU5r2lgQt0+NI6k/bV3iN7Le1RJCP/wuSDCTZjzsU1igB7UnIN2Y70CqCjIeVH9qlxaI1YAC9lwFv1FZvsiueYeJP1n39mmXCSELVtzxgIBEX5yaIHNbbGXd+e8JUgcO8vJ2JA2kJudaU+xfYR5SY//+J1kPsNSbnBnM25LL+LjeRB3QTfqV5sFq8ORWcIMITvkEaRfP3PVcOzb+hO4Ren4ezhJuyADulmvG8a9Kxxk6ymzBbE7a93SGVbxp7OQNEmvTn5+B9wJ7/l1mtvZL2TilrDZBQVMYWrGuUGpA="
"scandit": "AZ70hgtZLmFWHbYP+BDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YfzKaR2xbfjsImjXJN7C/TE1CYXsv3DeqIzgDRA/kII4LzqoICWm9mnwejER1kMu1Vf+1NWbwUuYJjsQVQXYkwkWgnv53obevBZtjpgZqIzlStufIU+sPNEWSd9BlrLmiSEu6nXC5lbstyPJVaqihsFoBPNJ0Q+IgJUjqaxhTf6hGtNB3Rqcpv0ZT349NdK9mQ+9lAQYCwXdmYbELdlbZVSja0aJQlkf0TaAjixDbNcR32/VMIw98F2YxVRVWPnADUaJ9Bn8oa7NkOJIUfB7VAg1KtUl8IIwSXvRk436UtPZGxg+PQM7UXX/TgiBuqCQWdUChoH/QAoY2fyKRKgXJHnGdt+pBRQ2OYs7TZWCJLZsLKgnHV+eWh3Y3EB1hN9cmcszdnmYiyD1gCY8pSETFbWV01nVPvIuUTc+jMipv3NN81VQDS5/eU1VJNpRuDiufdBKJv0jjHQk+hTdqe+2GSmMD24ZG8FQXJZZPBCodWR1A81T8toCaIAGHnOfZeO7nxZu9Uo+6ohrVxc5F4szSizra+M3zfuFnm5FGgxCKt5uibLYgRyNcmR8/9vqTfMZgalTklbKHEFw1i+xOwrFrqRMh7FoovDRoI3QuaHLpV+ZUpZo3zTOQofdXPthKAmSLc11cjn509oTnnXUBBoBn/hErrwC/v8dZep1YEKM3wjfuIFsHRrsECswqNjwN/yOZCirm2VvFJEPMuKIwy+8jdNGncm8v4R9Br3c+wYJ8696L7Dg0iFShoyoP4OqIvBNY+dYyy935yGkIF2KI6l01xQZcWoHSVKPd6/78Iwy4lTKhtOqtu7ETJqOE53gQpR9jaAY0RiBB1SLm+Jbwt3ipSJiCDgKmkVmk5AU9HC0XYR/erjg13HF4hIcpLPW1ZWMKSxTqZ8z8FMJilInBgfcBwvjNE5seWvdFKltvlItnGhSh7BLUZ5UInDhl00NJBE2PdstDvRvQjLKvYUoFly3jONVsUfuQpzrcjT0g6gEgL8ZtlmaF11owcCvqhNDxWuTAJTX/xXf6WehxEB0qy3xCdxahbuneS3DPI0z2kAQwrx19i5r+RKQ8bWDaQ/OGY47sLPJgDgijEz16CvAMBSN0PvOso6FstsT/ynR3LLTPcY0QDw21Dv3wNHNa9HbanJcb3/MuhZGlrKp7"
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -74,7 +74,13 @@
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
"scandit": "Ac2kvx5ZOzjvFl/LuAd6wds3C30YJ4g8Cm6PX4sgUnKPePVMuH+rFQIyVNn1YdS3myORojEOBsIZWhMw2nRUGBtOnQ5FO+cRHgQu0pkP+VG6OYvt8ETUTn8Aa2f9bmfqclO3LI8WN8psWr+adkZEtqNTvCgyDLZaICh8S7RfmwJVVWaOPX4LDagGhLDPS2YQdg+ibpR0l0ZlX2h/3GttofE64HOlBN3QtDB8yihHJNgVcUVy2UQVS+BXOyvIfZEFbFgPYVt5HZ0aQgcISlYVAmJvRsHyeKYRUnIi3ZN73EQmHzWcOV4/HWVoTs1MTW0mxV074vNwqExYW9LYmz9zgLUD7FMnKMFClkzRBHRN1CC7brosCnFkZWQp3CV4Ua48Fn9GW0Zpn/4MIZgzSU26inl5ZnT3dCc2+3BH3Us1uugTUrOPwFuwsPpr4NZYXWJMOmcm4kBBVJd0Uwk28GyZM7x1hXADcQgcc23+gDtbbUZWVmr3TE7GBcV3j+XUeOk8nHZw3DsYJ46MT2sSfks3QXRl4tBkBad6M+UxW4tb2IQC/4K9IXKhDN8VYXpzrrRHJNvOiH5+NrixthDZHHd/MGNuBDrJFOuXq8L7O6PxBoVZ0NPXmCO5vKyYdhBz5gJ5u3vNkSke7p+cDcvFicsVyNWRaaoFL0UN9gxFVMY8hkJKBZyGxZL2LEDXD44PxPsDnygpDC1Jyc7pkhEA0fKSc9aXZb1HDpwe1hqyyRVBtHkgPiN78GmJImbaUlj8XsK3yVyAjs2XvPR0/3ASHS53ViwzWKL3Oi8I515IJDtDhlvrkeh7MrWMaEu9k6ZQXJi1uJsh3JGfyS+yDlPjePVdsVqnZ4uw9pKMXvjlJeNcIiGM2Cf5S89nk2Qe/56MrKB9Frm6Q5wQHai8TNdCZYBi67dmAX8KHPXvEU4K6KyyW89YGcabZQ3eOJDr4oqW9ZcYPBttREdH3WI/HxvpEq6bqoDhT9AxpIWEMVb6y/DcDHSEresepibug4qOr9xOPq0yk2uiWYhPubFnCk7thQCXOGv9crWnQoOrt9c1qoaWXM4YmKqfcaQ67Tn+uFQmYTZyqw4jlJU4GgKe5/GNCVQM5aNUg1J0Px1NlFCS+rrIDyMQp0byFcgTd/E9sA5d1+YZKHKmJiQwEAz6oU9yyoUlxntSI42GHB/UttPc7Hj14V5+oJ+Yz+CZodmkXFg57Vx4NuxveNtO"
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
@@ -15,7 +15,7 @@
<app-root>
<div class="grid place-items-center h-screen">
<div class="grid grid-flow-row gap-4 items-center justify-center">
<div class="flex flex-col items-center">
<div id="init-loader" class="flex flex-col items-center">
<img class="animate-spin" src="/assets/images/spinner.svg" alt="spinner animation" />
</div>
<div id="init-status" class="text-center">App wird geladen</div>

View File

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

View File

@@ -1,25 +1,12 @@
import { Injectable } from '@angular/core';
import {
Observable,
of,
BehaviorSubject,
Subject,
merge,
combineLatest,
} from 'rxjs';
import { Observable, of, BehaviorSubject, Subject, merge, combineLatest } from 'rxjs';
import { delay, distinctUntilChanged, map } from 'rxjs/operators';
import { createRandomStringId, createRandomId } from './util';
import {
product,
removeSourcePriceAndProductFromProduct,
} from './data/product';
import { product, removeSourcePriceAndProductFromProduct } from './data/product';
import { RemissionService } from '../services/remission.service';
import { RemissionProcess } from '../models/remission-process';
import { RemissionProduct } from '../models/remission-product';
import {
remissionProducts,
createRemissionProduct,
} from './data/remission-products';
import { remissionProducts, createRemissionProduct } from './data/remission-products';
import { remissionProcessTemplateMock } from './data/remission-process-template';
import { ShippingDocument } from '../models/shipping-document';
import { shippingDocument } from './data/shipping-document';
@@ -41,33 +28,21 @@ import { ActionResult, CapacityType } from '../models';
@Injectable({ providedIn: 'root' })
export class MockRemissionService extends RemissionService {
private delayInMs = 100;
private remissionSubjectStore = new Map<
number,
BehaviorSubject<RemissionProcess>
>();
private remissionSubjectStore = new Map<number, BehaviorSubject<RemissionProcess>>();
private remissionSubjectIdRef = new Map<number, number>();
private reloadProductsSubject = new Subject();
private productSubject = new BehaviorSubject<RemissionProduct[]>(
remissionProducts
);
private reloadProductsSubject = new Subject<void>();
private productSubject = new BehaviorSubject<RemissionProduct[]>(remissionProducts);
private getRemissionFromStore = (
remissionProcessId: number
): BehaviorSubject<RemissionProcess> => {
private getRemissionFromStore = (remissionProcessId: number): BehaviorSubject<RemissionProcess> => {
const remission =
this.remissionSubjectStore.get(remissionProcessId) ||
this.remissionSubjectStore.get(
this.remissionSubjectIdRef.get(remissionProcessId)
);
this.remissionSubjectStore.get(this.remissionSubjectIdRef.get(remissionProcessId));
return remission;
};
private saveRemissionInStore = (
remissionProcessId: number,
remissionSubject: BehaviorSubject<RemissionProcess>
) => {
private saveRemissionInStore = (remissionProcessId: number, remissionSubject: BehaviorSubject<RemissionProcess>) => {
this.remissionSubjectStore.set(remissionProcessId, remissionSubject);
};
@@ -80,27 +55,20 @@ export class MockRemissionService extends RemissionService {
return processSubject.pipe(distinctUntilChanged(), delay(this.delayInMs));
}
getRemissionProducts(params: {
remissionProcessId: number;
}): Observable<{
getRemissionProducts(params: { remissionProcessId: number }): Observable<{
skip?: number;
take?: number;
hits?: number;
items: RemissionProduct[];
completed: boolean;
}> {
const processSubject = this.getRemissionFromStore(
params.remissionProcessId
);
const processSubject = this.getRemissionFromStore(params.remissionProcessId);
if (!processSubject) {
throw new Error('Prozess nicht gefunden');
}
const process$ = merge(
processSubject,
this.reloadProductsSubject.pipe(map((_) => processSubject.value))
);
const process$ = merge(processSubject, this.reloadProductsSubject.pipe(map((_) => processSubject.value)));
const products$ = this.productSubject.asObservable();
@@ -126,24 +94,19 @@ export class MockRemissionService extends RemissionService {
const productGroupFilter = filter.filter['421228ce'] || [];
if (productGroupFilter.length > 0) {
result.items = result.items.filter((product) =>
productGroupFilter.some((s) => product.productGroup === s)
);
result.items = result.items.filter((product) => productGroupFilter.some((s) => product.productGroup === s));
}
const actionFilter = filter.filter['ce9ee0be'] || [];
result.hits = result.items.length;
result.items = result.items.slice(
result.skip,
result.skip + result.take
);
result.items = result.items.slice(result.skip, result.skip + result.take);
result.completed = result.skip + result.take >= result.hits;
return result;
})
}),
);
}
@@ -153,9 +116,7 @@ export class MockRemissionService extends RemissionService {
createShippingDocument?: boolean;
};
}): Observable<RemissionProcess> {
const processSubject = this.remissionSubjectStore.get(
params.remissionProcessId
);
const processSubject = this.remissionSubjectStore.get(params.remissionProcessId);
if (!processSubject) {
throw new Error('Prozess nicht gefunden');
@@ -214,12 +175,8 @@ export class MockRemissionService extends RemissionService {
}
}
completeRemission(params: {
remissionProcessId: number;
}): Observable<boolean> {
let processSubject = this.remissionSubjectStore.get(
params.remissionProcessId
);
completeRemission(params: { remissionProcessId: number }): Observable<boolean> {
let processSubject = this.remissionSubjectStore.get(params.remissionProcessId);
if (!processSubject) {
const refId = this.remissionSubjectIdRef.get(params.remissionProcessId);
@@ -236,9 +193,7 @@ export class MockRemissionService extends RemissionService {
}
}
getRemission(params: {
remissionProcessId: number;
}): Observable<RemissionProcess> {
getRemission(params: { remissionProcessId: number }): Observable<RemissionProcess> {
const remission = this.getRemissionFromStore(params.remissionProcessId);
if (!remission) {
@@ -248,11 +203,7 @@ export class MockRemissionService extends RemissionService {
return remission.pipe(distinctUntilChanged(), delay(this.delayInMs));
}
isPrintingRequired({
remissionProcessId,
}: {
remissionProcessId: number;
}): Observable<boolean> {
isPrintingRequired({ remissionProcessId }: { remissionProcessId: number }): Observable<boolean> {
const remission = this.getRemissionFromStore(remissionProcessId);
if (!remission) {
@@ -272,10 +223,7 @@ export class MockRemissionService extends RemissionService {
return of(printers).pipe(delay(this.delayInMs));
}
printRemissionList(params: {
remissionProcessId: number;
printerKey: string;
}): Observable<boolean> {
printRemissionList(params: { remissionProcessId: number; printerKey: string }): Observable<boolean> {
const remission = this.getRemissionFromStore(params.remissionProcessId);
if (!remission) {
@@ -285,11 +233,7 @@ export class MockRemissionService extends RemissionService {
return of(true).pipe(delay(this.delayInMs));
}
printShippingDocument(params: {
remissionProcessId: number;
shippingDocumentId?: number;
printerKey: string;
}): Observable<boolean> {
printShippingDocument(params: { remissionProcessId: number; shippingDocumentId?: number; printerKey: string }): Observable<boolean> {
const remission = this.getRemissionFromStore(params.remissionProcessId);
if (!remission || !params.printerKey) {
@@ -307,8 +251,7 @@ export class MockRemissionService extends RemissionService {
getFilters(params: { remissionProcessId: number }): Observable<Filter[]> {
const remission = this.getRemissionFromStore(params.remissionProcessId);
const filterForGivenSourceType =
remission.value.filter.source === 'zentral' ? filter : filterUeberlauf;
const filterForGivenSourceType = remission.value.filter.source === 'zentral' ? filter : filterUeberlauf;
return of(filterForGivenSourceType).pipe(delay(this.delayInMs));
}
@@ -327,38 +270,24 @@ export class MockRemissionService extends RemissionService {
return of(remissionSources).pipe(delay(this.delayInMs));
}
getShippingDocuments(params: {
remissionProcessId: number;
shippingDocumentId: number;
}): Observable<ShippingDocument> {
getShippingDocuments(params: { remissionProcessId: number; shippingDocumentId: number }): Observable<ShippingDocument> {
const remissionSub = this.getRemissionFromStore(params.remissionProcessId);
return remissionSub.pipe(
map((remission) =>
remission.shippingDocuments.find(
(shippingDoc) => shippingDoc.id === params.shippingDocumentId
)
),
map((remission) => remission.shippingDocuments.find((shippingDoc) => shippingDoc.id === params.shippingDocumentId)),
distinctUntilChanged(),
delay(this.delayInMs)
delay(this.delayInMs),
);
}
deleteProductFromRemissionList(input: {
remissionProcessId: number;
remissionProductId: number;
}): Observable<ActionResult<boolean>> {
deleteProductFromRemissionList(input: { remissionProcessId: number; remissionProductId: number }): Observable<ActionResult<boolean>> {
return of({
error: false,
result: true,
});
}
addProductToRemit(input: {
product: Product;
remissionReason: string;
remissionQuantity: number;
}): Observable<RemissionProduct> {
addProductToRemit(input: { product: Product; remissionReason: string; remissionQuantity: number }): Observable<RemissionProduct> {
const productToAdd = removeSourcePriceAndProductFromProduct({ ...product });
const newProduct = createRemissionProduct({
@@ -382,9 +311,7 @@ export class MockRemissionService extends RemissionService {
quantity?: number;
inStock?: number;
}): Observable<ActionResult<boolean>> {
const processSubject = this.getRemissionFromStore(
params.remissionProcessId
);
const processSubject = this.getRemissionFromStore(params.remissionProcessId);
const process = processSubject.value;
const products = this.productSubject.value;
@@ -392,22 +319,15 @@ export class MockRemissionService extends RemissionService {
throw new Error('Prozess nicht gefunden');
}
let productToAdd = products.find(
(product) => product.id === params.remissionProductId
);
let newProducts = products.filter(
(product) => product.id !== params.remissionProductId
);
let productToAdd = products.find((product) => product.id === params.remissionProductId);
let newProducts = products.filter((product) => product.id !== params.remissionProductId);
if (!!params.placementType || !!params.quantity) {
const productToUpdateInProductList = {
...productToAdd,
remissionQuantity: productToAdd.remissionQuantity - params.quantity,
};
newProducts =
productToAdd.remissionQuantity - params.quantity < 1
? newProducts
: [...newProducts, productToUpdateInProductList];
newProducts = productToAdd.remissionQuantity - params.quantity < 1 ? newProducts : [...newProducts, productToUpdateInProductList];
productToAdd = {
...productToAdd,
@@ -415,9 +335,7 @@ export class MockRemissionService extends RemissionService {
};
}
const shippingDocumentToUpdate = process.shippingDocuments.find(
(document) => document.id === params.shippingDocumentId
);
const shippingDocumentToUpdate = process.shippingDocuments.find((document) => document.id === params.shippingDocumentId);
const updatedShippingDocument = {
...shippingDocumentToUpdate,
products: [...shippingDocumentToUpdate.products, productToAdd],
@@ -426,9 +344,7 @@ export class MockRemissionService extends RemissionService {
const updatedProcess = {
...process,
shippingDocuments: [
...process.shippingDocuments.filter(
(document) => document.id !== params.shippingDocumentId
),
...process.shippingDocuments.filter((document) => document.id !== params.shippingDocumentId),
updatedShippingDocument,
],
};
@@ -448,9 +364,7 @@ export class MockRemissionService extends RemissionService {
remissionProductId: number;
placementType?: string;
}): Observable<boolean> {
const processSubject = this.getRemissionFromStore(
params.remissionProcessId
);
const processSubject = this.getRemissionFromStore(params.remissionProcessId);
const process = processSubject.value;
const products = this.productSubject.value;
@@ -458,44 +372,30 @@ export class MockRemissionService extends RemissionService {
throw new Error('Prozess nicht gefunden');
}
const shippingDocumentToUpdate = process.shippingDocuments.find(
(document) => document.id === params.shippingDocumentId
);
const shippingDocumentToUpdate = process.shippingDocuments.find((document) => document.id === params.shippingDocumentId);
const updatedShippingDocument = {
...shippingDocumentToUpdate,
products: shippingDocumentToUpdate.products.filter(
(product) => product.id !== params.remissionProductId
),
products: shippingDocumentToUpdate.products.filter((product) => product.id !== params.remissionProductId),
};
const productToRemove = shippingDocumentToUpdate.products.find(
(product) => product.id === params.remissionProductId
);
const productInProductList = products.find(
(product) => product.id === params.remissionProductId
);
const productToRemove = shippingDocumentToUpdate.products.find((product) => product.id === params.remissionProductId);
const productInProductList = products.find((product) => product.id === params.remissionProductId);
const updatedProductInProductList = !!productInProductList
? {
...productInProductList,
remissionQuantity:
Number(productInProductList.remissionQuantity) +
Number(productToRemove.remissionQuantity),
remissionQuantity: Number(productInProductList.remissionQuantity) + Number(productToRemove.remissionQuantity),
}
: productToRemove;
const updatedProcess = {
...process,
shippingDocuments: [
...process.shippingDocuments.filter(
(document) => document.id !== params.shippingDocumentId
),
...process.shippingDocuments.filter((document) => document.id !== params.shippingDocumentId),
updatedShippingDocument,
],
};
const updatedProducts = [
...this.productSubject.value.filter(
(product) => product.id !== updatedProductInProductList.id
),
...this.productSubject.value.filter((product) => product.id !== updatedProductInProductList.id),
updatedProductInProductList,
];
@@ -509,12 +409,8 @@ export class MockRemissionService extends RemissionService {
this.reloadProductsSubject.next();
}
createShippingDocument(params: {
remissionProcessId: number;
}): Observable<ShippingDocument> {
const processSubject = this.getRemissionFromStore(
params.remissionProcessId
);
createShippingDocument(params: { remissionProcessId: number }): Observable<ShippingDocument> {
const processSubject = this.getRemissionFromStore(params.remissionProcessId);
if (!processSubject) {
throw new Error('Prozess nicht gefunden');
@@ -526,10 +422,7 @@ export class MockRemissionService extends RemissionService {
id: createRandomId(),
shippingDocumentNumber: createRandomStringId(),
};
const updatedShippingDocuments = [
...process.shippingDocuments,
newShippingDocument,
];
const updatedShippingDocuments = [...process.shippingDocuments, newShippingDocument];
const updatedProcess = {
...process,
shippingDocuments: updatedShippingDocuments,
@@ -545,18 +438,14 @@ export class MockRemissionService extends RemissionService {
shippingDocumentId: number;
containerId: string;
}): Observable<ActionResult<boolean>> {
const processSubject = this.getRemissionFromStore(
params.remissionProcessId
);
const processSubject = this.getRemissionFromStore(params.remissionProcessId);
if (!processSubject) {
throw new Error('Prozess nicht gefunden');
}
const process = processSubject.value;
const shippingContainerToUpdate = process.shippingDocuments.find(
(document) => document.id === params.shippingDocumentId
);
const shippingContainerToUpdate = process.shippingDocuments.find((document) => document.id === params.shippingDocumentId);
const updatedShippingContainer = {
...shippingContainerToUpdate,
isCompleted: true,
@@ -564,9 +453,7 @@ export class MockRemissionService extends RemissionService {
const updatedProcess = {
...process,
shippingDocuments: [
...process.shippingDocuments.filter(
(document) => document.id !== params.shippingDocumentId
),
...process.shippingDocuments.filter((document) => document.id !== params.shippingDocumentId),
updatedShippingContainer,
],
};
@@ -579,9 +466,7 @@ export class MockRemissionService extends RemissionService {
}).pipe(delay(this.delayInMs));
}
completeRemissions(params: {
remissionProcessId: number;
}): Observable<boolean> {
completeRemissions(params: { remissionProcessId: number }): Observable<boolean> {
return;
}
@@ -605,9 +490,7 @@ export class MockRemissionService extends RemissionService {
return of(true);
}
deleteShippingDocument(): Observable<
ActionResult<{ deleted: boolean; completedRemissionsExist: boolean }>
> {
deleteShippingDocument(): Observable<ActionResult<{ deleted: boolean; completedRemissionsExist: boolean }>> {
return of({
result: {
deleted: true,
@@ -620,10 +503,7 @@ export class MockRemissionService extends RemissionService {
return;
}
getCapacities(params: {
selectedFilters?: { [filterId: string]: string[] };
supplierId: number;
}): Observable<CapacityType[]> {
getCapacities(params: { selectedFilters?: { [filterId: string]: string[] }; supplierId: number }): Observable<CapacityType[]> {
return of([]);
}
}

View File

@@ -35,7 +35,11 @@
</div>
<div class="branch-actions">
<button *ngIf="(branch.id | stockInfo: (inStock$ | async))?.availableQuantity > 0" class="cta-reserve" (click)="reserve(branch)">
<button
*ngIf="(branch.id | stockInfo: (inStock$ | async))?.availableQuantity > 0 && branch?.isShippingEnabled"
class="cta-reserve"
(click)="reserve(branch)"
>
Reservieren
</button>

View File

@@ -37,7 +37,7 @@ export class ModalAvailabilitiesComponent {
(branch) =>
branch &&
branch?.isOnline &&
branch?.isShippingEnabled &&
// branch?.isShippingEnabled && ------ Rausgenommen aufgrund des Tickets #4712
branch?.isOrderingEnabled &&
branch?.id !== userbranch?.id &&
branch?.branchType === 1

View File

@@ -10,7 +10,7 @@
<div class="page-price-update-item__item-card p-5 h-[212px] bg-white">
<div class="page-price-update-item__item-thumbnail text-center mr-4 w-[47px] h-[73px]">
<img
class="page-price-update-item__item-image w-[47px] h-[73px]"
class="page-price-update-item__item-image w-[47px] max-h-[73px]"
loading="lazy"
*ngIf="item?.product?.ean | productImage; let productImage"
[src]="productImage"

View File

@@ -1,7 +1,7 @@
<div class="page-article-details__wrapper">
<div #detailsContainer class="page-article-details__container px-5" *ngIf="store.item$ | async; let item">
<div class="page-article-details__product-details mb-3">
<div class="page-article-details__product-bookmark justify-self-end">
<div class="page-article-details__product-bookmark flex fixed justify-self-end">
<div *ngIf="showArchivBadge$ | async" class="archiv-badge">
<button [uiOverlayTrigger]="archivTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]">
<img src="/assets/images/bookmark_benachrichtigung_archiv.svg" alt="Archiv Badge" />
@@ -30,9 +30,9 @@
</div>
<div *ngIf="showPromotionBadge$ | async" class="promotion-badge">
<button [uiOverlayTrigger]="promotionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]">
<ui-icon-badge icon="gift" alt="Prämienkatalog Badge"></ui-icon-badge>
<shared-icon-badge icon="gift" alt="Prämienkatalog Badge"></shared-icon-badge>
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #promotionTooltip [closeable]="true">
Dieser Artikel befindet sich im Prämienkatalog.
Der Artikel ist als Prämie für {{ promotionPoints$ | async }} Punkte erhältlich.
</ui-tooltip>
</button>
</div>

View File

@@ -58,11 +58,11 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
this.store.isDownloadAvailabilityAvailable$,
]).pipe(
map((values) => values.some((v) => v)),
shareReplay()
shareReplay(),
);
showDeliveryTruck$ = combineLatest([this.store.isDeliveryAvailabilityAvailable$, this.store.isDeliveryDigAvailabilityAvailable$]).pipe(
map(([delivery, digDelivery]) => delivery || digDelivery)
map(([delivery, digDelivery]) => delivery || digDelivery),
);
showDeliveryB2BTruck$ = combineLatest([
@@ -71,17 +71,22 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
]).pipe(map(([digDelivery, b2bDelivery]) => b2bDelivery && !digDelivery));
customerFeatures$ = this.applicationService.activatedProcessId$.pipe(
switchMap((processId) => this._domainCheckoutService.getCustomerFeatures({ processId }))
switchMap((processId) => this._domainCheckoutService.getCustomerFeatures({ processId })),
);
showSubscriptionBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'PFO')));
showPromotionBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'Promotion')));
hasPromotionFeature$ = this.store.item$.pipe(map((item) => !!item?.features?.find((i) => i.key === 'Promotion')));
promotionPoints$ = this.store.item$.pipe(map((item) => item?.redemptionPoints));
showPromotionBadge$ = combineLatest([this.hasPromotionFeature$, this.promotionPoints$]).pipe(
map(([hasPromotionFeature, promotionPoints]) => hasPromotionFeature && promotionPoints > 0),
);
showArchivBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'ARC')));
isBadgeVisible$ = combineLatest([this.showSubscriptionBadge$, this.showPromotionBadge$, this.showArchivBadge$]).pipe(
map(([showSubscriptionBadge, showPromotionBadge, showArchivBadge]) => showSubscriptionBadge || showPromotionBadge || showArchivBadge)
map(([showSubscriptionBadge, showPromotionBadge, showArchivBadge]) => showSubscriptionBadge || showPromotionBadge || showArchivBadge),
);
contributors$ = this.store.item$.pipe(map((item) => item?.product?.contributors?.split(';').map((m) => m.trim())));
@@ -96,11 +101,11 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
}
return this._datePipe.transform(date, 'dd. MMMM y');
})
}),
);
selectedBranchId$ = this.applicationService.activatedProcessId$.pipe(
switchMap((processId) => this.applicationService.getSelectedBranch$(processId))
switchMap((processId) => this.applicationService.getSelectedBranch$(processId)),
);
get isTablet$() {
@@ -126,7 +131,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
return 'Sie sehen den Bestand einer anderen Filiale.';
}
return '';
})
}),
);
priceMaintained$ = combineLatest([
@@ -136,8 +141,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
this.store.deliveryB2BAvailability$,
]).pipe(
map((availabilities) => {
return availabilities?.some((availability) => availability?.priceMaintained) ?? false;
})
return availabilities?.some((availability) => (availability as any)?.priceMaintained) ?? false;
}),
);
price$ = combineLatest([
@@ -169,7 +174,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
}
return null;
})
}),
);
constructor(
@@ -188,7 +193,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
private _environment: EnvironmentService,
private _router: Router,
private _domainCheckoutService: DomainCheckoutService,
private _store: Store
private _store: Store,
) {}
ngOnInit() {
@@ -196,8 +201,8 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
.pipe(
debounceTime(0),
switchMap((params) =>
this.applicationService.getSelectedBranch$(Number(params.processId)).pipe(map((selectedBranch) => ({ params, selectedBranch })))
)
this.applicationService.getSelectedBranch$(Number(params.processId)).pipe(map((selectedBranch) => ({ params, selectedBranch }))),
),
)
.subscribe(({ params, selectedBranch }) => {
const processId = Number(params.processId);
@@ -216,13 +221,13 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
const id$ = this.activatedRoute.params.pipe(
tap((_) => (this.showRecommendations = false)),
map((params) => Number(params?.id) || undefined),
filter((f) => !!f)
filter((f) => !!f),
);
const ean$ = this.activatedRoute.params.pipe(
tap((_) => (this.showRecommendations = false)),
map((params) => params?.ean || undefined),
filter((f) => !!f)
filter((f) => !!f),
);
const more$ = this.activatedRoute.params.subscribe(() => (this.showMore = false));
@@ -236,9 +241,9 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
this.store.item$
.pipe(
withLatestFrom(this.isTablet$),
filter(([item, isTablet]) => !!item)
filter(([item, isTablet]) => !!item),
)
.subscribe(([item, isTablet]) => (isTablet ? this.updateBreadcrumb(item) : this.updateBreadcrumbDesktop(item)))
.subscribe(([item, isTablet]) => (isTablet ? this.updateBreadcrumb(item) : this.updateBreadcrumbDesktop(item))),
);
}
@@ -338,7 +343,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
const item = await this.store.item$.pipe(first()).toPromise();
const modal = this.uiModal.open<BranchDTO>({
content: ModalAvailabilitiesComponent,
title: 'Weitere Verfügbarkeiten',
title: 'Bestände in anderen Filialen',
data: {
item,
},
@@ -349,7 +354,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
if (result?.data) {
this.showPurchasingModal(result.data);
}
})
}),
);
}
@@ -420,7 +425,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
first(),
switchMap((customerFeatures) => {
return this._domainCheckoutService.canSetCustomer({ processId: this.applicationService.activatedProcessId, customerFeatures });
})
}),
)
.toPromise();
this._router.navigate(nav.path, {
@@ -434,11 +439,10 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
async navigateToResultList() {
const processId = this.applicationService.activatedProcessId;
let crumbs = await this.breadcrumb
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog'])
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['catalog', 'details'])
.pipe(first())
.toPromise();
crumbs = crumbs.filter((crumb) => !crumb.tags?.includes('details'));
const crumb = crumbs[crumbs.length - 1];
if (!!crumb) {
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: crumb.params }).navigate();

View File

@@ -13,6 +13,7 @@ import { UiCommonModule } from '@ui/common';
import { OrderDeadlinePipeModule } from '@shared/pipes/order-deadline';
import { IconModule } from '@shared/components/icon';
import { ArticleDetailsTextComponent } from './article-details-text/article-details-text.component';
import { IconBadgeComponent } from 'apps/shared/components/icon/src/lib/badge/icon-badge.component';
@NgModule({
imports: [
@@ -28,6 +29,7 @@ import { ArticleDetailsTextComponent } from './article-details-text/article-deta
PipesModule,
OrderDeadlinePipeModule,
ArticleDetailsTextComponent,
IconBadgeComponent,
],
exports: [ArticleDetailsComponent, ArticleRecommendationsComponent],
declarations: [ArticleDetailsComponent, ArticleRecommendationsComponent],

View File

@@ -9,7 +9,7 @@
<p>Neben dem Titel "{{ item.product?.name }}" gibt es noch andere Artikel, die Sie interessieren könnten.</p>
<div class="articles">
<span class="label">
<span class="label mb-2">
<ui-icon icon="recommendation" size="20px"></ui-icon>
Artikel
</span>
@@ -28,9 +28,10 @@
(click)="close.emit()"
>
<img [src]="recommendation.product?.ean | productImage: 195:315:true" alt="product-image" />
<span class="format">{{ recommendation.product?.formatDetail }}</span>
<span class="price">{{ recommendation.catalogAvailability?.price?.value?.value | currency: ' ' }} EUR</span>
<div class="flex flex-col">
<span class="format">{{ recommendation.product?.formatDetail }}</span>
<span class="price">{{ recommendation.catalogAvailability?.price?.value?.value | currency: ' ' }} EUR</span>
</div>
</a>
</ui-slider>
</ng-container>

View File

@@ -29,12 +29,12 @@ p {
}
.article {
@apply flex flex-col mr-7 mt-4 no-underline text-black;
@apply flex flex-col mr-7 mt-4 no-underline text-black h-full min-w-[11rem] justify-between;
img {
@apply rounded-xl;
height: 315px;
max-width: 195px;
max-height: 19.6875rem;
max-width: 11rem;
box-shadow: 0 0 15px #949393;
}

View File

@@ -24,14 +24,14 @@ import { FilterAutocompleteProvider } from '@shared/components/filter';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleSearchComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
private _processId$: Observable<number>;
constructor(
private _breadcrumb: BreadcrumbService,
private _articleSearch: ArticleSearchService,
private _activatedRoute: ActivatedRoute,
private _navigationService: ProductCatalogNavigationService
private _navigationService: ProductCatalogNavigationService,
) {}
ngOnInit() {

View File

@@ -6,12 +6,10 @@ import { ArticleSearchComponent } from './article-search.component';
import { SearchResultsModule } from './search-results/search-results.module';
import { SearchMainModule } from './search-main/search-main.module';
import { SearchFilterModule } from './search-filter/search-filter.module';
import { ArticleSearchService } from './article-search.store';
@NgModule({
imports: [CommonModule, RouterModule, UiIconModule, SearchResultsModule, SearchMainModule, SearchFilterModule],
exports: [ArticleSearchComponent],
declarations: [ArticleSearchComponent],
providers: [ArticleSearchService],
})
export class ArticleSearchModule {}

View File

@@ -58,14 +58,14 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
}
}
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
constructor(
private articleSearch: ArticleSearchService,
private _environment: EnvironmentService,
private _activatedRoute: ActivatedRoute,
public application: ApplicationService,
private _navigationService: ProductCatalogNavigationService
private _navigationService: ProductCatalogNavigationService,
) {}
ngOnInit() {

View File

@@ -5,7 +5,7 @@
>
<div class="page-search-result-item__item-thumbnail text-center mr-4 w-[3.125rem] h-[4.9375rem]">
<img
class="page-search-result-item__item-image w-[3.125rem] h-[4.9375rem]"
class="page-search-result-item__item-image w-[3.125rem] max-h-[4.9375rem]"
loading="lazy"
*ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl"
[src]="thumbnailUrl"

View File

@@ -40,14 +40,15 @@
<div class="page-search-results__order-by mb-[0.125rem]" [class.page-search-results__order-by-primary]="primaryOutletActive$ | async">
<shared-order-by-filter
[orderBy]="(filter$ | async)?.orderBy"
(selectedOrderByChange)="search({ clear: true, orderBy: true }); updateBreadcrumbs()"
*ngIf="filter$ | async; let filter"
[orderBy]="filter?.orderBy"
(selectedOrderByChange)="search({ filter, clear: true, orderBy: true }); updateBreadcrumbs()"
>
</shared-order-by-filter>
</div>
<ng-container *ngIf="primaryOutletActive$ | async; else sideOutlet">
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="106 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="103 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
<a
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
[routerLink]="getDetailsPath(item.id)"
@@ -81,7 +82,7 @@
</ng-container>
<ng-template #sideOutlet>
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="222 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="191 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
<a
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
[routerLink]="getDetailsPath(item.id)"

View File

@@ -11,7 +11,7 @@ import {
AfterViewInit,
inject,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { EnvironmentService } from '@core/environment';
@@ -20,7 +20,7 @@ import { ItemDTO } from '@swagger/cat';
import { AddToShoppingCartDTO } from '@swagger/checkout';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { CacheService } from 'apps/core/cache/src/public-api';
import { debounce, isEqual } from 'lodash';
import { isEqual } from 'lodash';
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
import { debounceTime, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
import { ArticleSearchService } from '../article-search.store';
@@ -115,7 +115,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
private _checkoutService: DomainCheckoutService,
private _environment: EnvironmentService,
private _navigationService: ProductCatalogNavigationService,
private _availability: DomainAvailabilityService
private _availability: DomainAvailabilityService,
private _router: Router
) {}
ngOnInit() {
@@ -154,7 +155,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
const cleanQueryParams = this.cleanupQueryParams(queryParams);
if (this.route.outlet === 'primary' && processChanged) {
if (processChanged) {
this.scrollToItem(this._getScrollIndexFromCache());
}
@@ -198,9 +199,9 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
.subscribe(async ([searchCompleted, processId]) => {
const params = searchCompleted.state.filter.getQueryParams();
if (searchCompleted.state.searchState === '') {
// Keine Navigation bei OrderBy
// Ticket 4524 Korrekte Navigation bei orderBy mit aktuellen queryParams
if (searchCompleted?.orderBy) {
return;
return await this._router.navigate([], { queryParams: params });
}
// Navigation auf Details bzw. Results | Details wenn hits 1
@@ -208,7 +209,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
if (searchCompleted.state.hits === 1) {
const item = searchCompleted.state.items.find((f) => f);
const ean = this.route?.snapshot?.params?.ean;
const itemId = this.route?.snapshot?.params?.id ? Number(this.route?.snapshot?.params?.id) : item.id; // Nicht zum ersten Item der Liste springen wenn bereits eines selektiert ist
// Navigation from Cart uses ean
if (!!ean) {
@@ -223,7 +223,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
await this._navigationService
.getArticleDetailsPath({
processId,
itemId,
itemId: item.id,
extras: { queryParams: params },
})
.navigate();
@@ -271,7 +271,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
}
private _getScrollIndexFromCache(): number {
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN });
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN }) ?? 0;
}
scrollToItem(i?: number) {

View File

@@ -11,12 +11,14 @@ import { combineLatest, fromEvent, Observable, Subject } from 'rxjs';
import { first, map, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
import { ActionsSubject } from '@ngrx/store';
import { DomainAvailabilityService } from '@domain/availability';
import { provideComponentStore } from '@ngrx/component-store';
import { ArticleSearchService } from './article-search/article-search.store';
@Component({
selector: 'page-catalog',
templateUrl: 'page-catalog.component.html',
styleUrls: ['page-catalog.component.scss'],
providers: [],
providers: [provideComponentStore(ArticleSearchService)],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
@@ -31,7 +33,7 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
return `${this.breadcrumbRef?.nativeElement?.clientWidth}px`;
}
_onDestroy$ = new Subject<boolean>();
_onDestroy$ = new Subject<void>();
get isTablet$() {
return this._environmentService.matchTablet$;
@@ -58,7 +60,7 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
public auth: AuthService,
private _environmentService: EnvironmentService,
private _renderer: Renderer2,
private _actions: ActionsSubject
private _actions: ActionsSubject,
) {}
ngOnInit() {
@@ -74,7 +76,7 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
return 'Bitte wählen Sie eine Filiale aus, um den Bestand einer anderen Filiale zu sehen';
}
return '';
})
}),
);
}

View File

@@ -1,3 +1,5 @@
import { ShoppingCartItemDTO } from '@swagger/checkout';
export interface CheckoutDummyData extends ShoppingCartItemDTO {}
export interface CheckoutDummyData extends ShoppingCartItemDTO {
changeDataFromCart?: boolean;
}

View File

@@ -1,6 +1,5 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { ItemDTO } from '@swagger/cat';
import { DateAdapter } from '@ui/common';
@@ -9,7 +8,8 @@ import { Subject } from 'rxjs';
import { first, shareReplay, takeUntil } from 'rxjs/operators';
import { CheckoutDummyData } from './checkout-dummy-data';
import { CheckoutDummyStore } from './checkout-dummy.store';
import { CheckoutNavigationService } from '@shared/services';
import { CheckoutNavigationService, CustomerSearchNavigation } from '@shared/services';
import { Router } from '@angular/router';
@Component({
selector: 'page-checkout-dummy',
@@ -38,14 +38,15 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
_onDestroy$ = new Subject<void>();
constructor(
private _router: Router,
private _fb: UntypedFormBuilder,
private _dateAdapter: DateAdapter,
private _modal: UiModalService,
private _store: CheckoutDummyStore,
private _ref: UiModalRef<any, CheckoutDummyData>,
private readonly _applicationService: ApplicationService,
private readonly _checkoutNavigationService: CheckoutNavigationService
private readonly _checkoutNavigationService: CheckoutNavigationService,
private readonly _customerNavigationService: CustomerSearchNavigation,
private _router: Router
) {}
ngOnInit() {
@@ -58,7 +59,7 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
}
});
if (!!this._ref?.data && Object.keys(this._ref?.data).length !== 0) {
if (this.hasShoppingCartItemToUpdate()) {
const data = this._ref?.data;
this._store.patchState({ shoppingCartItem: data });
this.populateFormFromModalData(data);
@@ -149,6 +150,14 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
this.control.markAsUntouched();
}
hasShoppingCartItemToUpdate(): boolean {
const hasShoppingCartItem = !!this._ref.data?.id;
if (!!this._ref?.data && hasShoppingCartItem) {
return true;
}
return false;
}
async nextItem() {
if (this.control.invalid || this.control.disabled) {
this.control.enable();
@@ -158,7 +167,7 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
try {
const branch = await this._store.currentBranch$.pipe(first()).toPromise();
if (!!this._ref?.data && Object.keys(this._ref?.data).length !== 0) {
if (this.hasShoppingCartItemToUpdate()) {
await this._store.createAddToCartItem(this.control, branch, true);
this._store.updateCart(() => {});
} else {
@@ -187,16 +196,17 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
try {
const branch = await this._store.currentBranch$.pipe(first()).toPromise();
if (!!this._ref?.data && Object.keys(this._ref?.data).length !== 0) {
if (this.hasShoppingCartItemToUpdate()) {
await this._store.createAddToCartItem(this.control, branch, true);
this._store.updateCart(async () => {
// Set filter for navigation to customer search if customer is not set
const customer = await this._store.customer$.pipe(first()).toPromise();
const customerFilter = await this._store.customerFilter$.pipe(first()).toPromise();
let filter: { [key: string]: string };
if (!customer) {
if (!customer && !this._ref?.data?.changeDataFromCart) {
filter = customerFilter;
this._router.navigate(['/kunde', this._applicationService.activatedProcessId, 'customer', 'search'], {
const path = this._customerNavigationService.defaultRoute({ processId: this._applicationService.activatedProcessId }).path;
await this._router.navigate(path, {
queryParams: { customertype: filter.customertype },
});
} else {
@@ -211,9 +221,10 @@ export class CheckoutDummyComponent implements OnInit, OnDestroy {
const customer = await this._store.customer$.pipe(first()).toPromise();
const customerFilter = await this._store.customerFilter$.pipe(first()).toPromise();
let filter: { [key: string]: string };
if (!customer) {
if (!customer && !this._ref?.data?.changeDataFromCart) {
filter = customerFilter;
this._router.navigate(['/kunde', this._applicationService.activatedProcessId, 'customer', 'search'], {
const path = this._customerNavigationService.defaultRoute({ processId: this._applicationService.activatedProcessId }).path;
await this._router.navigate(path, {
queryParams: { customertype: filter.customertype },
});
} else {

View File

@@ -14,7 +14,7 @@
<div class="btn-wrapper">
<a class="cta-primary" [routerLink]="productSearchBasePath">Artikel suchen</a>
<button class="cta-secondary" (click)="openDummyModal()">Neuanlage</button>
<button class="cta-secondary" (click)="openDummyModal({})">Neuanlage</button>
</div>
</div>
</div>
@@ -54,7 +54,7 @@
<button
*ngIf="group.orderType === 'Dummy'"
class="text-brand border-none font-bold text-p1 outline-none pl-4"
(click)="openDummyModal()"
(click)="openDummyModal({ changeDataFromCart: true })"
>
Hinzufügen
</button>
@@ -81,8 +81,11 @@
*ngIf="group?.orderType !== undefined && (item.features?.orderType === 'Abholung' || item.features?.orderType === 'Rücklage')"
>
<ng-container *ngIf="item?.destination?.data?.targetBranch?.data; let targetBranch">
<ng-container *ngIf="i === 0 || targetBranch.id !== group.items[i - 1].destination?.data?.targetBranch?.data.id">
<div class="flex flex-row items-center px-5 pt-0 pb-[0.875rem] -mt-2 bg-[#F5F7FA]">
<ng-container *ngIf="i === 0 || checkIfMultipleDestinationsForOrderTypeExist(targetBranch, group, i)">
<div
class="flex flex-row items-center px-5 pt-0 pb-[0.875rem] -mt-2 bg-[#F5F7FA]"
[class.multiple-destinations]="checkIfMultipleDestinationsForOrderTypeExist(targetBranch, group, i)"
>
<span class="branch-name">{{ targetBranch?.name }} | {{ targetBranch | branchAddress }}</span>
</div>
<hr />

View File

@@ -105,6 +105,10 @@ h1 {
}
}
.multiple-destinations {
@apply py-[0.875rem] mt-0;
}
.icon-order-type {
@apply text-black mr-2;
}

View File

@@ -14,7 +14,7 @@ import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainCheckoutService } from '@domain/checkout';
import { AvailabilityDTO, DestinationDTO, ShoppingCartItemDTO } from '@swagger/checkout';
import { AvailabilityDTO, BranchDTO, DestinationDTO, ShoppingCartItemDTO } from '@swagger/checkout';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { PrintModalData, PrintModalComponent } from '@modal/printer';
import { delay, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
@@ -254,6 +254,10 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
});
}
checkIfMultipleDestinationsForOrderTypeExist(targetBranch: BranchDTO, group: { items: ShoppingCartItemDTO[] }, i: number) {
return i === 0 ? false : targetBranch.id !== group.items[i - 1].destination?.data?.targetBranch?.data.id;
}
async refreshAvailabilities() {
this.checkingOla$.next(true);
@@ -288,15 +292,15 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
this._store.notificationsControl = undefined;
}
openDummyModal(data?: CheckoutDummyData) {
openDummyModal({ data, changeDataFromCart = false }: { data?: CheckoutDummyData; changeDataFromCart?: boolean }) {
this.uiModal.open({
content: CheckoutDummyComponent,
data,
data: { ...data, changeDataFromCart },
});
}
changeDummyItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {
this.openDummyModal(shoppingCartItem);
this.openDummyModal({ data: shoppingCartItem, changeDataFromCart: true });
}
async changeItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {

View File

@@ -5,14 +5,10 @@
<ng-container *ngIf="buyer$ | async; let buyer">
<div *ngIf="!(showAddresses$ | async)" class="flex flex-row items-start justify-between p-5">
<div class="flex flex-row flex-wrap pr-4">
<ng-container *ngIf="!!buyer?.lastName && !!buyer?.firstName; else organisation">
<div class="mr-3">Nachname, Vorname</div>
<div class="font-bold">{{ buyer?.lastName }}, {{ buyer?.firstName }}</div>
<ng-container *ngIf="getNameFromBuyer(buyer); let name">
<div class="mr-3">{{ name.label }}</div>
<div class="font-bold">{{ name.value }}</div>
</ng-container>
<ng-template #organisation>
<div class="mr-3">Firmenname</div>
<div class="font-bold">{{ buyer?.organisation?.name }}</div>
</ng-template>
</div>
<button (click)="changeAddress()" class="text-p1 font-bold text-[#F70400]">
@@ -36,9 +32,9 @@
<ng-container *ngIf="payer$ | async; let payer">
<div *ngIf="showAddresses$ | async" class="flex flex-row items-start justify-between p-5 pt-0">
<div class="flex flex-row flex-wrap pr-4" data-address-type="Rechnungsadresse">
<div class="mr-3">Rechnungsadresse</div>
<div class="font-bold">
<div class="flex flex-row flex-wrap pr-4" data-address-type="Rechnungsadresse" data-which="Rechnungsadresse">
<div class="mr-3" data-what="title">Rechnungsadresse</div>
<div class="font-bold" data-what="address">
{{ payer | payerAddress }}
</div>
</div>
@@ -51,9 +47,9 @@
<ng-container *ngIf="payer$ | async; let payer">
<div *ngIf="showAddresses$ | async" class="flex flex-row items-start justify-between px-5">
<div class="flex flex-row flex-wrap pr-4" data-address-type="Lieferadresse">
<div class="mr-3">Lieferadresse</div>
<div class="font-bold">
<div class="flex flex-row flex-wrap pr-4" data-address-type="Lieferadresse" data-which="Lieferadresse">
<div class="mr-3" data-what="title">Lieferadresse</div>
<div class="font-bold" data-what="address">
{{ shippingAddress$ | async | shippingAddress }}
</div>
</div>

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnInit, inject } from '@angular/core';
import { emailNotificationValidator, mobileNotificationValidator } from '@shared/components/notification-channel-control';
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { combineLatest } from 'rxjs';
@@ -7,7 +7,8 @@ import { first, map, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators
import { ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { Router } from '@angular/router';
import { NotificationChannel } from '@swagger/checkout';
import { BuyerDTO, NotificationChannel } from '@swagger/checkout';
import { CustomerSearchNavigation } from '@shared/services';
@Component({
selector: 'page-checkout-review-details',
@@ -16,6 +17,8 @@ import { NotificationChannel } from '@swagger/checkout';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CheckoutReviewDetailsComponent implements OnInit {
customerNavigation = inject(CustomerSearchNavigation);
control: UntypedFormGroup;
customerFeatures$ = this._store.customerFeatures$;
@@ -23,20 +26,6 @@ export class CheckoutReviewDetailsComponent implements OnInit {
payer$ = this._store.payer$;
buyer$ = this._store.buyer$;
showAddresses$ = this._store.shoppingCartItems$.pipe(
takeUntil(this._store.orderCompleted),
withLatestFrom(this.customerFeatures$),
map(
([items, customerFeatures]) =>
items.some(
(item) =>
item.features?.orderType === 'Versand' ||
item.features?.orderType === 'B2B-Versand' ||
item.features?.orderType === 'DIG-Versand'
) || !!customerFeatures?.b2b
)
);
showNotificationChannels$ = combineLatest([this._store.shoppingCartItems$, this.payer$, this.buyer$]).pipe(
takeUntil(this._store.orderCompleted),
map(
@@ -65,6 +54,22 @@ export class CheckoutReviewDetailsComponent implements OnInit {
switchMap((processId) => this._domainCheckoutService.getShippingAddress({ processId }))
);
showAddresses$ = this._store.shoppingCartItems$.pipe(
takeUntil(this._store.orderCompleted),
withLatestFrom(this.customerFeatures$, this.payer$, this.shippingAddress$),
map(([items, customerFeatures, payer, shippingAddress]) => {
const hasShippingOrBillingAddresses = !!payer?.address || !!shippingAddress;
const hasShippingFeature = items.some(
(item) =>
item.features?.orderType === 'Versand' || item.features?.orderType === 'B2B-Versand' || item.features?.orderType === 'DIG-Versand'
);
const isB2bCustomer = !!customerFeatures?.b2b;
return hasShippingOrBillingAddresses && (hasShippingFeature || isB2bCustomer);
})
);
notificationChannelLoading$ = this._store.notificationChannelLoading$;
constructor(
@@ -118,6 +123,20 @@ export class CheckoutReviewDetailsComponent implements OnInit {
this._store.onNotificationChange(notificationChannels);
}
getNameFromBuyer(buyer: BuyerDTO): { value: string; label: string } {
if (buyer?.lastName && buyer?.firstName) {
return { value: `${buyer?.lastName}, ${buyer?.firstName}`, label: 'Nachname, Vorname' };
} else if (buyer?.lastName) {
return { value: buyer?.lastName, label: 'Nachname, Vorname' };
} else if (buyer?.firstName) {
return { value: buyer?.firstName, label: 'Nachname, Vorname' };
} else if (buyer?.organisation?.name) {
return { value: buyer?.organisation?.name, label: 'Firmenname' };
} else {
return;
}
}
async changeAddress() {
const processId = this._application.activatedProcessId;
const customer = await this._domainCheckoutService.getBuyer({ processId }).pipe(first()).toPromise();
@@ -126,7 +145,8 @@ export class CheckoutReviewDetailsComponent implements OnInit {
return;
}
const customerId = customer.source;
this._router.navigate(['/kunde', this._application.activatedProcessId, 'customer', 'search', `${customerId}`]);
await this.customerNavigation.navigateToDetails({ processId, customerId, customer: { customerNumber: customer.buyerNumber } });
// this._router.navigate(['/kunde', this._application.activatedProcessId, 'customer', 'search', `${customerId}`]);
}
async navigateToCustomerSearch(processId: number) {

View File

@@ -17,7 +17,7 @@ button {
grid-area: item-thumbnail;
@apply mr-8 w-[3.75rem] h-[5.9375rem];
img {
@apply w-[3.75rem] h-[5.9375rem] rounded shadow-cta;
@apply w-[3.75rem] max-h-[5.9375rem] rounded shadow-cta;
}
}

View File

@@ -19,17 +19,14 @@
placeholder="Eine Anmerkung hinzufügen"
[(ngModel)]="value"
[rows]="rows"
(ngModelChange)="check()"
(blur)="save()"
(ngModelChange)="updateValue()"
(blur)="updateValue()"
></textarea>
<div class="comment-actions py-4">
<button type="reset" class="clear pl-4" *ngIf="!disabled && !!value" (click)="clear(); triggerResize()">
<shared-icon icon="close" [size]="24"></shared-icon>
</button>
<button class="cta-save ml-4" type="submit" *ngIf="!disabled && isDirty" (click)="save()">
Speichern
</button>
</div>
</div>

View File

@@ -61,7 +61,7 @@ export class SpecialCommentComponent implements ControlValueAccessor {
clear() {
this.value = '';
this.save();
this.updateValue();
}
check() {
@@ -80,11 +80,12 @@ export class SpecialCommentComponent implements ControlValueAccessor {
this.cdr.markForCheck();
}
save() {
updateValue() {
this.initialValue = this.value;
this.onChange(this.value);
this.check();
}
setIsDirty(isDirty: boolean) {
this.isDirty = isDirty;
this.isDirtyChange.emit(isDirty);

View File

@@ -55,6 +55,8 @@
<span class="w-32">Vorgangs-ID</span>
<ng-container *ngIf="customer$ | async; let customer">
<a
data-which="Vorgangs-ID"
data-what="link"
*ngIf="customer$ | async; let customer"
class="font-bold text-[#0556B4] no-underline"
[routerLink]="['/kunde', processId, 'customer', 'search', customer?.id, 'orders', displayOrder.id]"
@@ -102,7 +104,7 @@
>
<div class="page-checkout-summary__items-thumbnail flex flex-row">
<a [routerLink]="getProductSearchDetailsPath(order?.product?.ean)" [queryParams]="getProductSearchDetailsQueryParams(order)">
<img class="w-[3.125rem] h-20 mr-2" [src]="order.product?.ean | productImage: 195:315:true" />
<img class="w-[3.125rem] max-h-20 mr-2" [src]="order.product?.ean | productImage: 195:315:true" />
</a>
</div>
@@ -226,11 +228,12 @@
<div class="absolute left-1/2 bottom-10 inline-grid grid-flow-col gap-4 justify-center transform -translate-x-1/2">
<button
*ifRole="'Store'"
[disabled]="isPrinting$ | async"
type="button"
class="px-6 py-2 rounded-full border-2 border-solid border-brand text-brand bg-white font-bold text-lg whitespace-nowrap h-14"
class="px-6 py-2 rounded-full border-2 border-solid border-brand text-brand bg-white font-bold text-lg whitespace-nowrap h-14 flex flex-row items-center justify-center print-button"
(click)="printOrderConfirmation()"
>
Bestellbestätigung drucken
<ui-spinner class="min-h-4 min-w-4" [show]="isPrinting$ | async"> Bestellbestätigung drucken </ui-spinner>
</button>
<button

View File

@@ -96,6 +96,12 @@ hr {
}
}
.print-button {
&:disabled {
@apply bg-inactive-branch border-solid border-inactive-branch text-white cursor-not-allowed;
}
}
.last {
@apply pb-5;
}

View File

@@ -16,7 +16,6 @@ import { DateAdapter } from '@ui/common';
import { CheckoutNavigationService, PickUpShelfOutNavigationService, ProductCatalogNavigationService } from '@shared/services';
import { EnvironmentService } from '@core/environment';
import { SendOrderConfirmationModalService } from '@shared/modals/send-order-confirmation-modal';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { ToasterService } from '@shared/shell';
@Component({
@@ -34,7 +33,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
private _toaster = inject(ToasterService);
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
processId = Date.now();
selectedDate = this.dateAdapter.today();
minDateDatepicker = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), -1);
@@ -53,17 +52,17 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
// Ticket #4228 Für die korrekte Gruppierung der Items bei gleichem Bestellziel (Aufsplitten von Abholung und Rücklage)
const ordersWithMultipleFeatures = filteredOrders.filter((order) =>
order.items.find((item) => item.features.orderType !== order.features.orderType)
order.items.find((item) => item.features.orderType !== order.features.orderType),
);
if (!!ordersWithMultipleFeatures) {
for (let orderWithMultipleFeatures of ordersWithMultipleFeatures) {
if (orderWithMultipleFeatures?.items?.length > 1) {
const itemsWithOrderFeature = orderWithMultipleFeatures.items.filter(
(item) => item.features.orderType === orderWithMultipleFeatures.features.orderType
(item) => item.features.orderType === orderWithMultipleFeatures.features.orderType,
);
const itemsWithDifferentOrderFeature = orderWithMultipleFeatures.items.filter(
(item) => item.features.orderType !== orderWithMultipleFeatures.features.orderType
(item) => item.features.orderType !== orderWithMultipleFeatures.features.orderType,
);
filteredOrders = [...filteredOrders.filter((order) => order.id !== orderWithMultipleFeatures.id)];
@@ -86,20 +85,20 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
};
});
}),
shareReplay()
shareReplay(),
);
hasAbholung$ = this.displayOrders$.pipe(
map((displayOrders) => displayOrders.filter((order) => order.features?.orderType === 'Abholung')?.length > 0)
map((displayOrders) => displayOrders.filter((order) => order.features?.orderType === 'Abholung')?.length > 0),
);
totalItemCount$ = this.displayOrders$.pipe(
map((displayOrders) =>
displayOrders.reduce(
(total, displayOrder) => total + displayOrder?.items?.reduce((subTotal, order) => subTotal + order?.quantity, 0),
0
)
)
0,
),
),
);
totalReadingPoints$ = this.displayOrders$.pipe(
@@ -123,7 +122,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
} else {
return NEVER;
}
})
}),
);
totalPrice$ = this.displayOrders$.pipe(
@@ -131,36 +130,42 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
displayOrders.reduce(
(total, displayOrder) =>
total + displayOrder?.items?.reduce((subTotal, order) => subTotal + order?.price?.value?.value * order.quantity, 0),
0
)
)
0,
),
),
);
isPrinting$ = new BehaviorSubject(false);
totalPriceCurrency$ = this.displayOrders$.pipe(map((displayOrders) => displayOrders[0]?.items[0]?.price?.value?.currency));
containsDeliveryOrder$ = this.displayOrders$.pipe(
map(
(displayOrders) =>
displayOrders.filter((o) => ['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(o.features?.orderType) > -1)?.length > 0
)
displayOrders.filter((o) => ['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(o.features?.orderType) > -1)?.length > 0,
),
);
customer$ = this.displayOrders$.pipe(
switchMap((o) => this.customerService.getCustomers(o[0].buyerNumber, { take: 5 })),
map((customers) => customers.result[0]),
shareReplay()
shareReplay(),
);
isB2BCustomer$ = this.customer$.pipe(map((customer) => customer?.features?.find((f) => f.key === 'b2b') != null));
takeNowOrders$ = this.displayOrders$.pipe(
map((displayOrders) => displayOrders.filter((o) => o.items.find((oi) => oi.features?.orderType === 'Rücklage') != null))
map((displayOrders) => displayOrders.filter((o) => o.items.find((oi) => oi.features?.orderType === 'Rücklage') != null)),
);
get isDesktop$() {
return this._environmentService.matchDesktopLarge$;
}
get isTablet() {
return this._environmentService.matchTablet();
}
expanded: boolean[] = [];
constructor(
@@ -179,7 +184,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
private _productNavigationService: ProductCatalogNavigationService,
private _shelfOutNavigationService: PickUpShelfOutNavigationService,
private _environmentService: EnvironmentService,
private _cdr: ChangeDetectorRef
private _cdr: ChangeDetectorRef,
) {
this.breadcrumb
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['checkout'])
@@ -291,22 +296,9 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
if (takeNowOrders.length != 1) return;
try {
for (const takeNowOrder of takeNowOrders) {
for (const orderItem of takeNowOrder.items.filter((item) => item.features?.orderType === 'Rücklage')) {
await this.omsService
.changeOrderStatus(takeNowOrder.id, orderItem.id, orderItem.subsetItems[0]?.id, {
processingStatus: 128,
})
.toPromise();
}
}
await this.router.navigate(
this._shelfOutNavigationService.detailRoute({
processId: Date.now(),
item: { orderId: takeNowOrders[0].id, orderNumber: takeNowOrders[0].orderNumber, processingStatus: 128 },
}).path
);
await this.router.navigate(this._shelfOutNavigationService.listRoute({ processId: Date.now() }).path, {
queryParams: { main_qs: takeNowOrders[0].orderNumber, filter_supplier_id: '16' },
});
} catch (e) {
console.error(e);
}
@@ -319,27 +311,57 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
}
async printOrderConfirmation() {
this.isPrinting$.next(true);
const orders = await this.displayOrders$.pipe(first()).toPromise();
await this.uiModal
.open({
content: PrintModalComponent,
data: {
printerType: 'Label',
print: async (printer) => {
try {
const result = await this.domainPrinterService.printOrder({ orderIds: orders.map((o) => o.id), printer }).toPromise();
this._toaster.open({ type: 'success', message: 'Bestellbestätigung wurde gedruckt' });
return result;
} catch (error) {
this._toaster.open({ type: 'danger', message: 'Fehler beim Drucken der Bestellbestätigung' });
}
const selectedPrinter = await this.domainPrinterService
.getAvailableLabelPrinters()
.pipe(
first(),
map((printers) => {
if (Array.isArray(printers)) return printers.find((printer) => printer.selected === true);
}),
)
.toPromise();
console.log(selectedPrinter);
if (!selectedPrinter || this.isTablet) {
await this.uiModal
.open({
content: PrintModalComponent,
data: {
printerType: 'Label',
printImmediately: !this.isTablet,
print: async (printer) => {
try {
const result = await this.domainPrinterService.printOrder({ orderIds: orders.map((o) => o.id), printer }).toPromise();
this._toaster.open({ type: 'success', message: 'Bestellbestätigung wurde gedruckt' });
return result;
} catch (error) {
this._toaster.open({ type: 'danger', message: 'Fehler beim Drucken der Bestellbestätigung' });
} finally {
this.isPrinting$.next(false);
}
},
} as PrintModalData,
config: {
panelClass: [],
showScrollbarY: false,
},
} as PrintModalData,
config: {
panelClass: [],
showScrollbarY: false,
},
})
.afterClosed$.toPromise();
})
.afterClosed$.toPromise();
this.isPrinting$.next(false);
} else {
try {
const result = await this.domainPrinterService
.printOrder({ orderIds: orders.map((o) => o.id), printer: selectedPrinter.key })
.toPromise();
this._toaster.open({ type: 'success', message: 'Bestellbestätigung wurde gedruckt' });
return result;
} catch (error) {
this._toaster.open({ type: 'danger', message: 'Fehler beim Drucken der Bestellbestätigung' });
} finally {
this.isPrinting$.next(false);
}
}
}
}

View File

@@ -1,58 +0,0 @@
import { Directive, HostListener, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { BranchDTO } from '@swagger/checkout';
import { PurchasingOptionsModalStore } from '../../modals/purchasing-options-modal/purchasing-options-modal.store';
/* tslint:disable: directive-selector */
@Directive({ selector: '[keyNavigation]' })
export class KeyNavigationDirective implements OnInit {
@Input() element: any;
@Input('keyNavigation') data: BranchDTO[];
@Output() closeDropdown = new EventEmitter<void>();
@Output() preselectBranch = new EventEmitter<BranchDTO>();
selectedData: BranchDTO;
position = 0;
posMarker = 0;
@HostListener('window:keyup', ['$event'])
keyEvent(event: KeyboardEvent) {
if (event.key === 'ArrowUp') {
if (this.position > 0) {
this.position--;
}
if (this.position <= this.posMarker - 4) {
this.element.scrollTop -= 44;
}
this.selectedData = this.data[this.position];
this.preselectBranch.emit(this.selectedData);
}
if (event.key === 'ArrowDown') {
if (this.position < this.data.length - 1) {
this.position++;
}
if (this.position >= 4) {
this.posMarker = this.position;
this.element.scrollTop += 44;
}
this.selectedData = this.data[this.position];
this.preselectBranch.emit(this.selectedData);
}
if (event.key === 'Enter') {
this.purchasingOptionsModalStore.setBranch(this.selectedData);
this.position = 0;
this.closeDropdown.emit();
}
}
constructor(private purchasingOptionsModalStore: PurchasingOptionsModalStore) {}
ngOnInit() {
this.selectedData = this.data[this.position];
this.preselectBranch.emit(this.selectedData);
}
}

View File

@@ -1,12 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { KeyNavigationDirective } from './key-navigation.directive';
@NgModule({
imports: [CommonModule],
exports: [KeyNavigationDirective],
declarations: [KeyNavigationDirective],
providers: [],
})
export class KeyNavigationModule {}

View File

@@ -140,6 +140,19 @@
</ng-container>
</ng-template>
</div>
<div class="page-customer-order-details-item__tracking-details" *ngIf="getOrderItemTrackingData(orderItem); let trackingData">
<div class="label">{{ trackingData.length > 1 ? 'Sendungsnummern' : 'Sendungsnummer' }}</div>
<ng-container *ngFor="let tracking of trackingData">
<ng-container *ngIf="tracking.trackingProvider === 'DHL' && !isNative; else noTrackingLink">
<a class="value text-[#0556B4]" [href]="getTrackingNumberLink(tracking.trackingNumber)" target="_blank"
>{{ tracking.trackingProvider }}: {{ tracking.trackingNumber }}</a
>
</ng-container>
<ng-template #noTrackingLink>
<p class="value">{{ tracking.trackingProvider }}: {{ tracking.trackingNumber }}</p>
</ng-template>
</ng-container>
</div>
<hr class="border-[#EDEFF0] border-t-2 my-4" />

View File

@@ -22,7 +22,7 @@ button {
.page-customer-order-details-item__thumbnail {
img {
@apply rounded shadow-cta w-[3.625rem] h-[5.9375rem];
@apply rounded shadow-cta w-[3.625rem] max-h-[5.9375rem];
}
}
@@ -55,6 +55,18 @@ button {
}
}
.page-customer-order-details-item__tracking-details {
@apply flex gap-x-7;
.label {
@apply w-[8.125rem];
}
.value {
@apply flex flex-row items-center font-bold;
}
}
.page-customer-order-details-item__comment {
textarea {
@apply w-full flex-grow rounded bg-[#EDEFF0] border-[#AEB7C1] border border-solid outline-none text-p2 p-4;

View File

@@ -18,6 +18,7 @@ import { isEqual } from 'lodash';
import { combineLatest, NEVER, Subject, Observable } from 'rxjs';
import { catchError, filter, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
import { CustomerOrderDetailsStore } from '../customer-order-details.store';
import { EnvironmentService } from '@core/environment';
export interface CustomerOrderDetailsItemComponentState {
orderItem?: OrderItemListItemDTO;
@@ -77,12 +78,12 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
switchMap((oi) =>
this._omsService
.getCompletedTasks({ orderId: oi.orderId, orderItemId: oi.orderItemId, orderItemSubsetId: oi.orderItemSubsetId, take: 4, skip: 0 })
.pipe(catchError(() => NEVER))
)
.pipe(catchError(() => NEVER)),
),
);
canChangeQuantity$ = combineLatest([this.orderItem$, this._store.fetchPartial$]).pipe(
map(([item, partialPickup]) => ([16, 8192].includes(item?.processingStatus) || partialPickup) && item.quantity > 1)
map(([item, partialPickup]) => ([16, 8192].includes(item?.processingStatus) || partialPickup) && item.quantity > 1),
);
get quantity() {
@@ -108,7 +109,7 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
}
readonly selected$ = combineLatest([this.orderItem$, this._store.selectedeOrderItemSubsetIds$]).pipe(
map(([orderItem, selectedItems]) => selectedItems.includes(orderItem?.orderItemSubsetId))
map(([orderItem, selectedItems]) => selectedItems.includes(orderItem?.orderItemSubsetId)),
);
@Output()
@@ -119,7 +120,7 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
}
readonly selectable$ = combineLatest([this._store.items$, this._store.itemsSelectable$, this._store.fetchPartial$]).pipe(
map(([orderItems, selectable, fetchPartial]) => orderItems.length > 1 && selectable && fetchPartial)
map(([orderItems, selectable, fetchPartial]) => orderItems.length > 1 && selectable && fetchPartial),
);
get receipts() {
@@ -140,13 +141,18 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
more$ = this.select((s) => s.more);
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
get isNative() {
return this._environment.isNative();
}
constructor(
private _store: CustomerOrderDetailsStore,
private _domainReceiptService: DomainReceiptService,
private _omsService: DomainOmsService,
private _cdr: ChangeDetectorRef
private _cdr: ChangeDetectorRef,
private _environment: EnvironmentService,
) {
super({
more: false,
@@ -188,11 +194,11 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
done?.([]);
}
},
() => {}
)
)
)
)
() => {},
),
),
),
),
);
async saveSpecialComment() {
@@ -231,6 +237,38 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
return orderItems?.find((orderItem) => orderItem.data.id === orderItemListItem.orderItemId)?.data?.features?.orderType;
}
getOrderItemTrackingData(orderItemListItem: OrderItemListItemDTO): Array<{ trackingProvider: string; trackingNumber: string }> {
const orderItems = this.order?.items;
const completeTrackingInformation = orderItems
?.find((orderItem) => orderItem.data.id === orderItemListItem.orderItemId)
?.data?.subsetItems?.find((subsetItem) => subsetItem.id === orderItemListItem.orderItemSubsetId)?.data?.trackingNumber;
if (!completeTrackingInformation) {
return;
}
// Beispielnummer: 'DHL: 124124' - Bei mehreren Tracking-Informationen muss noch ein Splitter eingebaut werden, je nach dem welcher Trenner verwendet wird
const trackingInformationPairs = completeTrackingInformation.split(':').map((obj) => obj.trim());
return this._trackingTransformationHelper(trackingInformationPairs);
}
// Macht aus einem String Array ein Array von Objekten mit den keys trackingProvider und trackingNumber
private _trackingTransformationHelper(trackingInformationPairs: string[]): Array<{ trackingProvider: string; trackingNumber: string }> {
return trackingInformationPairs.reduce(
(acc, current, index, array) => {
if (index % 2 === 0) {
acc.push({ trackingProvider: current, trackingNumber: array[index + 1] });
}
return acc;
},
[] as { trackingProvider: string; trackingNumber: string }[],
);
}
getTrackingNumberLink(trackingNumber: string) {
return `https://www.dhl.de/de/privatkunden/dhl-sendungsverfolgung.html?piececode=${trackingNumber}`;
}
triggerResize() {
this.autosize.reset();
}

View File

@@ -345,7 +345,7 @@ export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnD
if (action.command.includes('ARRIVED')) {
navigateTo = await this.arrivedActionNavigation();
}
if (action.command.includes('PRINT_PRICEDIFFQRCODELABEL')) {
if (action.command.includes('PRINT_PRICEDIFFQRCODELABEL') || action.command.includes('BACKTOSTOCK')) {
navigateTo = 'main';
}

View File

@@ -22,7 +22,7 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
processId = Number(this._activatedRoute?.parent?.snapshot?.data?.processId);
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
@ViewChild(FilterComponent, { static: false })
uiFilter: FilterComponent;
@@ -79,7 +79,7 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
private _environment: EnvironmentService,
private _activatedRoute: ActivatedRoute,
public application: ApplicationService,
private _navigationService: CustomerOrdersNavigationService
private _navigationService: CustomerOrdersNavigationService,
) {}
ngOnInit() {

View File

@@ -23,7 +23,7 @@ import { CustomerOrdersNavigationService } from '@shared/services';
],
})
export class CustomerOrderSearchComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
private _processId$: Observable<number>;
get isTablet() {
@@ -35,7 +35,7 @@ export class CustomerOrderSearchComponent implements OnInit, OnDestroy {
private _navigationService: CustomerOrdersNavigationService,
private _breadcrumb: BreadcrumbService,
private _activatedRoute: ActivatedRoute,
private _environment: EnvironmentService
private _environment: EnvironmentService,
) {}
ngOnInit() {

View File

@@ -5,7 +5,6 @@ import { CustomerOrderSearchComponent } from './customer-order-search.component'
import { CustomerOrderSearchFilterComponent, OrderBranchIdInputComponent } from './customer-order-search-filter';
import { RouterModule } from '@angular/router';
import { UiSpinnerModule } from '@ui/spinner';
import { CustomerOrderSearchStore } from './customer-order-search.store';
import { IconComponent, IconModule } from '@shared/components/icon';
import { FilterModule } from '@shared/components/filter';
import { CustomerOrderSearchMainModule } from './search-main';
@@ -22,7 +21,6 @@ import { CustomerOrderSearchMainModule } from './search-main';
CustomerOrderSearchMainModule,
],
exports: [CustomerOrderSearchComponent],
providers: [CustomerOrderSearchStore],
declarations: [CustomerOrderSearchComponent, CustomerOrderSearchFilterComponent],
})
export class CustomerOrderSearchModule {}

View File

@@ -7,7 +7,7 @@ import { Filter } from '@shared/components/filter';
import { BranchDTO, ListResponseArgsOfOrderItemListItemDTO, OrderItemListItemDTO, QuerySettingsDTO } from '@swagger/oms';
import { isResponseArgs } from '@utils/object';
import { Observable, Subject } from 'rxjs';
import { switchMap, tap, withLatestFrom } from 'rxjs/operators';
import { switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
export interface CustomerOrderSearchState {
defaultSettings?: QuerySettingsDTO;
@@ -125,7 +125,12 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
searchStarted = new Subject<{ clear?: boolean; silentReload?: boolean }>();
constructor(private _domainGoodsInService: DomainCustomerOrderService, private _cache: CacheService) {
cancelSearch$ = new Subject<void>();
constructor(
private _domainGoodsInService: DomainCustomerOrderService,
private _cache: CacheService,
) {
super({
fetching: false,
silentFetching: false,
@@ -192,10 +197,15 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
});
}
cancelSearchRequest() {
this.cancelSearch$.next();
this.patchState({ fetching: false, silentFetching: false });
}
search = this.effect((options$: Observable<{ clear?: boolean; siletReload?: boolean }>) =>
options$.pipe(
tap((_) => {
this.searchStarted.next();
tap((opt) => {
this.searchStarted.next(opt);
this.patchState({ message: undefined });
}),
withLatestFrom(this.results$, this.filter$, this.selectedBranch$),
@@ -238,6 +248,7 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
}
return this._domainGoodsInService.search(queryToken).pipe(
takeUntil(this.cancelSearch$),
tapResponse(
(res) => {
let _results: OrderItemListItemDTO[] = [];
@@ -281,10 +292,10 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
}
this.patchState({ fetching: false, silentFetching: false });
console.error('GoodsInSearchStore.search()', err);
}
)
},
),
);
})
)
}),
),
);
}

View File

@@ -17,10 +17,7 @@ import { ApplicationService } from '@core/application';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
filter$ = this._customerOrderSearchStore.filter$.pipe(
filter((f) => !!f),
take(1)
);
filter$ = this._customerOrderSearchStore.filter$.pipe(filter((f) => !!f));
loading$ = this._customerOrderSearchStore.fetching$;

View File

@@ -12,7 +12,7 @@
>
<div class="page-customer-order-item__item-thumbnail text-center mr-4 w-[3.125rem] h-[4.9375rem]">
<img
class="page-customer-order-item__item-image w-[3.125rem] h-[4.9375rem]"
class="page-customer-order-item__item-image w-[3.125rem] max-h-[4.9375rem]"
loading="lazy"
*ngIf="item?.product?.ean | productImage; let productImage"
[src]="productImage"

View File

@@ -42,8 +42,10 @@ export interface CustomerOrderSearchResultsState {
styleUrls: ['customer-order-search-results.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerOrderSearchResultsComponent extends ComponentStore<CustomerOrderSearchResultsState>
implements OnInit, AfterViewInit, OnDestroy {
export class CustomerOrderSearchResultsComponent
extends ComponentStore<CustomerOrderSearchResultsState>
implements OnInit, AfterViewInit, OnDestroy
{
@ViewChildren(CustomerOrderItemComponent) listItems: QueryList<CustomerOrderItemComponent>;
@ViewChild(UiScrollContainerComponent) scrollContainer: UiScrollContainerComponent;
@@ -63,7 +65,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
listEmpty$ = combineLatest([this.loading$, this.hits$]).pipe(
map(([loading, hits]) => !loading && hits === 0),
shareReplay()
shareReplay(),
);
message$ = this._customerOrderSearchStore.message$;
@@ -76,9 +78,9 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
actions$ = combineLatest([this.items$, this.selectedOrderItemSubsetIds$]).pipe(
map(([items, selectedOrderItemSubsetIds]) =>
items?.find((item) => selectedOrderItemSubsetIds.find((orderItemSubsetId) => item.orderItemSubsetId === orderItemSubsetId))
items?.find((item) => selectedOrderItemSubsetIds.find((orderItemSubsetId) => item.orderItemSubsetId === orderItemSubsetId)),
),
map((item) => item?.actions?.filter((action) => this.selectionRules(action)))
map((item) => item?.actions?.filter((action) => this.selectionRules(action))),
);
get selectedItems() {
@@ -99,7 +101,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
processId$ = this._activatedRoute.parent.data.pipe(map((data) => +data.processId));
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
trackByFn: TrackByFunction<OrderItemListItemDTO> = (index, item) => `${item.orderId}${item.orderItemId}${item.orderItemSubsetId}`;
@@ -108,7 +110,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
filter$ = this._customerOrderSearchStore.filter$.pipe(filter((f) => !!f));
hasFilter$ = combineLatest([this.filter$, this._customerOrderSearchStore.defaultSettings$]).pipe(
map(([filter, defaultFilter]) => !isEqual(filter?.getQueryParams(), Filter.create(defaultFilter).getQueryParams()))
map(([filter, defaultFilter]) => !isEqual(filter?.getQueryParams(), Filter.create(defaultFilter).getQueryParams())),
);
get isTablet$() {
@@ -148,7 +150,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
private _navigationService: CustomerOrdersNavigationService,
private _application: ApplicationService,
private _cache: CacheService,
private _router: Router
private _router: Router,
) {
super({
selectedOrderItemSubsetIds: [],
@@ -166,17 +168,16 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
const clean = { ...queryParams };
delete clean['updateResults'];
}
})
}),
);
this._searchResultSubscription.add(
this.processId$
combineLatest([this.processId$, this._activatedRoute.queryParams])
.pipe(
debounceTime(150),
withLatestFrom(this._activatedRoute.queryParams),
switchMap(([processId, params]) =>
this._application.getSelectedBranch$(processId).pipe(map((selectedBranch) => ({ processId, params, selectedBranch })))
)
this._application.getSelectedBranch$(processId).pipe(map((selectedBranch) => ({ processId, params, selectedBranch }))),
),
)
.subscribe(async ({ processId, params, selectedBranch }) => {
const branchChanged = selectedBranch?.id !== this._customerOrderSearchStore?.selectedBranch?.id;
@@ -185,16 +186,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
if (processChanged) {
if (!!this._customerOrderSearchStore.processId && this._customerOrderSearchStore.filter instanceof Filter) {
const queryToken = {
...this._customerOrderSearchStore.filter?.getQueryParams(),
processId,
branchId: String(selectedBranch?.id),
};
this._customerOrderSearchStore.setCache({
queryToken,
hits: this._customerOrderSearchStore.hits,
results: this._customerOrderSearchStore.results,
});
await this.updateBreadcrumb(processId, this._customerOrderSearchStore.filter?.getQueryParams());
}
this._customerOrderSearchStore.patchState({ processId });
@@ -212,6 +203,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()))) {
this._customerOrderSearchStore.setQueryParams(params);
const queryToken = {
...this._customerOrderSearchStore.filter.getQueryParams(),
processId,
@@ -246,14 +238,14 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
const process = await this._application.getProcessById$(processId).pipe(first()).toPromise();
if (!!process) {
await this.updateBreadcrumb(processId, params);
await this.createBreadcrumb(processId, params);
await this.updateBreadcrumb(processId, params);
}
if (this._activatedRoute?.outlet === 'primary') {
await this.removeDetailsBreadcrumb(processId);
}
})
}),
);
this._searchResultSubscription.add(
@@ -271,12 +263,12 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
await this.navigateToDetails(
processId,
result?.results?.result?.find((_) => true),
queryParams
queryParams,
);
} else if ((!!result?.clear || this._activatedRoute.outlet === 'primary') && this.isDesktopLarge) {
await this._navigationService.getCustomerOrdersResultsPath(processId, { queryParams }).navigate();
}
})
}),
);
this._customerOrderSearchStore.searchResultClearedSubject.pipe(takeUntil(this._onDestroy$)).subscribe((_) => this.clearSelectedItems());
@@ -288,13 +280,13 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
...this.cleanupQueryParams(this._customerOrderSearchStore?.filter?.getQueryParams()),
main_qs: this.sharedFilterInputGroupMain?.uiInput?.value,
};
this._customerOrderSearchStore?.setQueryParams(queryParams);
})
}),
);
this._router.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
if (event instanceof NavigationStart) {
this.cacheResults();
this._addScrollPositionToCache();
}
});
@@ -312,7 +304,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
if (this._activatedRoute.outlet === 'primary') {
this._cache.set<number>(
{ processId: this._customerOrderSearchStore.processId, token: this.SCROLL_POSITION_TOKEN },
this.scrollContainer?.scrollPos
this.scrollContainer?.scrollPos,
);
}
}
@@ -335,18 +327,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
this._onDestroy$.complete();
this._searchResultSubscription.unsubscribe();
const queryToken = {
...this._customerOrderSearchStore.filter?.getQueryParams(),
processId: this._customerOrderSearchStore.processId,
branchId: String(this._customerOrderSearchStore.selectedBranch?.id),
};
this._customerOrderSearchStore.setCache({
queryToken,
hits: this._customerOrderSearchStore.hits,
results: this._customerOrderSearchStore.results,
});
await this.updateBreadcrumb(this._customerOrderSearchStore.processId, this._customerOrderSearchStore.filter?.getQueryParams());
}
@@ -364,25 +344,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
return clean;
}
async removeBreadcrumbs(processId: number) {
const editCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'edit']).pipe(first()).toPromise();
const historyCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'history'])
.pipe(first())
.toPromise();
editCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
historyCrumbs.forEach((crumb) => {
this._breadcrumb.removeBreadcrumb(crumb.id, true);
});
await this.removeDetailsBreadcrumb(processId);
}
async removeDetailsBreadcrumb(processId: number) {
const detailsCrumbs = await this._breadcrumb
.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'details'])
@@ -394,7 +355,19 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
});
}
async createMainBreadcrumb(processId: number, params: Record<string, string>) {
await this._breadcrumb.addBreadcrumbIfNotExists({
key: processId,
name: 'Kundenbestellung',
path: this._navigationService.getCustomerOrdersBasePath(processId).path,
params,
tags: ['customer-order', 'main', 'filter'],
section: 'customer',
});
}
async createBreadcrumb(processId: number, params: Record<string, string>) {
await this.createMainBreadcrumb(processId, params);
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: processId,
name: this.getBreadcrumbName(params),
@@ -423,6 +396,20 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
}
}
cacheResults() {
const queryToken = {
...this._customerOrderSearchStore.filter?.getQueryParams(),
processId: this._customerOrderSearchStore.processId,
branchId: String(this._customerOrderSearchStore.selectedBranch?.id),
};
this._customerOrderSearchStore.setCache({
queryToken,
hits: this._customerOrderSearchStore.hits,
results: this._customerOrderSearchStore.results,
});
}
getBreadcrumbName(params: Record<string, string>) {
const input = params?.main_qs;
@@ -442,8 +429,8 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
search({ filter, clear = false }: { filter?: Filter; clear?: boolean }) {
if (!!filter) {
this.sharedFilterInputGroupMain.cancelAutocomplete();
this._customerOrderSearchStore.setQueryParams(filter?.getQueryParams());
}
this._customerOrderSearchStore.search({ clear });
}

View File

@@ -3,18 +3,21 @@ import { ActivatedRoute } from '@angular/router';
import { ApplicationService } from '@core/application';
import { AuthService } from '@core/auth';
import { EnvironmentService } from '@core/environment';
import { provideComponentStore } from '@ngrx/component-store';
import { BranchSelectorComponent } from '@shared/components/branch-selector';
import { BreadcrumbComponent } from '@shared/components/breadcrumb';
import { BranchDTO } from '@swagger/checkout';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { Observable, Subject, fromEvent } from 'rxjs';
import { first, map, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
import { CustomerOrderSearchStore } from './customer-order-search';
@Component({
selector: 'page-customer-order',
templateUrl: 'customer-order.component.html',
styleUrls: ['customer-order.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [provideComponentStore(CustomerOrderSearchStore)],
})
export class CustomerOrderComponent implements OnInit {
@ViewChild(BreadcrumbComponent, { read: ElementRef }) breadcrumbRef: ElementRef<HTMLElement>;
@@ -27,7 +30,7 @@ export class CustomerOrderComponent implements OnInit {
return `${this.breadcrumbRef?.nativeElement?.clientWidth}px`;
}
_onDestroy$ = new Subject<boolean>();
_onDestroy$ = new Subject<void>();
get isTablet$() {
return this._environmentService.matchTablet$;
@@ -39,13 +42,24 @@ export class CustomerOrderComponent implements OnInit {
private _uiModal: UiModalService,
private _renderer: Renderer2,
private _environmentService: EnvironmentService,
public auth: AuthService
public auth: AuthService,
private _store: CustomerOrderSearchStore,
) {}
ngOnInit(): void {
this.selectedBranch$ = this.application.activatedProcessId$.pipe(
switchMap((processId) => this.application.getSelectedBranch$(Number(processId)))
switchMap((processId) => this.application.getSelectedBranch$(Number(processId))),
);
/* Ticket #4544 - Suchrequest abbrechen bei Prozesswechsel
/ um zu verhindern, dass die Suche in einen anderen Kundenbestellungen Prozess übernommen wird
/ bei Prozesswechsel zwischen 2 Kundenbestellungen Prozessen
*/
this.processId$.pipe(takeUntil(this._onDestroy$)).subscribe((processId) => {
if (Number(processId) !== this._store.processId) {
this._store.cancelSearchRequest();
}
});
}
ngAfterViewInit(): void {

View File

@@ -41,8 +41,10 @@ export interface CustomerTypeSelectorState {
},
],
})
export class CustomerTypeSelectorComponent extends ComponentStore<CustomerTypeSelectorState>
implements OnInit, OnDestroy, ControlValueAccessor {
export class CustomerTypeSelectorComponent
extends ComponentStore<CustomerTypeSelectorState>
implements OnInit, OnDestroy, ControlValueAccessor
{
@ViewChildren(UiCheckboxComponent)
checkboxes: QueryList<UiCheckboxComponent>;
@@ -133,7 +135,7 @@ export class CustomerTypeSelectorComponent extends ComponentStore<CustomerTypeSe
return result;
}),
shareReplay(1)
shareReplay(1),
);
}
@@ -144,7 +146,11 @@ export class CustomerTypeSelectorComponent extends ComponentStore<CustomerTypeSe
onChange = (value: string) => {};
onTouched = () => {};
constructor(private _checkoutService: DomainCheckoutService, private _cache: CacheService, private _cdr: ChangeDetectorRef) {
constructor(
private _checkoutService: DomainCheckoutService,
private _cache: CacheService,
private _cdr: ChangeDetectorRef,
) {
super({
processId: undefined,
customerType: undefined,
@@ -165,10 +171,10 @@ export class CustomerTypeSelectorComponent extends ComponentStore<CustomerTypeSe
this.patchState({ options });
this._cache.set('customerTypeOptions', options);
},
(err) => {}
)
)
)
(err) => {},
),
),
),
);
});
@@ -197,7 +203,7 @@ export class CustomerTypeSelectorComponent extends ComponentStore<CustomerTypeSe
setValue(value: { p4mUser?: boolean; customerType?: string } | string) {
const initial = { p4mUser: this.p4mUser, customerType: this.customerType };
if (isString(value)) {
if (typeof value === 'string') {
this.value = value;
} else {
if (isBoolean(value.p4mUser)) {

View File

@@ -38,7 +38,6 @@
(onInit)="addAddressGroup($event)"
(onDestroy)="removeAddressGroup()"
[data]="data?.address"
[tabIndexStart]="nameFormBlock?.tabIndexEnd + 1"
[requiredMarks]="addressRequiredMarks"
[validatorFns]="addressValidatorFns"
[readonly]="readonly"

View File

@@ -7,8 +7,7 @@
[tabindex]="tabIndexStart"
[autofocus]="focusAfterInit"
>
<shared-select-option [value]="2">Herr</shared-select-option>
<shared-select-option [value]="4">Frau</shared-select-option>
<shared-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value">{{ gender.label }}</shared-select-option>
</shared-select>
</shared-form-control>

View File

@@ -2,7 +2,7 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { FormBlockGroup } from '../form-block';
import { NameFormBlockData } from './name-form-block-data';
import { Gender } from '@swagger/crm';
import { GenderSettingsService } from '@shared/services';
@Component({
selector: 'app-name-form-block',
@@ -15,18 +15,7 @@ export class NameFormBlockComponent extends FormBlockGroup<NameFormBlockData> {
return this.tabIndexStart + 3;
}
displayGenderNameFn = (gender: Gender) => {
if (gender == 2) {
return 'Herr';
}
if (gender == 4) {
return 'Frau';
}
return undefined;
};
constructor() {
constructor(public genderSettings: GenderSettingsService) {
super();
}

View File

@@ -1,13 +1,13 @@
import { HttpErrorResponse } from '@angular/common/http';
import { ChangeDetectorRef, Directive, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';
import { AbstractControl, AsyncValidatorFn, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { AbstractControl, AsyncValidatorFn, UntypedFormControl, UntypedFormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { CrmCustomerService } from '@domain/crm';
import { AddressDTO, CustomerDTO, CustomerInfoDTO, PayerDTO, ShippingAddressDTO } from '@swagger/crm';
import { AddressDTO, CustomerDTO, PayerDTO, ShippingAddressDTO } from '@swagger/crm';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { UiValidators } from '@ui/validators';
import { isNull, merge } from 'lodash';
import { isNull } from 'lodash';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import {
first,
@@ -76,7 +76,7 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
protected modal: UiModalService,
protected breadcrumb: BreadcrumbService,
protected cdr: ChangeDetectorRef,
protected customerSearchNavigation: CustomerSearchNavigation
protected customerSearchNavigation: CustomerSearchNavigation,
) {
this._initProcessId$();
}
@@ -88,7 +88,7 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
map((data) => +data.processId),
tap((processId) => (this.latestProcessId = processId)),
distinctUntilChanged(),
shareReplay(1)
shareReplay(1),
);
this.processId$
@@ -106,7 +106,8 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
}
ngOnDestroy(): void {
this.updateBreadcrumb(this.latestProcessId, this.formData);
// Fix für #4676 - Breadcrumb wurde beim Schließen des Prozesses neu erstellt und nicht korrekt gelöscht
// this.updateBreadcrumb(this.latestProcessId, this.formData);
this.onDestroy$.next();
this.onDestroy$.complete();
this.busy$.complete();
@@ -117,7 +118,7 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
const formData = await this.activatedRoute.queryParams
.pipe(
map((params) => params['formData']),
first()
first(),
)
.toPromise();
@@ -189,6 +190,47 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
this.cdr.markForCheck();
}
minBirthDateValidator = (): ValidatorFn => {
return (control: AbstractControl): ValidationErrors | null => {
const minAge = 16; // 16 years
if (!control.value) {
return null;
}
const controlBirthDate = new Date(control.value);
const minBirthDate = new Date();
minBirthDate.setFullYear(minBirthDate.getFullYear() - minAge);
// Check if customer is over 16 years old
if (this._checkIfAgeOver16(controlBirthDate, minBirthDate)) {
return null;
} else {
return { minBirthDate: `Teilnahme ab ${minAge} Jahren` };
}
};
};
private _checkIfAgeOver16(inputDate: Date, minBirthDate: Date): boolean {
// Check year
if (inputDate.getFullYear() < minBirthDate.getFullYear()) {
return true;
}
// Check Year + Month
else if (inputDate.getFullYear() === minBirthDate.getFullYear() && inputDate.getMonth() < minBirthDate.getMonth()) {
return true;
}
// Check Year + Month + Day
else if (
inputDate.getFullYear() === minBirthDate.getFullYear() &&
inputDate.getMonth() === minBirthDate.getMonth() &&
inputDate.getDate() <= minBirthDate.getDate()
) {
return true;
}
return false;
}
emailExistsValidator: AsyncValidatorFn = (control) => {
return of(control.value).pipe(
tap((_) => this.customerExists$.next(false)),
@@ -209,7 +251,7 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
return of({ invalid: 'E-Mail ist ungültig' });
}
}
})
}),
);
}),
tap((error) => {
@@ -218,7 +260,7 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
}
control.markAsTouched();
this.cdr.markForCheck();
})
}),
);
};
@@ -265,13 +307,13 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
return of({ invalid: 'Kundenkartencode ist ungültig' });
}
}
})
}),
);
}),
tap(() => {
control.markAsTouched();
this.cdr.markForCheck();
})
}),
);
};
@@ -377,7 +419,7 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
} catch (error) {
this.form.enable();
setTimeout(() => {
this.addressFormBlock.setAddressValidationError(error.error.invalidProperties);
this.deviatingDeliveryAddressFormBlock.setAddressValidationError(error.error.invalidProperties);
}, 10);
return;

View File

@@ -41,7 +41,7 @@ export class CreateB2BCustomerComponent extends AbstractCreateCustomer {
deviatingNameRequiredMarks: (keyof NameFormBlockData)[] = ['gender', 'firstName', 'lastName'];
deviatingNameValidationFns: Record<string, ValidatorFn[]> = {
gender: [Validators.required, Validators.min(1)],
gender: [Validators.required],
firstName: [Validators.required],
lastName: [Validators.required],
};

View File

@@ -25,7 +25,7 @@ export class CreateGuestCustomerComponent extends AbstractCreateCustomer {
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
firstName: [Validators.required],
lastName: [Validators.required],
gender: [Validators.required, Validators.min(1)],
gender: [Validators.required],
title: [],
};
@@ -44,7 +44,7 @@ export class CreateGuestCustomerComponent extends AbstractCreateCustomer {
deviatingNameRequiredMarks: (keyof NameFormBlockData)[] = ['gender', 'firstName', 'lastName'];
deviatingNameValidationFns: Record<string, ValidatorFn[]> = {
gender: [Validators.required, Validators.min(1)],
gender: [Validators.required],
firstName: [Validators.required],
lastName: [Validators.required],
};

View File

@@ -64,6 +64,12 @@
>
</app-name-form-block>
<p class="info" *ngIf="customerType === 'webshop-p4m'">
Wir werden Ihnen Werbung zu ähnlichen Produkten oder Dienstleistungen aus unserem Sortiment per E-Mail zusenden. Sie können der
Verwendung Ihrer Daten jederzeit z.B. mittels der in den E-Mails enthaltenen Abmeldelinks widersprechen, ohne dass hierfür andere als
die Übermittlungskosten nach den Basistarifen entstehen.
</p>
<app-email-form-block
class="flex-grow"
#email

View File

@@ -36,7 +36,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
firstName: [Validators.required],
lastName: [Validators.required],
gender: [Validators.required, Validators.min(1)],
gender: [Validators.required],
title: [],
};
@@ -70,9 +70,9 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
agbValidatorFns = [Validators.requiredTrue];
birthDateValidatorFns = [Validators.required];
birthDateValidatorFns = [];
existingCustomer$: Observable<CustomerInfoDTO | null>;
existingCustomer$: Observable<CustomerInfoDTO | CustomerDTO | null>;
ngOnInit(): void {
super.ngOnInit();
@@ -84,7 +84,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
return this.fetchCustomerInfo();
}
return of(null);
})
}),
);
this.existingCustomer$
@@ -94,12 +94,12 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
if (info) {
return this.customerService.getCustomer(info.id, 2).pipe(
map((res) => res.result),
catchError((err) => NEVER)
catchError((err) => NEVER),
);
}
return NEVER;
}),
withLatestFrom(this.processId$)
withLatestFrom(this.processId$),
)
.subscribe(([customer, processId]) => {
if (customer) {
@@ -138,6 +138,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
initMarksAndValidators() {
this.asyncLoyaltyCardValidatorFn = [this.checkLoyalityCardValidator];
this.birthDateValidatorFns = [Validators.required, this.minBirthDateValidator()];
if (this._customerType === 'webshop') {
this.emailRequiredMark = true;
this.emailValidatorFn = [Validators.required, Validators.email, validateEmail];
@@ -165,7 +166,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
data: err,
});
return [null];
})
}),
);
}

View File

@@ -36,7 +36,7 @@ export class CreateStoreCustomerComponent extends AbstractCreateCustomer {
nameValidationFns: Record<string, ValidatorFn[]> = {
title: [],
gender: [Validators.required, Validators.min(1)],
gender: [Validators.required],
firstName: [Validators.required],
lastName: [Validators.required],
};

View File

@@ -25,7 +25,7 @@ export class CreateWebshopCustomerComponent extends AbstractCreateCustomer {
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
firstName: [Validators.required],
lastName: [Validators.required],
gender: [Validators.required, Validators.min(1)],
gender: [Validators.required],
title: [],
};

View File

@@ -22,14 +22,14 @@ export class UpdateP4MWebshopCustomerComponent extends AbstractCreateCustomer im
agbValidatorFns = [Validators.requiredTrue];
birthDateValidatorFns = [Validators.required];
birthDateValidatorFns = [Validators.required, this.minBirthDateValidator()];
nameRequiredMarks: (keyof NameFormBlockData)[] = ['gender', 'firstName', 'lastName'];
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
firstName: [Validators.required],
lastName: [Validators.required],
gender: [Validators.required, Validators.min(1)],
gender: [Validators.required],
title: [],
};

View File

@@ -13,8 +13,7 @@
<form [formGroup]="formGroup" (ngSubmit)="save()">
<shared-form-control label="Anrede">
<shared-select formControlName="gender" placeholder="Anrede" tabindex="1" [autofocus]="true">
<shared-select-option [value]="2">Herr</shared-select-option>
<shared-select-option [value]="4">Frau</shared-select-option>
<shared-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value">{{ gender.label }}</shared-select-option>
</shared-select>
</shared-form-control>

View File

@@ -9,7 +9,7 @@ import { map } from 'rxjs/operators';
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
import { AddressSelectionModalService } from '@shared/modals/address-selection-modal';
import { CustomerSearchStore } from '../store';
import { CustomerSearchNavigation } from '@shared/services';
import { CustomerSearchNavigation, GenderSettingsService } from '@shared/services';
import { IconComponent } from '@shared/components/icon';
import { combineLatest } from 'rxjs';
import { RouterLink } from '@angular/router';
@@ -35,7 +35,7 @@ import { RouterLink } from '@angular/router';
})
export class AddBillingAddressMainViewComponent {
formGroup = new FormGroup({
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
gender: new FormControl<Gender>(undefined, [Validators.required]),
title: new FormControl<string>(undefined),
firstName: new FormControl<string>(undefined, [Validators.required]),
lastName: new FormControl<string>(undefined, [Validators.required]),
@@ -59,7 +59,8 @@ export class AddBillingAddressMainViewComponent {
private _customerService: CrmCustomerService,
private _addressSelection: AddressSelectionModalService,
private _store: CustomerSearchStore,
private _navigation: CustomerSearchNavigation
private _navigation: CustomerSearchNavigation,
public genderSettings: GenderSettingsService
) {}
async save() {

View File

@@ -27,8 +27,7 @@
<shared-form-control label="Anrede">
<shared-select formControlName="gender" placeholder="Anrede" tabindex="4" [autofocus]="true">
<shared-select-option [value]="2">Herr</shared-select-option>
<shared-select-option [value]="4">Frau</shared-select-option>
<shared-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value">{{ gender.label }}</shared-select-option>
</shared-select>
</shared-form-control>

View File

@@ -9,7 +9,7 @@ import { map, takeUntil } from 'rxjs/operators';
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
import { AddressSelectionModalService } from '@shared/modals/address-selection-modal';
import { CustomerSearchStore } from '../store';
import { CustomerSearchNavigation } from '@shared/services';
import { CustomerSearchNavigation, GenderSettingsService } from '@shared/services';
import { Subject, combineLatest } from 'rxjs';
import { RouterLink } from '@angular/router';
import { IconComponent } from '@shared/components/icon';
@@ -41,7 +41,7 @@ export class AddShippingAddressMainViewComponent implements OnInit, OnDestroy {
);
formGroup = new FormGroup({
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
gender: new FormControl<Gender>(undefined, [Validators.required]),
title: new FormControl<string>(undefined),
firstName: new FormControl<string>(undefined, [Validators.required]),
lastName: new FormControl<string>(undefined, [Validators.required]),
@@ -65,7 +65,8 @@ export class AddShippingAddressMainViewComponent implements OnInit, OnDestroy {
private _customerService: CrmCustomerService,
private _addressSelection: AddressSelectionModalService,
private _store: CustomerSearchStore,
private _navigation: CustomerSearchNavigation
private _navigation: CustomerSearchNavigation,
public genderSettings: GenderSettingsService
) {}
ngOnInit() {

View File

@@ -1,6 +1,6 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, inject, effect, untracked } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { BehaviorSubject, Subject, Subscription } from 'rxjs';
import { BehaviorSubject, Subject, Subscription, fromEvent } from 'rxjs';
import { CustomerSearchStore } from './store/customer-search.store';
import { provideComponentStore } from '@ngrx/component-store';
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
@@ -8,6 +8,7 @@ import { delay, filter, first, switchMap, takeUntil } from 'rxjs/operators';
import { CustomerCreateNavigation, CustomerSearchNavigation } from '@shared/services';
import { CustomerSearchMainAutocompleteProvider } from './providers/customer-search-main-autocomplete.provider';
import { FilterAutocompleteProvider } from '@shared/components/filter';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({
selector: 'page-customer-search',
@@ -24,6 +25,10 @@ import { FilterAutocompleteProvider } from '@shared/components/filter';
],
})
export class CustomerSearchComponent implements OnInit, OnDestroy {
private searchStore = inject(CustomerSearchStore);
keyEscPressed = toSignal(fromEvent(document, 'keydown').pipe(filter((e: KeyboardEvent) => e.key === 'Escape')));
get breadcrumb() {
let breadcrumb: string;
@@ -38,7 +43,7 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
private _breadcrumbs$ = this._store.processId$.pipe(
filter((id) => !!id),
switchMap((id) => this._breadcrumbService.getBreadcrumbsByKeyAndTag$(id, 'customer'))
switchMap((id) => this._breadcrumbService.getBreadcrumbsByKeyAndTag$(id, 'customer')),
);
side$ = new BehaviorSubject<string | undefined>(undefined);
@@ -73,7 +78,7 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
private _router: Router,
private _breadcrumbService: BreadcrumbService,
private _navigation: CustomerSearchNavigation,
private _createNavigation: CustomerCreateNavigation
private _createNavigation: CustomerCreateNavigation,
) {}
ngOnInit(): void {
@@ -542,4 +547,11 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
this._breadcrumbService.removeBreadcrumb(orderDetailsHistoryBreadcrumb.id);
}
}
cancelSearchEffect = effect(() => {
const event = this.keyEscPressed();
untracked(() => {
this.searchStore.cancelSearch();
});
});
}

View File

@@ -39,7 +39,9 @@
<div class="customer-details-customer-main-data px-5 py-3 grid grid-flow-row gap-3">
<div class="flex flex-row">
<div class="data-label">Erstellungsdatum</div>
<div class="data-value">{{ created$ | async | date: 'dd.MM.yyyy' }} | {{ created$ | async | date: 'HH:mm' }} Uhr</div>
<div *ngIf="created$ | async; let created" class="data-value">
{{ created | date: 'dd.MM.yyyy' }} | {{ created | date: 'HH:mm' }} Uhr
</div>
</div>
<div class="flex flex-row">
<div class="data-label">Kundennummer</div>
@@ -111,7 +113,7 @@
</div>
<div class="customer-details-customer-main-row">
<div class="data-label">Land</div>
<div class="data-value">{{ country$ | async | country }}</div>
<div *ngIf="country$ | async; let country" class="data-value">{{ country | country }}</div>
</div>
<div class="customer-details-customer-main-row">
<div class="data-label">Festnetznr.</div>
@@ -123,6 +125,16 @@
</div>
<ng-container *ngIf="!(isBusinessKonto$ | async)">
<div class="customer-details-customer-main-row">
<div class="data-label">Geburtstag</div>
<div class="data-value">{{ dateOfBirth$ | async | date: 'dd.MM.yyyy' }}</div>
</div>
</ng-container>
<ng-container *ngIf="!(isBusinessKonto$ | async) && (organisationName$ | async)">
<div class="customer-details-customer-main-row">
<div class="data-label">Firmenname</div>
<div class="data-value">{{ organisationName$ | async }}</div>
</div>
<div class="customer-details-customer-main-row">
<div class="data-label">Abteilung</div>
<div class="data-value">{{ department$ | async }}</div>

View File

@@ -1,7 +1,7 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, inject } from '@angular/core';
import { Subject, combineLatest } from 'rxjs';
import { first, map, switchMap, takeUntil } from 'rxjs/operators';
import { CustomerSearchNavigation } from '@shared/services';
import { CustomerSearchNavigation, GenderSettingsService } from '@shared/services';
import { CustomerSearchStore } from '../store';
import { ShippingAddressDTO, NotificationChannel, ShoppingCartDTO, PayerDTO, BuyerDTO } from '@swagger/checkout';
import { DomainCheckoutService } from '@domain/checkout';
@@ -10,16 +10,11 @@ import { UiModalService } from '@ui/modal';
import { ComponentStore } from '@ngrx/component-store';
import { ApplicationService } from '@core/application';
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
import { Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { log, logAsync } from '@utils/common';
import { CrmCustomerService } from '@domain/crm';
import { MessageModalComponent, MessageModalData } from '@shared/modals/message-modal';
const GENDER_MAP = {
2: 'Herr',
4: 'Frau',
};
export interface CustomerDetailsViewMainState {
isBusy: boolean;
shoppingCart: ShoppingCartDTO;
@@ -34,11 +29,13 @@ export interface CustomerDetailsViewMainState {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDetailsViewMainState> implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _onDestroy$ = new Subject<void>();
customerService = inject(CrmCustomerService);
fetching$ = this._store.fetchingCustomer$;
fetching$ = combineLatest([this._store.fetchingCustomer$, this._store.fetchingCustomerList$]).pipe(
map(([fetchingCustomer, fetchingList]) => fetchingCustomer || fetchingList),
);
isBusy$ = this.select((s) => s.isBusy);
@@ -49,31 +46,31 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
customerId$ = this._store.customerId$;
historyRoute$ = combineLatest([this.processId$, this.customerId$]).pipe(
map(([processId, customerId]) => this._navigation.historyRoute({ processId, customerId }))
map(([processId, customerId]) => this._navigation.historyRoute({ processId, customerId })),
);
ordersRoute$ = combineLatest([this.processId$, this.customerId$]).pipe(
map(([processId, customerId]) => this._navigation.ordersRoute({ processId, customerId }))
map(([processId, customerId]) => this._navigation.ordersRoute({ processId, customerId })),
);
isB2b$ = this._store.isBusinessKonto$;
editRoute$ = combineLatest([this.processId$, this.customerId$, this.isB2b$]).pipe(
map(([processId, customerId, isB2b]) => this._navigation.editRoute({ processId, customerId, isB2b }))
map(([processId, customerId, isB2b]) => this._navigation.editRoute({ processId, customerId, isB2b })),
);
showEditButton$ = combineLatest([this._store.isOnlinekonto$, this._store.isBestellungOhneKonto$]).pipe(
map(([isOnlinekonto, isBestellungOhneKonto]) => !isOnlinekonto && !isBestellungOhneKonto)
map(([isOnlinekonto, isBestellungOhneKonto]) => !isOnlinekonto && !isBestellungOhneKonto),
);
hasKundenkarte$ = combineLatest([this._store.isKundenkarte$, this._store.isOnlineKontoMitKundenkarte$]).pipe(
map(([isKundenkarte, isOnlineKontoMitKundenkarte]) => isKundenkarte || isOnlineKontoMitKundenkarte)
map(([isKundenkarte, isOnlineKontoMitKundenkarte]) => isKundenkarte || isOnlineKontoMitKundenkarte),
);
kundenkarteRoute$ = combineLatest([this.hasKundenkarte$, this.processId$, this.customerId$]).pipe(
map(([hasKundenkarte, processId, customerId]) =>
hasKundenkarte ? this._navigation.kundenkarteRoute({ processId, customerId }) : undefined
)
hasKundenkarte ? this._navigation.kundenkarteRoute({ processId, customerId }) : undefined,
),
);
customerType$ = this._store.select((s) => s.customer?.features?.find((f) => f.enabled)?.description);
@@ -86,7 +83,7 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
customerNumberBeeline$ = this._store.select((s) => s.customer?.linkedRecords?.find((r) => r.repository === 'beeline')?.number);
gender$ = this._store.select((s) => GENDER_MAP[s.customer?.gender]);
gender$ = this._store.select((s) => this._genderSettings.getGenderByValue(s.customer?.gender));
title$ = this._store.select((s) => s.customer?.title);
@@ -124,6 +121,8 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
shoppingCartHasNoItems$ = this.shoppingCartHasItems$.pipe(map((hasItems) => !hasItems));
dateOfBirth$ = this._store.select((s) => s.customer?.dateOfBirth);
get isBusy() {
return this.get((s) => s.isBusy);
}
@@ -181,6 +180,10 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
return this.get((s) => s.payer);
}
get snapshot() {
return this._activatedRoute.snapshot;
}
constructor(
private _store: CustomerSearchStore,
private _navigation: CustomerSearchNavigation,
@@ -189,7 +192,9 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
private _application: ApplicationService,
private _catalogNavigation: ProductCatalogNavigationService,
private _checkoutNavigation: CheckoutNavigationService,
private _router: Router
private _router: Router,
private _activatedRoute: ActivatedRoute,
private _genderSettings: GenderSettingsService,
) {
super({ isBusy: false, shoppingCart: undefined, shippingAddress: undefined, payer: undefined });
}
@@ -216,12 +221,27 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
this._checkoutService.getShoppingCart({
processId: pid,
latest: true,
})
)
}),
),
)
.subscribe((shoppingCart) => {
this.patchState({ shoppingCart });
});
// #4564 Fix - Da der Kunde bei einer erneuten Suche auf undefined gesetzt wird, muss man diesen erneut nach erfolgreicher Suche setzen.
// Dies geschieht bereits in der customer-search.component.ts immer wenn eine Navigation ausgeführt wird (sprich für Fälle in denen ein neuer Kunde gesetzt wird).
// Falls aber nach exakt dem gleichen Kunden gesucht wird, muss man diesen hier manuell erneut setzen, ansonsten bleibt dieser im Store auf undefined.
// Da dies nur für die Detailansicht relevant ist, wird hier auch auf hits 1 überprüft, ansonsten befindet man sich sowieso in der Trefferliste.
this._store.customerListResponse$.pipe(takeUntil(this._onDestroy$)).subscribe(([result]) => {
if (result.hits === 1) {
const customerId = result?.result[0].id;
const selectedCustomerId = +this.snapshot.params.customerId;
if (customerId === selectedCustomerId) {
this._store.selectCustomer({ customerId });
}
}
});
}
ngOnDestroy() {
@@ -409,12 +429,12 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
@log
_patchProcessName() {
let name = `${this.customer.firstName} ${this.customer.lastName}`;
let name = `${this.customer.firstName ?? ''} ${this.customer.lastName ?? ''}`;
// Ticket #4458 Es kann vorkommen, dass B2B Konten keinen Firmennamen hinterlegt haben
// zusätzlich kanne es bei Mitarbeiter Konten vorkommen, dass die Namen in der Organisation statt im Kundennamen hinterlegt sind
if ((this._store.isBusinessKonto && this.customer.organisation?.name) || (!this.customer.firstName && !this.customer.lastName)) {
name = `${this.customer.organisation?.name}`;
name = `${this.customer.organisation?.name ?? ''}`;
}
this._application.patchProcess(this.processId, {

View File

@@ -13,8 +13,7 @@
<form [formGroup]="formGroup" (ngSubmit)="save()">
<shared-form-control label="Anrede">
<shared-select formControlName="gender" placeholder="Anrede" tabindex="1" [autofocus]="true">
<shared-select-option [value]="2">Herr</shared-select-option>
<shared-select-option [value]="4">Frau</shared-select-option>
<shared-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value">{{ gender.label }}</shared-select-option>
</shared-select>
</shared-form-control>

View File

@@ -9,7 +9,7 @@ import { map, switchMap, takeUntil } from 'rxjs/operators';
import { AsyncPipe, NgForOf } from '@angular/common';
import { AddressSelectionModalService } from '@shared/modals/address-selection-modal';
import { CustomerSearchStore } from '../store';
import { CustomerSearchNavigation } from '@shared/services';
import { CustomerSearchNavigation, GenderSettingsService } from '@shared/services';
import { ComponentStore } from '@ngrx/component-store';
import { Subject, combineLatest } from 'rxjs';
import { ActivatedRoute, RouterLink } from '@angular/router';
@@ -39,7 +39,7 @@ export class EditBillingAddressMainViewComponent extends ComponentStore<EditBill
private _cdr = inject(ChangeDetectorRef);
formGroup = new FormGroup({
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
gender: new FormControl<Gender>(undefined, [Validators.required]),
title: new FormControl<string>(undefined),
firstName: new FormControl<string>(undefined, [Validators.required]),
lastName: new FormControl<string>(undefined, [Validators.required]),
@@ -74,7 +74,8 @@ export class EditBillingAddressMainViewComponent extends ComponentStore<EditBill
private _store: CustomerSearchStore,
private _navigation: CustomerSearchNavigation,
private _activatedRoute: ActivatedRoute,
private _modal: UiModalService
private _modal: UiModalService,
public genderSettings: GenderSettingsService
) {
super({ payer: undefined });
}

View File

@@ -28,8 +28,7 @@
<ui-form-control [clearable]="true" label="Anrede" variant="inline">
<ui-select formControlName="gender" tabindex="4">
<ui-select-option [value]="2" label="Herr"></ui-select-option>
<ui-select-option [value]="4" label="Frau"></ui-select-option>
<ui-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value" [label]="gender.label"></ui-select-option>
</ui-select>
</ui-form-control>
<ui-form-control [clearable]="true" label="Titel" variant="inline">

View File

@@ -16,8 +16,7 @@
<form *ngIf="control" [formGroup]="control" (ngSubmit)="submit()">
<ui-form-control [clearable]="true" label="Anrede" variant="inline">
<ui-select formControlName="gender" tabindex="1">
<ui-select-option [value]="2" label="Herr"></ui-select-option>
<ui-select-option [value]="4" label="Frau"></ui-select-option>
<ui-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value" [label]="gender.label"></ui-select-option>
</ui-select>
</ui-form-control>
<ui-form-control [clearable]="true" label="Titel" variant="inline">

View File

@@ -12,7 +12,7 @@ import { validateEmail } from '../../validators/email-validator';
import { genderLastNameValidator } from '../../validators/gender-b2b-validator';
import { camelCase } from 'lodash';
import { CustomerSearchStore } from '../store';
import { CustomerSearchNavigation, NavigationRoute } from '@shared/services';
import { CustomerSearchNavigation, GenderSettingsService, NavigationRoute } from '@shared/services';
@Component({ template: '' })
export abstract class CustomerDataEditComponent implements OnInit {
@@ -41,7 +41,8 @@ export abstract class CustomerDataEditComponent implements OnInit {
private cdr: ChangeDetectorRef,
private location: Location,
private _store: CustomerSearchStore,
private _navigation: CustomerSearchNavigation
private _navigation: CustomerSearchNavigation,
public genderSettings: GenderSettingsService
) {}
ngOnInit() {
@@ -90,7 +91,7 @@ export abstract class CustomerDataEditComponent implements OnInit {
this.control = fb.group(
{
gender: fb.control(customerDTO?.gender, [isBranch ? Validators.min(1) : () => null, isBranch ? Validators.required : () => null]),
gender: fb.control(customerDTO?.gender, [isBranch ? Validators.required : () => null]),
title: fb.control(customerDTO?.title),
lastName: fb.control(customerDTO?.lastName, [Validators.required]),
firstName: fb.control(customerDTO?.firstName, [Validators.required]),

View File

@@ -27,8 +27,7 @@
<shared-form-control label="Anrede">
<shared-select formControlName="gender" placeholder="Anrede" tabindex="4" [autofocus]="true">
<shared-select-option [value]="2">Herr</shared-select-option>
<shared-select-option [value]="4">Frau</shared-select-option>
<shared-select-option *ngFor="let gender of genderSettings.genders" [value]="gender.value">{{ gender.label }}</shared-select-option>
</shared-select>
</shared-form-control>

View File

@@ -9,7 +9,7 @@ import { map, switchMap, takeUntil } from 'rxjs/operators';
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
import { AddressSelectionModalService } from '@shared/modals/address-selection-modal';
import { CustomerSearchStore } from '../store';
import { CustomerSearchNavigation } from '@shared/services';
import { CustomerSearchNavigation, GenderSettingsService } from '@shared/services';
import { Subject, combineLatest } from 'rxjs';
import { IconComponent } from '@shared/components/icon';
import { ActivatedRoute, RouterLink } from '@angular/router';
@@ -50,7 +50,7 @@ export class EditShippingAddressMainViewComponent extends ComponentStore<EditShi
);
formGroup = new FormGroup({
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
gender: new FormControl<Gender>(undefined, [Validators.required]),
title: new FormControl<string>(undefined),
firstName: new FormControl<string>(undefined, [Validators.required]),
lastName: new FormControl<string>(undefined, [Validators.required]),
@@ -83,7 +83,8 @@ export class EditShippingAddressMainViewComponent extends ComponentStore<EditShi
private _customerService: CrmCustomerService,
private _addressSelection: AddressSelectionModalService,
private _store: CustomerSearchStore,
private _navigation: CustomerSearchNavigation
private _navigation: CustomerSearchNavigation,
public genderSettings: GenderSettingsService
) {
super({ shippingAddress: undefined });
}

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