Compare commits

...

273 Commits

Author SHA1 Message Date
Nino
5dbcc5b5f1 #4546 HSC Customer Orders Fixed Navigation Link Active Styling 2023-12-28 14:32:47 +01: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
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
Lorenz Hilpert
f0b653fd0f Merged PR 1699: #4533 Breadcrumb wird falsch dargestellt
#4533 Breadcrumb wird falsch dargestellt
2023-12-20 16:17:39 +00:00
Lorenz Hilpert
14eba6e5ea Merged PR 1698: #4485 Kundendaten werden übernommen wenn welche vorhanden sind
#4485 Kundendaten werden übernommen wenn welche vorhanden sind
2023-12-20 14:55:04 +00:00
Nino Righi
5073693fc2 Merged PR 1697: #4530 Notification Batch Messages for each type
#4530 Notification Batch Messages for each type
2023-12-19 13:34:22 +00:00
Nino Righi
f3cb6236a5 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
2023-12-15 16:03:34 +00:00
Lorenz Hilpert
4ab9890313 #4485 #4500 Kundenkartenkonto anlage Verhalten and Suche angeglichen 2023-12-15 14:34:24 +01:00
Nino Righi
a8cd6ce844 Merged PR 1693: #4522 Pickup Shelf List Added margin-bottom as gap between item groups, remov...
#4522 Pickup Shelf List Added margin-bottom as gap between item groups, removed unused code
2023-12-12 14:18:07 +00:00
Lorenz Hilpert
3bba23cc76 Merged PR 1692: #4380 Abholung als Standard wenn möglich und keine Auswahl getroffen wurde
#4380 Abholung als Standard wenn möglich und keine Auswahl getroffen wurde
2023-12-12 12:43:13 +00:00
Nino Righi
e25f176a7b Merged PR 1691: #4521 Fix Check Page Catalog List if List got already fetched completely
#4521 Fix Check Page Catalog List if List got already fetched completely
2023-12-12 09:39:22 +00:00
Nino Righi
a169d2a4e9 Merged PR 1690: #4518 Fix Pickup Shelf In List additionally Fetch Items after queryParams change
#4518 Fix Pickup Shelf In List additionally Fetch Items after queryParams change
2023-12-11 15:55:32 +00:00
Nino Righi
6a9caa432e Merged PR 1689: #4519 #4520 Page Catalog Search Breadcrumb and Navigation fixes
#4519 #4520 Page Catalog Search Breadcrumb and Navigation fixes
2023-12-11 09:23:35 +00:00
Nino Righi
389948c077 Merged PR 1688: #4510 Fix Time Format in Customer Details
#4510 Fix Time Format in Customer Details
2023-12-08 09:30:29 +00:00
Nino Righi
e7724ed8b9 Merged PR 1687: #4509 Implemented Loading Spinner on Pickup Shelf Details Pages
#4509 Implemented Loading Spinner on Pickup Shelf Details Pages
2023-12-07 10:03:39 +00:00
Nino Righi
c7f1b27fdf Merged PR 1686: #4508 Pickup Shelf In Clear Cover Items after Routing to new Details page
#4508 Pickup Shelf In Clear Cover Items after Routing to new Details page
2023-12-06 15:18:55 +00:00
Lorenz Hilpert
135f0255b8 IPad2 PDP Styling 2023-12-06 16:18:06 +01:00
Nino Righi
65a7aa569d Merged PR 1685: Scroll Position Fix After Process Change
Scroll Position Fix After Process Change
2023-12-05 18:14:32 +00:00
Nino Righi
211eaa6175 Merged PR 1684: Page Catalog Result List - Removed maxBufferSize, Updated ScrollPosition Handling
Page Catalog Result List - Removed maxBufferSize, Updated ScrollPosition Handling
2023-12-05 16:59:23 +00:00
Lorenz Hilpert
8097c6ad9e Merge branch 'performance' into develop 2023-12-05 10:48:17 +01:00
Lorenz Hilpert
b0d76b01d7 Merge branch 'release/3.0' into develop 2023-12-05 10:47:34 +01:00
Lorenz Hilpert
626fd0081f Merge branch 'develop' into release/3.0 2023-12-05 10:47:00 +01:00
Lorenz Hilpert
362fca74bc Warenausgbae list IPad 2 2023-12-04 16:29:14 +01:00
Lorenz Hilpert
b8f0a29f79 IPad 2 Scrolling 2023-12-04 16:22:39 +01:00
Lorenz Hilpert
f54400f00d Merge branch 'develop' into performance 2023-12-04 14:23:30 +01:00
Lorenz Hilpert
54094695b1 #4505 RD // Einbuchen - Bearbeiten Seite wird mal in Split Screen Ansicht mal ohne angezeigt 2023-12-04 13:27:43 +01:00
Nino Righi
1e3e9588da Merged PR 1683: #4501 #4502 Fixed by reverting Changes from #4490
#4501 #4502 Fixed by reverting Changes from #4490
2023-12-04 11:43:07 +00:00
Nino
f04705b659 Merge branch 'release/3.0' into develop 2023-12-01 17:04:02 +01:00
Nino
c22672fad0 Fixed Styling and Layout Issues 2023-12-01 13:07:59 +01:00
Nino
59673a47db Tablet size fix, Navigate After SearchCompleted on Page Catalog and Page Customer Orders 2023-11-30 18:24:32 +01:00
Nino
e56ea0bd4e MaxBufferSize Update Result List Page Catalog 2023-11-30 18:15:58 +01:00
Nino Righi
f8c4d4a842 Merged PR 1682: #4416 RD Checkout, WA, WE, Notifications Component Changes
#4416 RD Checkout, WA, WE, Notifications Component Changes
2023-11-30 16:06:37 +00:00
Lorenz Hilpert
c4dd9214a3 Splitscreen side outlet rendern nur wenn nötig 2023-11-30 16:55:08 +01:00
Nino Righi
4d74b3a89e Merged PR 1679: #4491 Mark Shelf Children Sides Active based on view param
#4491 Mark Shelf Children Sides Active based on view param
2023-11-29 15:31:35 +00:00
Nino Righi
b0b3fd40ce Merged PR 1681: #4490 Improvement added Filter parameter orderitemprocessingstatus on details...
#4490 Improvement added Filter parameter orderitemprocessingstatus on details page request
2023-11-29 15:04:58 +00:00
Lorenz Hilpert
3404c930c5 #4485 - anlage von Kundenkartenkonto - Datenübernahme 2023-11-29 16:04:18 +01:00
Nino Righi
abcd940ed3 Merged PR 1678: #4493 Fix Breadcrumb Navigation if comming from other List
#4493 Fix Breadcrumb Navigation if comming from other List
2023-11-29 10:45:51 +00:00
Nino Righi
c1756942b2 Merged PR 1680: #4492 Added Navigation based On View after certain Actions
#4492 Added Navigation based On View after certain Actions
2023-11-29 10:45:14 +00:00
Lorenz Hilpert
ea4d036066 Merge branch 'develop' into release/3.0 2023-11-24 09:51:21 +01:00
Lorenz Hilpert
101a34bd3f #4484 RD // Online Konto anlegen via "Kundensuche | Keine Suchergebnisse" wirft Fehlermeldung 2023-11-23 18:06:49 +01:00
Lorenz Hilpert
99bad149cb attribute für e2e tests 2023-11-23 13:57:55 +01:00
Lorenz Hilpert
a0bff7164c #4481 RD // Warenkorb - Klick auf "Speichern" bei Benachrichtigung "Email" oder "SMS" enabled "Bestellen"-Button nicht 2023-11-22 15:30:07 +01:00
Lorenz Hilpert
0c4a4130b9 #4488 RD // TK - Artikel-Link führt auf Dashboard 2023-11-21 15:58:38 +01:00
Lorenz Hilpert
8dd1211729 #4487 RD // Abholfach - ändern des vsl. Lieferdatums und "zurückgelegt bis"-Datum wirft Fehler 2023-11-21 10:42:14 +01:00
Lorenz Hilpert
a2f1b8b624 #4478 RD // Abholfach - Routing löst Suche aus 2023-11-20 14:22:32 +01:00
Lorenz Hilpert
98a331ffe5 #4482 RD // Abholfach - Split-Screen raus bei "Bearbeiten" + ""Historie" 2023-11-20 13:38:11 +01:00
Lorenz Hilpert
c0f97c9bae Add doNotTrack option to article search 2023-11-10 18:52:23 +01:00
Lorenz Hilpert
960ffa165f Merge branch 'develop' into release/3.0 2023-11-10 18:33:13 +01:00
Lorenz Hilpert
6bdfbe2eff Refactor breadcrumb filtering in side menu
component
2023-11-10 18:32:56 +01:00
Lorenz Hilpert
80342e61ac #4470 Updated icon names in icons.json 2023-11-10 16:31:27 +01:00
Lorenz Hilpert
6bf3894e4d Add data attributes to pickup shelf list item
component HTML
2023-11-10 15:07:11 +01:00
Lorenz Hilpert
aab29838bf #4470 Add new customer order types to icons and label
colors
2023-11-10 14:35:38 +01:00
Lorenz Hilpert
856ca5651e #4475 Refactor remission-list.component.html button
styles
2023-11-10 14:31:57 +01:00
Lorenz Hilpert
a7d4b8d7fb #4273 Refactor navigation logic in search results and
product card components
2023-11-10 14:09:01 +01:00
Lorenz Hilpert
62d260473c #4474 Refactor clearFilter method to use main_qs from
filter query params
2023-11-09 11:50:02 +01:00
Lorenz Hilpert
bde52a2526 Merge branch 'release/3.0' into develop 2023-11-09 11:39:41 +01:00
Lorenz Hilpert
6243b03cfc #4466 Revert changes to fix display issue in
PickupShelfDetailsBaseComponent
2023-11-08 16:39:03 +01:00
Lorenz Hilpert
d24841800e #4468 Refactor setSelectedOrderItemQuantity to use
updater
2023-11-08 13:39:31 +01:00
Nino Righi
f60628c769 Merged PR 1677: #4467 Fix Display Price even if Price is 0
#4467 Fix Display Price even if Price is 0
2023-11-08 11:55:55 +00:00
Nino Righi
034f697da5 Merged PR 1676: #4462 Added focusSearchbox additionally on side nav click
#4462 Added focusSearchbox additionally on side nav click
2023-11-08 11:55:13 +00:00
Lorenz Hilpert
ec9f80767b Merge branch 'release/3.0' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into release/3.0 2023-11-08 11:05:32 +01:00
Lorenz Hilpert
a5e569cf05 #4451 #4463 Add name method to PickupShelfInService and
PickupShelfOutService
2023-11-08 11:04:58 +01:00
Nino Righi
b62259f9b4 Merged PR 1675: #4457 Fix Filter Routing on Close
#4457 Fix Filter Routing on Close
2023-11-08 09:42:24 +00:00
Nino Righi
95baeaa8a8 Merged PR 1674: #4462 Cursor Should Select Searchbox on Initial Navigation
#4462 Cursor Should Select Searchbox on Initial Navigation
2023-11-07 16:49:56 +00:00
Nino Righi
a5b9115a91 Merged PR 1673: #4459 Fix Reorder Modal handling after Closing Modal without changes
#4459 Fix Reorder Modal handling after Closing Modal without changes
2023-11-07 16:03:23 +00:00
Lorenz Hilpert
1885c58d86 #4464 Add setSelectedOrderItemQuantity method to update
selected order item quantity
2023-11-07 17:01:57 +01:00
Lorenz Hilpert
add55a47d6 Merge branches 'release/3.0' and 'release/3.0' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into release/3.0 2023-11-07 16:29:37 +01:00
Lorenz Hilpert
129f49a9ee #4461 Add sharedRegexRouterLinkActive directive to
side-menu component
2023-11-07 16:29:18 +01:00
Nino Righi
9560eb7ad6 Merged PR 1671: #4458 Fixed Process Naming
#4458 Fixed Process Naming
2023-11-07 15:09:18 +00:00
Nino Righi
772ba29a8e Merged PR 1672: #4457 Fixed routing after Filter close
#4457 Fixed routing after Filter close
2023-11-07 15:08:31 +00:00
Nino Righi
8ac8f6cc1f Merged PR 1670: #4459 Removed Side Outlet after REORDER completed
#4459 Removed Side Outlet after REORDER completed
2023-11-07 12:23:46 +00:00
Lorenz Hilpert
a0f496475c #4453 Add customerInfoDTO to createCustomerRoute 2023-11-06 15:21:36 +01:00
Lorenz Hilpert
8979a388ee #4454 Fix button placement and remove unnecessary
sorting in main actions selector
2023-11-06 14:27:08 +01:00
Lorenz Hilpert
8b9a209c49 #4450 Add RunCheckTrigger to
PickupShelfInDetailsComponent and
PickupShelfOutDetailsComponent
2023-11-06 14:07:27 +01:00
Lorenz Hilpert
f4c3e3ceee #4452 Reset selected compartment info in
PickupShelfDetailsBaseComponent
2023-11-06 13:55:40 +01:00
Lorenz Hilpert
5ca8a83f25 Merge branch 'release/3.0' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into release/3.0 2023-11-03 15:19:10 +01:00
Lorenz Hilpert
006011885f #4448 Add "Create new customer" link to search views 2023-11-03 15:18:50 +01:00
Nino Righi
9c9e061f6d Merged PR 1669: #4431 Adjusted Process Tab Logic for Customer Order Area
#4431 Adjusted Process Tab Logic for Customer Order Area
2023-11-03 09:13:10 +00:00
Lorenz Hilpert
c9782a7d29 Merge branch 'develop' into release/3.0 2023-10-30 09:42:49 +01:00
Nino Righi
59de82def8 Merged PR 1668: #4425 HSC Customer Orders Result List Refactor - Cache, Breadcrumb, Branch Dr...
#4425 HSC Customer Orders Result List Refactor - Cache, Breadcrumb, Branch Dropdown, Process and Scroll Position Fixes
2023-10-27 14:57:05 +00:00
Lorenz Hilpert
dc2617bb5d #4429 Refactor orderItems update in
PickupShelfDetailsStore
2023-10-27 15:14:28 +02:00
Lorenz Hilpert
d80e621563 #4424 Add filter functionality to
getOrderItemsByOrderNumberOrCompartmentCode method
2023-10-27 14:56:44 +02:00
Lorenz Hilpert
63c02e4605 #4424 Add orderItemSubsetId to order item details. 2023-10-27 11:57:42 +02:00
Lorenz Hilpert
3f93fe0869 #4423 Add download availability to shopping cart item 2023-10-27 10:51:44 +02:00
Lorenz Hilpert
9011f76e95 Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2023-10-27 10:26:05 +02:00
Lorenz Hilpert
dd88e4ad3e Fix breadcrumb filter in side menu component 2023-10-27 10:25:48 +02:00
Nino Righi
a0869aa4a5 Merged PR 1667: #4421 Reset Filter When Navigate from Article Recommendations
#4421 Reset Filter When Navigate from Article Recommendations
2023-10-26 14:54:05 +00:00
Nino Righi
1107264d7c Merged PR 1665: #4417 Abholfach Listen Routing Breadcrumb
#4417 Abholfach Listen Routing Breadcrumb
2023-10-26 14:31:28 +00:00
Nino Righi
31512546d3 Merged PR 1666: #4420 Fix Customer Area Billing Addresses Added Margin
#4420 Fix Customer Area Billing Addresses Added Margin
2023-10-26 14:31:04 +00:00
Lorenz Hilpert
183e7b6945 Add orderItemSubsetId to
PickupShelfDetailsBaseComponent and remove
unnecessary RxJS operators
2023-10-26 16:28:42 +02:00
Nino
fba465d573 Merge branch 'develop' into release/3.0 2023-10-25 16:17:18 +02:00
Lorenz Hilpert
dd6784e3b3 #4396 Archive pickup shelf services 2023-10-25 15:01:07 +02:00
Lorenz Hilpert
9caa0fc0fa Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2023-10-25 13:44:03 +02:00
Lorenz Hilpert
1102fb4608 #4396 Add all_branches filter to PickupShelfInService
and PickupShelfOutService
2023-10-25 13:43:46 +02:00
Nino Righi
012cc6ac67 Merged PR 1664: #4411 Fix Routing after Applying Filter
#4411 Fix Routing after Applying Filter
2023-10-25 11:23:43 +00:00
Lorenz Hilpert
72de7efc1d #4410 Refactor checkout-summary.component.html layout 2023-10-25 12:34:13 +02:00
Lorenz Hilpert
b8c7bbec88 Revert "Merged PR 1659: #4396 Fix RD getOrderItems on Details page now with correct filter settings"
This reverts commit 9def487ab8.
2023-10-25 11:25:18 +02:00
Lorenz Hilpert
608513b6dc Merged PR 1662: #4405 Fix navigation and error handling in pickup shelf
Bitte noch nicht freigeben. Möchte im daily noch was klären

#4405 Fix navigation and error handling in pickup shelf
components
2023-10-25 09:21:10 +00:00
Nino Righi
fa78eca087 Merged PR 1663: #4408 Fix Side Nav Dropdown gets opened if route is Active
#4408 Fix Side Nav Dropdown gets opened if route is Active
2023-10-24 18:52:31 +00:00
Lorenz Hilpert
77fda0f939 #1989 Add message modal component for customer details
view.
2023-10-24 20:30:56 +02:00
Lorenz Hilpert
81d210a77b #4407 Refactor getLastActivatedCustomerProcessId$() to
filter processes by type 'cart' and handle
undefined lastCustomerProcess
2023-10-24 16:21:01 +02:00
Lorenz Hilpert
5ab4456040 Merge branch 'develop' into release/3.0 2023-10-24 14:21:59 +02:00
Nino Righi
2db45c900a Merged PR 1661: #4406 No Large increase of Global Font Size possible if on screen size tablet
#4406 No Large increase of Global Font Size possible if on screen size tablet
2023-10-24 11:55:13 +00:00
Nino Righi
705dc23908 Merged PR 1660: #4256 Fix HSC Kundenbestellungen Filter and Search History handling
#4256 Fix HSC Kundenbestellungen Filter and Search History handling
2023-10-24 08:22:26 +00:00
Nino Righi
9def487ab8 Merged PR 1659: #4396 Fix RD getOrderItems on Details page now with correct filter settings
#4396 Fix RD getOrderItems on Details page now with correct filter settings
2023-10-23 16:30:16 +00:00
Lorenz Hilpert
50e08f115a #4392 iPad - WA - langer Titel schiebt sich unter Status 2023-10-23 16:15:29 +02:00
Nino Righi
b15693a914 Merged PR 1657: #4403 Fix Abholfach Einbuchen Code Structure
#4403 Fix Abholfach Einbuchen Code Structure
2023-10-23 12:25:38 +00:00
Nino Righi
cbf23b6f30 Merged PR 1658: #4401 Fix Article Search Results Breadcrumb updates now correctly and fixed N...
#4401 Fix Article Search Results Breadcrumb updates now correctly and fixed Navigation after Applying Filter on all screen sizes
2023-10-23 12:25:08 +00:00
Nino
fc45efb4af Merge branch 'develop' into release/3.0 2023-10-20 16:25:57 +02:00
Nino
b4fbcd6d16 Remission Filter Button Styling, Filter Overlay Max Width Changed 2023-10-20 16:23:39 +02:00
Nino
c54e5c27ae #4396 Removed Filter All Branches on Pickup Shelf Result List Request 2023-10-20 15:52:49 +02:00
Nino
4b80765b26 Merge branch 'develop' into feature/4340-RD-Shell-Nav-Menue-Expand-Dropdown 2023-10-20 15:45:43 +02:00
Nino
d223e064c2 #4397 Fix RD Abholfach Zubuchen mit Zusatz 2023-10-20 15:43:49 +02:00
Nino
1f8d6c5898 #4340 Fix Open Dropdown on Init Side-Menu Component 2023-10-19 15:22:25 +02:00
Nino
66555e9c7e Merge branch 'develop' into feature/4340-RD-Shell-Nav-Menue-Expand-Dropdown 2023-10-19 14:58:25 +02:00
Nino
bb81b8f826 Merge branch 'develop' into release/3.0 2023-10-19 13:26:35 +02:00
Nino Righi
ecb5a77fdd Merged PR 1651: #4389 RD Added Pickup Shelf List In Item Loader and trackByGroupFn
#4389 RD Added Pickup Shelf List In Item Loader and trackByGroupFn
2023-10-19 11:24:58 +00:00
Nino Righi
6652c2f97e Merged PR 1650: #4391 Assign new CompartmentCode if action differs from "book in", otherwise...
#4391 Assign new CompartmentCode if action differs from "book in", otherwise use latestCompartmentCode
2023-10-19 08:51:22 +00:00
Nino Righi
e9f16f72cb Merged PR 1649: #4340 Expand Dropdown on Click if menu is on assigned Section
#4340 Expand Dropdown on Click if menu is on assigned Section
2023-10-19 08:19:28 +00:00
Nino Righi
a687b1771f Merged PR 1648: #4358 Improved implementation by using order$ as observable
#4358 Improved implementation by using order$ as observable
2023-10-19 08:18:56 +00:00
Nino
ec79e315e5 #4340 Expand Dropdown on Click if menu is on assigned Section 2023-10-18 15:52:41 +02:00
Nino Righi
af10e66b1a Merged PR 1647: #4310 Show Button always centered
#4310 Show Button always centered
2023-10-18 09:08:17 +00:00
Lorenz Hilpert
2d8a0f514d #4372 RD/ Scanbutton fehlt in Kundensuche 2023-10-18 11:07:06 +02:00
Lorenz Hilpert
02ade0a377 Skandit SDK Lizenz Update 2023-10-18 10:58:17 +02:00
Nino Righi
c2943037d9 Merged PR 1646: #4390 Customer Area, removed navigation cards
#4390 Customer Area, removed navigation cards
2023-10-17 16:10:18 +00:00
Nino Righi
c30d8fa5fd Merged PR 1645: #4374 #4358 Change EstimatedShippingDate, preferredPickUpDate and pickUpDeadl...
#4374 #4358 Change EstimatedShippingDate, preferredPickUpDate and pickUpDeadline on Shelf In and Out fixed
2023-10-17 14:49:50 +00:00
Nino Righi
4fd10bc8a4 Merged PR 1644: #4364 Check if Order is Kulturpass Order if no customer is available
#4364 Check if Order is Kulturpass Order if no customer is available
2023-10-17 13:46:23 +00:00
Lorenz Hilpert
1d25fec9ff #3025 RD // HSC+ Filiale- Automatische Bestellbestätigung per Mail 2023-10-17 15:41:49 +02:00
Nino Righi
6f77527d59 Merged PR 1643: #4382 Changed Layout inside Purchase Options Modal
#4382 Changed Layout inside Purchase Options Modal
2023-10-16 16:21:38 +00:00
Nino Righi
7a2cd5cef8 Merged PR 1642: #4360 Autocomplete Provider Implementation
#4360 Autocomplete Provider Implementation
2023-10-16 16:20:59 +00:00
Lorenz Hilpert
d1de4d96d8 Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2023-10-16 18:20:45 +02:00
Lorenz Hilpert
dd1652c3a6 Logs entfernt 2023-10-16 18:20:30 +02:00
Lorenz Hilpert
66fd2eed81 #4355 Lieferadresse bearbeiten nicht möglich 2023-10-16 18:20:13 +02:00
Nino Righi
f5d90f97e8 Merged PR 1641: #4370 Only show compartment code if available, never display orderNumber
#4370 Only show compartment code if available, never display orderNumber
2023-10-16 15:39:46 +00:00
Nino Righi
ade3800568 Merged PR 1640: #4348 #4366 Multiple Bugfixes Shelf Edit with changing CompartmentInfo or CompartmentCode
#4348 #4366 Multiple Bugfixes Shelf Edit with changing CompartmentInfo or CompartmentCode
2023-10-16 15:38:48 +00:00
Lorenz Hilpert
486e2e5a28 Fix Build Error 2023-10-16 15:53:22 +02:00
Lorenz Hilpert
86d3b4e3f5 Merge branch 'develop' into release/3.0 2023-10-16 15:51:20 +02:00
Nino Righi
4d669731fb Merged PR 1639: #4376 Styling Fix
#4376 Styling Fix
2023-10-16 13:05:11 +00:00
Nino Righi
5bc81f7048 Merged PR 1638: #4368 Fix Navigation On Filter Close
#4368 Fix Navigation On Filter Close
2023-10-16 13:04:03 +00:00
Nino Righi
6cf6dec001 Merged PR 1636: #4370 Fix dont show compartment or orderNumber if item has processingStatus 1...
#4370 Fix dont show compartment or orderNumber if item has processingStatus 16 or 8192
2023-10-16 13:03:37 +00:00
Nino Righi
526d82752c Merged PR 1637: #4367 Fix Navigation to Shelf Out from Checkout Summary
#4367 Fix Navigation to Shelf Out from Checkout Summary
2023-10-16 07:48:06 +00:00
Nino Righi
7565b2bb54 Merged PR 1635: #4373 Changed Wording and Color of preferredPickUpDate in Checkout Summary
#4373 Changed Wording and Color of preferredPickUpDate in Checkout Summary
2023-10-16 07:45:28 +00:00
Nino Righi
692a32f4d7 Merged PR 1634: #4362 Scroll Position Handling, Bugfix between multiple open Shelf Out processes
#4362 Scroll Position Handling, Bugfix between multiple open Shelf Out processes
2023-10-16 07:44:55 +00:00
Lorenz Hilpert
4039ffdf20 #4365 Weitere Navigationen umgebaut 2023-10-12 16:25:09 +02:00
Lorenz Hilpert
cb39b3b79b #4365 RD // Abholfach - Alte Bestellpostensuche Seite ist erreichbar 2023-10-12 16:18:21 +02:00
Lorenz Hilpert
fe3dfd00ab #4354 Kundendaten erfassen // Rechnungs und Lieferadresse als Standard Adresse festlegen 2023-10-12 16:02:16 +02:00
Nino Righi
08a4b5a2ca Merged PR 1633: #4349 Unselect Items after handle action, update actions on list item
#4349 Unselect Items after handle action, update actions on list item
2023-10-12 13:23:12 +00:00
Lorenz Hilpert
e8a036b6df #4357 Warenausgabe - Trefferliste nachladen unendlich 2023-10-11 17:26:43 +02:00
Lorenz Hilpert
ff698ec6b2 #4345 Warenausgabe - Mehrere Artikel mit verschiedene Status werden zusammen gebunden 2023-10-11 16:40:41 +02:00
Lorenz Hilpert
8644ea515b Merge branch 'feature/splitscreen-pickup-shelf-design' into develop 2023-10-10 14:25:52 +02:00
Lorenz Hilpert
10a77c25e9 Zubuchen im Wareneingang Fix 2023-10-10 14:23:08 +02:00
Lorenz Hilpert
25d3adc28c Bugfix Navigation and Process Creation 2023-10-10 14:03:47 +02:00
Lorenz Hilpert
5cdbbb995f Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-10-10 12:05:20 +02:00
Lorenz Hilpert
01a26ef57a Console Logs removed 2023-10-10 12:05:06 +02:00
Lorenz Hilpert
3b337ea127 Cleanup Cache 2023-10-10 12:04:40 +02:00
Nino
c66ef5ba91 Trefferlisten Small Desktop Breakpoint, Eingebaut in Artikelsuche, Kundensuche, Warenausgabe, Einbuchen 2023-10-10 11:53:27 +02:00
Nino
b2edcf8a20 #4347 Fix Navigation on Edit Page from Shelf In and Out 2023-10-09 17:41:00 +02:00
Nino
06af33e37e Added compartmentInfo to every compartmentCode navigation 2023-10-09 17:14:26 +02:00
Nino
1a217d0870 Open New Process if navgation on customer area triggers 2023-10-09 16:01:31 +02:00
Nino
d6c52baf53 Navigation and Styling to Edit Pages 2023-10-09 15:15:23 +02:00
Lorenz Hilpert
f923fdefa4 #4344 Warenausgabe - Statusänderung via Bestellposten Details Seite wird nur nach Seiten Refresh angezeigt 2023-10-09 14:18:55 +02:00
Lorenz Hilpert
bd674a0e14 #4338 Warenausgabe - "Bearbeiten" bringt Fehler 2023-10-09 13:34:34 +02:00
Nino
bd3f8af924 Process Id Fix 2023-10-06 17:57:36 +02:00
Nino
5e720876ac Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-10-06 17:43:25 +02:00
Nino
8543db465b Cover Anzeige und Cover navigation 2023-10-06 17:43:06 +02:00
Lorenz Hilpert
ec4951d8dd #4343 Warenausgabe - Öffnen Bestellposten mit Abholfach-Zusatz navigiert zu leere Seite 2023-10-06 15:02:49 +02:00
Lorenz Hilpert
17f1846c69 Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-10-06 14:15:54 +02:00
Lorenz Hilpert
6b71a544fe renamed selected into displayed for displaying items and added compartment info 2023-10-06 14:15:38 +02:00
Nino
7f1f097179 Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-10-06 14:13:20 +02:00
Nino
a7003b84bf Removed Extends Base Component from Shelf In List 2023-10-06 14:12:59 +02:00
Nino
8a84e69ce3 Fix Pickup Shelf Details In 2023-10-06 14:10:57 +02:00
Lorenz Hilpert
3ebd50a8c1 #4341 Warenausgabe - Kacheln in Trefferliste sind rechts abgeschnitten 2023-10-06 13:43:08 +02:00
Nino
1f7a952c91 Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-10-06 11:25:36 +02:00
Nino
560ef57915 Abholfach Details Selected Item and Filter Back Navigation to Details 2023-10-06 11:21:09 +02:00
Lorenz Hilpert
4fb81526ae Split Screen classes fuer e2e tests 2023-10-06 11:13:11 +02:00
Lorenz Hilpert
4bee8117ee #4342 Warenausgabe - Bearbeiten Seite beinhaltet komische Text 2023-10-06 10:25:01 +02:00
Nino
66991684d2 Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-10-05 17:48:43 +02:00
Nino
06abcbff51 Update Pickup Shelf In Details 2023-10-05 17:48:19 +02:00
Nino
17197461f7 Update Domain Service Shelf In 2023-10-05 17:47:25 +02:00
Lorenz Hilpert
f036190019 Build Error Fix 2023-10-05 17:41:33 +02:00
Lorenz Hilpert
e68975b0f7 Load Cover Items - Implemented In Details Store 2023-10-05 17:26:12 +02:00
Lorenz Hilpert
692c1cb596 Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-10-05 17:16:31 +02:00
Lorenz Hilpert
7ace3b7685 #4338 Warenausgabe - "Bearbeiten" bringt Fehler 2023-10-05 17:16:10 +02:00
Nino
6d6077c54f Added Cover Page and Updated Navigation Routes (added OrderItemSubsetId) 2023-10-05 17:14:23 +02:00
Nino
b60913de3c Shelf In Navigation Update on Shared Components 2023-10-05 15:27:07 +02:00
Nino
357b89f1ea Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-10-05 15:14:37 +02:00
Nino
c37b05d4b8 Pickup Shelf In List Select Items, Routing and Actions Update 2023-10-05 15:14:21 +02:00
Lorenz Hilpert
94fe011d49 #4337 Warenaugabe - Abholfachnummer wird lanksbündig angezeigt 2023-10-05 14:16:40 +02:00
Lorenz Hilpert
0e458e81d8 #4336 Warenausgabe - automatisches nachladen durch Scrollen 2023-10-05 14:12:15 +02:00
Lorenz Hilpert
efbdb134a9 Cleanup 2023-10-05 11:43:20 +02:00
Lorenz Hilpert
a97d87ed7b #4335 Fehler bei Öffnung einer Bestellung mit Status "eingetroffen" 2023-10-05 11:40:47 +02:00
Lorenz Hilpert
cb56cfcb00 #4332 Upgrade Bestellung ohne Konto" leitet zum Dashboard weiter 2023-10-05 10:58:36 +02:00
Nino
d13cf0ef8d Init PickUpShelfIn page, Navigation and SubPages 2023-10-04 18:06:28 +02:00
Nino
e6afd6887a Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-10-04 15:29:10 +02:00
Nino
7378b7db53 Removed many unnecessary history endpoint requests 2023-10-04 15:28:53 +02:00
Lorenz Hilpert
70f9bb0f73 Skip fetch while another request is being executed 2023-10-04 15:13:57 +02:00
Lorenz Hilpert
2fa7451716 Notification in Details laden und Fix Request lists 2023-10-04 14:29:03 +02:00
Lorenz Hilpert
871aeaed1f Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-10-04 13:20:42 +02:00
Lorenz Hilpert
09f0337489 Fetching OrderItemSubsetTasks 2023-10-04 13:19:26 +02:00
Nino
51f36de7dd Merge branch 'develop' into feature/splitscreen-pickup-shelf-design 2023-10-04 12:46:33 +02:00
Nino
0999e1ea51 Fetch Partial Bugfix, Breadcrumb Fix, Details Store Selectors Refactor 2023-10-04 12:45:52 +02:00
Lorenz Hilpert
d079b276cf Attribute für e2e 2023-10-04 10:42:48 +02:00
Nino
e1bd87418c Aufrufe geupdated, bugfixing 2023-10-02 18:10:53 +02:00
Nino
d06af28e11 Merge branch 'develop' into feature/splitscreen-pickup-shelf-design 2023-10-02 17:11:06 +02:00
Nino
c123a29b1d Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-10-02 17:08:01 +02:00
Nino
1398a3bdee Added more/less functionality and Added Nav Menu to Customer Area, Small Styling Adjustments 2023-10-02 17:07:43 +02:00
Lorenz Hilpert
340e866aed Funktion zum updaten der OrderItemSubsets 2023-10-02 16:26:24 +02:00
Nino
9338162906 Added Navigation After Actions on Detail Page 2023-10-02 16:03:50 +02:00
Nino
23d61bfa60 Navigation to History and Edit page, Implementation of Edit page, Centered Edit Page Action Buttons 2023-10-02 14:57:55 +02:00
Lorenz Hilpert
7409053cef #4330 Kundendaten // Upgrade Versandbestellung (oder gemischt)-Kundendatensatz wirft Fehler 2023-09-29 17:54:32 +02:00
Nino
0c372b0245 Updated Store and Base component implementation, Fixed Tags Component (also in Customer-Orders), Added new function declarations to implement, Added History and Edit Breadcrumb management 2023-09-29 16:58:16 +02:00
Lorenz Hilpert
dd68405522 #4331 Bestellung // Button "Bestellen" ist erst lange ausgegraut (wird aber nach eine Zeit klickbar) 2023-09-29 16:55:08 +02:00
Lorenz Hilpert
69e792ae41 Merge tag '4323-WBS-Fehlende-Wannenummer' into develop
4323-WBS-Fehlende-Wannenummer
2023-09-29 16:15:57 +02:00
Lorenz Hilpert
02b507aca9 Merge branch 'hotfix/4323-WBS-Fehlende-Wannenummer' 2023-09-29 16:10:57 +02:00
Lorenz Hilpert
05f94c65fc 4323 Remi // WBS ohne Wannennummer -> Fehlermeldung 2023-09-29 16:09:46 +02:00
Lorenz Hilpert
8b6ebd1820 Merge branch 'develop' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into develop 2023-09-29 11:08:16 +02:00
Lorenz Hilpert
45cb411e17 #4329 HSC - OLA im Warenkorb 2023-09-29 11:07:54 +02:00
Nino Righi
dec66de61d Merged PR 1632: #4322 Fix Checkout Summary If an Order has no items, dont display that Order
#4322 Fix Checkout Summary If an Order has no items, dont display that Order
2023-09-29 08:54:08 +00:00
Nino
e9a490d7f3 Details Page Completed Store hookup 2023-09-28 17:19:42 +02:00
Nino
280b28a474 Merge branch 'develop' into feature/splitscreen-pickup-shelf-design 2023-09-28 10:16:41 +02:00
Lorenz Hilpert
cfba5f34d4 #4317 AddressSelectionModalComponent - warning entfernt 2023-09-27 18:37:52 +02:00
Nino
582e2d988c Pickup Shelf Out Details Page 2023-09-27 18:16:44 +02:00
Lorenz Hilpert
4c627986d1 #4185 OLA im Warenkorb - spinner ersetzt 2023-09-27 18:01:01 +02:00
Nino
299dab43b9 Init Pickup Shelf Out Details Page and startet Header Implementation 2023-09-26 17:30:28 +02:00
Nino
ce0823a6fd Pickup Shelf Out List Actions Implementation 2023-09-26 17:02:30 +02:00
Nino
e0963e9b65 Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-09-25 20:45:37 +02:00
Nino
a88552f975 List Styling Completed 2023-09-25 20:44:16 +02:00
Lorenz Hilpert
66a7bab287 Action Handler Impl 2023-09-25 19:04:37 +02:00
Lorenz Hilpert
088d9e1b6f Group Pipe von UiCommon in eigene Lib ausgelagert 2023-09-25 11:40:43 +02:00
Lorenz Hilpert
29619b2fec Paging Abholfach Store Fix 2023-09-22 17:05:52 +02:00
Nino
a5effc89a7 Merge branch 'feature/splitscreen-pickup-shelf-design' of https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend into feature/splitscreen-pickup-shelf-design 2023-09-22 16:19:25 +02:00
Nino
e7fe2a2676 Update Result List Splitscreen 2023-09-22 16:17:33 +02:00
Lorenz Hilpert
53bb01db2d Fix Process Change 2023-09-22 15:05:59 +02:00
Nino
003d0cbcb2 Merge branch 'develop' into feature/splitscreen-pickup-shelf-design 2023-09-22 10:49:31 +02:00
Nino
a71c627a30 Pickup Shelf List Init 2023-09-22 10:45:15 +02:00
Lorenz Hilpert
261c851514 Caching und State Änderungen für Ausgewählte Items 2023-09-21 17:08:51 +02:00
Nino
58cfcba738 Merge branch 'develop' into feature/splitscreen-pickup-shelf-design 2023-09-21 16:39:25 +02:00
Nino Righi
50dac899c9 Merged PR 1631: #4304 Page Article Search Fixed Routing Bug After Searching Article with EAN...
#4304 Page Article Search Fixed Routing Bug After Searching Article with EAN inside Filter Page with Small Desktop Screen Size
2023-09-21 08:11:23 +00:00
Nino
9fe25f0f73 Added Loading Spinner and Searchbox Hint Message To Search Main and Filter View 2023-09-20 18:09:47 +02:00
Nino
678c961ea9 Merge branch 'develop' into feature/splitscreen-pickup-shelf-design 2023-09-20 17:32:39 +02:00
Lorenz Hilpert
f6b4633ac4 #4303 Kundensuche - kein Splittscreen 2023-09-20 14:45:53 +02:00
Lorenz Hilpert
0ecd2916a2 Caching Abholfach Liste 2023-09-20 14:29:57 +02:00
Lorenz Hilpert
e786b60bfc #4303 Kundensuche - kein Splittscreen 2023-09-20 10:29:31 +02:00
Nino
8c079e9064 Splitscreen Search Main Design 2023-09-19 17:51:32 +02:00
Nino
3b08fe438b Merge branch 'develop' into feature/splitscreen-pickup-shelf-design 2023-09-19 15:55:57 +02:00
Nino
fedbdcc35c Init Splitscreen Routing 2023-09-19 15:30:55 +02:00
Lorenz Hilpert
ac656ddc04 Breadcrumb fue abholfach 2023-09-19 15:23:28 +02:00
Nino Righi
85d8d75da8 Merged PR 1630: #4299 Take Price and priceMaintained from Catalog First
#4299 Take Price and priceMaintained from Catalog First
2023-09-19 12:20:54 +00:00
Nino
e6d389d848 Added Main Side View Component 2023-09-19 10:58:05 +02:00
Lorenz Hilpert
664053f231 Store for Detail Pages 2023-09-19 09:52:35 +02:00
Nino Righi
18b2230dd7 Merged PR 1629: #4301 RD Design Adjustments
#4301 RD Design Adjustments
2023-09-18 14:05:28 +00:00
Lorenz Hilpert
0876ed3acc Added Directive Shared Scroll Container 2023-09-18 15:48:33 +02:00
Lorenz Hilpert
7b4edbee8b Html Tag Fuer PickupShelfFilter Fix 2023-09-18 15:35:22 +02:00
Lorenz Hilpert
05ef1edfeb Wording Fix WE WA 2023-09-18 15:34:06 +02:00
Lorenz Hilpert
fb8db78bbd Breadcrumb Fix 2023-09-18 15:06:36 +02:00
Lorenz Hilpert
aecc4a477f Fix delayWhenFilterIsNotReady 2023-09-18 14:56:22 +02:00
Lorenz Hilpert
fd63ce8b3c Abholfach Infrastruktur - WE und WA 2023-09-18 14:23:16 +02:00
Nino Righi
423cd498cf Merged PR 1628: #4271 Added canAdd request for adding items to Kulturpass Cart Modal
#4271 Added canAdd request for adding items to Kulturpass Cart Modal
2023-09-18 08:42:26 +00:00
Nino Righi
b421c8b08c Merged PR 1627: #4298 Removed Add New Process Menu for Tablet
#4298 Removed Add New Process Menu for Tablet
2023-09-14 15:22:30 +00:00
Nino
4304286f48 Fix Show Filter Page on Tablet if Navigation To Filter Triggered from Result Page (Article Search and Customer Orders) 2023-09-14 16:23:50 +02:00
Nino Righi
df308c98ff Merged PR 1626: #4299 Hotfix Kulturpass Fallback if Delivery Availability Is Missing
#4299 Hotfix Kulturpass Fallback if Delivery Availability Is Missing
2023-09-14 14:02:44 +00:00
Lorenz Hilpert
1d1221e8c5 Navigation Fix Customer Guard ohne Prozess Id 2023-09-14 10:55:53 +02:00
Lorenz Hilpert
e2a6eac0a2 #4258 Abstände Breadcrumb 2023-09-13 18:43:30 +02:00
Lorenz Hilpert
4e54baf9fb Fix Customer Navigation 2023-09-13 18:38:35 +02:00
Lorenz Hilpert
7593e420de #4258 Abstände 2 2023-09-13 18:17:56 +02:00
Lorenz Hilpert
84ca80a1c9 #4258 Abstände 1 2023-09-13 18:17:43 +02:00
Lorenz Hilpert
7265f4d4ce Shell Navigation Kunden 2023-09-13 16:29:47 +02:00
Lorenz Hilpert
fca1eacc6e Bugfix Navigation Kunden und Prozesserstellung 2023-09-13 16:26:25 +02:00
Lorenz Hilpert
b440ddbe82 Merge branch 'hotfix/4270-4269-Archiv-Artikel'
(cherry picked from commit f4df6e8799)
2023-08-30 14:38:54 +02:00
Lorenz Hilpert
d6e0d92132 (cherry picked from commit d09b5b1ce7) 2023-08-24 20:18:19 +02:00
Lorenz Hilpert
b16ffa4352 Merge branch 'develop' into release/3.0 2023-08-11 15:34:45 +02:00
571 changed files with 29630 additions and 4424 deletions

View File

@@ -86,6 +86,28 @@ export class ApplicationService {
return this.getProcessById$(processId).pipe(map((process) => process?.data?.selectedBranch));
}
readonly REGEX_PROCESS_NAME = /^Vorgang \d+$/;
async createCustomerProcess(processId?: number): Promise<ApplicationProcess> {
const processes = await this.getProcesses$('customer').pipe(first()).toPromise();
const processIds = processes.filter((x) => this.REGEX_PROCESS_NAME.test(x.name)).map((x) => +x.name.split(' ')[1]);
const maxId = processIds.length > 0 ? Math.max(...processIds) : 0;
const process: ApplicationProcess = {
id: processId ?? Date.now(),
type: 'cart',
name: `Vorgang ${maxId + 1}`,
section: 'customer',
closeable: true,
};
await this.createProcess(process);
return process;
}
async createProcess(process: ApplicationProcess) {
const existingProcess = await this.getProcessById$(process?.id).pipe(first()).toPromise();
if (existingProcess?.id === process?.id) {

View File

@@ -1,14 +1,24 @@
import { Injectable } from '@angular/core';
import { CacheOptions } from './cache-options';
import { Cached } from './cached';
import { interval } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class CacheService {
constructor() {}
constructor() {
this._registerCleanupTask();
}
set(token: Object, data: any, options?: CacheOptions) {
_registerCleanupTask() {
this.cleanup();
interval(1000 * 60).subscribe(() => {
this.cleanup();
});
}
set<T>(token: Object, data: T, options?: CacheOptions) {
const persist = options?.persist;
const ttl = options?.ttl;
const cached: Cached = {
@@ -17,6 +27,8 @@ export class CacheService {
if (ttl) {
cached.until = Date.now() + ttl;
} else {
cached.until = Date.now() + 1000 * 60 * 60 * 12;
}
if (persist) {
@@ -29,13 +41,15 @@ export class CacheService {
return cached;
}
get<T = any>(token: Object, from: 'session' | 'persist' = 'session'): T {
get<T = any>(token: Object, from?: 'session' | 'persist'): T {
let cached: Cached;
if (from === 'session') {
cached = this.deserialize(sessionStorage.getItem(this.getKey(token)));
} else if (from === 'persist') {
cached = this.deserialize(localStorage.getItem(this.getKey(token)));
} else {
cached = this.deserialize(sessionStorage.getItem(this.getKey(token))) || this.deserialize(localStorage.getItem(this.getKey(token)));
}
if (!cached) {
@@ -59,7 +73,8 @@ export class CacheService {
}
private getKey(token: Object) {
return this.hash(JSON.stringify(token));
const key = `CacheService_` + this.hash(JSON.stringify(token));
return key;
}
private hash(data: string): string {
@@ -77,4 +92,25 @@ export class CacheService {
private deserialize(data: string): Cached {
return JSON.parse(data);
}
cleanup() {
// get all keys created by this service by looking for the service name and remove the entries
// that ttl is expired
let localStorageKeys = Object.keys(localStorage).filter((key) => key.startsWith('CacheService_'));
let seesionStorageKeys = Object.keys(sessionStorage).filter((key) => key.startsWith('CacheService_'));
localStorageKeys.forEach((key) => {
const cached = this.deserialize(localStorage.getItem(key));
if (cached.until < Date.now()) {
localStorage.removeItem(key);
}
});
seesionStorageKeys.forEach((key) => {
const cached = this.deserialize(sessionStorage.getItem(key));
if (cached.until < Date.now()) {
sessionStorage.removeItem(key);
}
});
}
}

View File

@@ -6,7 +6,7 @@ import { map } from 'rxjs/operators';
const MATCH_TABLET = '(max-width: 1023px)';
const MATCH_DESKTOP_SMALL = '(min-width: 1024px) and (max-width: 1439px)';
const MATCH_DESKTOP_SMALL = '(min-width: 1024px) and (max-width: 1279px)';
const MATCH_DESKTOP = '(min-width: 1280px)';

View File

@@ -27,7 +27,9 @@ import {
StoreCheckoutPayerService,
StoreCheckoutBranchService,
ItemsResult,
ShoppingCartItemDTO,
KulturPassService,
ProductDTO,
KulturPassResult,
} from '@swagger/checkout';
import {
DisplayOrderDTO,
@@ -37,22 +39,20 @@ import {
ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString,
} from '@swagger/oms';
import { isNullOrUndefined, memorize } from '@utils/common';
import { combineLatest, Observable, of, concat, isObservable, throwError, interval, zip, EMPTY, Subscription } from 'rxjs';
import { combineLatest, Observable, of, concat, isObservable, throwError, interval as rxjsInterval } from 'rxjs';
import {
bufferCount,
catchError,
debounceTime,
distinctUntilChanged,
filter,
first,
map,
mergeMap,
share,
shareReplay,
switchMap,
take,
tap,
withLatestFrom,
startWith,
} from 'rxjs/operators';
import * as DomainCheckoutSelectors from './store/domain-checkout.selectors';
@@ -63,8 +63,6 @@ import { ApplicationService } from '@core/application';
import { CustomerDTO } from '@swagger/crm';
import { Config } from '@core/config';
import parseDuration from 'parse-duration';
import { CheckoutEntity } from './store/defs/checkout.entity';
import { isEqual } from 'lodash';
@Injectable()
export class DomainCheckoutService {
@@ -84,7 +82,8 @@ export class DomainCheckoutService {
private _paymentService: StoreCheckoutPaymentService,
private _buyerService: StoreCheckoutBuyerService,
private _payerService: StoreCheckoutPayerService,
private _branchService: StoreCheckoutBranchService
private _branchService: StoreCheckoutBranchService,
private _kulturpassService: KulturPassService
) {}
//#region shoppingcart
@@ -231,6 +230,10 @@ export class DomainCheckoutService {
);
}
canAddItemsKulturpass(payload: ProductDTO[]): Observable<KulturPassResult[]> {
return this._kulturpassService.KulturPassCanAddForKulturPass({ payload }).pipe(map((response) => response?.result));
}
canAddItems({
processId,
payload,
@@ -701,57 +704,60 @@ export class DomainCheckoutService {
* @returns true if the availability of all items is valid
*/
validateOlaStatus({ processId, interval }: { processId: number; interval?: number }): Observable<boolean> {
return new Observable<boolean>((observer) => {
const enity$ = this.store.select(DomainCheckoutSelectors.selectCheckoutEntityByProcessId, { processId });
return rxjsInterval(interval ?? this.olaExpiration / 10).pipe(
startWith(0),
switchMap(() =>
this.store.select(DomainCheckoutSelectors.selectCheckoutEntityByProcessId, { processId }).pipe(
map((entity) => {
const now = Date.now();
if (!entity || !entity.shoppingCart || !entity.shoppingCart.items) {
return;
}
const olaExpiration = this.olaExpiration;
const itemAvailabilityTimestamp = entity.itemAvailabilityTimestamp ?? {};
const shoppingCart = entity.shoppingCart;
let timeout: any;
const timestamps = shoppingCart.items
?.map((i) => i.data)
?.filter((item) => !!item?.features?.orderType)
?.map((item) => {
const orderType = item.features.orderType;
let subscription: Subscription;
let timestamp = itemAvailabilityTimestamp[`${item.id}_${orderType}`];
function check() {
const now = Date.now();
if (timestamp) {
return timestamp;
}
subscription?.unsubscribe();
subscription = enity$.pipe(take(1)).subscribe((entity) => {
if (!entity || !entity.shoppingCart || !entity.shoppingCart.items) {
return;
}
if (orderType.endsWith('Versand')) {
timestamp =
itemAvailabilityTimestamp[`${item.id}_Versand`] ??
itemAvailabilityTimestamp[`${item.id}_DIG-Versand`] ??
itemAvailabilityTimestamp[`${item.id}_B2B-Versand`];
}
const itemAvailabilityTimestamp = entity.itemAvailabilityTimestamp ?? {};
const shoppingCart = entity.shoppingCart;
return timestamp;
})
?.filter((timestamp) => !!timestamp);
const timestamps = shoppingCart.items
?.map((i) => i.data)
?.filter((item) => !!item?.features?.orderType)
?.map((item) => itemAvailabilityTimestamp[`${item.id}_${item.features.orderType}`]);
if (timestamps?.length > 0) {
const oldestTimestamp = Math.min(...timestamps);
const expirationTimestamp = oldestTimestamp + this.olaExpiration;
return expirationTimestamp > now;
}
if (timestamps?.length > 0) {
const oldestTimestamp = Math.min(...timestamps);
observer.next(now - oldestTimestamp < olaExpiration);
}
timeout = setTimeout(() => {
check.call(this);
}, interval ?? olaExpiration / 10);
});
}
check.call(this);
return () => {
subscription?.unsubscribe();
clearTimeout(timeout);
};
}).pipe(distinctUntilChanged());
return false;
})
)
),
distinctUntilChanged()
);
}
validateAvailabilities({ processId }: { processId: number }): Observable<boolean> {
return this.getShoppingCart({ processId }).pipe(
map((shoppingCart) => {
const items = shoppingCart?.items?.map((item) => item.data) || [];
return items.every((i) => this.availabilityService.isAvailable({ availability: i.availability }));
})
);
@@ -1015,6 +1021,7 @@ export class DomainCheckoutService {
//#region Common
@memorize()
canSetCustomer({
processId,
customerFeatures,
@@ -1022,24 +1029,26 @@ export class DomainCheckoutService {
processId: number;
customerFeatures?: { [key: string]: string };
}): Observable<{ ok?: boolean; filter?: { [key: string]: string }; message?: string; create?: InputDTO }> {
return this.getShoppingCart({ processId }).pipe(
first(),
mergeMap((shoppingCart) =>
this._shoppingCartService
.StoreCheckoutShoppingCartCanAddBuyer({
shoppingCartId: shoppingCart.id,
payload: { customerFeatures },
})
.pipe(
map((response) => ({
ok: response.result.ok,
filter: response.result.queryToken?.filter || {},
message: response.message,
create: response.result.create,
}))
)
return this.getShoppingCart({ processId })
.pipe(
first(),
mergeMap((shoppingCart) =>
this._shoppingCartService
.StoreCheckoutShoppingCartCanAddBuyer({
shoppingCartId: shoppingCart.id,
payload: { customerFeatures },
})
.pipe(
map((response) => ({
ok: response.result.ok,
filter: response.result.queryToken?.filter || {},
message: response.message,
create: response.result.create,
}))
)
)
)
);
.pipe(shareReplay(1));
}
setNotificationChannels({ processId, notificationChannels }: { processId: number; notificationChannels: NotificationChannel }): void {

View File

@@ -22,5 +22,5 @@ export interface CheckoutEntity {
specialComment: string;
notificationChannels: NotificationChannel;
olaErrorIds: number[];
itemAvailabilityTimestamp: Record<string, number>;
itemAvailabilityTimestamp: Record<string, number | undefined>;
}

View File

@@ -0,0 +1,88 @@
import { ActionHandler } from '@core/command';
import {
AcceptedActionHandler,
ArrivedActionHandler,
AssembledActionHandler,
AvailableForDownloadActionHandler,
BackToStockActionHandler,
CanceledByBuyerActionHandler,
CanceledByRetailerActionHandler,
CanceledBySupplierActionHandler,
CreateShippingNoteActionHandler,
DeliveredActionHandler,
DetermineSupplierActionHandler,
DispatchedActionHandler,
DownloadedActionHandler,
FetchedActionHandler,
InProcessActionHandler,
NotAvailableActionHandler,
NotFetchedActionHandler,
OrderAtSupplierActionHandler,
OrderingActionHandler,
OverdueActionHandler,
PackedActionHandler,
ParkedActionHandler,
PlacedActionHandler,
PreparationForShippingActionHandler,
PrintCompartmentLabelActionHandler,
PrintShippingNoteActionHandler,
ReOrderActionHandler,
RedirectedInternaqllyActionHandler,
RequestedActionHandler,
ReserverdActionHandler,
ReturnedByBuyerActionHandler,
ShippingNoteActionHandler,
SupplierTemporarilyOutOfStockActionHandler,
ReOrderedActionHandler,
CollectOnDeliveryNoteActionHandler,
PrintPriceDiffQrCodeLabelActionHandler,
CollectWithSmallAmountinvoiceActionHandler,
PrintSmallamountinvoiceActionHandler,
ShopWithKulturpassActionHandler,
ChangeOrderItemStatusBaseActionHandler,
CreateReturnItemActionHandler,
} from './action-handlers';
import { Type } from '@angular/core';
export const ActionHandlerServices: Type<ActionHandler>[] = [
AcceptedActionHandler,
ArrivedActionHandler,
AssembledActionHandler,
AvailableForDownloadActionHandler,
BackToStockActionHandler,
CanceledByBuyerActionHandler,
CanceledByRetailerActionHandler,
CanceledBySupplierActionHandler,
CreateShippingNoteActionHandler,
DeliveredActionHandler,
DetermineSupplierActionHandler,
DispatchedActionHandler,
DownloadedActionHandler,
FetchedActionHandler,
InProcessActionHandler,
NotAvailableActionHandler,
NotFetchedActionHandler,
OrderAtSupplierActionHandler,
OrderingActionHandler,
OverdueActionHandler,
PackedActionHandler,
ParkedActionHandler,
PlacedActionHandler,
PreparationForShippingActionHandler,
PrintCompartmentLabelActionHandler,
PrintShippingNoteActionHandler,
ReOrderActionHandler,
RedirectedInternaqllyActionHandler,
RequestedActionHandler,
ReserverdActionHandler,
ReturnedByBuyerActionHandler,
ShippingNoteActionHandler,
SupplierTemporarilyOutOfStockActionHandler,
ReOrderedActionHandler,
CollectOnDeliveryNoteActionHandler,
PrintPriceDiffQrCodeLabelActionHandler,
CollectWithSmallAmountinvoiceActionHandler,
PrintSmallamountinvoiceActionHandler,
ShopWithKulturpassActionHandler,
CreateReturnItemActionHandler,
];

View File

@@ -2,6 +2,7 @@
* Public API Surface of oms
*/
export * from './lib/action-handler-services';
export * from './lib/goods.service';
export * from './lib/receipt.service';
export * from './lib/oms.service';

View File

@@ -0,0 +1,60 @@
import { Injectable, inject } from '@angular/core';
import { AbholfachService, AutocompleteTokenDTO, ListResponseArgsOfDBHOrderItemListItemDTO, QueryTokenDTO } from '@swagger/oms';
import { PickupShelfIOService } from './pickup-shelf-io.service';
import { Observable, throwError } from 'rxjs';
import { Filter } from '@shared/components/filter';
@Injectable({ providedIn: 'root' })
export class PickupShelfInService extends PickupShelfIOService {
private _abholfachService = inject(AbholfachService);
name() {
return 'PickupShelfInService';
}
getQuerySettings() {
return this._abholfachService.AbholfachWareneingangQuerySettings();
}
search(queryToken: QueryTokenDTO) {
return this._abholfachService.AbholfachWareneingang(queryToken);
}
complete(autocompleteToken: AutocompleteTokenDTO) {
return this._abholfachService.AbholfachWareneingangAutocomplete(autocompleteToken);
}
getOrderItemsByOrderNumberOrCompartmentCode(args: {
orderNumber?: string;
compartmentCode?: string;
filter?: Filter;
}): Observable<ListResponseArgsOfDBHOrderItemListItemDTO> {
if (!args.orderNumber && !args.compartmentCode) {
return throwError(
'PickupShelfInService.getOrderItemsByOrderNumberOrCompartmentCode(): Either orderNumber or compartmentCode must be provided.'
);
}
const { orderdate } = args.filter?.getQueryToken()?.filter ?? {};
return this._abholfachService.AbholfachWareneingang({
input: {
qs: args.compartmentCode ?? args.orderNumber,
},
filter: {
archive: String(true),
all_branches: String(true),
orderdate,
},
});
}
getOrderItemsByCustomerNumber(args: { customerNumber: string }): Observable<ListResponseArgsOfDBHOrderItemListItemDTO> {
return this._abholfachService.AbholfachWareneingang({
filter: { orderitemprocessingstatus: '16;128;8192;1048576' },
input: {
customer_name: args.customerNumber,
},
});
}
}

View File

@@ -0,0 +1,29 @@
import { Injectable } from '@angular/core';
import { Filter } from '@shared/components/filter';
import {
AutocompleteTokenDTO,
ListResponseArgsOfDBHOrderItemListItemDTO,
QueryTokenDTO,
ResponseArgsOfIEnumerableOfAutocompleteDTO,
ResponseArgsOfQuerySettingsDTO,
} from '@swagger/oms';
import { Observable } from 'rxjs';
@Injectable()
export abstract class PickupShelfIOService {
abstract name(): string;
abstract getQuerySettings(): Observable<ResponseArgsOfQuerySettingsDTO>;
abstract search(queryToken: QueryTokenDTO): Observable<ListResponseArgsOfDBHOrderItemListItemDTO>;
abstract complete(autocompleteToken: AutocompleteTokenDTO): Observable<ResponseArgsOfIEnumerableOfAutocompleteDTO>;
abstract getOrderItemsByOrderNumberOrCompartmentCode(args: {
orderNumber?: string;
compartmentCode?: string;
filter?: Filter;
}): Observable<ListResponseArgsOfDBHOrderItemListItemDTO>;
abstract getOrderItemsByCustomerNumber(args: { customerNumber: string }): Observable<ListResponseArgsOfDBHOrderItemListItemDTO>;
}

View File

@@ -0,0 +1,55 @@
import { Injectable, inject } from '@angular/core';
import { AbholfachService, AutocompleteTokenDTO, ListResponseArgsOfDBHOrderItemListItemDTO, QueryTokenDTO } from '@swagger/oms';
import { PickupShelfIOService } from './pickup-shelf-io.service';
import { Observable, throwError } from 'rxjs';
import { Filter } from '@shared/components/filter';
@Injectable({ providedIn: 'root' })
export class PickupShelfOutService extends PickupShelfIOService {
private _abholfachService = inject(AbholfachService);
name() {
return 'PickupShelfOutService';
}
getQuerySettings() {
return this._abholfachService.AbholfachWarenausgabeQuerySettings();
}
search(queryToken: QueryTokenDTO) {
return this._abholfachService.AbholfachWarenausgabe(queryToken);
}
complete(autocompleteToken: AutocompleteTokenDTO) {
return this._abholfachService.AbholfachWarenausgabeAutocomplete(autocompleteToken);
}
getOrderItemsByOrderNumberOrCompartmentCode(args: {
orderNumber?: string;
compartmentCode?: string;
filter?: Filter;
}): Observable<ListResponseArgsOfDBHOrderItemListItemDTO> {
if (!args.orderNumber && !args.compartmentCode) {
return throwError(
'PickupShelfOutService.getOrderItemsByOrderNumberOrCompartmentCode(): Either orderNumber or compartmentCode must be provided.'
);
}
const { orderdate } = args.filter?.getQueryToken()?.filter ?? {};
return this._abholfachService.AbholfachWarenausgabe({
input: {
qs: args.compartmentCode ?? args.orderNumber,
},
filter: {
archive: String(true),
all_branches: String(true),
orderdate,
},
});
}
getOrderItemsByCustomerNumber(args: { customerNumber: string }): Observable<ListResponseArgsOfDBHOrderItemListItemDTO> {
throw new Error('Method not implemented.');
}
}

View File

@@ -0,0 +1,27 @@
import { Injectable, inject } from '@angular/core';
import { DBHOrderItemListItemDTO, OrderItemDTO, OrderItemSubsetDTO, OrderService } from '@swagger/oms';
@Injectable({ providedIn: 'root' })
export class PickupShelfService {
private _orderService = inject(OrderService);
getOrderByOrderId(orderId: number) {
return this._orderService.OrderGetOrder(orderId);
}
patchOrderItemSubset(item: DBHOrderItemListItemDTO, changes: Partial<OrderItemSubsetDTO>) {
return this._orderService.OrderPatchOrderItemSubset({
orderId: item.orderId,
orderItemId: item.orderItemId,
orderItemSubsetId: item.orderItemSubsetId,
orderItemSubset: changes,
});
}
getOrderItemSubsetTasks(item: DBHOrderItemListItemDTO) {
return this._orderService.OrderGetOrderItemSubsetTasks({
orderId: item.orderId,
orderItemId: item.orderItemId,
orderItemSubsetId: item.orderItemSubsetId,
});
}
}

View File

@@ -0,0 +1,4 @@
export * from './lib/pickup-shelf-in.service';
export * from './lib/pickup-shelf-io.service';
export * from './lib/pickup-shelf-out.service';
export * from './lib/pickup-shelf.service';

View File

@@ -1,4 +1,4 @@
import { isDevMode, NgModule } from '@angular/core';
import { inject, isDevMode, NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {
CanActivateCartGuard,
@@ -22,6 +22,9 @@ 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';
const routes: Routes = [
{
@@ -36,111 +39,111 @@ const routes: Routes = [
canActivate: [IsAuthenticatedGuard],
children: [
{
path: '',
canActivate: [],
path: 'kunde',
component: MainComponent,
children: [
{
path: 'kunde',
component: MainComponent,
children: [
{
path: 'dashboard',
loadChildren: () => import('@page/dashboard').then((m) => m.DashboardModule),
},
{
path: 'product',
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
canActivate: [CanActivateProductGuard],
},
{
path: ':processId/product',
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
canActivate: [CanActivateProductWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'order',
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
canActivate: [CanActivateCustomerOrdersGuard],
},
{
path: ':processId/order',
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
canActivate: [CanActivateCustomerOrdersWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'customer',
loadChildren: () => import('@page/customer').then((m) => m.CustomerModule),
canActivate: [CanActivateCustomerGuard],
},
{
path: ':processId/customer',
loadChildren: () => import('@page/customer').then((m) => m.CustomerModule),
canActivate: [CanActivateCustomerWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'cart',
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
canActivate: [CanActivateCartGuard],
},
{
path: ':processId/cart',
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
canActivate: [CanActivateCartWithProcessIdGuard],
},
{
path: 'goods/out',
loadChildren: () => import('@page/goods-out').then((m) => m.GoodsOutModule),
canActivate: [CanActivateGoodsOutGuard],
},
{
path: ':processId/goods/out',
loadChildren: () => import('@page/goods-out').then((m) => m.GoodsOutModule),
canActivate: [CanActivateGoodsOutWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{ path: '**', redirectTo: 'dashboard', pathMatch: 'full' },
],
resolve: { section: CustomerSectionResolver },
path: 'dashboard',
loadChildren: () => import('@page/dashboard').then((m) => m.DashboardModule),
},
{
path: 'filiale',
component: MainComponent,
children: [
{
path: 'task-calendar',
loadChildren: () => import('@page/task-calendar').then((m) => m.PageTaskCalendarModule),
canActivate: [CanActivateTaskCalendarGuard],
},
{
path: 'goods/in',
loadChildren: () => import('@page/goods-in').then((m) => m.GoodsInModule),
canActivate: [CanActivateGoodsInGuard],
},
{
path: 'remission',
loadChildren: () => import('@page/remission').then((m) => m.PageRemissionModule),
canActivate: [CanActivateRemissionGuard],
},
{
path: 'package-inspection',
loadChildren: () => import('@page/package-inspection').then((m) => m.PackageInspectionModule),
canActivate: [CanActivatePackageInspectionGuard],
},
{
path: 'assortment',
loadChildren: () => import('@page/assortment').then((m) => m.AssortmentModule),
canActivate: [CanActivateAssortmentGuard],
},
{ path: '**', redirectTo: 'task-calendar', pathMatch: 'full' },
],
resolve: { section: BranchSectionResolver },
path: 'product',
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
canActivate: [CanActivateProductGuard],
},
{ path: '**', redirectTo: 'kunde', pathMatch: 'full' },
{
path: ':processId/product',
loadChildren: () => import('@page/catalog').then((m) => m.PageCatalogModule),
canActivate: [CanActivateProductWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'order',
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
canActivate: [CanActivateCustomerOrdersGuard],
},
{
path: ':processId/order',
loadChildren: () => import('@page/customer-order').then((m) => m.CustomerOrderModule),
canActivate: [CanActivateCustomerOrdersWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'customer',
loadChildren: () => import('@page/customer').then((m) => m.CustomerModule),
canActivate: [CanActivateCustomerGuard],
},
{
path: ':processId/customer',
loadChildren: () => import('@page/customer').then((m) => m.CustomerModule),
canActivate: [CanActivateCustomerWithProcessIdGuard],
resolve: { processId: ProcessIdResolver },
},
{
path: 'cart',
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
canActivate: [CanActivateCartGuard],
},
{
path: ':processId/cart',
loadChildren: () => import('@page/checkout').then((m) => m.PageCheckoutModule),
canActivate: [CanActivateCartWithProcessIdGuard],
},
{
path: 'pickup-shelf',
canActivate: [ProcessIdGuard],
// NOTE: This is a workaround for the canActivate guard not being called
loadChildren: () => import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
},
{
path: ':processId/pickup-shelf',
canActivate: [ActivateProcessIdGuard],
loadChildren: () => import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
},
{ path: '**', redirectTo: 'dashboard', pathMatch: 'full' },
],
resolve: { section: CustomerSectionResolver },
},
{
path: 'filiale',
component: MainComponent,
children: [
{
path: 'task-calendar',
loadChildren: () => import('@page/task-calendar').then((m) => m.PageTaskCalendarModule),
canActivate: [CanActivateTaskCalendarGuard],
},
{
path: 'pickup-shelf',
canActivate: [ActivateProcessIdWithConfigKeyGuard('pickupShelf')],
// NOTE: This is a workaround for the canActivate guard not being called
loadChildren: () => import('@page/pickup-shelf').then((m) => m.PickupShelfInModule),
},
{
path: 'goods/in',
loadChildren: () => import('@page/goods-in').then((m) => m.GoodsInModule),
canActivate: [CanActivateGoodsInGuard],
},
{
path: 'remission',
loadChildren: () => import('@page/remission').then((m) => m.PageRemissionModule),
canActivate: [CanActivateRemissionGuard],
},
{
path: 'package-inspection',
loadChildren: () => import('@page/package-inspection').then((m) => m.PackageInspectionModule),
canActivate: [CanActivatePackageInspectionGuard],
},
{
path: 'assortment',
loadChildren: () => import('@page/assortment').then((m) => m.AssortmentModule),
canActivate: [CanActivateAssortmentGuard],
},
{ path: '**', redirectTo: 'task-calendar', pathMatch: 'full' },
],
resolve: { section: BranchSectionResolver },
},
{ path: '**', redirectTo: 'kunde', pathMatch: 'full' },
],
},
];

View File

@@ -0,0 +1,46 @@
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@angular/router';
import { ApplicationService } from '@core/application';
import { Config } from '@core/config';
import { take } from 'rxjs/operators';
export const ActivateProcessIdGuard: CanActivateFn = async (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
const application = inject(ApplicationService);
const processIdStr = route.params.processId;
if (!processIdStr) {
return false;
}
const processId = Number(processIdStr);
// Check if Process already exists
const process = await application.getProcessById$(processId).pipe(take(1)).toPromise();
if (!process) {
application.createCustomerProcess(processId);
}
application.activateProcess(processId);
return true;
};
export const ActivateProcessIdWithConfigKeyGuard: (key: string) => CanActivateFn = (key) => async (
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
) => {
const application = inject(ApplicationService);
const config = inject(Config);
const processId = config.get(`process.ids.${key}`);
if (isNaN(processId)) {
return false;
}
application.activateProcess(processId);
return true;
};

View File

@@ -15,11 +15,12 @@ export class CanActivateCustomerOrdersWithProcessIdGuard {
.toPromise();
if (!process) {
const processes = await this._applicationService.getProcesses$('customer').pipe(first()).toPromise();
await this._applicationService.createProcess({
id: +route.params.processId,
type: 'customer-order',
type: 'cart',
section: 'customer',
name: `Kundenbestellungen`,
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
}
@@ -46,6 +47,18 @@ export class CanActivateCustomerOrdersWithProcessIdGuard {
processNumber(processes: ApplicationProcess[]) {
const processNumbers = processes?.map((process) => Number(process?.name?.replace(/\D/g, '')));
return !!processNumbers && processNumbers?.length > 0 ? Math.max(...processNumbers) + 1 : 1;
return !!processNumbers && processNumbers.length > 0 ? this.findMissingNumber(processNumbers) : 1;
}
findMissingNumber(processNumbers: number[]) {
// Ticket #3272 Bei Klick auf "+" bzw. neuen Prozess hinzufügen soll der neue Tab immer die höchste Nummer haben (wie aktuell im Produktiv)
// ----------------------------------------------------------------------------------------------------------------------------------------
// for (let missingNumber = 1; missingNumber < Math.max(...processNumbers); missingNumber++) {
// if (!processNumbers.find((number) => number === missingNumber)) {
// return missingNumber;
// }
// }
return Math.max(...processNumbers) + 1;
}
}

View File

@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { CustomerSearchNavigation } from '@shared/services';
import { first } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
@@ -9,7 +10,8 @@ export class CanActivateCustomerGuard {
constructor(
private readonly _applicationService: ApplicationService,
private readonly _checkoutService: DomainCheckoutService,
private readonly _router: Router
private readonly _router: Router,
private readonly _navigation: CustomerSearchNavigation
) {}
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
@@ -41,11 +43,17 @@ export class CanActivateCustomerGuard {
await this.fromCartProcess(processes);
return false;
} else {
await this._router.navigate(['/kunde', String(lastActivatedProcessId), 'customer']);
await this.navigateToDefaultRoute(lastActivatedProcessId);
}
return false;
}
async navigateToDefaultRoute(processId: number) {
const route = this._navigation.defaultRoute({ processId });
await this._router.navigate(route.path, { queryParams: route.queryParams });
}
// Bei offener Artikelsuche/Kundensuche und Klick auf Footer Kundensuche
async fromCartProcess(processes: ApplicationProcess[]) {
const newProcessId = Date.now();
@@ -56,7 +64,7 @@ export class CanActivateCustomerGuard {
name: `Vorgang ${this.processNumber(processes.filter((process) => process.type === 'cart'))}`,
});
await this._router.navigate(['/kunde', String(newProcessId), 'customer']);
await this.navigateToDefaultRoute(newProcessId);
}
// Bei offener Bestellbestätigung und Klick auf Footer Kundensuche
@@ -74,7 +82,7 @@ export class CanActivateCustomerGuard {
});
// Navigation
await this._router.navigate(['/kunde', String(processId), 'customer']);
await this.navigateToDefaultRoute(processId);
}
// Bei offener Warenausgabe und Klick auf Footer Kundensuche
@@ -98,7 +106,7 @@ export class CanActivateCustomerGuard {
});
// Navigation
await this._router.navigate(['/kunde', String(processId), 'customer']);
await this.navigateToDefaultRoute(processId);
}
processNumber(processes: ApplicationProcess[]) {

View File

@@ -15,7 +15,7 @@ export class CanActivateGoodsInGuard {
id: this._config.get('process.ids.goodsIn'),
type: 'goods-in',
section: 'branch',
name: 'Abholfach',
name: '',
});
}
this._applicationService.activateProcess(this._config.get('process.ids.goodsIn'));

View File

@@ -0,0 +1,33 @@
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { ApplicationService } from '@core/application';
import { take } from 'rxjs/operators';
export const ProcessIdGuard: CanActivateFn = async (
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Promise<boolean | UrlTree> => {
const application = inject(ApplicationService);
const router = inject(Router);
const process = await application.getLastActivatedProcessWithSection$('customer').pipe(take(1)).toPromise();
const processId = process?.id ?? Date.now();
const originalUrl = state.url?.split('?')[0] ?? '';
let url: string = '';
if (originalUrl.startsWith('/kunde')) {
url = originalUrl.replace('/kunde', `/kunde/${processId}`);
} else {
url = originalUrl;
}
if (originalUrl === url) {
return true;
}
await router.navigateByUrl(url);
return false;
};

View File

@@ -6,6 +6,10 @@
"name": "account",
"data": "M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"
},
{
"name": "account-circle",
"data": "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7.07,18.28C7.5,17.38 10.12,16.5 12,16.5C13.88,16.5 16.5,17.38 16.93,18.28C15.57,19.36 13.86,20 12,20C10.14,20 8.43,19.36 7.07,18.28M18.36,16.83C16.93,15.09 13.46,14.5 12,14.5C10.54,14.5 7.07,15.09 5.64,16.83C4.62,15.5 4,13.82 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,13.82 19.38,15.5 18.36,16.83M12,6C10.06,6 8.5,7.56 8.5,9.5C8.5,11.44 10.06,13 12,13C13.94,13 15.5,11.44 15.5,9.5C15.5,7.56 13.94,6 12,6M12,11A1.5,1.5 0 0,1 10.5,9.5A1.5,1.5 0 0,1 12,8A1.5,1.5 0 0,1 13.5,9.5A1.5,1.5 0 0,1 12,11Z"
},
{
"name": "package-variant-closed",
"data": "M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L10.11,5.22L16,8.61L17.96,7.5L12,4.15M6.04,7.5L12,10.85L13.96,9.75L8.08,6.35L6.04,7.5M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z"
@@ -411,6 +415,10 @@
{
"name": "isa-box-out",
"alias": "Versandbestellung (oder gemischt)"
},
{
"name": "package-variant-closed",
"alias": "Bestellung ohne Konto"
},{
"name": "person",
"alias": "Onlinekonto"

View File

@@ -67,12 +67,13 @@
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000,
"assortment": 6000
"assortment": 6000,
"pickupShelf": 7000
}
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "AZ7zLw2eLmFWHbYP4RDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YewGQYEESXjpRwGX1NYmKY3pXHnAn2DeqIzh2an+FUu9socQlbQnJiHJHoWBAqcqWSua+P12tc95P3s9aaEEYvSjUy7Md88f7N+sk6zZbUmqbMXeXqmZwdkmRoUY/2w0CiiiA4gBFHgu4sMeNQ9dWyfxKTUPf5AnsxnuYpCt5KLxJWSYDv8HHj0mx8DCJTe1m2ony97Lge3JbJ5Dd+Zz6SCwqik7fv53Qole9s/3m66lYFWKAzWRKkHN1zts78CmPxPb+AAHVoqlBM3duvYmnCxxGOmlXabKUNuDR2ExaMu/nlo532jqqy25Cet/FP1UAs96ZGRgzEcHxGPp6kA53lJ15zd+cxz6G93E83AmYJkhddXBQElWEaGtQRfrEzRGmvcksR+V8MMYjGmhkVbQxGGqpnfP4IxbuEFcef6bxxTiulzo75gXoqZTt+7C1qpDcrMM3Yp0Z8RBw3JlV2tLk4FYFZpxY8QrXIcjvRYKExtQ9e5sSbST4Vx95YhEUd6iX0SBPDzcmgR4/Ef6gvJfoWgz68+rqhBGckphdHi2Mf/pYuAlh2jbwtrkErE2xWARBejR/UcU/A3F7k9RkFd5/QZC7qhsE6bZH7uhpkptIbi5XkXagwYy1oJD7yJs4VLOJteYWferRm8h1auxXew5tL8VLHciF+lLj6h8PTUDt2blLgUjHtualqlCwdSTzJyYwk4oswGGDk6E48X7LXpzuhtR8TYTOi2REN0uuTbO/slFBRw+CaYUnD0LjB9p2lb8ndcdV9adzBKmwPxiOtlOELQ=="
"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=="
},
"@shared/icon": "/assets/icons.json"

View File

@@ -66,12 +66,13 @@
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000,
"assortment": 6000
"assortment": 6000,
"pickupShelf": 7000
}
},
"checkForUpdates": 900000,
"licence": {
"scandit": "AZ7zLw2eLmFWHbYP4RDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YewGQYEESXjpRwGX1NYmKY3pXHnAn2DeqIzh2an+FUu9socQlbQnJiHJHoWBAqcqWSua+P12tc95P3s9aaEEYvSjUy7Md88f7N+sk6zZbUmqbMXeXqmZwdkmRoUY/2w0CiiiA4gBFHgu4sMeNQ9dWyfxKTUPf5AnsxnuYpCt5KLxJWSYDv8HHj0mx8DCJTe1m2ony97Lge3JbJ5Dd+Zz6SCwqik7fv53Qole9s/3m66lYFWKAzWRKkHN1zts78CmPxPb+AAHVoqlBM3duvYmnCxxGOmlXabKUNuDR2ExaMu/nlo532jqqy25Cet/FP1UAs96ZGRgzEcHxGPp6kA53lJ15zd+cxz6G93E83AmYJkhddXBQElWEaGtQRfrEzRGmvcksR+V8MMYjGmhkVbQxGGqpnfP4IxbuEFcef6bxxTiulzo75gXoqZTt+7C1qpDcrMM3Yp0Z8RBw3JlV2tLk4FYFZpxY8QrXIcjvRYKExtQ9e5sSbST4Vx95YhEUd6iX0SBPDzcmgR4/Ef6gvJfoWgz68+rqhBGckphdHi2Mf/pYuAlh2jbwtrkErE2xWARBejR/UcU/A3F7k9RkFd5/QZC7qhsE6bZH7uhpkptIbi5XkXagwYy1oJD7yJs4VLOJteYWferRm8h1auxXew5tL8VLHciF+lLj6h8PTUDt2blLgUjHtualqlCwdSTzJyYwk4oswGGDk6E48X7LXpzuhtR8TYTOi2REN0uuTbO/slFBRw+CaYUnD0LjB9p2lb8ndcdV9adzBKmwPxiOtlOELQ=="
"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=="
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -48,7 +48,7 @@
"rootUrl": "https://isa-test.paragon-data.net/wws/v1"
},
"@domain/checkout": {
"olaExpiration": "30s"
"olaExpiration": "5m"
},
"hubs": {
"notifications": {
@@ -68,12 +68,13 @@
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000,
"assortment": 6000
"assortment": 6000,
"pickupShelf": 7000
}
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "AZ7zLw2eLmFWHbYP4RDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YewGQYEESXjpRwGX1NYmKY3pXHnAn2DeqIzh2an+FUu9socQlbQnJiHJHoWBAqcqWSua+P12tc95P3s9aaEEYvSjUy7Md88f7N+sk6zZbUmqbMXeXqmZwdkmRoUY/2w0CiiiA4gBFHgu4sMeNQ9dWyfxKTUPf5AnsxnuYpCt5KLxJWSYDv8HHj0mx8DCJTe1m2ony97Lge3JbJ5Dd+Zz6SCwqik7fv53Qole9s/3m66lYFWKAzWRKkHN1zts78CmPxPb+AAHVoqlBM3duvYmnCxxGOmlXabKUNuDR2ExaMu/nlo532jqqy25Cet/FP1UAs96ZGRgzEcHxGPp6kA53lJ15zd+cxz6G93E83AmYJkhddXBQElWEaGtQRfrEzRGmvcksR+V8MMYjGmhkVbQxGGqpnfP4IxbuEFcef6bxxTiulzo75gXoqZTt+7C1qpDcrMM3Yp0Z8RBw3JlV2tLk4FYFZpxY8QrXIcjvRYKExtQ9e5sSbST4Vx95YhEUd6iX0SBPDzcmgR4/Ef6gvJfoWgz68+rqhBGckphdHi2Mf/pYuAlh2jbwtrkErE2xWARBejR/UcU/A3F7k9RkFd5/QZC7qhsE6bZH7uhpkptIbi5XkXagwYy1oJD7yJs4VLOJteYWferRm8h1auxXew5tL8VLHciF+lLj6h8PTUDt2blLgUjHtualqlCwdSTzJyYwk4oswGGDk6E48X7LXpzuhtR8TYTOi2REN0uuTbO/slFBRw+CaYUnD0LjB9p2lb8ndcdV9adzBKmwPxiOtlOELQ=="
"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=="
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -67,12 +67,13 @@
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000,
"assortment": 6000
"assortment": 6000,
"pickupShelf": 7000
}
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "AZZzfQ+eLFl3Dzf1QSBag1lDibIoOPh4W33erRIRe3SDUMkHDX8eczEjd2TnfRMWoE5lXOBGtESCWICN9EbrmI1S9Lu5APsvvEOD+K54ADwIVawx0HNZRAc8/+9Vf/izcEGOFQFGBQJyR6vzdzFv5HcjznhxI9E3LiF+uVQPtCqsVYzpkMWIrC5VCg2uwNrj9Bw6f8zYi/lZPrDMS5yVKVcajeK7sh9QAq17dR0opjIIuP5t5nDEJ7hnITwtTR5HaM6cX/KhKpTILOgKexvLYqrK6QJWpU85sDwqwn6T7av4V68qL3XrUo60dScop4QsvraQe1HkRsffl6DkAEoX0RNMS5qVWjGerW7lvA/DQd9hsAO3jWFDR9hVDyt2VvmzzFKnHYqTYxC5qG4bCEJ0RJjy6tEP5Q7vL5SxWygVadmjPv+TwDOCS7DxzxIjcO+BXQY7gW6qn0hx9fXzyvO3avrGWqyImMlgEApZq+36ANqtRcPD/stEe4i0N9dSPhYoHPcc/9/9jpts43FozlgfY4wY8Wt5ybB3X0caISMmB/klFIJKKN7num439z3+Xk7ENB/Xvb0XAtnOt/cuxQYsGQ7fb62GOO/7Va5fdE9ZfaIJsS5ToE6oIbV04pLUssJf9cUMsyPFVELYSJmyGPQQFRz0TTxxRvPapIWrfa2x5x3hYUpNTAdY3v0fN9l/1ZqNSBmIBLH/LoXaVJQ2DydGD1/QFZ2Z/S7zTYKg5/cSEpUgiYtbwutNZSjRH29ucSizC524k+Zst95T8G7LJaWCT8SQAcKXqCnjpiEGWzD++h0jXjn6BWjUnIHi0te+27vF/z6UQL00sWco5hUIqF66EiU="
"scandit": "AVljxT/dG+TAIDDL2jTxm843juR2OtZ6lHLxRpYR7x9uYiSvY2IAHdRx8tjsf9KU7wK0F5cAeb/nLMHF6Vor9ps79wvuBQw6G3N0IW978b78ZUgPOFzxHUAMuD8dbkDZlX8r9y1cOd9sT3UNEwGrQ4siUt2oCkigyTxJAgYs1ijnjQid7q42hHk3tMXywrAYeu5MhF0TV1H77DRDMxPHD/xiR0zhFQRB2Dtnm1+e3LHKCyQjZ/zknEpQB6HS7UbCBoEDj4tohb83E6oqmQFWwt85/Jk9f49gxXakIcNODnQI5H63kSqpEmV9Al1a5L+WGZ6Bq1gwBbnD8FBXlVqxoooiFXW7jzzBa9LNmQiQ5J8yEkIsPeyOHec7F4ERvVONSMYwWyH39ZweSiRsZRM1UsFPhN96bCT5MEwkjPFn4gji6TPGEceJZvV3HwsiCT5Bgjla4bvDsZ2jYvAr9tSij8kIii9dHvsWlrimt+szHJLSz+8uNI6jAvXyr2f3oRxZD/F9osZHVWkgtAc+vVWqkxVJCqmpmoHOXI6TFSqSjYHddhZyU5r2lgQt0+NI6k/bV3iN7Le1RJCP/wuSDCTZjzsU1igB7UnIN2Y70CqCjIeVH9qlxaI1YAC9lwFv1FZvsiueYeJP1n39mmXCSELVtzxgIBEX5yaIHNbbGXd+e8JUgcO8vJ2JA2kJudaU+xfYR5SY//+J1kPsNSbnBnM25LL+LjeRB3QTfqV5sFq8ORWcIMITvkEaRfP3PVcOzb+hO4Ren4ezhJuyADulmvG8a9Kxxk6ymzBbE7a93SGVbxp7OQNEmvTn5+B9wJ7/l1mtvZL2TilrDZBQVMYWrGuUGpA="
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -67,12 +67,13 @@
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000,
"assortment": 6000
"assortment": 6000,
"pickupShelf": 7000
}
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "AZZzfQ+eLFl3Dzf1QSBag1lDibIoOPh4W33erRIRe3SDUMkHDX8eczEjd2TnfRMWoE5lXOBGtESCWICN9EbrmI1S9Lu5APsvvEOD+K54ADwIVawx0HNZRAc8/+9Vf/izcEGOFQFGBQJyR6vzdzFv5HcjznhxI9E3LiF+uVQPtCqsVYzpkMWIrC5VCg2uwNrj9Bw6f8zYi/lZPrDMS5yVKVcajeK7sh9QAq17dR0opjIIuP5t5nDEJ7hnITwtTR5HaM6cX/KhKpTILOgKexvLYqrK6QJWpU85sDwqwn6T7av4V68qL3XrUo60dScop4QsvraQe1HkRsffl6DkAEoX0RNMS5qVWjGerW7lvA/DQd9hsAO3jWFDR9hVDyt2VvmzzFKnHYqTYxC5qG4bCEJ0RJjy6tEP5Q7vL5SxWygVadmjPv+TwDOCS7DxzxIjcO+BXQY7gW6qn0hx9fXzyvO3avrGWqyImMlgEApZq+36ANqtRcPD/stEe4i0N9dSPhYoHPcc/9/9jpts43FozlgfY4wY8Wt5ybB3X0caISMmB/klFIJKKN7num439z3+Xk7ENB/Xvb0XAtnOt/cuxQYsGQ7fb62GOO/7Va5fdE9ZfaIJsS5ToE6oIbV04pLUssJf9cUMsyPFVELYSJmyGPQQFRz0TTxxRvPapIWrfa2x5x3hYUpNTAdY3v0fN9l/1ZqNSBmIBLH/LoXaVJQ2DydGD1/QFZ2Z/S7zTYKg5/cSEpUgiYtbwutNZSjRH29ucSizC524k+Zst95T8G7LJaWCT8SQAcKXqCnjpiEGWzD++h0jXjn6BWjUnIHi0te+27vF/z6UQL00sWco5hUIqF66EiU="
"scandit": "AVljxT/dG+TAIDDL2jTxm843juR2OtZ6lHLxRpYR7x9uYiSvY2IAHdRx8tjsf9KU7wK0F5cAeb/nLMHF6Vor9ps79wvuBQw6G3N0IW978b78ZUgPOFzxHUAMuD8dbkDZlX8r9y1cOd9sT3UNEwGrQ4siUt2oCkigyTxJAgYs1ijnjQid7q42hHk3tMXywrAYeu5MhF0TV1H77DRDMxPHD/xiR0zhFQRB2Dtnm1+e3LHKCyQjZ/zknEpQB6HS7UbCBoEDj4tohb83E6oqmQFWwt85/Jk9f49gxXakIcNODnQI5H63kSqpEmV9Al1a5L+WGZ6Bq1gwBbnD8FBXlVqxoooiFXW7jzzBa9LNmQiQ5J8yEkIsPeyOHec7F4ERvVONSMYwWyH39ZweSiRsZRM1UsFPhN96bCT5MEwkjPFn4gji6TPGEceJZvV3HwsiCT5Bgjla4bvDsZ2jYvAr9tSij8kIii9dHvsWlrimt+szHJLSz+8uNI6jAvXyr2f3oRxZD/F9osZHVWkgtAc+vVWqkxVJCqmpmoHOXI6TFSqSjYHddhZyU5r2lgQt0+NI6k/bV3iN7Le1RJCP/wuSDCTZjzsU1igB7UnIN2Y70CqCjIeVH9qlxaI1YAC9lwFv1FZvsiueYeJP1n39mmXCSELVtzxgIBEX5yaIHNbbGXd+e8JUgcO8vJ2JA2kJudaU+xfYR5SY//+J1kPsNSbnBnM25LL+LjeRB3QTfqV5sFq8ORWcIMITvkEaRfP3PVcOzb+hO4Ren4ezhJuyADulmvG8a9Kxxk6ymzBbE7a93SGVbxp7OQNEmvTn5+B9wJ7/l1mtvZL2TilrDZBQVMYWrGuUGpA="
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -68,12 +68,13 @@
"taskCalendar": 3000,
"remission": 4000,
"packageInspection": 5000,
"assortment": 6000
"assortment": 6000,
"pickupShelf": 7000
}
},
"checkForUpdates": 3600000,
"licence": {
"scandit": "AZ7zLw2eLmFWHbYP4RDq8VAEgAxmNGYcPU8YpOc3DryEXj4zMzYQFrQuUm0YewGQYEESXjpRwGX1NYmKY3pXHnAn2DeqIzh2an+FUu9socQlbQnJiHJHoWBAqcqWSua+P12tc95P3s9aaEEYvSjUy7Md88f7N+sk6zZbUmqbMXeXqmZwdkmRoUY/2w0CiiiA4gBFHgu4sMeNQ9dWyfxKTUPf5AnsxnuYpCt5KLxJWSYDv8HHj0mx8DCJTe1m2ony97Lge3JbJ5Dd+Zz6SCwqik7fv53Qole9s/3m66lYFWKAzWRKkHN1zts78CmPxPb+AAHVoqlBM3duvYmnCxxGOmlXabKUNuDR2ExaMu/nlo532jqqy25Cet/FP1UAs96ZGRgzEcHxGPp6kA53lJ15zd+cxz6G93E83AmYJkhddXBQElWEaGtQRfrEzRGmvcksR+V8MMYjGmhkVbQxGGqpnfP4IxbuEFcef6bxxTiulzo75gXoqZTt+7C1qpDcrMM3Yp0Z8RBw3JlV2tLk4FYFZpxY8QrXIcjvRYKExtQ9e5sSbST4Vx95YhEUd6iX0SBPDzcmgR4/Ef6gvJfoWgz68+rqhBGckphdHi2Mf/pYuAlh2jbwtrkErE2xWARBejR/UcU/A3F7k9RkFd5/QZC7qhsE6bZH7uhpkptIbi5XkXagwYy1oJD7yJs4VLOJteYWferRm8h1auxXew5tL8VLHciF+lLj6h8PTUDt2blLgUjHtualqlCwdSTzJyYwk4oswGGDk6E48X7LXpzuhtR8TYTOi2REN0uuTbO/slFBRw+CaYUnD0LjB9p2lb8ndcdV9adzBKmwPxiOtlOELQ=="
"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=="
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core';
import { Router } from '@angular/router';
import { PickupShelfInNavigationService } from '@shared/services';
import { UiFilter } from '@ui/filter';
import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
@@ -9,6 +10,8 @@ import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ModalNotificationsRemissionGroupComponent {
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
@Input()
notifications: MessageBoardItemDTO[];
@@ -18,8 +21,14 @@ export class ModalNotificationsRemissionGroupComponent {
constructor(private _router: Router) {}
itemSelected(item: MessageBoardItemDTO) {
const defaultNav = this._pickupShelfInNavigationService.listRoute();
const queryParams = UiFilter.getQueryParamsFromQueryTokenDTO(item.queryToken);
this._router.navigate(['/filiale/goods/in/results'], { queryParams });
this._router.navigate(defaultNav.path, {
queryParams: {
...defaultNav.queryParams,
...queryParams,
},
});
this.navigated.emit();
}
}

View File

@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core';
import { Router } from '@angular/router';
import { PickupShelfInNavigationService } from '@shared/services';
import { UiFilter } from '@ui/filter';
import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
@@ -9,6 +10,7 @@ import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ModalNotificationsReservationGroupComponent {
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
@Input()
notifications: MessageBoardItemDTO[];
@@ -18,8 +20,14 @@ export class ModalNotificationsReservationGroupComponent {
constructor(private _router: Router) {}
itemSelected(item: MessageBoardItemDTO) {
const defaultNav = this._pickupShelfInNavigationService.listRoute();
const queryParams = UiFilter.getQueryParamsFromQueryTokenDTO(item.queryToken);
this._router.navigate(['/filiale/goods/in/results'], { queryParams });
this._router.navigate(defaultNav.path, {
queryParams: {
...defaultNav.queryParams,
...queryParams,
},
});
this.navigated.emit();
}
}

View File

@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core';
import { Router } from '@angular/router';
import { PickupShelfInNavigationService } from '@shared/services';
import { UiFilter } from '@ui/filter';
import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
@@ -9,6 +10,8 @@ import { MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ModalNotificationsTaskCalendarGroupComponent {
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
@Input()
notifications: MessageBoardItemDTO[];
@@ -18,8 +21,14 @@ export class ModalNotificationsTaskCalendarGroupComponent {
constructor(private _router: Router) {}
itemSelected(item: MessageBoardItemDTO) {
const defaultNav = this._pickupShelfInNavigationService.listRoute();
const queryParams = UiFilter.getQueryParamsFromQueryTokenDTO(item.queryToken);
this._router.navigate(['/filiale/goods/in/results'], { queryParams });
this._router.navigate(defaultNav.path, {
queryParams: {
...defaultNav.queryParams,
...queryParams,
},
});
this.navigated.emit();
}
}

View File

@@ -40,7 +40,7 @@ export class ModalNotificationsComponent extends ComponentStore<ModalNotificatio
private _categorySelector = (state: ModalNotificationComponentState) => {
const selectedArea = this._selectedAreaSelector(state);
console.log('_categorySelector', state.notifications[selectedArea]?.[0]?.category);
return state.notifications[selectedArea]?.[0]?.category;
};

View File

@@ -1,3 +1,3 @@
<shared-breadcrumb class="my-4" [key]="breadcrumbKey"></shared-breadcrumb>
<shared-breadcrumb [key]="breadcrumbKey"></shared-breadcrumb>
<router-outlet></router-outlet>

View File

@@ -1,10 +1,393 @@
<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 *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" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #archivTooltip [closeable]="true">
<ng-container *ngIf="isAvailable$ | async; else notAvailable">
Archivtitel. Wird nicht mehr gedruckt. Artikel ist bestellbar, weil lieferbar.
</ng-container>
<ng-template #notAvailable>
Archivtitel. Wird nicht mehr gedruckt. Nicht bestellbar.
</ng-template>
</ui-tooltip>
</button>
</div>
<div *ngIf="showSubscriptionBadge$ | async">
<button
[uiOverlayTrigger]="subscribtionTooltip"
class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]"
>
<img src="/assets/images/bookmark_subscription.svg" alt="Fortsetzungsartikel Badge" />
</button>
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #subscribtionTooltip [closeable]="true"
>Artikel ist ein Fortsetzungsartikel,<br />
Artikel muss über eine Aboabteilung<br />
bestellt werden.
</ui-tooltip>
</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>
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #promotionTooltip [closeable]="true">
Dieser Artikel befindet sich im Prämienkatalog.
</ui-tooltip>
</button>
</div>
</div>
<div class="page-article-details__product-image-recessions flex flex-col items-center">
<div class="page-article-details__product-image">
<button class="border-none outline-none bg-transparent relative" (click)="showImages()">
<img
class="max-h-[19.6875rem] max-w-[12.1875rem] rounded"
(load)="loadImage()"
[src]="item.imageId | productImage: 195:315:true"
alt="product image"
/>
<ui-icon
class="absolute text-[#A7B9CB] inline-block bottom-[0.875rem] right-[1.125rem]"
*ngIf="imageLoaded$ | async"
icon="search_add"
size="25px"
></ui-icon>
</button>
</div>
<button
(click)="showReviews()"
class="page-article-details__product-recessions flex flex-col mt-2 items-center bg-transparent border-none outline-none"
*ngIf="item.reviews?.length > 0"
>
<ui-stars [rating]="store.reviewRating$ | async"></ui-stars>
<div class="text-p2 text-[#0556B4] font-bold">{{ item.reviews.length }} Rezensionen</div>
</button>
</div>
<div class="page-article-details__product-contributors">
<a
*ngFor="let contributor of contributors$ | async; let last = last"
class="text-[#0556B4] font-semibold no-underline text-p2"
[routerLink]="resultsPath"
[queryParams]="{ main_qs: contributor, main_author: 'author' }"
>
{{ contributor }}{{ last ? '' : ';' }}
</a>
</div>
<div class="page-article-details__product-print justify-self-end" [class.mt-4]="isBadgeVisible$ | async">
<button class="bg-transparent text-brand font-bold text-lg outline-none border-none p-0" (click)="print()">Drucken</button>
</div>
<div class="page-article-details__product-title text-h3 font-bold mb-6">
{{ item.product?.name }}
</div>
<div class="page-article-details__product-misc flex flex-col mb-4">
<div
class="page-article-details__product-format flex items-center font-bold text-p3"
*ngIf="item?.product?.format && item?.product?.formatDetail"
>
<img
*ngIf="item?.product?.format !== '--'"
class="flex mr-2 h-[1.125rem]"
[src]="'/assets/images/Icon_' + item.product?.format + '.svg'"
[alt]="item.product?.formatDetail"
/>
{{ item.product?.formatDetail }}
</div>
<div class="page-article-details__product-volume" *ngIf="item?.product?.volume">Band/Reihe {{ item?.product?.volume }}</div>
<div class="page-article-details__product-publication">{{ publicationDate$ | async }}</div>
</div>
<div class="page-article-details__product-price-info flex flex-col mb-4 flex-nowrap self-end">
<div class="page-article-details__product-price font-bold text-xl self-end" *ngIf="price$ | async; let price">
{{ price?.value?.value | currency: price?.value?.currency:'code' }}
</div>
<div *ngIf="price$ | async; let price" class="page-article-details__product-price-bound self-end">
{{ price?.vat?.vatType | vat: (priceMaintained$ | async) }}
</div>
<div class="page-article-details__product-points self-end" *ngIf="store.promotionPoints$ | async; let promotionPoints">
{{ promotionPoints }} Lesepunkte
</div>
</div>
<div class="page-article-details__product-origin-infos flex flex-col mb-4">
<div class="page-article-details__product-manufacturer" data-name="product-manufacturer">{{ item.product?.manufacturer }}</div>
<div class="page-article-details__product-language" *ngIf="item?.product?.locale" data-name="product-language">
{{ item?.product?.locale }}
</div>
</div>
<div class="page-article-details__product-stock flex justify-end items-center">
<div class="h-5 w-16 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="store.fetchingTakeAwayAvailability$ | async"></div>
<button
class="flex flex-row py-4 pl-4"
type="button"
[uiOverlayTrigger]="tooltip"
[overlayTriggerDisabled]="!(stockTooltipText$ | async)"
(click)="showTooltip()"
*ngIf="!(store.fetchingTakeAwayAvailability$ | async)"
>
<ng-container *ngIf="store.takeAwayAvailability$ | async; let takeAwayAvailability">
<ui-icon class="mr-2 mb-1" icon="home" size="15px"></ui-icon>
<span class="font-bold text-p3">{{ takeAwayAvailability.inStock || 0 }}x</span>
</ng-container>
</button>
<ui-tooltip #tooltip yPosition="above" xPosition="after" [yOffset]="-12" [closeable]="true">
{{ stockTooltipText$ | async }}
</ui-tooltip>
</div>
<div class="page-article-details__product-ean-specs flex flex-col">
<div class="page-article-details__product-ean" data-name="product-ean">{{ item.product?.ean }}</div>
<div class="page-article-details__product-specs">
<ng-container *ngIf="item?.specs?.length > 0">
{{ (item?.specs)[0]?.value }}
</ng-container>
</div>
</div>
<div class="page-article-details__product-availabilities flex flex-row items-center justify-end mt-4">
<div
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
*ngIf="store.fetchingTakeAwayAvailability$ | async; else showAvailabilityTakeAwayIcon"
></div>
<ng-template #showAvailabilityTakeAwayIcon>
<div
*ngIf="store.isTakeAwayAvailabilityAvailable$ | async"
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center"
>
<ui-icon class="mx-1" icon="shopping_bag" size="18px"> </ui-icon>
</div>
</ng-template>
<div
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
*ngIf="store.fetchingPickUpAvailability$ | async; else showAvailabilityPickUpIcon"
></div>
<ng-template #showAvailabilityPickUpIcon>
<div
#uiOverlayTrigger="uiOverlayTrigger"
[uiOverlayTrigger]="orderDeadlineTooltip"
*ngIf="store.isPickUpAvailabilityAvailable$ | async"
class="page-article-details__product-pick-up-availability w-[2.25rem] h-[2.25rem] cursor-pointer bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
[class.tooltip-active]="uiOverlayTrigger.opened"
>
<shared-icon icon="isa-box-out" [size]="24"></shared-icon>
</div>
<ui-tooltip [warning]="true" yPosition="above" xPosition="after" [yOffset]="-12" #orderDeadlineTooltip [closeable]="true">
<b>{{ (store.pickUpAvailability$ | async)?.orderDeadline | orderDeadline }}</b>
</ui-tooltip>
</ng-template>
<div
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
*ngIf="store.fetchingDeliveryAvailability$ | async; else showAvailabilityDeliveryIcon"
></div>
<ng-template #showAvailabilityDeliveryIcon>
<div
*ngIf="showDeliveryTruck$ | async"
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
>
<ui-icon class="-mb-[0.3125rem] -mt-[0.3125rem] mx-1" icon="truck" size="30px"></ui-icon>
</div>
</ng-template>
<div
class="h-5 w-6 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]"
*ngIf="store.fetchingDeliveryB2BAvailability$ | async; else showAvailabilityDeliveryB2BIcon"
></div>
<ng-template #showAvailabilityDeliveryB2BIcon>
<div
*ngIf="showDeliveryB2BTruck$ | async"
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
>
<ui-icon class="-mb-[0.625rem] -mt-[0.625rem] mx-1" icon="truck_b2b" size="30px"> </ui-icon>
</div>
</ng-template>
<span *ngIf="store.isDownload$ | async" class="flex flex-row items-center">
<div class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3">
<ui-icon class="mx-1" icon="download" size="18px"></ui-icon>
</div>
</span>
</div>
<div class="page-article-details__shelf-ssc">
<div class="page-article-details__ssc flex justify-end my-2 font-bold text-lg">
<div class="w-52 h-5 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="fetchingAvailabilities$ | async"></div>
<ng-container *ngIf="!(fetchingAvailabilities$ | async)">
<div class="text-right" *ngIf="store.sscText$ | async; let sscText">
{{ sscText }}
</div>
</ng-container>
</div>
<div class="page-article-details__shelfinfo text-right" *ngIf="store.isDownload$ | async">
<ng-container
*ngIf="
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
else stockInfos
"
>
<span data-name="compartment">
{{ (item?.stockInfos)[0]?.compartment }}
</span>
/
<br />
<span data-name="shelf-info-label">
{{ (item?.shelfInfos)[0]?.label }}
</span>
</ng-container>
<ng-template #stockInfos>
<ng-container *ngIf="item?.stockInfos && (item?.stockInfos)[0]?.compartment; else shelfInfos">
{{ (item?.stockInfos)[0]?.compartment }}
</ng-container>
</ng-template>
<ng-template #shelfInfos>
<ng-container *ngIf="item?.shelfInfos && (item?.shelfInfos)[0]?.label">
<span data-name="shelf-info-label">{{ (item?.shelfInfos)[0]?.label }}</span>
</ng-container>
</ng-template>
</div>
<div class="page-article-details__shelfinfo text-right" *ngIf="!(store.isDownload$ | async)">
<ng-container
*ngIf="
item?.stockInfos && item?.shelfInfos && (item?.stockInfos)[0]?.compartment && (item?.shelfInfos)[0]?.label;
else stockInfos2
"
>
<span data-name="compartment">{{ (item?.stockInfos)[0]?.compartment }}</span>
/
<br />
<span data-name="shelf-info-label">{{ (item?.shelfInfos)[0]?.label }}</span>
</ng-container>
<ng-template #stockInfos2>
<ng-container *ngIf="item?.stockInfos && (item?.stockInfos)[0]?.compartment; else shelfInfos2">
{{ (item?.stockInfos)[0]?.compartment }}
</ng-container>
</ng-template>
<ng-template #shelfInfos2>
<ng-container *ngIf="item?.shelfInfos && (item?.shelfInfos)[0]?.label">
{{ (item?.shelfInfos)[0]?.label }}
</ng-container>
</ng-template>
</div>
</div>
</div>
<div class="page-article-details__product-formats-container mt-3" *ngIf="item.family?.length > 0">
<hr class="bg-[#E6EFF9] border-t-2" />
<div class="pt-3">
<div class="page-article-details__product-formats">
<span class="mr-2">Auch verfügbar als</span>
<ui-slider [scrollDistance]="250">
<a
class="mr-4 text-[#0556B4] font-bold no-underline px-2"
*ngFor="let format of item.family"
[routerLink]="getDetailsPath(format.product.ean)"
queryParamsHandling="preserve"
>
<span class="flex items-center">
<img
class="mr-2"
*ngIf="!!format.product?.format"
[src]="'/assets/images/OF_Icon_' + format.product?.format + '.svg'"
alt="format icon"
/>
{{ format.product?.formatDetail }}
<span class="ml-1">{{ format.catalogAvailability?.price?.value?.value | currency: '€' }}</span>
</span>
</a>
</ui-slider>
</div>
</div>
</div>
<hr class="bg-[#E6EFF9] border-t-2 my-3" />
<div #description class="page-article-details__product-description flex flex-col flex-grow mb-6" *ngIf="item.texts?.length > 0">
<page-article-details-text class="block box-border" [text]="item.texts[0]"> </page-article-details-text>
<div class="box-border">
<button
class="font-bold flex flex-row text-[#0556B4] items-center mt-2"
*ngIf="!showMore && item?.texts?.length > 1"
(click)="showMore = !showMore"
>
Mehr <ui-icon class="ml-2" size="15px" icon="arrow"></ui-icon>
</button>
</div>
<div *ngIf="showMore" class="page-article-details__product-description-text flex flex-col whitespace-pre-line break-words box-border">
<span *ngFor="let text of item.texts | slice: 1">
<h3 class="my-4 text-p2 font-bold">{{ text.label }}</h3>
{{ text.value }}
</span>
<button class="font-bold flex flex-row text-[#0556B4] items-center mt-2" (click)="showMore = !showMore">
<ui-icon class="transform ml-0 mr-2 rotate-180" size="15px" icon="arrow"></ui-icon> Weniger
</button>
<button class="page-article-details__scroll-top-cta" (click)="scrollTop(description)">
<ui-icon class="text-[#0556B4]" icon="arrow" size="20px"></ui-icon>
</button>
</div>
<div class="h-28 box-border"></div>
</div>
</div>
<div class="page-article-details__product-recommendations relative">
<button
*ngIf="store.item$ | async; let item"
class="shadow-[#dce2e9_0px_-2px_18px_0px] mb-5 border-none outline-none flex items-center px-5 h-14 min-h-[3.5rem] bg-white w-full"
(click)="showRecommendations = true"
>
<span class="uppercase text-[#0556B4] font-bold text-p3">Empfehlungen</span>
<img class="absolute right-5 -top-[0.125rem] h-12" src="assets/images/recommendation_tag.png" alt="recommendation icon" />
</button>
</div>
</div>
<div
class="page-article-details__actions absolute bottom-32 left-1/2 -translate-x-1/2 whitespace-nowrap"
*ngIf="store.item$ | async; let item"
>
<button
*ngIf="!(store.isDownload$ | async)"
class="text-brand border-2 border-brand bg-white font-bold text-lg px-[1.375rem] py-4 rounded-full mr-[1.875rem]"
(click)="showAvailabilities()"
>
Bestände in anderen Filialen
</button>
<button
class="text-white bg-brand border-brand font-bold text-lg px-[1.375rem] py-4 rounded-full border-none no-underline"
(click)="showPurchasingModal()"
[disabled]="!(isAvailable$ | async) || (fetchingAvailabilities$ | async) || (item?.features && (item?.features)[0]?.key === 'PFO')"
>
In den Warenkorb
</button>
</div>
<div class="page-article-details__recommendations-overlay absolute top-0 inset-x-0" @slideYAnimation *ngIf="showRecommendations">
<page-article-recommendations (close)="showRecommendations = false"></page-article-recommendations>
</div>
<!--
<ng-container *ngIf="!showRecommendations">
<div #detailsContainer class="page-article-details__container px-5 relative">
<ng-container *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 *ngIf="showArchivBadge$ | async" class="archiv-badge">
<button [uiOverlayTrigger]="archivTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
<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" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #archivTooltip [closeable]="true">
<ng-container *ngIf="isAvailable$ | async; else notAvailable">
@@ -17,7 +400,10 @@
</button>
</div>
<div *ngIf="showSubscriptionBadge$ | async">
<button [uiOverlayTrigger]="subscribtionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
<button
[uiOverlayTrigger]="subscribtionTooltip"
class="p-0 m-0 outline-none border-none bg-transparent relative -top-[0.3125rem]"
>
<img src="/assets/images/bookmark_subscription.svg" alt="Fortsetzungsartikel Badge" />
</button>
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #subscribtionTooltip [closeable]="true"
@@ -27,7 +413,7 @@
</ui-tooltip>
</div>
<div *ngIf="showPromotionBadge$ | async" class="promotion-badge">
<button [uiOverlayTrigger]="promotionTooltip" class="p-0 m-0 outline-none border-none bg-transparent relative -top-px-5">
<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>
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #promotionTooltip [closeable]="true">
Dieser Artikel befindet sich im Prämienkatalog.
@@ -46,7 +432,7 @@
alt="product image"
/>
<ui-icon
class="absolute text-[#A7B9CB] inline-block bottom-[14px] right-[18px]"
class="absolute text-[#A7B9CB] inline-block bottom-[0.875rem] right-[1.125rem]"
*ngIf="imageLoaded$ | async"
icon="search_add"
size="25px"
@@ -196,7 +582,7 @@
*ngIf="showDeliveryTruck$ | async"
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
>
<ui-icon class="-mb-px-5 -mt-px-5 mx-1" icon="truck" size="30px"></ui-icon>
<ui-icon class="-mb-[0.3125rem] -mt-[0.3125rem] mx-1" icon="truck" size="30px"></ui-icon>
</div>
</ng-template>
@@ -209,7 +595,7 @@
*ngIf="showDeliveryB2BTruck$ | async"
class="w-[2.25rem] h-[2.25rem] bg-[#D8DFE5] rounded-[5px_5px_0px_5px] flex items-center justify-center ml-3"
>
<ui-icon class="-mb-px-10 -mt-px-10 mx-1" icon="truck_b2b" size="30px"> </ui-icon>
<ui-icon class="-mb-[0.625rem] -mt-[0.625rem] mx-1" icon="truck_b2b" size="30px"> </ui-icon>
</div>
</ng-template>
@@ -222,7 +608,7 @@
<div class="page-article-details__shelf-ssc">
<div class="page-article-details__ssc flex justify-end my-2 font-bold text-lg">
<div class="w-52 h-px-20 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="fetchingAvailabilities$ | async"></div>
<div class="w-52 h-5 bg-[#e6eff9] animate-[load_0.75s_linear_infinite]" *ngIf="fetchingAvailabilities$ | async"></div>
<ng-container *ngIf="!(fetchingAvailabilities$ | async)">
<div class="text-right" *ngIf="store.sscText$ | async; let sscText">
{{ sscText }}
@@ -294,7 +680,7 @@
class="mr-4 text-[#0556B4] font-bold no-underline px-2"
*ngFor="let format of item.family"
[routerLink]="getDetailsPath(format.product.ean)"
[queryParamsHandling]="!(isTablet$ | async) ? 'preserve' : ''"
queryParamsHandling="preserve"
>
<span class="flex items-center">
<img
@@ -350,7 +736,7 @@
>
<button
*ngIf="!(store.isDownload$ | async)"
class="text-brand border-2 border-brand bg-white font-bold text-lg px-[1.375rem] py-4 rounded-full mr-px-30"
class="text-brand border-2 border-brand bg-white font-bold text-lg px-[1.375rem] py-4 rounded-full mr-[1.875rem]"
(click)="showAvailabilities()"
>
Bestände in anderen Filialen
@@ -382,4 +768,4 @@
<div class="page-article-details__recommendations-overlay absolute rounded-t" @slideYAnimation *ngIf="showRecommendations">
<page-article-recommendations (close)="showRecommendations = false"></page-article-recommendations>
</div>
</div> -->

View File

@@ -1,9 +1,13 @@
:host {
@apply box-border block h-split-screen-tablet desktop-small:h-split-screen-desktop;
@apply box-border block relative;
}
.page-article-details__wrapper {
@apply grid grid-rows-[1fr_auto] h-split-screen-tablet max-h-split-screen-tablet desktop-small:h-split-screen-desktop desktop-small:max-h-split-screen-desktop;
}
.page-article-details__container {
@apply h-full w-full overflow-y-scroll overflow-hidden bg-white rounded shadow-card flex flex-col;
@apply overflow-scroll bg-white rounded shadow-card flex flex-col;
}
.page-article-details__product-details {
@@ -84,12 +88,10 @@
}
.page-article-details__scroll-top-cta {
@apply flex items-center justify-center self-end border-none outline-none bg-white relative rounded p-0 mt-8 mr-4;
@apply w-[3.625rem] h-[3.625rem] flex items-center justify-center self-end border-none outline-none bg-white relative rounded p-0 mt-8 mr-4;
box-shadow: 0px 0px 20px 0px rgba(89, 100, 112, 0.5);
transform: rotate(-90deg);
border-radius: 100%;
width: 58px;
height: 58px;
}
.page-article-details__actions {

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ElementRef, ViewChild } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ElementRef, ViewChild, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { DomainPrinterService } from '@domain/printer';
@@ -20,7 +20,7 @@ import { DateAdapter } from '@ui/common';
import { DatePipe } from '@angular/common';
import { PurchaseOptionsModalService } from '@shared/modals/purchase-options-modal';
import { EnvironmentService } from '@core/environment';
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
import { CheckoutNavigationService, ProductCatalogNavigationService, CustomerSearchNavigation } from '@shared/services';
import { DomainCheckoutService } from '@domain/checkout';
import { Store } from '@ngrx/store';
@@ -33,6 +33,8 @@ import { Store } from '@ngrx/store';
animations: [slideYAnimation],
})
export class ArticleDetailsComponent implements OnInit, OnDestroy {
private _customerSearchNavigation = inject(CustomerSearchNavigation);
private readonly subscriptions = new Subscription();
showRecommendations: boolean;
@@ -411,6 +413,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
}
async navigateToCustomerSearch() {
const nav = this._customerSearchNavigation.defaultRoute({ processId: this.applicationService.activatedProcessId });
try {
const response = await this.customerFeatures$
.pipe(
@@ -420,11 +423,11 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
})
)
.toPromise();
this._router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search'], {
this._router.navigate(nav.path, {
queryParams: { filter_customertype: response.filter.customertype },
});
} catch (error) {
this._router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search']);
this._router.navigate(nav.path);
}
}

View File

@@ -24,7 +24,7 @@
class="article"
*ngFor="let recommendation of store.recommendations$ | async"
[routerLink]="getDetailsPath(recommendation.product.ean)"
[queryParams]="{ main_qs: recommendation.product.ean }"
[queryParams]="{ main_qs: recommendation.product.ean, filter_format: '' }"
(click)="close.emit()"
>
<img [src]="recommendation.product?.ean | productImage: 195:315:true" alt="product-image" />

View File

@@ -7,7 +7,6 @@ import { ArticleSearchService } from './article-search.store';
import { FocusSearchboxEvent } from './focus-searchbox.event';
import { ArticleSearchMainAutocompleteProvider } from './providers';
import { ProductCatalogNavigationService } from '@shared/services';
import { EnvironmentService } from '@core/environment';
import { FilterAutocompleteProvider } from '@shared/components/filter';
@Component({
@@ -28,16 +27,11 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
private _processId$: Observable<number>;
get isTablet() {
return this._environmentService.matchTablet();
}
constructor(
private _breadcrumb: BreadcrumbService,
private _articleSearch: ArticleSearchService,
private _activatedRoute: ActivatedRoute,
private _navigationService: ProductCatalogNavigationService,
private _environmentService: EnvironmentService
private _navigationService: ProductCatalogNavigationService
) {}
ngOnInit() {
@@ -49,26 +43,6 @@ export class ArticleSearchComponent implements OnInit, OnDestroy {
this.removeBreadcrumbs(processId);
this.addOrUpdateBreadcrumbs(processId, queryParams);
});
this._articleSearch.searchCompleted
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
.subscribe(async ([searchCompleted, processId]) => {
if (searchCompleted.state.searchState === '') {
const params = searchCompleted.state.filter.getQueryParams();
if (searchCompleted.state.hits === 1) {
const item = searchCompleted.state.items.find((f) => f);
await this._navigationService
.getArticleDetailsPath({
processId,
itemId: item.id,
extras: { queryParams: this.isTablet ? undefined : params },
})
.navigate();
} else {
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
}
}
});
}
cleanupQueryParams(params: Record<string, string> = {}) {

View File

@@ -16,7 +16,6 @@ export interface ArticleSearchState {
hits: number;
selectedBranch: BranchDTO;
selectedItemIds: number[];
scrollPosition: number;
defaultSettings?: UISettingsDTO;
}
@@ -44,12 +43,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
return this.get((s) => s.items);
}
scrollPosition$ = this.select((s) => s.scrollPosition);
get scrollPosition() {
return this.get((s) => s.scrollPosition);
}
selectedBranch$ = this.select((s) => s.selectedBranch);
get selectedBranch() {
@@ -92,7 +85,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
searchState: '',
selectedItemIds: [],
selectedBranch: undefined,
scrollPosition: 0,
});
this.setDefaultFilter();
}
@@ -113,10 +105,6 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
this.patchState({ selectedBranch });
}
setScrollPosition(scrollPosition: number) {
this.patchState({ scrollPosition });
}
async setDefaultFilter(defaultQueryParams?: Record<string, string>) {
const defaultSettings = await this.catalog.getSettings().toPromise();
@@ -160,7 +148,7 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
}
}
search = this.effect((options$: Observable<{ clear?: boolean; orderBy?: boolean }>) =>
search = this.effect((options$: Observable<{ clear?: boolean; orderBy?: boolean; doNotTrack?: boolean }>) =>
options$.pipe(
tap((options) => {
this.searchStarted.next({ clear: options?.clear });
@@ -178,6 +166,7 @@ export class ArticleSearchService extends ComponentStore<ArticleSearchState> {
take: 25,
friendlyName: this.friendlyName,
stockId: selectedBranch?.id,
doNotTrack: options?.doNotTrack,
}).pipe(
tapResponse(
(res) => {

View File

@@ -6,7 +6,7 @@ import { map, takeUntil, withLatestFrom } from 'rxjs/operators';
import { ArticleSearchService } from '../article-search.store';
import { ActivatedRoute } from '@angular/router';
import { ProductCatalogNavigationService } from '@shared/services';
import { Filter, FilterComponent, FilterInput } from 'apps/shared/components/filter/src/lib';
import { Filter, FilterComponent } from 'apps/shared/components/filter/src/lib';
@Component({
selector: 'page-article-search-filter',
@@ -28,10 +28,6 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
showFilter: boolean = false;
get isDesktop() {
return this._environment.matchDesktop();
}
get isTablet() {
return this._environment.matchTablet();
}
@@ -73,6 +69,7 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
) {}
ngOnInit() {
this.showFilter = this.sideOutlet !== 'search';
this._activatedRoute.queryParams
.pipe(takeUntil(this._onDestroy$))
.subscribe(async (queryParams) => await this.articleSearch.setDefaultFilter(queryParams));
@@ -91,6 +88,26 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
await this.articleSearch.setDefaultFilter(queryParams);
});
this.articleSearch.searchCompleted
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
.subscribe(async ([searchCompleted, processId]) => {
if (searchCompleted.state.searchState === '') {
const params = searchCompleted.state.filter.getQueryParams();
if (searchCompleted.state.hits === 1) {
const item = searchCompleted.state.items.find((f) => f);
await this._navigationService
.getArticleDetailsPath({
processId,
itemId: item.id,
extras: { queryParams: params },
})
.navigate();
} else {
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
}
}
});
}
ngOnDestroy(): void {
@@ -105,17 +122,17 @@ export class ArticleSearchFilterComponent implements OnInit, OnDestroy {
.pipe(takeUntil(this._onDestroy$), withLatestFrom(this._processId$))
.subscribe(async ([searchCompleted, processId]) => {
if (searchCompleted.state.searchState === '') {
// Check if desktop is necessary, otherwise it would trigger navigation twice (Inside Article-Search.component and here)
if (searchCompleted.state.hits === 1 && !this.isDesktop) {
const params = searchCompleted.state.filter.getQueryParams();
if (searchCompleted.state.hits === 1) {
const item = searchCompleted.state.items.find((f) => f);
await this._navigationService
.getArticleDetailsPath({
processId,
itemId: item.id,
extras: { queryParams: params },
})
.navigate();
} else if (!this.isDesktop) {
const params = searchCompleted.state.filter.getQueryParams();
} else {
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
}
}

View File

@@ -4,11 +4,12 @@ import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { DomainCatalogService } from '@domain/catalog';
import { combineLatest, NEVER, Subscription } from 'rxjs';
import { catchError, debounceTime, first, switchMap, map } from 'rxjs/operators';
import { catchError, debounceTime, first, switchMap, map, tap } from 'rxjs/operators';
import { ArticleSearchService } from '../article-search.store';
import { isEqual } from 'lodash';
import { EnvironmentService } from '@core/environment';
import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/filter/src/lib';
import { ProductCatalogNavigationService } from '@shared/services';
@Component({
selector: 'page-article-search-main',
@@ -17,7 +18,10 @@ import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/fi
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleSearchMainComponent implements OnInit, OnDestroy {
readonly history$ = this.catalog.getSearchHistory({ take: 7 }).pipe(catchError(() => NEVER));
readonly history$ = this.catalog.getSearchHistory({ take: 7 }).pipe(
map((history) => history.filter((h) => !!h.friendlyName)),
catchError(() => NEVER)
);
fetching$ = this.searchService.fetching$;
@@ -49,7 +53,8 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
private route: ActivatedRoute,
private application: ApplicationService,
private breadcrumb: BreadcrumbService,
private _environment: EnvironmentService
private _environment: EnvironmentService,
private _navigationService: ProductCatalogNavigationService
) {}
ngOnInit() {
@@ -77,6 +82,14 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
}
this.removeResultsAndDetailsBreadcrumbs(processId);
// #4519 und #4520 Durch den Performance Umbau, wird auf allen größen kleiner als der Splitscreen die Article-Search Komponente nicht geladen
// Stattdessen werden die Komponenten search-main und search-filter geladen. Einige Funktionen von Article-Search müssen trotzdem aufgerufen werden
if (!this._environment.matchDesktopLarge()) {
this.resetFilter(queryParams);
this.removeCheckoutBreadcrumb(processId);
this.addOrUpdateBreadcrumbs(processId, queryParams);
}
})
);
@@ -160,6 +173,27 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
});
}
resetFilter(queryParams: Record<string, string>) {
if (Object.keys(queryParams).length === 0) {
this.searchService.resetFilter();
}
}
async removeCheckoutBreadcrumb(processId: number) {
this.breadcrumb.removeBreadcrumbsByKeyAndTags(processId, ['checkout']);
}
async addOrUpdateBreadcrumbs(processId: number, queryParams: Record<string, string>) {
await this.breadcrumb.addBreadcrumbIfNotExists({
key: processId,
name: 'Artikelsuche',
path: this._navigationService.getArticleSearchBasePath(processId).path,
params: queryParams,
tags: ['catalog', 'main'],
section: 'customer',
});
}
async removeResultsAndDetailsBreadcrumbs(processId: number) {
const resultsCrumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'results']).pipe(first()).toPromise();
const detailCrumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'details']).pipe(first()).toPromise();

View File

@@ -1,14 +1,11 @@
<a
<div
class="page-search-result-item__item-card hover p-5 desktop-small:px-4 desktop-small:py-[0.625rem] h-[13.25rem] desktop-small:h-[11.3125rem] bg-white border border-solid border-transparent rounded"
[class.page-search-result-item__item-card-primary]="primaryOutletActive"
[routerLink]="detailsPath"
[routerLinkActive]="!isTablet && !primaryOutletActive ? 'active' : ''"
[queryParamsHandling]="!isTablet ? 'preserve' : ''"
(click)="isDesktop ? scrollIntoView() : ''"
[class.active]="isActive"
>
<div class="page-search-result-item__item-thumbnail text-center mr-4 w-[50px] h-[79px]">
<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-[50px] h-[79px]"
class="page-search-result-item__item-image w-[3.125rem] h-[4.9375rem]"
loading="lazy"
*ngIf="item?.imageId | thumbnailUrl; let thumbnailUrl"
[src]="thumbnailUrl"
@@ -122,4 +119,4 @@
</ng-container>
</div>
</div>
</a>
</div>

View File

@@ -1,5 +1,5 @@
import { DatePipe } from '@angular/common';
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, HostBinding, ElementRef } from '@angular/core';
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output, HostBinding } from '@angular/core';
import { ApplicationService } from '@core/application';
import { EnvironmentService } from '@core/environment';
import { DomainAvailabilityService, DomainInStockService } from '@domain/availability';
@@ -54,6 +54,8 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
@Input()
primaryOutletActive?: boolean = false;
@Input() isActive: boolean;
@Output()
selectedChange = new EventEmitter<ItemDTO>();
@@ -78,13 +80,8 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
return this._environment.matchTablet();
}
get isDesktop() {
return this._environment.matchDesktop();
}
get detailsPath() {
return this._navigationService.getArticleDetailsPath({ processId: this.applicationService.activatedProcessId, itemId: this.item?.id })
.path;
get isDesktopLarge() {
return this._environment.matchDesktopLarge();
}
get resultsPath() {
@@ -141,7 +138,6 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
private _availability: DomainAvailabilityService,
private _environment: EnvironmentService,
private _navigationService: ProductCatalogNavigationService,
private _elRef: ElementRef<HTMLElement>,
private _store: Store
) {
super({
@@ -150,18 +146,9 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
});
}
scrollIntoView() {
this._elRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
setSelected() {
const isSelected = this._articleSearchService.selectedItemIds.includes(this.item?.id);
this._articleSearchService.setSelected({ selected: !isSelected, itemId: this.item?.id });
// #4135 Code auskommentiert bis zur Klärung
// if (!this.isTablet) {
// this.selectedChange.emit(this.item);
// }
}
async showTooltip() {

View File

@@ -3,10 +3,10 @@
[class.pb-4]="!(primaryOutletActive$ | async)"
[class.flex-col]="!(primaryOutletActive$ | async)"
>
<div class="flex flex-row w-full desktop-small:w-min" [class.desktop-large:w-full]="!(primaryOutletActive$ | async)">
<div class="flex flex-row w-full desktop:w-min" [class.desktop-large:w-full]="!(primaryOutletActive$ | async)">
<shared-filter-input-group-main
*ngIf="filter$ | async; let filter"
class="block mr-3 w-full desktop-small:w-[23.5rem]"
class="block mr-3 w-full desktop:w-[23.5rem]"
[class.desktop-large:w-full]="!(primaryOutletActive$ | async)"
[hint]="searchboxHint$ | async"
[loading]="fetching$ | async"
@@ -38,7 +38,7 @@
</div>
</div>
<div class="page-search-results__order-by" [class.page-search-results__order-by-primary]="primaryOutletActive$ | async">
<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()"
@@ -46,29 +46,27 @@
</shared-order-by-filter>
</div>
<div class="h-full relative">
<cdk-virtual-scroll-viewport
#scrollContainer
class="product-list h-full"
[itemSize]="(primaryOutletActive$ | async) ? 98 : 181"
minBufferPx="1200"
[maxBufferPx]="maxBufferCdkScrollContainer$ | async"
(scrolledIndexChange)="scrolledIndexChange($event)"
>
<search-result-item
class="page-search-results__result-item"
[class.page-search-results__result-item-primary]="primaryOutletActive$ | async"
*cdkVirtualFor="let item of results$ | async; trackBy: trackByItemId"
(selectedChange)="addToCart($event)"
[selected]="isSelected(item)"
[selectable]="isSelectable(item)"
[item]="item"
[primaryOutletActive]="primaryOutletActive$ | async"
></search-result-item>
<page-search-result-item-loading
[primaryOutletActive]="primaryOutletActive$ | async"
*ngIf="fetching$ | async"
></page-search-result-item-loading>
<ng-container *ngIf="primaryOutletActive$ | async; else sideOutlet">
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="106 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
<a
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
[routerLink]="getDetailsPath(item.id)"
routerLinkActive
#rla="routerLinkActive"
queryParamsHandling="preserve"
(click)="scrollToItem(i)"
>
<search-result-item
class="page-search-results__result-item page-search-results__result-item-primary"
(selectedChange)="addToCart($event)"
[selected]="isSelected(item)"
[selectable]="isSelectable(item)"
[item]="item"
[primaryOutletActive]="true"
[isActive]="rla.isActive"
></search-result-item>
</a>
<page-search-result-item-loading [primaryOutletActive]="true" *ngIf="fetching$ | async"></page-search-result-item-loading>
</cdk-virtual-scroll-viewport>
<div class="actions z-sticky h-0">
<button
@@ -80,4 +78,38 @@
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
</button>
</div>
</div>
</ng-container>
<ng-template #sideOutlet>
<cdk-virtual-scroll-viewport class="product-list" [itemSize]="222 * (scale$ | async)" (scrolledIndexChange)="scrolledIndexChange($event)">
<a
*cdkVirtualFor="let item of results$ | async; let i = index; trackBy: trackByItemId"
[routerLink]="getDetailsPath(item.id)"
routerLinkActive
#rla="routerLinkActive"
queryParamsHandling="preserve"
(click)="scrollToItem(i)"
>
<search-result-item
class="page-search-results__result-item"
(selectedChange)="addToCart($event)"
[selected]="isSelected(item)"
[selectable]="isSelectable(item)"
[item]="item"
[primaryOutletActive]="false"
[isActive]="rla.isActive"
></search-result-item>
</a>
<page-search-result-item-loading [primaryOutletActive]="false" *ngIf="fetching$ | async"></page-search-result-item-loading>
</cdk-virtual-scroll-viewport>
<div class="actions z-sticky h-0">
<button
[disabled]="loading$ | async"
*ngIf="(selectedItemIds$ | async)?.length > 0"
class="cta-cart cta-action-primary"
(click)="addToCart()"
>
<ui-spinner [show]="loading$ | async">In den Warenkorb legen</ui-spinner>
</button>
</div>
</ng-template>

View File

@@ -1,18 +1,18 @@
:host {
@apply box-border grid h-split-screen-tablet desktop-small:h-split-screen-desktop;
@apply box-border grid h-split-screen-tablet desktop-small:h-split-screen-desktop relative;
grid-template-rows: auto auto 1fr;
}
.product-list {
@apply m-0 p-0 mt-px-2;
@apply m-0 p-0;
}
.page-search-results__result-item {
@apply mb-px-10;
@apply mb-[0.625rem];
}
.page-search-results__result-item-primary {
@apply mb-[5px];
@apply mb-[0.3125rem];
}
.page-search-results__order-by {

View File

@@ -9,6 +9,7 @@ import {
QueryList,
TrackByFunction,
AfterViewInit,
inject,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ApplicationService } from '@core/application';
@@ -19,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 { isEqual } from 'lodash';
import { debounce, isEqual } from 'lodash';
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
import { debounceTime, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
import { ArticleSearchService } from '../article-search.store';
@@ -28,6 +29,8 @@ import { SearchResultItemComponent } from './search-result-item.component';
import { ProductCatalogNavigationService } from '@shared/services';
import { Filter, FilterInputGroupMainComponent } from 'apps/shared/components/filter/src/lib';
import { DomainAvailabilityService, ItemData } from '@domain/availability';
import { asapScheduler } from 'rxjs';
import { ShellService } from '@shared/shell';
@Component({
selector: 'page-search-results',
@@ -37,7 +40,7 @@ import { DomainAvailabilityService, ItemData } from '@domain/availability';
})
export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterViewInit {
@ViewChildren(SearchResultItemComponent) listItems: QueryList<SearchResultItemComponent>;
@ViewChild('scrollContainer', { static: true })
@ViewChild(CdkVirtualScrollViewport, { static: false })
scrollContainer: CdkVirtualScrollViewport;
@ViewChild(FilterInputGroupMainComponent, { static: false })
@@ -59,6 +62,10 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
})
);
getProcessId(): number {
return this.application.activatedProcessId;
}
loading$ = new BehaviorSubject<boolean>(false);
private subscriptions = new Subscription();
@@ -69,8 +76,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
return this._environment.matchTablet();
}
get isDesktop() {
return this._environment.matchDesktop();
get isDesktopLarge() {
return this._environment.matchDesktopLarge();
}
hasFilter$ = combineLatest([this.searchService.filter$, this.searchService.defaultSettings$]).pipe(
@@ -89,23 +96,14 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
}
get primaryOutletActive$() {
return this._environment.matchTablet$.pipe(map((matches) => !matches && this.route.outlet === 'primary'));
return this._environment.matchDesktop$.pipe(map((matches) => matches && this.route.outlet === 'primary'));
}
// Ticket #4169 Splitscreen
// Render genug Artikel um bei Navigation auf Trefferliste | PDP zum angewählten Artikel zu Scrollen
maxBufferCdkScrollContainer$ = this.results$.pipe(
withLatestFrom(this.primaryOutletActive$),
map(([results, primaryOutlet]) => {
if (!primaryOutlet && results?.length > 0) {
// Splitscreen mode: Items Length * Item Pixel Height
const maxBufferSize = results.length * 181;
return maxBufferSize >= 1200 ? maxBufferSize : 1200;
} else {
return 1200;
}
})
);
private readonly SCROLL_INDEX_TOKEN = 'CATALOG_RESULTS_LIST_SCROLL_INDEX';
shellService = inject(ShellService);
scale$ = this.shellService.scale$;
constructor(
public searchService: ArticleSearchService,
@@ -156,9 +154,8 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
const cleanQueryParams = this.cleanupQueryParams(queryParams);
// Scroll to scroll_position in great result list
if (!!queryParams?.scroll_position && this.route.outlet === 'primary') {
this.scrollTop(Number(queryParams.scroll_position ?? 0));
if (this.route.outlet === 'primary' && processChanged) {
this.scrollToItem(this._getScrollIndexFromCache());
}
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
@@ -174,11 +171,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
) {
this.search({ clear: true });
} else {
if (!this.isDesktop || this.route.outlet === 'primary') {
this.scrollTop(Number(queryParams.scroll_position ?? 0));
} else {
this.scrollItemIntoView();
}
const selectedItemIds: Array<string> = queryParams?.selected_item_ids?.split(',') ?? [];
for (const id of selectedItemIds) {
if (id) {
@@ -194,7 +186,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
await this.createBreadcrumb(processId, queryParams);
}
if (!this.isDesktop || this.route?.outlet === 'primary') {
if (this.route?.outlet === 'primary') {
await this.removeDetailsBreadcrumb(processId);
}
})
@@ -224,7 +216,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
.getArticleDetailsPathByEan({
processId,
ean,
extras: { queryParams: this.isTablet ? undefined : params },
extras: { queryParams: params },
})
.navigate();
} else {
@@ -232,12 +224,24 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
.getArticleDetailsPath({
processId,
itemId,
extras: { queryParams: this.isTablet ? undefined : params },
extras: { queryParams: params },
})
.navigate();
}
} else if ((searchCompleted?.clear || this.route.outlet === 'primary') && !this.isTablet) {
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
} else if (searchCompleted?.clear || this.route.outlet === 'primary') {
const ean = this.route?.snapshot?.params?.ean;
if (ean) {
await this._navigationService
.getArticleDetailsPathByEan({
processId,
ean,
extras: { queryParams: params },
})
.navigate();
} else {
await this._navigationService.getArticleSearchResultsPath(processId, { queryParams: params }).navigate();
}
}
}
})
@@ -259,7 +263,41 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
}
ngAfterViewInit(): void {
this.scrollItemIntoView();
this.scrollToItem(this._getScrollIndexFromCache());
}
private _addScrollIndexToCache(index: number): void {
this.cache.set<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN }, index);
}
private _getScrollIndexFromCache(): number {
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN });
}
scrollToItem(i?: number) {
let index = i;
if (!index) {
index = this._getScrollIndexFromCache();
} else {
this._addScrollIndexToCache(index);
}
asapScheduler.schedule(() => {
this.scrollContainer.scrollToIndex(index, 'smooth');
}, 150);
}
scrolledIndexChange(index: number) {
const completeListFetched = this.searchService.items.length === this.searchService.hits;
if (index && !completeListFetched && this.searchService.items.length <= this.scrollContainer?.getRenderedRange()?.end) {
this.search({ clear: false });
}
if (this.getProcessId() === this.searchService.processId) {
this._addScrollIndexToCache(index);
}
}
async ngOnDestroy() {
@@ -290,42 +328,17 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
this.sharedFilterInputGroupMain.cancelAutocomplete();
}
this.searchService.search({ clear, orderBy });
this.searchService.search({ clear, orderBy, doNotTrack: true });
}
scrollTop(scrollPos: number) {
setTimeout(() => this.scrollContainer.scrollTo({ top: scrollPos }), 0);
}
scrollItemIntoView() {
setTimeout(() => {
const item = this.listItems?.find((item) => item.item.id === Number(this.route?.snapshot?.params?.id));
item?.scrollIntoView();
}, 0);
}
async scrolledIndexChange(index: number) {
const results = await this.results$.pipe(first()).toPromise();
const hits = await this.hits$.pipe(first()).toPromise();
if (results.length >= hits) {
return;
}
if (!this.isDesktop || this.route.outlet === 'primary') {
this.searchService.setScrollPosition(this.scrollContainer.measureScrollOffset('top'));
}
if (index >= results.length - 20 && results.length - 20 > 0) {
this.search({ clear: false });
}
getDetailsPath(itemId: number) {
return this._navigationService.getArticleDetailsPath({ processId: this.application.activatedProcessId, itemId }).path;
}
async updateBreadcrumbs(
processId: number = this.searchService.processId,
queryParams: Record<string, string> = this.searchService.filter?.getQueryParams()
) {
const scroll_position = this.searchService.scrollPosition;
const selected_item_ids = this.searchService?.selectedItemIds?.toString();
if (queryParams) {
@@ -335,7 +348,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
.toPromise();
const name = queryParams.main_qs ? queryParams.main_qs : 'Alle Artikel';
const params = { ...queryParams, scroll_position, selected_item_ids };
const params = { ...queryParams, selected_item_ids };
for (const crumb of crumbs) {
this.breadcrumb.patchBreadcrumb(crumb.id, {
@@ -388,7 +401,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
cleanupQueryParams(params: Record<string, string> = {}) {
const clean = { ...params };
delete clean['scroll_position'];
delete clean['selected_item_ids'];
for (const key in clean) {
@@ -480,6 +492,9 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
// #4180 Für Download Artikel muss hier immer zwingend der logistician gesetzt werden, da diese Artikel direkt zugeordnet dem Warenkorb hinzugefügt werden
const downloadAvailability = await this._availability.getDownloadAvailability({ item: downloadItem }).pipe(first()).toPromise();
shoppingCartItem.destination = { data: { target: 16, logistician: downloadAvailability?.logistician } };
if (downloadAvailability) {
shoppingCartItem.availability = { ...shoppingCartItem.availability, ...downloadAvailability };
}
canAddItemsPayload.push({
availabilities: [{ ...item.catalogAvailability, format: 'DL' }],
id: item.product.catalogProductNumber,

View File

@@ -1,4 +1,4 @@
<shared-breadcrumb class="mb-5 desktop-small:mb-9" [key]="activatedProcessId$ | async" [tags]="['catalog']">
<shared-breadcrumb [key]="activatedProcessId$ | async" [tags]="['catalog']">
<shared-branch-selector
[uiOverlayTrigger]="tooltip"
[overlayTriggerDisabled]="stockTooltipDisabled"

View File

@@ -31,7 +31,7 @@ shared-branch-selector.shared-branch-selector-opend {
shared-branch-selector.shared-branch-selector-opend
.shared-branch-selector-input-container
.shared-branch-selector-input-icon {
@apply pl-0 border-l-0;
@apply border-l-0;
}
::ng-deep .tablet page-catalog shared-breadcrumb:focus-within .shared-breadcrumb__suffix {

View File

@@ -38,7 +38,7 @@
<page-checkout-review-details></page-checkout-review-details>
</ng-container>
<ng-container *ngFor="let group of groupedItems$ | async; let lastGroup = last">
<ng-container *ngFor="let group of groupedItems$ | async; let lastGroup = last; trackBy: trackByGroupedItems">
<ng-container *ngIf="group?.orderType !== undefined">
<hr />
<div class="row item-group-header bg-[#F5F7FA]">
@@ -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

@@ -8,15 +8,16 @@ import {
QueryList,
AfterViewInit,
TrackByFunction,
inject,
} from '@angular/core';
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 } from 'rxjs/operators';
import { delay, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
import { Subject, NEVER, combineLatest, BehaviorSubject, Subscription } from 'rxjs';
import { DomainCatalogService } from '@domain/catalog';
import { BreadcrumbService } from '@core/breadcrumb';
@@ -29,6 +30,7 @@ import { EnvironmentService } from '@core/environment';
import { CheckoutReviewStore } from './checkout-review.store';
import { ToasterService } from '@shared/shell';
import { ShoppingCartItemComponent } from './shopping-cart-item/shopping-cart-item.component';
import { CustomerSearchNavigation } from '@shared/services';
@Component({
selector: 'page-checkout-review',
@@ -37,6 +39,9 @@ import { ShoppingCartItemComponent } from './shopping-cart-item/shopping-cart-it
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit {
private _onDestroy$ = new Subject<void>();
private _customerSearchNavigation = inject(CustomerSearchNavigation);
checkingOla$ = new BehaviorSubject<boolean>(false);
payer$ = this._store.payer$;
@@ -54,6 +59,9 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
map((items) => items?.filter((item) => item?.features?.orderType === undefined))
);
trackByGroupedItems: TrackByFunction<{ orderType: string; destination: DestinationDTO; items: ShoppingCartItemDTO[] }> = (_, item) =>
item?.orderType + item?.destination?.id;
groupedItems$ = this._store.shoppingCartItems$.pipe(
takeUntil(this._store.orderCompleted),
map((items) =>
@@ -164,6 +172,7 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
showOrderButtonSpinner: boolean;
checkoutIsInValid$ = this.applicationService.activatedProcessId$.pipe(
takeUntil(this._onDestroy$),
switchMap((processId) => this.domainCheckoutService.checkoutIsValid({ processId })),
map((valid) => !valid)
);
@@ -176,8 +185,6 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
return this._environmentService.matchDesktopLarge$;
}
private _onDestroy$ = new Subject<void>();
@ViewChildren(ShoppingCartItemComponent)
private _shoppingCartItems: QueryList<ShoppingCartItemComponent>;
@@ -224,13 +231,15 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
this.resetControl();
this._onDestroy$.next();
this._onDestroy$.complete();
this.checkingOla$.complete();
this.quantityError$.complete();
this.olaCheckSubscription?.unsubscribe();
}
registerOlaCechk() {
this.olaCheckSubscription?.unsubscribe();
this.olaCheckSubscription = this.applicationService.activatedProcessId$
.pipe(
takeUntil(this._onDestroy$),
delay(250),
switchMap((processId) =>
this.domainCheckoutService.validateOlaStatus({
@@ -245,12 +254,16 @@ 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);
for (let itemComp of this._shoppingCartItems.toArray()) {
await itemComp.refreshAvailability();
await new Promise((resolve) => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 100));
}
this.checkingOla$.next(false);
}
@@ -467,6 +480,8 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
}
async navigateToCustomerSearch(processId: number) {
const nav = this._customerSearchNavigation.defaultRoute({ processId });
try {
const response = await this.customerFeatures$
.pipe(
@@ -477,11 +492,11 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
)
.toPromise();
this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search'], {
this.router.navigate(nav.path, {
queryParams: { filter_customertype: response.filter.customertype },
});
} catch (error) {
this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', 'search']);
this.router.navigate(nav.path);
}
}
@@ -501,7 +516,8 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
return;
}
const customerId = customer.source;
this.router.navigate(['/kunde', this.applicationService.activatedProcessId, 'customer', `${customerId}`]);
const nav = this._customerSearchNavigation.detailsRoute({ processId, customerId });
this.router.navigate(nav.path);
}
async order() {

View File

@@ -20,7 +20,7 @@ import { CheckoutReviewDetailsComponent } from './details/checkout-review-detail
import { CheckoutReviewStore } from './checkout-review.store';
import { IconModule } from '@shared/components/icon';
import { TextFieldModule } from '@angular/cdk/text-field';
import { LoaderComponent } from '@shared/components/loader';
import { LoaderComponent, SkeletonLoaderComponent } from '@shared/components/loader';
@NgModule({
imports: [
@@ -41,6 +41,7 @@ import { LoaderComponent } from '@shared/components/loader';
SharedNotificationChannelControlModule,
TextFieldModule,
LoaderComponent,
SkeletonLoaderComponent,
],
exports: [CheckoutReviewComponent, CheckoutReviewDetailsComponent],
declarations: [CheckoutReviewComponent, SpecialCommentComponent, ShoppingCartItemComponent, CheckoutReviewDetailsComponent],

View File

@@ -36,7 +36,7 @@
<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">
<div class="flex flex-row flex-wrap pr-4" data-address-type="Rechnungsadresse">
<div class="mr-3">Rechnungsadresse</div>
<div class="font-bold">
{{ payer | payerAddress }}
@@ -51,7 +51,7 @@
<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">
<div class="flex flex-row flex-wrap pr-4" data-address-type="Lieferadresse">
<div class="mr-3">Lieferadresse</div>
<div class="font-bold">
{{ shippingAddress$ | async | shippingAddress }}

View File

@@ -16,7 +16,7 @@ import { NotificationChannel } from '@swagger/checkout';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CheckoutReviewDetailsComponent implements OnInit {
control = this._store.notificationsControl;
control: UntypedFormGroup;
customerFeatures$ = this._store.customerFeatures$;
@@ -96,15 +96,18 @@ export class CheckoutReviewDetailsComponent implements OnInit {
selectedNotificationChannel = 1;
}
this.control = fb.group({
notificationChannel: new UntypedFormGroup({
selected: new UntypedFormControl(selectedNotificationChannel),
email: new UntypedFormControl(communicationDetails ? communicationDetails.email : '', emailNotificationValidator),
mobile: new UntypedFormControl(communicationDetails ? communicationDetails.mobile : '', mobileNotificationValidator),
}),
});
this._store.notificationsControl = this.control;
if (!this._store.notificationsControl) {
this.control = fb.group({
notificationChannel: new UntypedFormGroup({
selected: new UntypedFormControl(selectedNotificationChannel),
email: new UntypedFormControl(communicationDetails ? communicationDetails.email : '', emailNotificationValidator),
mobile: new UntypedFormControl(communicationDetails ? communicationDetails.mobile : '', mobileNotificationValidator),
}),
});
this._store.notificationsControl = this.control;
} else {
this.control = this._store.notificationsControl;
}
}
setAgentComment(agentComment: string) {

View File

@@ -43,13 +43,16 @@
<div *ngIf="notAvailable$ | async">
<span class="text-brand item-date">Nicht verfügbar</span>
</div>
<ui-spinner class="ava-loader" [show]="refreshingAvailabilit$ | async">
<div class="item-date" [class.ssc-changed]="sscChanged$ | async" *ngIf="orderType === 'Abholung'">
<shared-skeleton-loader class="w-40" *ngIf="refreshingAvailabilit$ | async; else avaTmplt"> </shared-skeleton-loader>
<ng-template #avaTmplt>
<div class="item-date" [class.availability-changed]="estimatedShippingDateChanged$ | async" *ngIf="orderType === 'Abholung'">
Abholung ab {{ item?.availability?.estimatedShippingDate | date }}
</div>
<div
class="item-date"
[class.ssc-changed]="sscChanged$ | async"
[class.availability-changed]="estimatedShippingDateChanged$ | async"
*ngIf="orderType === 'Versand' || orderType === 'B2B-Versand' || orderType === 'DIG-Versand'"
>
<ng-container *ngIf="item?.availability?.estimatedDelivery; else estimatedShippingDate">
@@ -59,7 +62,7 @@
</ng-container>
<ng-template #estimatedShippingDate> Versand {{ item?.availability?.estimatedShippingDate | date }} </ng-template>
</div>
</ui-spinner>
</ng-template>
<div class="item-availability-message" *ngIf="olaError$ | async">
Artikel nicht verfügbar

View File

@@ -101,7 +101,7 @@ button {
}
}
.ssc-changed {
.availability-changed {
@apply text-dark-goldenrod;
}

View File

@@ -7,6 +7,7 @@ import { ComponentStore } from '@ngrx/component-store';
import { ProductCatalogNavigationService } from '@shared/services';
import { ItemType, ShoppingCartItemDTO } from '@swagger/checkout';
import { cloneDeep } from 'lodash';
import moment from 'moment';
import { combineLatest } from 'rxjs';
import { filter, first, map, shareReplay, switchMap } from 'rxjs/operators';
@@ -18,6 +19,7 @@ export interface ShoppingCartItemComponentState {
refreshingAvailability: boolean;
sscChanged: boolean;
sscTextChanged: boolean;
estimatedShippingDateChanged: boolean;
}
@Component({
@@ -137,6 +139,8 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
sscChanged$ = this.select((s) => s.sscChanged || s.sscTextChanged);
estimatedShippingDateChanged$ = this.select((s) => s.estimatedShippingDateChanged);
notAvailable$ = this.item$.pipe(
map((item) => {
const availability = item?.availability;
@@ -161,7 +165,14 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
private _environment: EnvironmentService,
private _cdr: ChangeDetectorRef
) {
super({ item: undefined, orderType: '', refreshingAvailability: false, sscChanged: false, sscTextChanged: false });
super({
item: undefined,
orderType: '',
refreshingAvailability: false,
sscChanged: false,
sscTextChanged: false,
estimatedShippingDateChanged: false,
});
}
ngOnInit() {}
@@ -192,6 +203,11 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
if (currentAvailability.sscText !== availability.sscText) {
this.ssctextChanged();
}
if (
moment(currentAvailability.estimatedShippingDate).startOf('day').diff(moment(availability.estimatedShippingDate).startOf('day'))
) {
this.estimatedShippingDateChanged();
}
} catch (error) {}
this.patchRefreshingAvailability(false);
@@ -206,16 +222,17 @@ export class ShoppingCartItemComponent extends ComponentStore<ShoppingCartItemCo
}
ssctextChanged() {
this._zone.run(() => {
this.patchState({ sscTextChanged: true });
this._cdr.markForCheck();
});
this.patchState({ sscTextChanged: true });
this._cdr.markForCheck();
}
sscChanged() {
this._zone.run(() => {
this.patchState({ sscChanged: true });
this._cdr.markForCheck();
});
this.patchState({ sscChanged: true });
this._cdr.markForCheck();
}
estimatedShippingDateChanged() {
this.patchState({ estimatedShippingDateChanged: true });
this._cdr.markForCheck();
}
}

View File

@@ -1,202 +1,201 @@
<div class="flex flex-col bg-white rounded pt-10">
<div class="rounded-[50%] bg-[#26830C] w-8 h-8 flex items-center justify-center self-center">
<shared-icon class="text-white" icon="done" [size]="24"></shared-icon>
</div>
<div class="summary-wrapper">
<div class="flex flex-col bg-white rounded pt-10 mb-24">
<div class="rounded-[50%] bg-[#26830C] w-8 h-8 flex items-center justify-center self-center">
<shared-icon class="text-white" icon="done" [size]="24"></shared-icon>
</div>
<h1 class="text-center text-h2 my-1 font-bold">Bestellbestätigung</h1>
<p class="text-center text-p1 mb-10">
Nachfolgend erhalten Sie die Übersicht Ihrer Bestellung.
</p>
<h1 class="text-center text-h2 my-1 font-bold">Bestellbestätigung</h1>
<p class="text-center text-p1 mb-10">
Nachfolgend erhalten Sie die Übersicht Ihrer Bestellung.
</p>
<ng-container *ngFor="let displayOrder of displayOrders$ | async; let i = index; let orderLast = last">
<ng-container *ngIf="i === 0">
<div class="flex flex-row items-center bg-white shadow-card min-h-[3.3125rem]">
<div class="text-h3 font-bold px-5 py-[0.875rem]">
{{ displayOrder?.buyer | buyerName }}
<ng-container *ngFor="let displayOrder of displayOrders$ | async; let i = index; let orderLast = last">
<ng-container *ngIf="i === 0">
<div class="flex flex-row items-center bg-white shadow-card min-h-[3.3125rem]">
<div class="text-h3 font-bold px-5 py-[0.875rem]">
{{ displayOrder?.buyer | buyerName }}
</div>
</div>
<hr />
</ng-container>
<div class="flex flex-row items-center bg-[#F5F7FA] min-h-[3.3125rem]">
<div class="flex flex-row items-center justify-center px-5 py-[0.875rem]">
<shared-icon
*ngIf="(displayOrder?.items)[0]?.features?.orderType !== 'Dummy'"
class="mr-2"
[size]="(displayOrder?.items)[0]?.features?.orderType === 'B2B-Versand' ? 36 : 24"
[icon]="(displayOrder?.items)[0]?.features?.orderType"
></shared-icon>
<p class="text-p1 font-bold mr-3">{{ (displayOrder?.items)[0]?.features?.orderType }}</p>
<div
*ngIf="
(displayOrder?.items)[0]?.features?.orderType === 'Abholung' || (displayOrder?.items)[0]?.features?.orderType === 'Rücklage';
else shippingAddress
"
>
{{ displayOrder.targetBranch?.name }}, {{ displayOrder.targetBranch | branchAddress }}
</div>
<ng-template #shippingAddress>
{{ displayOrder.shippingAddress | branchAddress }}
</ng-template>
<div *ngIf="(displayOrder?.items)[0]?.features?.orderType === 'Download'">
| {{ displayOrder.buyer?.communicationDetails?.email }}
</div>
</div>
</div>
<hr />
</ng-container>
<div class="flex flex-row items-center bg-[#F5F7FA] min-h-[3.3125rem]">
<div class="flex flex-row items-center justify-center px-5 py-[0.875rem]">
<shared-icon
*ngIf="(displayOrder?.items)[0]?.features?.orderType !== 'Dummy'"
class="mr-2"
[size]="(displayOrder?.items)[0]?.features?.orderType === 'B2B-Versand' ? 36 : 24"
[icon]="(displayOrder?.items)[0]?.features?.orderType"
></shared-icon>
<p class="text-p1 font-bold mr-3">{{ (displayOrder?.items)[0]?.features?.orderType }}</p>
<div
*ngIf="
(displayOrder?.items)[0]?.features?.orderType === 'Abholung' || (displayOrder?.items)[0]?.features?.orderType === 'Rücklage';
else shippingAddress
"
>
{{ displayOrder.targetBranch?.name }}, {{ displayOrder.targetBranch | branchAddress }}
</div>
<ng-template #shippingAddress>
{{ displayOrder.shippingAddress | branchAddress }}
</ng-template>
<div *ngIf="(displayOrder?.items)[0]?.features?.orderType === 'Download'">
| {{ displayOrder.buyer?.communicationDetails?.email }}
</div>
</div>
</div>
<hr />
<div class="flex flex-col px-5 pt-7 pb-[1.875rem] bg-white">
<div class="flex flex-row justify-between items-center mb-[0.375rem]">
<div class="flex flex-row">
<span class="w-32">Vorgangs-ID</span>
<ng-container *ngIf="customer$ | async; let customer">
<a
*ngIf="customer$ | async; let customer"
class="font-bold text-[#0556B4] no-underline"
[routerLink]="['/kunde', processId, 'customer', 'search', customer?.id, 'orders', displayOrder.id]"
[queryParams]="{ main_qs: customer?.customerNumber, filter_customertype: '' }"
>{{ displayOrder.orderNumber }}</a
>
</ng-container>
<ui-spinner class="text-[#0556B4] h-4 w-4" [show]="!(customer$ | async)"> </ui-spinner>
</div>
<button
*ifRole="'Store'"
type="button"
class="cta-print bg-transparent text-brand font-bold text-p1 outline-none border-none pr-0"
(click)="openPrintModal(displayOrder.id)"
>
Drucken
</button>
</div>
<div class="flex flex-row justify-between items-center">
<div class="flex flex-row">
<span class="w-32">Bestelldatum</span>
<strong>{{ displayOrder.orderDate | date }}</strong>
<div class="flex flex-col px-5 py-4 bg-white" [attr.data-order-type]="(displayOrder?.items)[0]?.features?.orderType">
<div class="flex flex-row justify-between items-center mb-[0.375rem]">
<div class="flex flex-row">
<span class="w-32">Vorgangs-ID</span>
<ng-container *ngIf="customer$ | async; let customer">
<a
*ngIf="customer$ | async; let customer"
class="font-bold text-[#0556B4] no-underline"
[routerLink]="['/kunde', processId, 'customer', 'search', customer?.id, 'orders', displayOrder.id]"
[queryParams]="{ main_qs: customer?.customerNumber, filter_customertype: '' }"
>{{ displayOrder.orderNumber }}</a
>
</ng-container>
<ui-spinner class="text-[#0556B4] h-4 w-4" [show]="!(customer$ | async)"> </ui-spinner>
</div>
</div>
<div class="flex flex-row justify-between items-center">
<div class="flex flex-row">
<span class="w-32">Bestelldatum</span>
<strong>{{ displayOrder.orderDate | date }}</strong>
</div>
</div>
</div>
<div class="mr-4">
<button
(click)="expanded[i] = !expanded[i]"
type="button"
class="text-[#0556B4] font-bold flex flex-row items-center justify-center"
[class.flex-row-reverse]="!expanded[i]"
>
<shared-icon
class="mr-1"
icon="arrow-back"
[size]="20"
[class.ml-1]="!expanded[i]"
[class.rotate-180]="!expanded[i]"
></shared-icon>
{{ expanded[i] ? 'Weniger' : 'Mehr' }}
</button>
</div>
<button
(click)="expanded[i] = !expanded[i]"
type="button"
class="text-[#0556B4] font-bold flex flex-row items-center justify-center"
[class.flex-row-reverse]="!expanded[i]"
>
<shared-icon
class="mr-1"
icon="arrow-back"
[size]="20"
[class.ml-1]="!expanded[i]"
[class.rotate-180]="!expanded[i]"
></shared-icon>
{{ expanded[i] ? 'Weniger' : 'Mehr' }}
</button>
</div>
</div>
<ng-container *ngFor="let order of displayOrder.items; let last = last">
<ng-container *ngIf="expanded[i]">
<div
class="page-checkout-summary__items-tablet px-5 pb-[1.875rem] bg-white"
[class.page-checkout-summary__items]="isDesktop$ | async"
[class.last]="last"
>
<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" />
</a>
</div>
<ng-container *ngFor="let order of displayOrder.items; let last = last">
<ng-container *ngIf="expanded[i]">
<div
class="page-checkout-summary__items-tablet px-5 pb-[1.875rem] bg-white"
[class.page-checkout-summary__items]="isDesktop$ | async"
[class.last]="last"
>
<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" />
</a>
</div>
<div class="page-checkout-summary__items-title whitespace-nowrap overflow-ellipsis overflow-hidden">
<a
class="font-bold no-underline text-[#0556B4]"
[routerLink]="getProductSearchDetailsPath(order?.product?.ean)"
[queryParams]="getProductSearchDetailsQueryParams(order)"
>{{ order?.product?.name }}</a
>
</div>
<div class="page-checkout-summary__items-title whitespace-nowrap overflow-ellipsis overflow-hidden">
<a
class="font-bold no-underline text-[#0556B4]"
[routerLink]="getProductSearchDetailsPath(order?.product?.ean)"
[queryParams]="getProductSearchDetailsQueryParams(order)"
>{{ order?.product?.name }}</a
>
</div>
<div class="page-checkout-summary__items-ssc" *ngIf="(order?.subsetItems)[0]; let subsetItem">
<span class="mr-2">{{ subsetItem.supplierName }}</span>
<span *ngIf="subsetItem?.ssc && subsetItem?.sscText" class="font-bold border-l border-black pl-2"
>{{ subsetItem.ssc }} - {{ subsetItem.sscText }}</span
>
</div>
<div class="page-checkout-summary__items-ssc" *ngIf="(order?.subsetItems)[0]; let subsetItem">
<span class="mr-2">{{ subsetItem.supplierName }}</span>
<span *ngIf="subsetItem?.ssc && subsetItem?.sscText" class="font-bold border-l border-black pl-2"
>{{ subsetItem.ssc }} - {{ subsetItem.sscText }}</span
>
</div>
<div class="page-checkout-summary__items-quantity font-bold justify-self-end">
<span> {{ order.quantity }}x </span>
</div>
<div class="page-checkout-summary__items-quantity font-bold justify-self-end">
<span> {{ order.quantity }}x </span>
</div>
<div class="page-checkout-summary__items-price font-bold justify-self-end">
<span> {{ order.price?.value?.value | currency: ' ' }} {{ order.price?.value?.currency }} </span>
</div>
<div class="page-checkout-summary__items-price font-bold justify-self-end">
<span> {{ order.price?.value?.value | currency: ' ' }} {{ order.price?.value?.currency }} </span>
</div>
<div class="page-checkout-summary__items-delivery product-details">
<div class="delivery-row" [ngSwitch]="order?.features?.orderType">
<ng-container *ngSwitchCase="'Abholung'">
<span class="order-type"
>Abholung ab {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}
<ng-container [ngTemplateOutlet]="abholfrist" [ngTemplateOutletContext]="{ order: order }"> </ng-container>
</span>
</ng-container>
<ng-container *ngSwitchCase="'Rücklage'">
<span class="order-type"
>{{ order?.features?.orderType }}
<ng-container [ngTemplateOutlet]="abholfrist" [ngTemplateOutletContext]="{ order: order }"> </ng-container>
</span>
</ng-container>
<ng-container *ngSwitchCase="['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(order?.features?.orderType) > -1">
<ng-container *ngIf="(order?.subsetItems)[0]?.estimatedDelivery; else estimatedShippingDate">
<div class="page-checkout-summary__items-delivery product-details">
<div class="delivery-row" [ngSwitch]="order?.features?.orderType">
<ng-container *ngSwitchCase="'Abholung'">
<span class="order-type"
>Zustellung zwischen
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }} und
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}</span
>
>Abholung ab {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}
<ng-container [ngTemplateOutlet]="abholfrist" [ngTemplateOutletContext]="{ order: order }"> </ng-container>
</span>
</ng-container>
<ng-template #estimatedShippingDate>
<span class="order-type">Versanddatum {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}</span>
</ng-template>
</ng-container>
<ng-container *ngSwitchDefault>
<span class="order-type">{{ order?.features?.orderType }}</span>
</ng-container>
<ng-container *ngSwitchCase="'Rücklage'">
<span class="order-type"
>{{ order?.features?.orderType }}
<ng-container [ngTemplateOutlet]="abholfrist" [ngTemplateOutletContext]="{ order: order }"> </ng-container>
</span>
</ng-container>
<ng-container *ngSwitchCase="['Versand', 'B2B-Versand', 'DIG-Versand'].indexOf(order?.features?.orderType) > -1">
<ng-container *ngIf="(order?.subsetItems)[0]?.estimatedDelivery; else estimatedShippingDate">
<span class="order-type"
>Zustellung zwischen
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.start | date: 'EEE, dd.MM.')?.replace('.', '') }} und
{{ ((order?.subsetItems)[0]?.estimatedDelivery?.stop | date: 'EEE, dd.MM.')?.replace('.', '') }}</span
>
</ng-container>
<ng-template #estimatedShippingDate>
<span class="order-type">Versanddatum {{ (order?.subsetItems)[0]?.estimatedShippingDate | date }}</span>
</ng-template>
</ng-container>
<ng-container *ngSwitchDefault>
<span class="order-type">{{ order?.features?.orderType }}</span>
</ng-container>
</div>
</div>
</div>
</ng-container>
<hr *ngIf="last" />
</ng-container>
<ng-container *ngIf="orderLast">
<div class="flex flex-row justify-between items-center min-h-[3.3125rem] bg-white px-5 py-4 rounded-b">
<span *ngIf="totalReadingPoints$ | async; let totalReadingPoints" class="text-p2 font-bold"
>{{ totalItemCount$ | async }} Artikel | {{ totalReadingPoints }} Lesepunkte</span
>
<div class="flex flex-row items-center justify-center">
<div class="text-p1 font-bold flex flex-row items-center">
<div class="mr-1">Gesamtsumme {{ totalPrice$ | async | currency: ' ' }} {{ totalPriceCurrency$ | async }}</div>
</div>
<div
class="bg-brand text-white font-bold text-p1 outline-none border-none rounded-full px-6 py-3 ml-2"
*ngIf="(takeNowOrders$ | async)?.length === 1 && (isB2BCustomer$ | async)"
>
<button class="cta-goods-out" (click)="navigateToShelfOut()">Zur Warenausgabe</button>
</div>
</div>
</div>
</ng-container>
<hr *ngIf="last" />
</ng-container>
<ng-container *ngIf="orderLast">
<div class="flex flex-row justify-between items-center min-h-[3.3125rem] bg-white px-5 py-4 rounded-b">
<span *ngIf="totalReadingPoints$ | async; let totalReadingPoints" class="text-p2 font-bold"
>{{ totalItemCount$ | async }} Artikel | {{ totalReadingPoints }} Lesepunkte</span
>
<div class="flex flex-row items-center justify-center">
<div class="text-p1 font-bold flex flex-row items-center">
<div class="mr-1">Gesamtsumme {{ totalPrice$ | async | currency: ' ' }} {{ totalPriceCurrency$ | async }}</div>
</div>
<div
class="bg-brand text-white font-bold text-p1 outline-none border-none rounded-full px-6 py-3 ml-2"
*ngIf="(takeNowOrders$ | async)?.length === 1 && (isB2BCustomer$ | async)"
>
<button class="cta-goods-out" (click)="navigateToGoodsOut()">Zur Warenausgabe</button>
</div>
</div>
</div>
</ng-container>
</ng-container>
</div>
</div>
<ng-template #abholfrist let-order="order">
<div *ngIf="!(updatingPreferredPickUpDate$ | async)[(order?.subsetItems)[0].id]" class="inline-flex">
<button [uiOverlayTrigger]="deadlineDatepicker" #deadlineDatepickerTrigger="uiOverlayTrigger" class="flex flex-row items-center">
<span class="mx-[0.625rem] font-normal">bis</span>
<strong class="border-r border-[#AEB7C1] pr-4 text-[#AEB7C1]">
{{ ((order?.subsetItems)[0]?.preferredPickUpDate | date: 'dd.MM.yy') || 'Auswählen' }}
<strong class="border-r border-[#AEB7C1] pr-4">
{{ ((order?.subsetItems)[0]?.preferredPickUpDate | date: 'dd.MM.yy') || 'TT.MM.JJJJ' }}
</strong>
<shared-icon class="text-[#596470] ml-4" [size]="24" icon="isa-calendar"></shared-icon>
@@ -222,3 +221,25 @@
</div>
<div class="fetching" *ngIf="!!(updatingPreferredPickUpDate$ | async)[(order?.subsetItems)[0].id]"></div>
</ng-template>
<div class="relative">
<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'"
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"
(click)="printOrderConfirmation()"
>
Bestellbestätigung drucken
</button>
<button
*ngIf="hasAbholung$ | 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"
(click)="sendOrderConfirmation()"
>
Bestellbestätigung senden
</button>
</div>
</div>

View File

@@ -1,5 +1,9 @@
:host {
@apply box-border bg-transparent rounded overflow-scroll h-split-screen-tablet desktop-small:h-split-screen-desktop flex flex-col justify-between;
@apply box-border bg-transparent relative;
}
.summary-wrapper {
@apply rounded overflow-scroll h-split-screen-tablet desktop-small:h-split-screen-desktop flex flex-col justify-between;
}
.fetching {

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Injector, OnDestroy, OnInit, inject } from '@angular/core';
import { DomainCheckoutService } from '@domain/checkout';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
@@ -13,8 +13,11 @@ import { ApplicationService } from '@core/application';
import { DomainPrinterService } from '@domain/printer';
import { BehaviorSubject, combineLatest, NEVER, Subject } from 'rxjs';
import { DateAdapter } from '@ui/common';
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
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({
selector: 'page-checkout-summary',
@@ -23,6 +26,14 @@ import { EnvironmentService } from '@core/environment';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CheckoutSummaryComponent implements OnInit, OnDestroy {
private _injector = inject(Injector);
get sendOrderConfirmationModalService() {
return this._injector.get(SendOrderConfirmationModalService);
}
private _toaster = inject(ToasterService);
private _onDestroy$ = new Subject();
processId = Date.now();
selectedDate = this.dateAdapter.today();
@@ -54,11 +65,16 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
const itemsWithDifferentOrderFeature = orderWithMultipleFeatures.items.filter(
(item) => item.features.orderType !== orderWithMultipleFeatures.features.orderType
);
filteredOrders = [
...filteredOrders.filter((order) => order.id !== orderWithMultipleFeatures.id),
{ ...orderWithMultipleFeatures, items: itemsWithOrderFeature },
{ ...orderWithMultipleFeatures, items: itemsWithDifferentOrderFeature },
];
filteredOrders = [...filteredOrders.filter((order) => order.id !== orderWithMultipleFeatures.id)];
if (itemsWithOrderFeature?.length > 0) {
filteredOrders = [...filteredOrders, { ...orderWithMultipleFeatures, items: itemsWithOrderFeature }];
}
if (itemsWithDifferentOrderFeature?.length > 0) {
filteredOrders = [...filteredOrders, { ...orderWithMultipleFeatures, items: itemsWithDifferentOrderFeature }];
}
}
}
}
@@ -73,6 +89,10 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
shareReplay()
);
hasAbholung$ = this.displayOrders$.pipe(
map((displayOrders) => displayOrders.filter((order) => order.features?.orderType === 'Abholung')?.length > 0)
);
totalItemCount$ = this.displayOrders$.pipe(
map((displayOrders) =>
displayOrders.reduce(
@@ -157,6 +177,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
private dateAdapter: DateAdapter,
private _navigation: CheckoutNavigationService,
private _productNavigationService: ProductCatalogNavigationService,
private _shelfOutNavigationService: PickUpShelfOutNavigationService,
private _environmentService: EnvironmentService,
private _cdr: ChangeDetectorRef
) {
@@ -216,20 +237,6 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
return { main_qs: item?.product?.ean, filter_format: item?.features?.orderType === 'Download' ? 'eb;dl' : undefined };
}
openPrintModal(id: number) {
this.uiModal.open({
content: PrintModalComponent,
data: {
printerType: 'Label',
print: (printer) => this.domainPrinterService.printOrder({ orderIds: [id], printer }).toPromise(),
} as PrintModalData,
config: {
panelClass: [],
showScrollbarY: false,
},
});
}
async updatePreferredPickUpDate(item: DisplayOrderItemDTO, date: Date) {
const data: Record<string, string> = {};
@@ -279,7 +286,7 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
this.domainCheckoutService.updateOrderItem(item);
}
async navigateToGoodsOut() {
async navigateToShelfOut() {
let takeNowOrders = await this.takeNowOrders$.pipe(first()).toPromise();
if (takeNowOrders.length != 1) return;
@@ -294,9 +301,45 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
}
}
this.router.navigate(['/kunde', 'goods', 'out', 'details', 'order', takeNowOrders[0].orderNumber, 128]);
await this.router.navigate(
this._shelfOutNavigationService.detailRoute({
processId: Date.now(),
item: { orderId: takeNowOrders[0].id, orderNumber: takeNowOrders[0].orderNumber, processingStatus: 128 },
}).path
);
} catch (e) {
console.error(e);
}
}
async sendOrderConfirmation() {
const orders = await this.displayOrders$.pipe(first()).toPromise();
await this.sendOrderConfirmationModalService.open(orders);
}
async printOrderConfirmation() {
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' });
}
},
} as PrintModalData,
config: {
panelClass: [],
showScrollbarY: false,
},
})
.afterClosed$.toPromise();
}
}

View File

@@ -1,3 +1,3 @@
<shared-breadcrumb class="mb-5 desktop-small:mb-9" [key]="breadcrumbKey$ | async" [tags]="['checkout']"></shared-breadcrumb>
<shared-breadcrumb [key]="breadcrumbKey$ | async" [tags]="['checkout']"></shared-breadcrumb>
<shared-splitscreen></shared-splitscreen>

View File

@@ -295,7 +295,7 @@
[disabled]="(!isKulturpass && !!orderItem?.features?.paid) || (changeDateDisabled$ | async)"
class="cta-pickup-preferred"
>
<strong *ngIf="preferredPickUpDate$ | async; let pickUpDate; else: selectTemplate">
<strong class="border-r border-[#AEB7C1] pr-4" *ngIf="preferredPickUpDate$ | async; let pickUpDate; else: selectTemplate">
{{ pickUpDate | date: 'dd.MM.yy' }}
</strong>
<ng-template #selectTemplate>

View File

@@ -87,7 +87,7 @@
<div class="label">ISBN/EAN</div>
<div class="value">{{ orderItem.product?.ean }}</div>
</div>
<div class="detail" *ngIf="!!orderItem.price">
<div class="detail" *ngIf="orderItem.price !== undefined">
<div class="label">Preis</div>
<div class="value">{{ orderItem.price | currency: 'EUR' }}</div>
</div>

View File

@@ -1,4 +1,13 @@
import { ChangeDetectionStrategy, Component, ContentChildren, OnDestroy, OnInit, QueryList } from '@angular/core';
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
ContentChild,
ContentChildren,
OnDestroy,
OnInit,
QueryList,
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { EnvironmentService } from '@core/environment';
@@ -13,6 +22,7 @@ import { CustomerOrderDetailsStore } from './customer-order-details.store';
import { CustomerOrderDetailsItemComponent } from './customer-order-details-item';
import { unionBy } from 'lodash';
import { CommandService } from '@core/command';
import { CustomerOrderDetailsTagsComponent } from './customer-order-details-tags';
@Component({
selector: 'page-customer-order-details',
@@ -21,10 +31,13 @@ import { CommandService } from '@core/command';
providers: [provideComponentStore(CustomerOrderDetailsStore)],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomerOrderDetailsComponent implements OnInit, OnDestroy {
export class CustomerOrderDetailsComponent implements OnInit, AfterViewInit, OnDestroy {
@ContentChildren(CustomerOrderDetailsItemComponent)
customerOrderDetailsItemComponents: QueryList<CustomerOrderDetailsItemComponent>;
@ContentChild(CustomerOrderDetailsTagsComponent, { static: false })
customerOrderDetailsTags: CustomerOrderDetailsTagsComponent;
buyerNumber$ = this._store.buyerNumber$;
compartmentCode$ = this._store.compartmentCode$;
@@ -146,10 +159,23 @@ export class CustomerOrderDetailsComponent implements OnInit, OnDestroy {
this.removeBreadcrumbs();
}
ngAfterViewInit(): void {
this._registerCustomerOrderDetailsTagsComponentChanges();
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
private _registerCustomerOrderDetailsTagsComponentChanges() {
this.subscriptions.add(
this.items$.subscribe((_) => {
this.customerOrderDetailsTags?.writeValue(this.compartmentInfo);
this.customerOrderDetailsTags?.registerOnChange((compartmentInfo) => (this._store.compartmentInfo = compartmentInfo));
})
);
}
itemsSelectable(items: OrderItemListItemDTO[]) {
this._store.patchState({
itemsSelectable: items?.some((item) => !!item?.actions && item?.actions?.length > 0),

View File

@@ -4,7 +4,7 @@ import { BreadcrumbService } from '@core/breadcrumb';
import { EnvironmentService } from '@core/environment';
import { DomainCustomerOrderService, DomainGoodsService, DomainOmsService } from '@domain/oms';
import { CustomerOrdersNavigationService } from '@shared/services';
import { OrderItemListItemDTO } from '@swagger/oms';
import { DBHOrderItemListItemDTO, OrderItemListItemDTO } from '@swagger/oms';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { combineLatest, Observable } from 'rxjs';
import { map, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';
@@ -87,10 +87,10 @@ export class CustomerOrderEditComponent implements OnInit {
});
}
async navigateToDetailsPage({ options }: { options?: { processingStatus?: number } }) {
async navigateToDetailsPage(changes?: Partial<DBHOrderItemListItemDTO>) {
const orderId = this._activatedRoute.snapshot.params.orderId;
const compartmentCode = this._activatedRoute.snapshot.params.compartmentCode;
const processingStatus = options?.processingStatus ? options.processingStatus : this._activatedRoute.snapshot.params.processingStatus;
const processingStatus = changes?.processingStatus ? changes.processingStatus : this._activatedRoute.snapshot.params.processingStatus;
await this._navigation
.getCustomerOrdersDetailsPath({

View File

@@ -83,6 +83,7 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
) {}
ngOnInit() {
this.showFilter = this.sideOutlet !== 'search';
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$)).subscribe((queryParams) => {
this._customerOrdersSearchStore.setQueryParams(queryParams);
});
@@ -102,6 +103,33 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
this._customerOrdersSearchStore.setQueryParams(queryParams);
});
this._customerOrdersSearchStore.searchResultSubject.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result.results.error) {
} else {
if (result.results.hits > 0) {
const queryParams = this._customerOrdersSearchStore.filter.getQueryParams();
if (result.results.hits === 1) {
const orderItem = result.results.result[0];
await this._navigationService
.getCustomerOrdersDetailsPath({
processId: this.processId,
processingStatus: orderItem?.processingStatus,
compartmentCode: orderItem?.compartmentCode ? encodeURIComponent(orderItem.compartmentCode) : undefined,
orderId: orderItem?.orderId ? orderItem.orderId : undefined,
extras: { queryParams },
})
.navigate();
} else {
await this._navigationService.getCustomerOrdersResultsPath(this.processId, { queryParams }).navigate();
}
} else {
this._customerOrdersSearchStore.setMessage('keine Suchergebnisse');
}
this._cdr.markForCheck();
}
});
this._initSettings();
this._initLoading$();
}
@@ -132,34 +160,6 @@ export class CustomerOrderSearchFilterComponent implements OnInit, OnDestroy {
const queryParams = filter.getQueryParams();
this._customerOrdersSearchStore.setQueryParams(queryParams);
await this.updateQueryParams(queryParams);
this._customerOrdersSearchStore.searchResultSubject.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
if (result.results.error) {
} else {
if (result.results.hits > 0) {
const queryParams = this._customerOrdersSearchStore.filter.getQueryParams();
if (result.results.hits === 1) {
const orderItem = result.results.result[0];
await this._navigationService
.getCustomerOrdersDetailsPath({
processId: this.processId,
processingStatus: orderItem?.processingStatus,
compartmentCode: orderItem?.compartmentCode ? encodeURIComponent(orderItem.compartmentCode) : undefined,
orderId: orderItem?.orderId ? orderItem.orderId : undefined,
extras: { queryParams },
})
.navigate();
} else {
await this._navigationService.getCustomerOrdersResultsPath(this.processId, { queryParams }).navigate();
}
} else {
this._customerOrdersSearchStore.setMessage('keine Suchergebnisse');
}
this._cdr.markForCheck();
}
});
this._customerOrdersSearchStore.search({ clear: true });
}

View File

@@ -174,8 +174,7 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
this.setSearchHistory(searchHistory?.slice(0, 7));
}
getCachedData() {
const queryToken = { ...this.filter?.getQueryToken(), processId: this.processId, branchId: String(this.selectedBranch?.id) } ?? {};
getCachedData({ queryToken }: { queryToken: Record<string, any> }) {
return (
this._cache.get<{
hits: number;
@@ -185,6 +184,14 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
);
}
setCache({ queryToken, hits, results }: { queryToken: Record<string, any>; hits: number; results: OrderItemListItemDTO[] }) {
this._cache.set(queryToken, {
hits,
results,
fetching: false,
});
}
search = this.effect((options$: Observable<{ clear?: boolean; siletReload?: boolean }>) =>
options$.pipe(
tap((_) => {
@@ -201,7 +208,6 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
if (options?.clear) {
this.searchResultClearedSubject.next();
this._cache.delete(filter?.getQueryToken());
}
}),
switchMap(([options, results, filter, branch]) => {
@@ -227,7 +233,7 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
queryToken.take = 50;
}
if (branch?.id) {
if (branch?.id && !!queryToken.filter) {
queryToken.filter['branch_id'] = String(branch?.id);
}
@@ -250,18 +256,10 @@ export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearch
silentFetching: false,
});
const queryToken = { ...filter?.getQueryToken(), processId: this.processId, branchId: String(branch?.id) };
if (res?.hits > 0 && options?.clear) {
this.saveSearchHistoryToSessionStorage();
}
this._cache.set(queryToken, {
hits: res.hits,
results: _results,
fetching: false,
});
this.searchResultSubject.next({ results: res, cached, clear: options?.clear });
if (res?.hits === 0) {

View File

@@ -62,15 +62,12 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
) {}
ngOnInit() {
// Clear scroll position
localStorage.removeItem(`SCROLL_POSITION_${this.processId}`);
this._subscriptions.add(
combineLatest([this.processId$, this._activatedRoute.queryParams])
.pipe(debounceTime(50))
.subscribe(([processId, queryParams]) => {
this.removeBreadcrumbs(processId);
this.updateBreadcrumb(processId, queryParams);
this.updateBreadcrumb(queryParams);
})
);
@@ -88,17 +85,19 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
})
);
// #4143 To make Splitscreen Search and Filter work combined
this._subscriptions.add(
this._customerOrderSearchStore.searchStarted.subscribe(async (_) => {
const queryParams = {
...this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()),
main_qs: this.filterInputGroup?.uiInput?.value,
};
// Im Zuge des Tickets #4256 auskommentiert, da es zu Problemen geführt hat
// In der Filter Komponente wird dies schon gemacht
this._customerOrderSearchStore.setQueryParams(queryParams);
})
);
// // #4143 To make Splitscreen Search and Filter work combined
// this._subscriptions.add(
// this._customerOrderSearchStore.searchStarted.subscribe(async (_) => {
// const queryParams = {
// ...this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()),
// main_qs: this.filterInputGroup?.uiInput?.value,
// };
// this._customerOrderSearchStore.setQueryParams(queryParams);
// })
// );
}
ngOnDestroy() {
@@ -147,15 +146,19 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
async search(filter: Filter) {
this._customerOrderSearchStore.setMessage('');
this.filterInputGroup?.cancelAutocomplete();
const queryParams = filter.getQueryParams();
this._customerOrderSearchStore.setQueryParams(queryParams);
await this.updateQueryParams(queryParams);
this._customerOrderSearchStore.search({ clear: true });
await this.updateQueryParams(this.processId);
}
async updateBreadcrumb(processId: number, params: Record<string, string>) {
async updateBreadcrumb(params: Record<string, string>) {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: processId,
key: this.processId,
name: 'Kundenbestellung',
path: this._navigationService.getCustomerOrdersBasePath(processId).path,
path: this._navigationService.getCustomerOrdersBasePath(this.processId).path,
tags: ['customer-order', 'main', 'filter'],
section: 'customer',
params,
@@ -164,12 +167,12 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
setQueryHistory(filter: Filter, query: string) {
filter.fromQueryParams({ main_qs: query });
const queryParams = filter.getQueryParams();
this._customerOrderSearchStore.setQueryParams(queryParams);
}
async updateQueryParams(processId: number) {
const queryParams = { ...this._customerOrderSearchStore.filter?.getQueryParams() };
queryParams.main_qs = queryParams.main_qs ?? '';
async updateQueryParams(queryParams: Record<string, string>) {
await this._router.navigate([], { queryParams });
this.updateBreadcrumb(processId, queryParams);
await this.updateBreadcrumb(queryParams);
}
}

View File

@@ -4,7 +4,7 @@
[routerLink]="detailsLink"
[routerLinkActive]="!isTablet && !primaryOutletActive ? 'active' : ''"
[queryParams]="queryParams"
(click)="isDesktop ? scrollIntoView() : ''"
(click)="isDesktopLarge ? scrollIntoView() : ''"
>
<div
class="page-customer-order-item__item-grid-container"

View File

@@ -74,6 +74,10 @@
grid-area: ordernumber;
}
.page-customer-order-item__item-special-comment {
grid-area: comment;
}
.page-customer-order-item__item-order-number-main {
@apply justify-self-start self-center;
}
@@ -82,25 +86,7 @@
grid-area: status;
}
.page-customer-order-item__item-select-bullet {
grid-area: select;
}
.active,
.hover:hover {
@apply bg-[#D8DFE5] border border-solid border-[#0556B4];
.page-customer-order-item__item-select-bullet {
.isa-select-bullet::before {
@apply bg-[#fff];
}
.isa-select-bullet:checked:before {
@apply bg-[#596470];
}
.isa-select-bullet:hover::before {
@apply bg-[#778490];
}
}
}

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ElementRef } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, ElementRef } from '@angular/core';
import { ApplicationService } from '@core/application';
import { EnvironmentService } from '@core/environment';
import { ComponentStore } from '@ngrx/component-store';
@@ -10,8 +10,6 @@ import { map } from 'rxjs/operators';
export interface CustomerOrderItemComponentState {
item?: OrderItemListItemDTO;
selected: boolean;
selectable: boolean;
}
@Component({
@@ -33,30 +31,12 @@ export class CustomerOrderItemComponent extends ComponentStore<CustomerOrderItem
readonly item$ = this.select((s) => s.item);
@Input()
selected: boolean;
selected$ = this.select((s) => s.selected);
@Input()
get selectable() {
return this.get((s) => s.selectable);
}
set selectable(selectable: boolean) {
if (this.selectable !== selectable) {
this.patchState({ selectable });
}
}
@Output()
selectedChange = new EventEmitter<boolean>();
get isTablet() {
return this._environment.matchTablet();
}
get isDesktop() {
return this._environment.matchDesktop();
get isDesktopLarge() {
return this._environment.matchDesktopLarge();
}
get queryParams() {
@@ -98,7 +78,7 @@ export class CustomerOrderItemComponent extends ComponentStore<CustomerOrderItem
private _applicationService: ApplicationService,
private _elRef: ElementRef
) {
super({ selected: false, selectable: false });
super({});
}
scrollIntoView() {

View File

@@ -72,10 +72,7 @@
*ngFor="let item of compartmentCodeGroup.items; let firstItem = first; trackBy: trackByFn"
class="page-customer-orders-results__result-item mb-[0.625rem]"
[class.page-customer-orders-results__result-item-main]="primaryOutletActive$ | async"
[selectable]="item | goodsOutItemSelectable: selectionRules:selectedItems"
[selected]="item | goodsOutItemSelected: selectedOrderItemSubsetIds"
[item]="item"
(selectedChange)="setSelectedItem(item, $event)"
[primaryOutletActive]="primaryOutletActive$ | async"
></page-customer-order-item>
</ng-container>

View File

@@ -8,10 +8,12 @@ import {
ViewChildren,
QueryList,
AfterViewInit,
inject,
DestroyRef,
} from '@angular/core';
import { debounceTime, filter, first, map, shareReplay, switchMap, take, takeUntil, withLatestFrom } from 'rxjs/operators';
import { debounceTime, filter, first, map, shareReplay, switchMap, takeUntil, withLatestFrom } from 'rxjs/operators';
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
import { ActivatedRoute, Params } from '@angular/router';
import { ActivatedRoute, NavigationStart, Router } from '@angular/router';
import { CustomerOrderSearchStore } from '../customer-order-search.store';
import { BehaviorSubject, combineLatest, Observable, Subject, Subscription } from 'rxjs';
import { BreadcrumbService } from '@core/breadcrumb';
@@ -27,6 +29,8 @@ import { CustomerOrdersNavigationService } from '@shared/services';
import { ApplicationService } from '@core/application';
import { Filter, FilterInputGroupMainComponent } from '@shared/components/filter';
import { CustomerOrderItemComponent } from './customer-order-item.component';
import { CacheService } from '@core/cache';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export interface CustomerOrderSearchResultsState {
selectedOrderItemSubsetIds: number[];
@@ -46,6 +50,8 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
@ViewChild(FilterInputGroupMainComponent, { static: false })
sharedFilterInputGroupMain: FilterInputGroupMainComponent;
destroyRef = inject(DestroyRef);
items$: Observable<OrderItemListItemDTO[]> = this._customerOrderSearchStore.results$;
itemLength$ = this.items$.pipe(map((items) => items?.length));
@@ -99,10 +105,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
private _searchResultSubscription = new Subscription();
filter$ = this._customerOrderSearchStore.filter$.pipe(
filter((f) => !!f),
take(1)
);
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()))
@@ -112,18 +115,10 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
return this._environment.matchTablet$;
}
get isTablet() {
return this._environment.matchTablet();
}
get isDesktopLarge() {
return this._environment.matchDesktopLarge();
}
get rightOutletParams(): Params {
return this._activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'right')?.snapshot?.params;
}
get filterRoute() {
const compartmentCode = this._activatedRoute?.snapshot?.params?.compartmentCode;
const orderId = this._activatedRoute?.snapshot?.params?.orderId;
@@ -141,6 +136,8 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
return this._environment.matchTablet$.pipe(map((matches) => !matches && this._activatedRoute.outlet === 'primary'));
}
private readonly SCROLL_POSITION_TOKEN = 'CUSTOMER_ORDERS_LIST_SCROLL_POSITION';
constructor(
private _customerOrderSearchStore: CustomerOrderSearchStore,
private _activatedRoute: ActivatedRoute,
@@ -149,7 +146,9 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
private _modal: UiModalService,
private _environment: EnvironmentService,
private _navigationService: CustomerOrdersNavigationService,
private _application: ApplicationService
private _application: ApplicationService,
private _cache: CacheService,
private _router: Router
) {
super({
selectedOrderItemSubsetIds: [],
@@ -173,7 +172,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
this._searchResultSubscription.add(
this.processId$
.pipe(
debounceTime(10),
debounceTime(150),
withLatestFrom(this._activatedRoute.queryParams),
switchMap(([processId, params]) =>
this._application.getSelectedBranch$(processId).pipe(map((selectedBranch) => ({ processId, params, selectedBranch })))
@@ -182,46 +181,67 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
.subscribe(async ({ processId, params, selectedBranch }) => {
const branchChanged = selectedBranch?.id !== this._customerOrderSearchStore?.selectedBranch?.id;
const processChanged = processId !== this._customerOrderSearchStore.processId;
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 });
}
if (branchChanged) {
this._customerOrderSearchStore.setBranch(selectedBranch);
}
this._customerOrderSearchStore.setQueryParams(params);
if (!(this._customerOrderSearchStore.filter instanceof UiFilter)) {
await this._customerOrderSearchStore.loadDefaultSettings();
}
if (this._customerOrderSearchStore.processId !== processId) {
this.scrollItemIntoView();
this.removeBreadcrumbs(processId);
const cleanQueryParams = this.cleanupQueryParams(params);
this._customerOrderSearchStore.patchState({ processId });
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()))) {
this._customerOrderSearchStore.setQueryParams(params);
const queryToken = {
...this._customerOrderSearchStore.filter.getQueryParams(),
processId,
branchId: String(selectedBranch?.id),
};
const data = this._customerOrderSearchStore.getCachedData({ queryToken });
const cache = this._customerOrderSearchStore.getCachedData();
if (cache?.results?.length > 0) {
if (data?.results?.length > 0) {
this._customerOrderSearchStore.patchState({
results: cache?.results,
hits: cache?.hits,
results: data?.results,
hits: data?.hits,
});
await this.updateBreadcrumb(
this._customerOrderSearchStore.processId,
this._customerOrderSearchStore.filter?.getQueryParams()
);
} else if (this._customerOrderSearchStore?.results?.length === 0) {
this._customerOrderSearchStore.search({ siletReload: true });
}
if (
data.results?.length === 0 &&
this._activatedRoute?.parent?.children?.find((childRoute) => childRoute?.outlet === 'side')?.snapshot?.routeConfig?.path !==
'filter'
) {
this.search({ clear: true });
}
} else if (branchChanged) {
this.search({ clear: true });
this._customerOrderSearchStore.search({ siletReload: true });
}
if (this._customerOrderSearchStore.getCachedData()?.results?.length > 0 && !this.isDesktopLarge) {
const scrollPos = params?.scroll_position;
if (!!scrollPos) {
const scrollPos = this._getScrollPositionFromCache();
if (!!scrollPos && this._activatedRoute.outlet === 'primary') {
setTimeout(() => {
this.scrollContainer?.scrollTo(scrollPos);
}
this.removeScrollPosition(params);
}, 150);
}
const process = await this._application.getProcessById$(processId).pipe(first()).toPromise();
@@ -230,7 +250,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
await this.createBreadcrumb(processId, params);
}
if (!this.isDesktopLarge || this._activatedRoute?.outlet === 'main') {
if (this._activatedRoute?.outlet === 'primary') {
await this.removeDetailsBreadcrumb(processId);
}
})
@@ -239,7 +259,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
this._searchResultSubscription.add(
this._customerOrderSearchStore.searchResultSubject.pipe(withLatestFrom(this.processId$)).subscribe(async ([result, processId]) => {
const queryParams = this._customerOrderSearchStore.filter?.getQueryParams();
await this.createBreadcrumb(processId, queryParams);
this.createBreadcrumb(processId, queryParams);
if (result.results.hits === 0) {
return;
@@ -253,7 +273,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
result?.results?.result?.find((_) => true),
queryParams
);
} else if ((!!result?.clear || this._activatedRoute.outlet === 'primary') && !this.isTablet) {
} else if ((!!result?.clear || this._activatedRoute.outlet === 'primary') && this.isDesktopLarge) {
await this._navigationService.getCustomerOrdersResultsPath(processId, { queryParams }).navigate();
}
})
@@ -265,25 +285,68 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
this._searchResultSubscription.add(
this._customerOrderSearchStore.searchStarted.subscribe(async (_) => {
const queryParams = {
...this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()),
...this.cleanupQueryParams(this._customerOrderSearchStore?.filter?.getQueryParams()),
main_qs: this.sharedFilterInputGroupMain?.uiInput?.value,
};
this._customerOrderSearchStore.setQueryParams(queryParams);
this._customerOrderSearchStore?.setQueryParams(queryParams);
})
);
this._router.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
if (event instanceof NavigationStart) {
this._addScrollPositionToCache();
}
});
}
ngAfterViewInit(): void {
this.scrollItemIntoView();
}
private _removeScrollPositionFromCache(): void {
this._cache.delete({ processId: this._customerOrderSearchStore.processId, token: this.SCROLL_POSITION_TOKEN });
}
private _addScrollPositionToCache(): void {
if (this._activatedRoute.outlet === 'primary') {
this._cache.set<number>(
{ processId: this._customerOrderSearchStore.processId, token: this.SCROLL_POSITION_TOKEN },
this.scrollContainer?.scrollPos
);
}
}
private _getScrollPositionFromCache(): number {
return this._cache.get<number>({ processId: this._customerOrderSearchStore.processId, token: this.SCROLL_POSITION_TOKEN });
}
// After Navigating to Result Side Outlet
scrollItemIntoView() {
setTimeout(() => {
const getPrimaryRouteParams = this._activatedRoute.parent?.children[0]?.snapshot?.params;
const item = this.listItems?.find((item) => item.item.orderId === Number(getPrimaryRouteParams?.orderId));
item?.scrollIntoView();
}, 150);
}
async ngOnDestroy() {
this._onDestroy$.next();
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());
}
@@ -301,14 +364,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
return clean;
}
removeScrollPosition(queryParams: Params) {
const scroll_pos = queryParams?.scroll_position;
if (!!scroll_pos) {
const clean = { ...queryParams };
delete clean['scroll_position'];
}
}
async removeBreadcrumbs(processId: number) {
const editCrumbs = await this._breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['customer-order', 'edit']).pipe(first()).toPromise();
@@ -357,15 +412,12 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
.pipe(first())
.toPromise();
const scroll_position = this.scrollContainer?.scrollPos;
const name = queryParams.main_qs ? queryParams.main_qs : 'Alle Artikel';
const params = { ...queryParams, scroll_position };
for (const crumb of crumbs) {
this._breadcrumb.patchBreadcrumb(crumb.id, {
name,
params,
params: queryParams,
});
}
}
@@ -377,13 +429,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
return input?.replace('ORD:', '') ?? 'Alle';
}
scrollItemIntoView() {
setTimeout(() => {
const item = this.listItems?.find((item) => item.item.orderId === Number(this._activatedRoute?.snapshot?.params?.orderId));
item?.scrollIntoView();
}, 150);
}
async loadMore() {
if (
this._customerOrderSearchStore.hits > this._customerOrderSearchStore.results.length &&

View File

@@ -1,4 +1,4 @@
<shared-breadcrumb class="mb-5 desktop-small:mb-9" [key]="processId$ | async" [tags]="['customer-order']">
<shared-breadcrumb [key]="processId$ | async" [tags]="['customer-order']">
<shared-branch-selector [branchType]="1" [value]="selectedBranch$ | async" (valueChange)="patchProcessData($event)">
</shared-branch-selector>
</shared-breadcrumb>

View File

@@ -31,7 +31,7 @@ shared-branch-selector.shared-branch-selector-opend {
shared-branch-selector.shared-branch-selector-opend
.shared-branch-selector-input-container
.shared-branch-selector-input-icon {
@apply pl-0 border-l-0;
@apply border-l-0;
}
::ng-deep .tablet page-customer-order shared-breadcrumb:focus-within .shared-breadcrumb__suffix {

View File

@@ -1,6 +1,7 @@
export const CustomerLabelColor = {
Abholfachbestellung: '#EDEFF0',
'Versandbestellung (oder gemischt)': '#EDEFF0',
'Bestellung ohne Konto': '#EDEFF0',
Onlinekonto: '#804279',
'Onlinekonto mit Kundenkarte': '#804279',
'Business Konto (auf Rechnung)': '#804279',
@@ -10,6 +11,7 @@ export const CustomerLabelColor = {
export const CustomerLabelTextColor = {
Abholfachbestellung: '#000000',
'Bestellung ohne Konto': '#000000',
'Versandbestellung (oder gemischt)': '#000000',
Onlinekonto: '#FFFFFF',
'Onlinekonto mit Kundenkarte': '#FFFFFF',

View File

@@ -4,10 +4,10 @@ import { AbstractControl, AsyncValidatorFn, UntypedFormControl, UntypedFormGroup
import { ActivatedRoute, Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { CrmCustomerService } from '@domain/crm';
import { AddressDTO, CustomerDTO, PayerDTO, ShippingAddressDTO } from '@swagger/crm';
import { AddressDTO, CustomerDTO, CustomerInfoDTO, PayerDTO, ShippingAddressDTO } from '@swagger/crm';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { UiValidators } from '@ui/validators';
import { isNull } from 'lodash';
import { isNull, merge } from 'lodash';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import {
first,
@@ -24,7 +24,12 @@ import {
} from 'rxjs/operators';
import { AddressFormBlockComponent, DeviatingAddressFormBlockComponent, DeviatingAddressFormBlockData } from '../components/form-blocks';
import { FormBlock } from '../components/form-blocks/form-block';
import { CustomerCreateFormData, decodeFormData, encodeFormData } from './customer-create-form-data';
import {
CustomerCreateFormData,
decodeFormData,
encodeFormData,
mapCustomerInfoDtoToCustomerCreateFormData,
} from './customer-create-form-data';
import { AddressSelectionModalService } from '../modals';
import { CustomerCreateNavigation, CustomerSearchNavigation } from '@shared/services';
@@ -224,7 +229,33 @@ export abstract class AbstractCreateCustomer implements OnInit, OnDestroy {
const customerId = this.formData?._meta?.customerDto?.id ?? this.formData?._meta?.customerInfoDto?.id;
return this.customerService.checkLoyaltyCard({ loyaltyCardNumber: value, customerId }).pipe(
map((response) => {
return !response?.error && (response as any)?.result === 1 ? null : { invalid: 'Kundenkartencode ist ungültig' };
if (response.error) {
throw response.message;
}
/**
* #4485 Kubi // Verhalten mit angelegte aber nicht verknüpfte Kundenkartencode in Kundensuche und Kundendaten erfassen ist nicht gleich
* Fall1: Kundenkarte hat Daten in point4more:
* Sobald Kundenkartencode in Feld "Kundenkartencode" reingegeben wird- werden die Daten von point4more in Formular "Kundendaten Erfassen" eingefügt und ersetzen (im Ganzen, nicht inkremental) die Daten in Felder, falls welche schon reingetippt werden.
* Fall2: Kundenkarte hat keine Daten in point4more:
* Sobald Kundenkartencode in Feld "Kundenkartencode" reingegeben wird- bleiben die Daten in Formular "Kundendaten Erfassen" in Felder, falls welche schon reingetippt werden.
*/
if (response.result && response.result.customer) {
const customer = response.result.customer;
const data = mapCustomerInfoDtoToCustomerCreateFormData(customer);
if (data.name.firstName && data.name.lastName) {
// Fall1
this._formData.next(data);
} else {
// Fall2 Hier müssen die Metadaten gesetzt werden um eine verknüfung zur kundenkarte zu ermöglichen.
const current = this.formData;
current._meta = data._meta;
current.p4m = data.p4m;
}
}
return null;
}),
catchError((error) => {
if (error instanceof HttpErrorResponse) {

View File

@@ -85,7 +85,9 @@
</app-deviating-address-form-block>
<div class="spacer"></div>
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
<div class="sticky w-full flex items-center justify-center">
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
</div>
</form>

View File

@@ -19,7 +19,7 @@ form {
}
button.cta-submit {
@apply sticky left-1/2 bottom-8 text-center bg-brand text-lg text-white font-bold px-7 py-3 rounded-full transform -translate-x-1/2 transition-all duration-200 ease-in-out;
@apply fixed bottom-16 desktop-small:bottom-8 text-center bg-brand text-lg text-white font-bold px-7 py-3 rounded-full transform transition-all duration-200 ease-in-out;
&:disabled {
@apply bg-active-branch cursor-not-allowed;

View File

@@ -98,7 +98,9 @@
</app-deviating-address-form-block>
<div class="spacer"></div>
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
<div class="sticky w-full flex items-center justify-center">
<button class="cta-submit" type="button" (click)="save()" [disabled]="form.invalid || form.pending">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
</div>
</form>

View File

@@ -143,7 +143,9 @@
<div class="spacer"></div>
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
<div class="sticky w-full flex items-center justify-center">
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
</div>
</form>

View File

@@ -84,9 +84,12 @@
</app-deviating-address-form-block>
<div class="spacer"></div>
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
<ui-spinner [show]="busy$ | async">
Speichern
</ui-spinner>
</button>
<div class="sticky w-full flex items-center justify-center">
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
<ui-spinner [show]="busy$ | async">
Speichern
</ui-spinner>
</button>
</div>
</form>

View File

@@ -98,7 +98,9 @@
</app-deviating-address-form-block>
<div class="spacer"></div>
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
<div class="sticky w-full flex items-center justify-center">
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
</div>
</form>

View File

@@ -56,7 +56,7 @@ export class CreateWebshopCustomerComponent extends AbstractCreateCustomer {
async saveCustomer(customer: CustomerDTO): Promise<CustomerDTO> {
const { customerDto, customerInfoDto } = this.formData?._meta ?? {};
const isUpgrade = !!(customerDto || customerInfoDto);
const isUpgrade = !!(customerDto || customerInfoDto)?.id;
if (isUpgrade) {
if (customerDto) {

View File

@@ -1,10 +1,3 @@
<a
*ngIf="customerSearchNavigation$ | async; let customerSearchNavigation"
[routerLink]="customerSearchNavigation.path"
[queryParams]="customerSearchNavigation.queryParams"
class="text-[1.375rem] font-bold text-[#596470] text-center py-4"
>Kundensuche</a
>
<div class="text-center pt-10 px-8 rounded-card side-view-shadow grow">
<h1 class="text-[1.625rem] font-bold">Kundendaten erfassen</h1>
<p class="text-lg mt-2">

View File

@@ -1,9 +1,6 @@
import { CommonModule } from '@angular/common';
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterModule } from '@angular/router';
import { ApplicationService } from '@core/application';
import { CustomerSearchNavigation } from '@shared/services';
import { map } from 'rxjs/operators';
@Component({
selector: 'page-customer-create-side-view',
@@ -14,9 +11,5 @@ import { map } from 'rxjs/operators';
imports: [CommonModule, RouterModule],
})
export class CustomerCreateSideViewComponent {
customerSearchNavigation$ = this._applicationService.activatedProcessId$.pipe(
map((processId) => this.searchNavigation.defaultRoute({ processId }))
);
constructor(public readonly searchNavigation: CustomerSearchNavigation, private _applicationService: ApplicationService) {}
constructor() {}
}

View File

@@ -113,7 +113,9 @@
<div class="spacer"></div>
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
<div class="sticky w-full flex items-center justify-center">
<button class="cta-submit" type="button" [disabled]="form.invalid || form.pending" (click)="save()">
<ui-spinner [show]="busy$ | async">Speichern</ui-spinner>
</button>
</div>
</form>

View File

@@ -1,3 +1,3 @@
<shared-breadcrumb [key]="processId$ | async" [tags]="['customer']" class="mb-5 desktop-small:mb-9"></shared-breadcrumb>
<shared-breadcrumb [key]="processId$ | async" [tags]="['customer']"></shared-breadcrumb>
<router-outlet></router-outlet>

View File

@@ -70,7 +70,7 @@
</shared-form-control>
<div class="text-center col-span-2">
<shared-checkbox>Diese Rechnungsadresse als Standard Adresse festlegen</shared-checkbox>
<shared-checkbox formControlName="isDefault">Diese Rechnungsadresse als Standard Adresse festlegen</shared-checkbox>
</div>
<div class="mt-6 text-center col-span-2">
<button

View File

@@ -86,7 +86,7 @@
</shared-form-control>
<div class="text-center col-span-2">
<shared-checkbox>Diese Lieferadresse als Standard Adresse festlegen</shared-checkbox>
<shared-checkbox formControlName="isDefault">Diese Lieferadresse als Standard Adresse festlegen</shared-checkbox>
</div>
<div class="mt-6 text-center col-span-2">
<button

View File

@@ -30,7 +30,7 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
breadcrumb = this.snapshot.data?.breadcrumb;
if (!breadcrumb) {
breadcrumb = this.firstChildSnapshot.data?.breadcrumb;
breadcrumb = this.firstChildSnapshot?.data?.breadcrumb;
}
return breadcrumb;
@@ -145,7 +145,6 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
let processId: number;
processId = this.snapshot.data?.processId;
if (!processId) {
processId = this.parentSnapshot?.data?.processId;
}
@@ -168,7 +167,7 @@ export class CustomerSearchComponent implements OnInit, OnDestroy {
side = this.snapshot.data?.side;
if (!side) {
side = this.firstChildSnapshot.data?.side;
side = this.firstChildSnapshot?.data?.side;
}
if (side !== this.side) {

View File

@@ -24,8 +24,8 @@
[checked]="!(selectedPayer$ | async)"
(change)="selectCustomerAddress()"
/>
<div class="ml-2 flex flex-row justify-between items-center grow">
<span class="truncate">
<div class="ml-2 flex flex-row justify-between items-start grow">
<span class="mr-4">
{{ customer$ | async | address }}
</span>
<a
@@ -51,8 +51,8 @@
[checked]="(selectedPayer$ | async)?.payer.id === assignedPayer.payer.id"
(change)="selectPayer(assignedPayer)"
/>
<div class="flex flex-row justify-between items-center grow">
<span class="ml-2">
<div class="ml-2 flex flex-row justify-between items-start grow">
<span class="mr-4">
{{ assignedPayer.payer.data | address }}
</span>
<ng-container *ngIf="canEditAddress$ | async">

View File

@@ -157,16 +157,19 @@ export class DetailsMainViewBillingAddressesComponent extends ComponentStore<Det
);
handleLoadAssignedPayersResponse = (response: ListResponseArgsOfAssignedPayerDTO) => {
const selectedPayer = response.result.reduce<AssignedPayerDTO>((selected, payer) => {
if (!selected) {
return payer;
const selectedPayer = response.result.reduce<AssignedPayerDTO>((prev, curr) => {
if (!prev) {
return curr;
}
if (new Date(selected.isDefault).getTime() < new Date(payer.isDefault).getTime()) {
return payer;
const prevDate = new Date(prev?.isDefault ?? 0);
const currDate = new Date(curr?.isDefault ?? 0);
if (prevDate > currDate) {
return prev;
}
return selected;
return curr;
}, undefined);
this.patchState({

View File

@@ -18,8 +18,8 @@
*ngIf="showCustomerAddress$ | async"
>
<input name="shipping-address" type="radio" [checked]="!(selectedShippingAddress$ | async)" (change)="selectCustomerAddress()" />
<div class="ml-2 flex flex-row justify-between items-center grow">
<span class="truncate">
<div class="ml-2 flex flex-row justify-between items-start grow">
<span class="mr-4">
{{ customer$ | async | address }}
</span>
<a
@@ -45,11 +45,18 @@
[checked]="(selectedShippingAddress$ | async)?.id === shippingAddress.id"
(change)="selectShippingAddress(shippingAddress)"
/>
<div class="ml-2 flex flex-row justify-between items-center grow">
<span class="ml-2">
<div class="ml-2 flex flex-row justify-between items-start grow">
<span class="mr-4">
{{ shippingAddress | address }}
</span>
<a *ngIf="canEditAddress$ | async" class="text-brand font-bold" type="button">
<a
*ngIf="editShippingAddressRoute$(shippingAddress.id) | async; let route"
class="text-brand font-bold"
type="button"
[routerLink]="route?.path"
[queryParams]="route?.queryParams"
[queryParamsHandling]="'merge'"
>
Bearbeiten
</a>
</div>

View File

@@ -71,6 +71,16 @@ export class DetailsMainViewDeliveryAddressesComponent extends ComponentStore<De
)
);
editShippingAddressRoute$ = (shippingAddressId: number) =>
combineLatest([this.canEditAddress$, this._store.processId$, this._store.customerId$]).pipe(
map(([canEditAddress, processId, customerId]) => {
if (canEditAddress) {
return this._navigation.editShippingAddressRoute({ processId, customerId, shippingAddressId });
}
return undefined;
})
);
canEditAddress$ = combineLatest([this._store.isKundenkarte$, this._store.isBusinessKonto$, this._store.isMitarbeiter$]).pipe(
map(([isKundenkarte, isBusinessKonto, isMitarbeiter]) => isKundenkarte || isBusinessKonto || isMitarbeiter)
);
@@ -150,16 +160,19 @@ export class DetailsMainViewDeliveryAddressesComponent extends ComponentStore<De
);
handleLoadShippingAddressesResponse = (response: ListResponseArgsOfAssignedPayerDTO) => {
const selectedShippingAddress = response.result.reduce<ShippingAddressDTO>((selected, shipping) => {
if (!this.showCustomerAddress && !selected) {
return shipping;
const selectedShippingAddress = response.result.reduce<ShippingAddressDTO>((prev, curr) => {
if (!this.showCustomerAddress && !prev) {
return curr;
}
if (selected && shipping && new Date(selected.isDefault).getTime() < new Date(shipping.isDefault).getTime()) {
return shipping;
const prevDate = new Date(prev?.isDefault ?? 0);
const currDate = new Date(curr?.isDefault ?? 0);
if (prevDate > currDate) {
return prev;
}
return selected;
return curr;
}, undefined);
this.patchState({

View File

@@ -39,7 +39,7 @@
<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 class="data-value">{{ created$ | async | date: 'dd.MM.yyyy' }} | {{ created$ | async | date: 'HH:mm' }} Uhr</div>
</div>
<div class="flex flex-row">
<div class="data-label">Kundennummer</div>

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
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';
@@ -12,6 +12,8 @@ import { ApplicationService } from '@core/application';
import { CheckoutNavigationService, ProductCatalogNavigationService } from '@shared/services';
import { 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',
@@ -34,6 +36,8 @@ export interface CustomerDetailsViewMainState {
export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDetailsViewMainState> implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
customerService = inject(CrmCustomerService);
fetching$ = this._store.fetchingCustomer$;
isBusy$ = this.select((s) => s.isBusy);
@@ -230,15 +234,21 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
if (this.isBusy) return;
this.setIsBusy(true);
if (this.shoppingCartHasItems && !(await this._canAddCustomerAsync())) {
const canAddCustomer = await this._canAddCustomerAsync();
if (this.shoppingCartHasItems && !canAddCustomer) {
this.setIsBusy(false);
return;
}
if (this.shippingAddress && !this._canAddShippingAddressAsync()) {
const canAddShippingAddress = await this._canAddShippingAddressAsync();
if (this.shippingAddress && !canAddShippingAddress) {
this.setIsBusy(false);
return;
}
if (!(await this._updateDestinationAsync())) {
const destinationUpdated = await this._updateDestinationAsync();
if (!destinationUpdated) {
this.setIsBusy(false);
return;
}
@@ -300,13 +310,15 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
return true;
}
const required = await this.customerService.canUpgrade(this.customer.id).toPromise();
const upgradeableTo = res.create;
const data: CantAddCustomerToCartData = {
message: res.message,
upgradeableTo,
customer: this.customer,
attributes: this.customer.attributes.map((a) => a.data),
required: res.create,
required,
};
await this._modalService
@@ -321,21 +333,44 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
@logAsync
async _canAddShippingAddressAsync() {
const res = await this._checkoutService
.canAddDestination({
processId: this.processId,
destinationDTO: {
target: 2,
shippingAddress: this.shippingAddress,
},
})
.toPromise();
try {
const res = await this._checkoutService
.canAddDestination({
processId: this.processId,
destinationDTO: {
target: 2,
shippingAddress: this.shippingAddress,
},
})
.toPromise();
if (typeof res === 'string') {
throw new Error(res);
if (typeof res === 'string') {
throw new Error(res);
}
return true;
} catch (error) {
this._modalService.open({
content: MessageModalComponent,
title: 'Warenkorb kann dem Kunden nicht zugewiesen werden',
data: {
message: 'Dieser Versand ist nur innerhalb Deutschlands möglich.',
actions: [
{ label: 'OK' },
{
label: 'Lieferadresse hinzufügen',
primary: true,
action: () => {
const nav = this._navigation.addShippingAddressRoute({ processId: this.processId, customerId: this.customer.id });
this._router.navigate(nav.path, { queryParams: nav.queryParams, queryParamsHandling: 'merge' });
},
},
],
} as MessageModalData,
});
return false;
}
return res;
}
@logAsync
@@ -376,7 +411,9 @@ export class CustomerDetailsViewMainComponent extends ComponentStore<CustomerDet
_patchProcessName() {
let name = `${this.customer.firstName} ${this.customer.lastName}`;
if (this._store.isBusinessKonto) {
// 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}`;
}

View File

@@ -70,13 +70,13 @@
</shared-form-control>
<div class="text-center col-span-2">
<shared-checkbox>Diese Rechnungsadresse als Standard Adresse festlegen</shared-checkbox>
<shared-checkbox formControlName="isDefault">Diese Rechnungsadresse als Standard Adresse festlegen</shared-checkbox>
</div>
<div class="mt-6 text-center col-span-2">
<button
[disabled]="formGroup.invalid || formGroup.disabled"
type="submit"
class="px-5 py-3 font-bold text-lg rounded-full bg-brand text-white"
class="px-5 py-3 font-bold text-lg rounded-full bg-brand text-white disabled:bg-gray-400 disabled:cursor-not-allowed"
>
Speichern
</button>

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ChangeDetectorRef, inject } from '@angular/core';
import { CheckboxComponent } from '@shared/components/checkbox';
import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { SelectModule } from '@shared/components/select';
@@ -36,6 +36,8 @@ export class EditBillingAddressMainViewComponent extends ComponentStore<EditBill
map(([processId, customerId]) => this._navigation.detailsRoute({ processId, customerId }))
);
private _cdr = inject(ChangeDetectorRef);
formGroup = new FormGroup({
gender: new FormControl<Gender>(0, [Validators.required, Validators.min(1)]),
title: new FormControl<string>(undefined),
@@ -103,6 +105,10 @@ export class EditBillingAddressMainViewComponent extends ComponentStore<EditBill
country: payer.address.country,
info: payer.address.info,
});
this.formGroup.markAllAsTouched();
this.formGroup.updateValueAndValidity();
this._cdr.markForCheck();
}
async save() {

View File

@@ -86,13 +86,13 @@
</shared-form-control>
<div class="text-center col-span-2">
<shared-checkbox>Diese Lieferadresse als Standard Adresse festlegen</shared-checkbox>
<shared-checkbox formControlName="isDefault">Diese Lieferadresse als Standard Adresse festlegen</shared-checkbox>
</div>
<div class="mt-6 text-center col-span-2">
<button
[disabled]="formGroup.invalid || formGroup.disabled"
type="submit"
class="px-5 py-3 font-bold text-lg rounded-full bg-brand text-white"
class="px-5 py-3 font-bold text-lg rounded-full bg-brand text-white disabled:bg-gray-400 disabled:cursor-not-allowed"
tabindex="15"
>
Speichern

View File

@@ -1,18 +1,23 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, inject, ChangeDetectorRef } from '@angular/core';
import { CheckboxComponent } from '@shared/components/checkbox';
import { FormControl, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { SelectModule } from '@shared/components/select';
import { FormControlComponent } from '@shared/components/form-control';
import { CrmCustomerService } from '@domain/crm';
import { AddressDTO, Gender, ShippingAddressDTO } from '@swagger/crm';
import { map, takeUntil } from 'rxjs/operators';
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 { Subject, combineLatest } from 'rxjs';
import { IconComponent } from '@shared/components/icon';
import { RouterLink } from '@angular/router';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { ComponentStore } from '@ngrx/component-store';
export interface EditShippingAddressMainViewState {
shippingAddress?: ShippingAddressDTO;
}
@Component({
selector: 'page-edit-shipping-address-main-view',
@@ -33,9 +38,13 @@ import { RouterLink } from '@angular/router';
CheckboxComponent,
],
})
export class EditShippingAddressMainViewComponent implements OnInit, OnDestroy {
export class EditShippingAddressMainViewComponent extends ComponentStore<EditShippingAddressMainViewState> implements OnInit, OnDestroy {
private _activatedRoute = inject(ActivatedRoute);
private _onDestroy = new Subject<void>();
private _cdr = inject(ChangeDetectorRef);
detailsRoute$ = combineLatest([this._store.processId$, this._store.customerId$]).pipe(
map(([processId, customerId]) => this._navigation.detailsRoute({ processId, customerId }))
);
@@ -61,14 +70,30 @@ export class EditShippingAddressMainViewComponent implements OnInit, OnDestroy {
isBusinessKonto$ = this._store.isBusinessKonto$;
shippingAddressId$ = this._activatedRoute.params.pipe(
map((params) => params.shippingAddressId),
switchMap((shippingAddressId) => this._customerService.getShippingAddress(shippingAddressId).pipe(map((res) => res.result)))
);
get shippingAddressId() {
return this.get((s) => s.shippingAddress?.id);
}
constructor(
private _customerService: CrmCustomerService,
private _addressSelection: AddressSelectionModalService,
private _store: CustomerSearchStore,
private _navigation: CustomerSearchNavigation
) {}
) {
super({ shippingAddress: undefined });
}
ngOnInit() {
this.shippingAddressId$.pipe(takeUntil(this._onDestroy)).subscribe((shippingAddress) => {
this.patchState({ shippingAddress });
this.patchFormGroup(shippingAddress);
});
this._store.customer$.pipe(takeUntil(this._onDestroy)).subscribe(() => {
if (this._store.isBusinessKonto) {
this.formGroup.controls.organisation.setValidators([Validators.required]);
@@ -78,6 +103,26 @@ export class EditShippingAddressMainViewComponent implements OnInit, OnDestroy {
});
}
patchFormGroup(shipping: ShippingAddressDTO) {
this.formGroup.patchValue({
gender: shipping.gender ?? 0,
title: shipping.title,
lastName: shipping.lastName,
firstName: shipping.firstName,
organisation: shipping.organisation?.name,
street: shipping.address.street,
streetNumber: shipping.address.streetNumber,
zipCode: shipping.address.zipCode,
city: shipping.address.city,
country: shipping.address.country,
info: shipping.address.info,
});
this.formGroup.markAllAsTouched();
this.formGroup.updateValueAndValidity();
this._cdr.markForCheck();
}
ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
@@ -127,7 +172,12 @@ export class EditShippingAddressMainViewComponent implements OnInit, OnDestroy {
address: addressValidationResult,
};
const result = await this._customerService.createShippingAddress(this._store.customerId, shippingAddress, formData.isDefault);
const result = await this._customerService.updateShippingAddress(
this._store.customerId,
this.shippingAddressId,
shippingAddress,
formData.isDefault
);
this._navigation.navigateToDetails({ processId: this._store.processId, customerId: this._store.customerId });
} catch (error) {

View File

@@ -14,7 +14,13 @@
</div>
</div>
<div class="px-3 bg-surface text-surface-content">
<shared-filter [filter]="filter" [loading]="fetching$ | async" (search)="search(filter)" [hint]="message$ | async"></shared-filter>
<shared-filter
[filter]="filter"
[loading]="fetching$ | async"
(search)="search(filter)"
[hint]="message$ | async"
[scanner]="true"
></shared-filter>
</div>
<div class="grid grid-flow-col gap-6 items-center justify-center mt-6">

View File

@@ -39,9 +39,7 @@ export class CustomerFilterMainViewComponent {
private _location: Location,
private _router: Router,
private _customerSearchNavigation: CustomerSearchNavigation
) {
console.log(this.isOnMainRoute);
}
) {}
search(filter: Filter) {
this._store.setFilter(filter);

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