Compare commits

...

103 Commits

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

Related work items: #4419
2024-01-15 12:38:18 +00:00
Nino Righi
55bd001146 Merged PR 1725: #4597 Fix Print Order Confirmation Immediately
#4597 Fix Print Order Confirmation Immediately
2024-01-12 19:50:00 +00:00
Lorenz Hilpert
a9f11426a7 Merge branch 'develop' into release/3.0 2024-01-11 18:47:50 +01:00
Nino Righi
10b86756d2 Merged PR 1724: #4564 Fix customer page empty details view
#4564 Fix customer page empty details view
2024-01-11 16:19:24 +00:00
Lorenz Hilpert
262dd084c1 Merged PR 1723: #4571 - Kundendaten erfassen // Bei Kundenkarten Formular mit Onlinekonto feh...
#4571 - Kundendaten erfassen // Bei Kundenkarten Formular mit Onlinekonto fehlt die Aufklärung über die Konditionen
2024-01-11 10:27:02 +00:00
Nino Righi
abc58c8a78 Merged PR 1722: #4569 Fix Pickup Shelf display searchboxHint correctly
#4569 Fix Pickup Shelf display searchboxHint correctly
2024-01-10 15:39:08 +00:00
Nino Righi
866cd23e41 Merged PR 1721: #4564 Always keep customer updated and check name properties correctly
#4564 Always keep customer updated and check name properties correctly
2024-01-09 19:12:45 +00:00
Lorenz Hilpert
fdcf12c022 Element naming - Vorgangs-ID 2024-01-09 13:54:31 +01:00
Nino Righi
432f1161af Merged PR 1720: #4553 Always take retailPrice from instock request for take away availability
#4553 Always take retailPrice from instock request for take away availability
2024-01-08 12:59:14 +00:00
Nino Righi
82dbce5744 Merged PR 1713: #4291 Gender Refactoring
#4291 Gender Refactoring
2024-01-08 11:46:24 +00:00
Nino
a4b9f5fcf1 Merge branch 'develop' into release/3.0 2024-01-05 14:17:29 +01:00
Nino Righi
7ea9359c30 Merged PR 1719: #4514 Added regex pattern to compartmentCode field inside goods in out order...
#4514 Added regex pattern to compartmentCode field inside goods in out order edit page
2024-01-05 12:09:43 +00:00
Nino Righi
b9a4b0d315 Merged PR 1718: #4552 Added whitespace
#4552 Added whitespace
2024-01-05 09:19:08 +00:00
Anastasiia Chetverykova
7809e7a2b5 Merged PR 1717: #4552 - Packstückprüfung Anzahl Exemplare
#4552 - Packstückprüfung Anzahl Exemplare
2024-01-04 13:35:57 +00:00
Nino Righi
9a8c74b148 Merged PR 1716: #4534 Goods In Out Order Edit Always Show Price with Two Decimal Places
#4534 Goods In Out Order Edit Always Show Price with Two Decimal Places
2024-01-04 10:36:37 +00:00
Nino Righi
ad62e67771 Merged PR 1715: #4525 Improved Scroll Position Handling in Goods In List and Remission Previe...
#4525 Improved Scroll Position Handling in Goods In List and Remission Preview Page
2024-01-04 09:23:28 +00:00
Nino Righi
6feb8079b7 Merged PR 1714: #4550 Fix Multiple Processes Bug
#4550 Fix Multiple Processes Bug
2024-01-04 08:23:31 +00:00
195 changed files with 1643 additions and 670 deletions

View File

@@ -458,10 +458,6 @@ export class DomainAvailabilityService {
return [2, 32, 256, 1024, 2048, 4096].some((code) => availability?.availabilityType === code);
}
private _priceIsEmpty(price: PriceDTO) {
return isEmpty(price?.value) || isEmpty(price?.vat);
}
private _mapToTakeAwayAvailability({
response,
supplier,
@@ -482,7 +478,7 @@ export class DomainAvailabilityService {
inStock: inStock,
supplierSSC: quantity <= inStock ? '999' : '',
supplierSSCText: quantity <= inStock ? 'Filialentnahme' : '',
price: this._priceIsEmpty(price) ? stockInfo?.retailPrice : price,
price: stockInfo?.retailPrice ?? price, // #4553 Es soll nun immer der retailPrice aus der InStock Abfrage verwendet werden, egal ob "price" empty ist oder nicht
supplier: { id: supplier?.id },
// TODO: Change after API Update
// LH: 2021-03-09 preis Property hat nun ein Fallback auf retailPrice

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

After

Width:  |  Height:  |  Size: 606 B

View File

@@ -75,6 +75,12 @@
"licence": {
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -74,5 +74,11 @@
"licence": {
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -76,5 +76,11 @@
"licence": {
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -75,5 +75,11 @@
"licence": {
"scandit": "AVljxT/dG+TAIDDL2jTxm843juR2OtZ6lHLxRpYR7x9uYiSvY2IAHdRx8tjsf9KU7wK0F5cAeb/nLMHF6Vor9ps79wvuBQw6G3N0IW978b78ZUgPOFzxHUAMuD8dbkDZlX8r9y1cOd9sT3UNEwGrQ4siUt2oCkigyTxJAgYs1ijnjQid7q42hHk3tMXywrAYeu5MhF0TV1H77DRDMxPHD/xiR0zhFQRB2Dtnm1+e3LHKCyQjZ/zknEpQB6HS7UbCBoEDj4tohb83E6oqmQFWwt85/Jk9f49gxXakIcNODnQI5H63kSqpEmV9Al1a5L+WGZ6Bq1gwBbnD8FBXlVqxoooiFXW7jzzBa9LNmQiQ5J8yEkIsPeyOHec7F4ERvVONSMYwWyH39ZweSiRsZRM1UsFPhN96bCT5MEwkjPFn4gji6TPGEceJZvV3HwsiCT5Bgjla4bvDsZ2jYvAr9tSij8kIii9dHvsWlrimt+szHJLSz+8uNI6jAvXyr2f3oRxZD/F9osZHVWkgtAc+vVWqkxVJCqmpmoHOXI6TFSqSjYHddhZyU5r2lgQt0+NI6k/bV3iN7Le1RJCP/wuSDCTZjzsU1igB7UnIN2Y70CqCjIeVH9qlxaI1YAC9lwFv1FZvsiueYeJP1n39mmXCSELVtzxgIBEX5yaIHNbbGXd+e8JUgcO8vJ2JA2kJudaU+xfYR5SY//+J1kPsNSbnBnM25LL+LjeRB3QTfqV5sFq8ORWcIMITvkEaRfP3PVcOzb+hO4Ren4ezhJuyADulmvG8a9Kxxk6ymzBbE7a93SGVbxp7OQNEmvTn5+B9wJ7/l1mtvZL2TilrDZBQVMYWrGuUGpA="
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -75,5 +75,11 @@
"licence": {
"scandit": "AVljxT/dG+TAIDDL2jTxm843juR2OtZ6lHLxRpYR7x9uYiSvY2IAHdRx8tjsf9KU7wK0F5cAeb/nLMHF6Vor9ps79wvuBQw6G3N0IW978b78ZUgPOFzxHUAMuD8dbkDZlX8r9y1cOd9sT3UNEwGrQ4siUt2oCkigyTxJAgYs1ijnjQid7q42hHk3tMXywrAYeu5MhF0TV1H77DRDMxPHD/xiR0zhFQRB2Dtnm1+e3LHKCyQjZ/zknEpQB6HS7UbCBoEDj4tohb83E6oqmQFWwt85/Jk9f49gxXakIcNODnQI5H63kSqpEmV9Al1a5L+WGZ6Bq1gwBbnD8FBXlVqxoooiFXW7jzzBa9LNmQiQ5J8yEkIsPeyOHec7F4ERvVONSMYwWyH39ZweSiRsZRM1UsFPhN96bCT5MEwkjPFn4gji6TPGEceJZvV3HwsiCT5Bgjla4bvDsZ2jYvAr9tSij8kIii9dHvsWlrimt+szHJLSz+8uNI6jAvXyr2f3oRxZD/F9osZHVWkgtAc+vVWqkxVJCqmpmoHOXI6TFSqSjYHddhZyU5r2lgQt0+NI6k/bV3iN7Le1RJCP/wuSDCTZjzsU1igB7UnIN2Y70CqCjIeVH9qlxaI1YAC9lwFv1FZvsiueYeJP1n39mmXCSELVtzxgIBEX5yaIHNbbGXd+e8JUgcO8vJ2JA2kJudaU+xfYR5SY//+J1kPsNSbnBnM25LL+LjeRB3QTfqV5sFq8ORWcIMITvkEaRfP3PVcOzb+hO4Ren4ezhJuyADulmvG8a9Kxxk6ymzBbE7a93SGVbxp7OQNEmvTn5+B9wJ7/l1mtvZL2TilrDZBQVMYWrGuUGpA="
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}

View File

@@ -76,5 +76,11 @@
"licence": {
"scandit": "Ae7z0WDdRDFqG6oYuAXzesYGJpDLDqt+xWtQHOESiOjaSkB7IEIDJAk534U+cg1zGnk++4hOEK9hXEGR01NLTjh76w1fDL0U63OUjo50EHBXIUvzAVSur3pRY+1ER7SvSEWaT0hDOLYvYrTpdECtt1graN9yMvJzXD38VJKUfssT92p+YENV2Hul3eXIvaVjHqXE/yvupF+MlOMMUMhX0/Km/yTU9H9SjBdsXYihZmYWbt2JotO3Zs1ojXb0+3La10xb01S1q0XdDN6El3XMVilEtdmrP3WoGois8vpQBvOCEvduxCfILFAqjeWXTZvXSut9u+kQKpK8uHW4rVV6iVClpZfPYqKJqTh78AI9gpnfb/zO9GfQEDS3g7wI5WbQKqaNRzhTVowFRri4Ep9R5TRC1bnd00RC4zVaMkbu5kBOA7YoRjgUiYWHKJpi/VokZWyN6u1lsi5mTUbQkm1ZWfX5I/iUVYBgyHZYl+8kfFkwLPXGZNrF4xqubjKiCZRQj0oyNjHOBeHqvAekzhk7scX2g/NN+liRQv4ur413b+uXacSiiYIrLhtGgzrz1KRrtu19uB5odk3LoerDoiYXat7wEg9zUYT/+uBfO2X+uS7L5LW0PMI3hV+joQVpDk5SlA2868Nx0KWtPWmMf7xCuFIhDskfBsXZNRTblqxkk0RzzSqtjx9ihGr+/Tuzm8Pm0s4OQqV7b+++/Zn+Vo4rCqMTwutjOO7dqhah5hbOT1MqY/6VcjCXyDad3BXXr+WYU4GtYTe8Ytjkm/ZTG3fImoDbMchEcqnCw3oxG5e/gkdurE8g/mZlFOtzAN7KkqIsg6qLaC5COjfLPXsi/A=="
},
"gender": {
"0": "Keine Anrede",
"1": "Enby",
"2": "Herr",
"4": "Frau"
},
"@shared/icon": "/assets/icons.json"
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -76,7 +76,12 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
showSubscriptionBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'PFO')));
showPromotionBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'Promotion')));
hasPromotionFeature$ = this.store.item$.pipe(map((item) => !!item?.features?.find((i) => i.key === 'Promotion')));
promotionPoints$ = this.store.item$.pipe(map((item) => item?.redemptionPoints));
showPromotionBadge$ = combineLatest([this.hasPromotionFeature$, this.promotionPoints$]).pipe(
map(([hasPromotionFeature, promotionPoints]) => hasPromotionFeature && promotionPoints > 0)
);
showArchivBadge$ = this.store.item$.pipe(map((item) => item?.features?.find((i) => i.key === 'ARC')));
@@ -338,7 +343,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
const item = await this.store.item$.pipe(first()).toPromise();
const modal = this.uiModal.open<BranchDTO>({
content: ModalAvailabilitiesComponent,
title: 'Weitere Verfügbarkeiten',
title: 'Bestände in anderen Filialen',
data: {
item,
},

View File

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

View File

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

View File

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

View File

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

View File

@@ -155,7 +155,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
const cleanQueryParams = this.cleanupQueryParams(queryParams);
if (this.route.outlet === 'primary' && processChanged) {
if (processChanged) {
this.scrollToItem(this._getScrollIndexFromCache());
}
@@ -209,7 +209,6 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
if (searchCompleted.state.hits === 1) {
const item = searchCompleted.state.items.find((f) => f);
const ean = this.route?.snapshot?.params?.ean;
const itemId = this.route?.snapshot?.params?.id ? Number(this.route?.snapshot?.params?.id) : item.id; // Nicht zum ersten Item der Liste springen wenn bereits eines selektiert ist
// Navigation from Cart uses ean
if (!!ean) {
@@ -224,7 +223,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
await this._navigationService
.getArticleDetailsPath({
processId,
itemId,
itemId: item.id,
extras: { queryParams: params },
})
.navigate();
@@ -272,7 +271,7 @@ export class ArticleSearchResultsComponent implements OnInit, OnDestroy, AfterVi
}
private _getScrollIndexFromCache(): number {
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN });
return this.cache.get<number>({ processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN }) ?? 0;
}
scrollToItem(i?: number) {

View File

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

View File

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

View File

@@ -14,7 +14,7 @@
<div class="btn-wrapper">
<a class="cta-primary" [routerLink]="productSearchBasePath">Artikel suchen</a>
<button class="cta-secondary" (click)="openDummyModal()">Neuanlage</button>
<button class="cta-secondary" (click)="openDummyModal({})">Neuanlage</button>
</div>
</div>
</div>
@@ -54,7 +54,7 @@
<button
*ngIf="group.orderType === 'Dummy'"
class="text-brand border-none font-bold text-p1 outline-none pl-4"
(click)="openDummyModal()"
(click)="openDummyModal({ changeDataFromCart: true })"
>
Hinzufügen
</button>

View File

@@ -292,15 +292,15 @@ export class CheckoutReviewComponent implements OnInit, OnDestroy, AfterViewInit
this._store.notificationsControl = undefined;
}
openDummyModal(data?: CheckoutDummyData) {
openDummyModal({ data, changeDataFromCart = false }: { data?: CheckoutDummyData; changeDataFromCart?: boolean }) {
this.uiModal.open({
content: CheckoutDummyComponent,
data,
data: { ...data, changeDataFromCart },
});
}
changeDummyItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {
this.openDummyModal(shoppingCartItem);
this.openDummyModal({ data: shoppingCartItem, changeDataFromCart: true });
}
async changeItem({ shoppingCartItem }: { shoppingCartItem: ShoppingCartItemDTO }) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,6 @@ import { DateAdapter } from '@ui/common';
import { CheckoutNavigationService, PickUpShelfOutNavigationService, ProductCatalogNavigationService } from '@shared/services';
import { EnvironmentService } from '@core/environment';
import { SendOrderConfirmationModalService } from '@shared/modals/send-order-confirmation-modal';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { ToasterService } from '@shared/shell';
@Component({
@@ -136,6 +135,8 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
)
);
isPrinting$ = new BehaviorSubject(false);
totalPriceCurrency$ = this.displayOrders$.pipe(map((displayOrders) => displayOrders[0]?.items[0]?.price?.value?.currency));
containsDeliveryOrder$ = this.displayOrders$.pipe(
@@ -161,6 +162,10 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
return this._environmentService.matchDesktopLarge$;
}
get isTablet() {
return this._environmentService.matchTablet();
}
expanded: boolean[] = [];
constructor(
@@ -291,22 +296,9 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
if (takeNowOrders.length != 1) return;
try {
for (const takeNowOrder of takeNowOrders) {
for (const orderItem of takeNowOrder.items.filter((item) => item.features?.orderType === 'Rücklage')) {
await this.omsService
.changeOrderStatus(takeNowOrder.id, orderItem.id, orderItem.subsetItems[0]?.id, {
processingStatus: 128,
})
.toPromise();
}
}
await this.router.navigate(
this._shelfOutNavigationService.detailRoute({
processId: Date.now(),
item: { orderId: takeNowOrders[0].id, orderNumber: takeNowOrders[0].orderNumber, processingStatus: 128 },
}).path
);
await this.router.navigate(this._shelfOutNavigationService.listRoute({ processId: Date.now() }).path, {
queryParams: { main_qs: takeNowOrders[0].orderNumber, filter_supplier_id: '16' },
});
} catch (e) {
console.error(e);
}
@@ -319,27 +311,57 @@ export class CheckoutSummaryComponent implements OnInit, OnDestroy {
}
async printOrderConfirmation() {
this.isPrinting$.next(true);
const orders = await this.displayOrders$.pipe(first()).toPromise();
await this.uiModal
.open({
content: PrintModalComponent,
data: {
printerType: 'Label',
print: async (printer) => {
try {
const result = await this.domainPrinterService.printOrder({ orderIds: orders.map((o) => o.id), printer }).toPromise();
this._toaster.open({ type: 'success', message: 'Bestellbestätigung wurde gedruckt' });
return result;
} catch (error) {
this._toaster.open({ type: 'danger', message: 'Fehler beim Drucken der Bestellbestätigung' });
}
const selectedPrinter = await this.domainPrinterService
.getAvailableLabelPrinters()
.pipe(
first(),
map((printers) => {
if (Array.isArray(printers)) return printers.find((printer) => printer.selected === true);
})
)
.toPromise();
console.log(selectedPrinter);
if (!selectedPrinter || this.isTablet) {
await this.uiModal
.open({
content: PrintModalComponent,
data: {
printerType: 'Label',
printImmediately: !this.isTablet,
print: async (printer) => {
try {
const result = await this.domainPrinterService.printOrder({ orderIds: orders.map((o) => o.id), printer }).toPromise();
this._toaster.open({ type: 'success', message: 'Bestellbestätigung wurde gedruckt' });
return result;
} catch (error) {
this._toaster.open({ type: 'danger', message: 'Fehler beim Drucken der Bestellbestätigung' });
} finally {
this.isPrinting$.next(false);
}
},
} as PrintModalData,
config: {
panelClass: [],
showScrollbarY: false,
},
} as PrintModalData,
config: {
panelClass: [],
showScrollbarY: false,
},
})
.afterClosed$.toPromise();
})
.afterClosed$.toPromise();
this.isPrinting$.next(false);
} else {
try {
const result = await this.domainPrinterService
.printOrder({ orderIds: orders.map((o) => o.id), printer: selectedPrinter.key })
.toPromise();
this._toaster.open({ type: 'success', message: 'Bestellbestätigung wurde gedruckt' });
return result;
} catch (error) {
this._toaster.open({ type: 'danger', message: 'Fehler beim Drucken der Bestellbestätigung' });
} finally {
this.isPrinting$.next(false);
}
}
}
}

View File

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

View File

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

View File

@@ -18,6 +18,7 @@ import { isEqual } from 'lodash';
import { combineLatest, NEVER, Subject, Observable } from 'rxjs';
import { catchError, filter, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
import { CustomerOrderDetailsStore } from '../customer-order-details.store';
import { EnvironmentService } from '@core/environment';
export interface CustomerOrderDetailsItemComponentState {
orderItem?: OrderItemListItemDTO;
@@ -142,11 +143,16 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
private _onDestroy$ = new Subject();
get isNative() {
return this._environment.isNative();
}
constructor(
private _store: CustomerOrderDetailsStore,
private _domainReceiptService: DomainReceiptService,
private _omsService: DomainOmsService,
private _cdr: ChangeDetectorRef
private _cdr: ChangeDetectorRef,
private _environment: EnvironmentService
) {
super({
more: false,
@@ -231,6 +237,35 @@ export class CustomerOrderDetailsItemComponent extends ComponentStore<CustomerOr
return orderItems?.find((orderItem) => orderItem.data.id === orderItemListItem.orderItemId)?.data?.features?.orderType;
}
getOrderItemTrackingData(orderItemListItem: OrderItemListItemDTO): Array<{ trackingProvider: string; trackingNumber: string }> {
const orderItems = this.order?.items;
const completeTrackingInformation = orderItems
?.find((orderItem) => orderItem.data.id === orderItemListItem.orderItemId)
?.data?.subsetItems?.find((subsetItem) => subsetItem.id === orderItemListItem.orderItemSubsetId)?.data?.trackingNumber;
if (!completeTrackingInformation) {
return;
}
// Beispielnummer: 'DHL: 124124' - Bei mehreren Tracking-Informationen muss noch ein Splitter eingebaut werden, je nach dem welcher Trenner verwendet wird
const trackingInformationPairs = completeTrackingInformation.split(':').map((obj) => obj.trim());
return this._trackingTransformationHelper(trackingInformationPairs);
}
// Macht aus einem String Array ein Array von Objekten mit den keys trackingProvider und trackingNumber
private _trackingTransformationHelper(trackingInformationPairs: string[]): Array<{ trackingProvider: string; trackingNumber: string }> {
return trackingInformationPairs.reduce((acc, current, index, array) => {
if (index % 2 === 0) {
acc.push({ trackingProvider: current, trackingNumber: array[index + 1] });
}
return acc;
}, [] as { trackingProvider: string; trackingNumber: string }[]);
}
getTrackingNumberLink(trackingNumber: string) {
return `https://www.dhl.de/de/privatkunden/dhl-sendungsverfolgung.html?piececode=${trackingNumber}`;
}
triggerResize() {
this.autosize.reset();
}

View File

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

View File

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

View File

@@ -170,10 +170,9 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
);
this._searchResultSubscription.add(
this.processId$
combineLatest([this.processId$, this._activatedRoute.queryParams])
.pipe(
debounceTime(150),
withLatestFrom(this._activatedRoute.queryParams),
switchMap(([processId, params]) =>
this._application.getSelectedBranch$(processId).pipe(map((selectedBranch) => ({ processId, params, selectedBranch })))
)
@@ -185,16 +184,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
if (processChanged) {
if (!!this._customerOrderSearchStore.processId && this._customerOrderSearchStore.filter instanceof Filter) {
const queryToken = {
...this._customerOrderSearchStore.filter?.getQueryParams(),
processId,
branchId: String(selectedBranch?.id),
};
this._customerOrderSearchStore.setCache({
queryToken,
hits: this._customerOrderSearchStore.hits,
results: this._customerOrderSearchStore.results,
});
await this.updateBreadcrumb(processId, this._customerOrderSearchStore.filter?.getQueryParams());
}
this._customerOrderSearchStore.patchState({ processId });
@@ -212,6 +201,7 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this._customerOrderSearchStore.filter.getQueryParams()))) {
this._customerOrderSearchStore.setQueryParams(params);
const queryToken = {
...this._customerOrderSearchStore.filter.getQueryParams(),
processId,
@@ -288,13 +278,13 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
...this.cleanupQueryParams(this._customerOrderSearchStore?.filter?.getQueryParams()),
main_qs: this.sharedFilterInputGroupMain?.uiInput?.value,
};
this._customerOrderSearchStore?.setQueryParams(queryParams);
})
);
this._router.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
if (event instanceof NavigationStart) {
this.cacheResults();
this._addScrollPositionToCache();
}
});
@@ -335,18 +325,6 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
this._onDestroy$.complete();
this._searchResultSubscription.unsubscribe();
const queryToken = {
...this._customerOrderSearchStore.filter?.getQueryParams(),
processId: this._customerOrderSearchStore.processId,
branchId: String(this._customerOrderSearchStore.selectedBranch?.id),
};
this._customerOrderSearchStore.setCache({
queryToken,
hits: this._customerOrderSearchStore.hits,
results: this._customerOrderSearchStore.results,
});
await this.updateBreadcrumb(this._customerOrderSearchStore.processId, this._customerOrderSearchStore.filter?.getQueryParams());
}
@@ -416,6 +394,20 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
}
}
cacheResults() {
const queryToken = {
...this._customerOrderSearchStore.filter?.getQueryParams(),
processId: this._customerOrderSearchStore.processId,
branchId: String(this._customerOrderSearchStore.selectedBranch?.id),
};
this._customerOrderSearchStore.setCache({
queryToken,
hits: this._customerOrderSearchStore.hits,
results: this._customerOrderSearchStore.results,
});
}
getBreadcrumbName(params: Record<string, string>) {
const input = params?.main_qs;
@@ -435,8 +427,8 @@ export class CustomerOrderSearchResultsComponent extends ComponentStore<Customer
search({ filter, clear = false }: { filter?: Filter; clear?: boolean }) {
if (!!filter) {
this.sharedFilterInputGroupMain.cancelAutocomplete();
this._customerOrderSearchStore.setQueryParams(filter?.getQueryParams());
}
this._customerOrderSearchStore.search({ clear });
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -36,7 +36,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
nameValidationFns: Record<keyof NameFormBlockData, ValidatorFn[]> = {
firstName: [Validators.required],
lastName: [Validators.required],
gender: [Validators.required, Validators.min(1)],
gender: [Validators.required],
title: [],
};
@@ -70,7 +70,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
agbValidatorFns = [Validators.requiredTrue];
birthDateValidatorFns = [Validators.required];
birthDateValidatorFns = [];
existingCustomer$: Observable<CustomerInfoDTO | null>;
@@ -138,6 +138,7 @@ export class CreateP4MCustomerComponent extends AbstractCreateCustomer implement
initMarksAndValidators() {
this.asyncLoyaltyCardValidatorFn = [this.checkLoyalityCardValidator];
this.birthDateValidatorFns = [Validators.required, this.minBirthDateValidator()];
if (this._customerType === 'webshop') {
this.emailRequiredMark = true;
this.emailValidatorFn = [Validators.required, Validators.email, validateEmail];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,6 @@ import { CrmCustomerService } from '@domain/crm';
import { Result } from '@domain/defs';
import { CustomerDTO, ListResponseArgsOfCustomerInfoDTO, QuerySettingsDTO } from '@swagger/crm';
import { Filter } from '@shared/components/filter';
import { isEmpty } from 'lodash';
import { DomainOmsService } from '@domain/oms';
import { OrderDTO, OrderListItemDTO } from '@swagger/oms';
import { hash } from '@utils/common';
@@ -304,7 +303,10 @@ export class CustomerSearchStore extends ComponentStore<CustomerSearchState> imp
),
withLatestFrom(this.filter$, this.processId$),
map(([a1, a2, a3]) => {
this.patchState({ fetchingCustomerList: true, customerList: [], customerListCount: 0, message: '' });
// #4564 Setze "customer" undefined immer wenn neu gesucht wird,
// da sonst Änderungen einer zweiten ISA am Kunden, selbst nach erneuter Suche, nicht geupdated werden,
// da noch der alte Kundendatensatz im Store gespeichert ist
this.patchState({ fetchingCustomerList: true, customerList: [], customer: undefined, customerListCount: 0, message: '' });
let retored = false;
if (a1.ignoreRestore) {

View File

@@ -5,8 +5,8 @@ export function genderLastNameValidator(isB2b: boolean): ValidatorFn | null {
return (control: UntypedFormGroup): ValidationErrors | null => {
const gender = control.get('gender').value;
const lastName = control.get('lastName').value;
if (!!lastName && gender === 0) {
control.get('gender').setValidators([Validators.required, Validators.min(1)]);
if (!!lastName) {
control.get('gender').setValidators([Validators.required]);
return { genderNotSet: true };
} else {
control.get('gender').setValidators(null);

View File

@@ -16,7 +16,7 @@ export function organisationB2bDeliveryValidator(): ValidatorFn | null {
lastName = control.get('shippingAddress').get('lastName');
organisation = control.get('shippingAddress').get('organisation').get('name');
isOrganisationSet = !!organisation.value;
isNameSet = gender.value !== 0 && !!firstName.value && !!lastName.value;
isNameSet = !!firstName.value && !!lastName.value;
if (control.get('differentShippingAddress').value === true) {
return validate(gender, firstName, lastName, organisation, isOrganisationSet, isNameSet);
@@ -28,14 +28,14 @@ export function organisationB2bDeliveryValidator(): ValidatorFn | null {
lastName = control.get('lastName');
organisation = control.get('organisation').get('name');
isOrganisationSet = !!organisation.value;
isNameSet = gender.value !== 0 && !!firstName.value && !!lastName.value;
isNameSet = !!firstName.value && !!lastName.value;
return validate(gender, firstName, lastName, organisation, isOrganisationSet, isNameSet);
}
};
}
function setNameValidation(gender: AbstractControl, firstName: AbstractControl, lastName: AbstractControl) {
gender.setValidators([Validators.required, Validators.min(1)]);
gender.setValidators([Validators.required]);
firstName.setValidators([Validators.required]);
lastName.setValidators([Validators.required]);
}

View File

@@ -16,7 +16,7 @@
ui-slider {
img {
@apply rounded cursor-pointer;
height: 160px;
max-height: 160px;
// max-width: aut;
}

View File

@@ -22,6 +22,7 @@ import { debounceTime, first, map, shareReplay, takeUntil, tap } from 'rxjs/oper
import { GoodsInListItemComponent } from './goods-in-list-item/goods-in-list-item.component';
import { GoodsInListStore } from './goods-in-list.store';
import { PickupShelfInNavigationService } from '@shared/services';
import { CacheService } from '@core/cache';
@Component({
selector: 'page-goods-in-list',
@@ -60,20 +61,21 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
private _onDestroy$ = new Subject();
private readonly SCROLL_POSITION_TOKEN = 'GOODS_IN_LIST_SCROLL_POSITION';
constructor(
private _breadcrumb: BreadcrumbService,
private _domainOmsService: DomainOmsService,
public store: GoodsInListStore,
private _router: Router,
private _route: ActivatedRoute,
private readonly _config: Config
private readonly _config: Config,
private _cache: CacheService
) {}
ngOnInit() {
this.store.setTake(Number(this._route.snapshot.queryParams.take ?? 25));
this._route.queryParams.pipe(takeUntil(this._onDestroy$), debounceTime(0)).subscribe(async (params) => {
const scrollPos = Number(params.scroll_position ?? 0);
// Initial Search - Always Search If No Params Are Set
if (
(Object.keys(params).length === 0 || this.store.results.length === 0) &&
@@ -82,7 +84,8 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
this.store.search({
cb: () => {
setTimeout(() => {
this.scrollContainer?.scrollTo(scrollPos);
this.scrollContainer?.scrollTo(this._getScrollPositionFromCache());
this._removeScrollPositionFromCache();
}, 0);
},
});
@@ -101,7 +104,8 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
this.store.search({
cb: () => {
setTimeout(() => {
this.scrollContainer?.scrollTo(scrollPos);
this.scrollContainer?.scrollTo(this._getScrollPositionFromCache());
this._removeScrollPositionFromCache();
}, 0);
},
});
@@ -117,12 +121,13 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
this._onDestroy$.next();
this._onDestroy$.complete();
this._addScrollPositionToCache();
this.updateBreadcrumb(this.store.filter.getQueryParams());
}
cleanupQueryParams(params: Record<string, string> = {}) {
const clean = { ...params };
delete clean['scroll_position'];
delete clean['take'];
delete clean['view'];
@@ -143,6 +148,21 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
this.listItems.changes.pipe(takeUntil(this._onDestroy$)).subscribe(() => this.registerEditSscDisabled());
}
private _removeScrollPositionFromCache(): void {
this._cache.delete({ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN });
}
private _addScrollPositionToCache(): void {
this._cache.set<number>(
{ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN },
this.scrollContainer?.scrollPos
);
}
private _getScrollPositionFromCache(): number {
return this._cache.get<number>({ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN });
}
navigateToDetails(orderItem: OrderItemListItemDTO) {
if (this.editSsc) {
return;
@@ -184,7 +204,6 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
}
async updateBreadcrumb(queryParams: Record<string, string> | Params = this.store.filter?.getQueryParams()) {
const scroll_position = this.scrollContainer?.scrollPos;
const take = this._route?.snapshot?.queryParams?.take;
if (queryParams) {
@@ -192,7 +211,7 @@ export class GoodsInListComponent implements OnInit, AfterViewInit, OnDestroy {
.getBreadcrumbsByKeyAndTags$(this._config.get('process.ids.goodsIn'), ['goods-in', 'list'])
.pipe(first())
.toPromise();
const params = { ...queryParams, scroll_position, take };
const params = { ...queryParams, take };
for (const crumb of crumbs) {
this._breadcrumb.patchBreadcrumb(crumb.id, {

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { KeyValueDTOOfStringAndString, OrderItemListItemDTO } from '@swagger/oms';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
@@ -10,6 +10,7 @@ import { GoodsInRemissionPreviewStore } from './goods-in-remission-preview.store
import { Config } from '@core/config';
import { ToasterService } from '@shared/shell';
import { PickupShelfInNavigationService } from '@shared/services';
import { CacheService } from '@core/cache';
@Component({
selector: 'page-goods-in-remission-preview',
@@ -22,8 +23,6 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
private _pickupShelfInNavigationService = inject(PickupShelfInNavigationService);
@ViewChild(UiScrollContainerComponent) scrollContainer: UiScrollContainerComponent;
private _scrollPosition: number;
items$ = this._store.results$;
itemLength$ = this.items$.pipe(map((items) => items?.length));
@@ -52,14 +51,16 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
byCompartmentCodeFn = (item: OrderItemListItemDTO) =>
!!item.compartmentInfo ? `${item.compartmentCode}_${item.compartmentInfo}` : item.compartmentCode;
private readonly SCROLL_POSITION_TOKEN = 'REMISSION_PREVIEW_SCROLL_POSITION';
constructor(
private _breadcrumb: BreadcrumbService,
private _store: GoodsInRemissionPreviewStore,
private _route: ActivatedRoute,
private _router: Router,
private _modal: UiModalService,
private _config: Config,
private _toast: ToasterService
private _toast: ToasterService,
private _cache: CacheService
) {}
ngOnInit(): void {
@@ -71,9 +72,25 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this._onDestroy$.next();
this._onDestroy$.complete();
this._addScrollPositionToCache();
this.updateBreadcrumb();
}
private _removeScrollPositionFromCache(): void {
this._cache.delete({ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN });
}
private _addScrollPositionToCache(): void {
this._cache.set<number>(
{ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN },
this.scrollContainer?.scrollPos
);
}
private _getScrollPositionFromCache(): number {
return this._cache.get<number>({ processId: this._config.get('process.ids.goodsIn'), token: this.SCROLL_POSITION_TOKEN });
}
async createBreadcrumb() {
await this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.goodsIn'),
@@ -93,7 +110,6 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
for (const crumb of crumbs) {
this._breadcrumb.patchBreadcrumb(crumb.id, {
name: crumb.name,
params: { scroll_position: this.scrollContainer?.scrollPos },
});
}
}
@@ -133,14 +149,11 @@ export class GoodsInRemissionPreviewComponent implements OnInit, OnDestroy {
this._store.searchResult$.pipe(takeUntil(this._onDestroy$)).subscribe(async (result) => {
await this.createBreadcrumb();
if (this._scrollPosition) {
this.scrollContainer?.scrollTo(Number(this._scrollPosition ?? 0));
this._scrollPosition = undefined;
}
this.scrollContainer?.scrollTo(this._getScrollPositionFromCache() ?? 0);
this._removeScrollPositionFromCache();
});
}
this._scrollPosition = this._route.snapshot.queryParams?.scroll_position;
this._store.search();
}

View File

@@ -11,7 +11,7 @@
}
.package-details-list-item__product-image {
@apply w-12 h-auto shadow;
@apply w-12 shadow max-h-full;
}
@screen desktop {
@@ -20,7 +20,7 @@
}
.package-details-list-item__product-image {
@apply w-auto h-full shadow mx-auto;
@apply w-auto max-h-full shadow mx-auto;
}
}

View File

@@ -5,3 +5,7 @@
.action-wrapper {
@apply grid grid-flow-col gap-4 justify-center my-6 fixed bottom-24 inset-x-0;
}
.annotation-layout {
@apply col-span-2;
}

View File

@@ -3,33 +3,7 @@
</div>
<ng-container *ngIf="packageDetails$ | async; else loader; let packageDetails" (sharedOnInit)="calculateListHeight()">
<div class="bg-background-liste">
<div #handlungsAnweisung [ngSwitch]="packageDetails.package.arrivalStatus | arrivalStatus">
<div class="bg-white" *ngSwitchCase="'Falsche Filiale'">
<p class="text-center text-xl py-10">
Stellen Sie dieses Packstück für die andere Filiale bereit. <br />
Der Spediteur holt es zum nächstmöglichen Zeitpunkt ab.
</p>
</div>
<div class="bg-white" *ngSwitchCase="'Offen'">
<p class="text-center text-xl py-10" *ngIf="!(childActions$ | async)">
Können Sie sich erinnern, dass Sie dieses Packstück <br />
ausgepackt haben?
</p>
<p class="text-center text-xl py-10" *ngIf="!!(childActions$ | async)">
Prüfen Sie bitte stichprobenartig den Filialbestand des <br />
dargestellten Artikels. Ist der angezeigte Filialbestand <br />
korrekt?
</p>
</div>
<div class="bg-white" *ngSwitchCase="'Fehlt'">
<p class="text-center text-xl py-10">
Prüfen Sie bitte stichprobenartig den Filialbestand <br />
des dargestellten Artikels. Ist der angezeigte Filialbestand <br />
korrekt?
</p>
</div>
<div class="pt-3" *ngSwitchDefault></div>
</div>
<div class="bg-white text-center text-xl py-10" [innerText]="packageDetails?.package?.features?.['description'] ?? ''"></div>
<div class="bg-white rounded-t shadow-card grid grid-flow-row p-4 gap-2">
<div class="grid grid-cols-6">
@@ -38,7 +12,7 @@
<span *ngIf="packageDetails.package.packageNumber && packageDetails.package.deliveryNoteNumber"> | </span>
{{ packageDetails.package.deliveryNoteNumber }}
</div>
<div class="col-span-3">
<div class="col-span-3" [class.annotation-layout]="packageDetails?.package?.features?.['annotation']">
<ng-container *ngIf="packageDetails.package.arrivalStatus !== 8; else irrlauferTmplt">
Filialstopp
<span class="font-bold ml-2">
@@ -52,7 +26,7 @@
</span>
</ng-template>
</div>
<div class="text-right">
<div data-which="Statusmeldung" class="text-right" *ngIf="!packageDetails?.package?.features?.['annotation']; else annotationTmpl">
<ng-container *ngIf="(packageDetails.package.arrivalStatus | arrivalStatus) === 'Fehlt'">
<button
class="isa-icon-button mr-2"
@@ -78,10 +52,16 @@
<div
class="isa-label text-white font-bold page-package-details__arrival-status"
[class]="packageDetails.package.arrivalStatus | arrivalStatusColorClass"
data-what="arrival-status"
>
{{ packageDetails.package.arrivalStatus | arrivalStatus }}
</div>
</div>
<ng-template #annotationTmpl>
<div class="page-package-details__annotation text-right text-[#5A728A] col-span-2" data-what="annotation">
{{ packageDetails?.package?.features?.['annotation'] }}
</div>
</ng-template>
</div>
<div class="grid grid-cols-6">
<div class="col-span-2">
@@ -97,7 +77,7 @@
</ng-container>
</div>
<div class="text-right">
<span class="font-bold">{{ packageDetails.package.items }}</span> Exemplare
<span class="font-bold">{{ packageDetails?.package?.items ?? '-' }}</span> Exemplare
</div>
</div>
</div>

View File

@@ -148,7 +148,7 @@ export class PackageDetailsComponent {
@HostListener('window:resize')
calculateListHeight() {
const handlungsAnweisungHeight = this.handlungsAnweisung?.nativeElement.offsetHeight;
const handlungsAnweisungHeight = this.handlungsAnweisung?.nativeElement?.offsetHeight ?? 0;
const windowHeight = window.innerHeight;
this.detailsListHeight = windowHeight - handlungsAnweisungHeight - 516;
this._cdr.markForCheck();

View File

@@ -5,14 +5,13 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Breadcrumb, BreadcrumbService } from '@core/breadcrumb';
import { take } from 'rxjs/operators';
import { NavigationRoute } from '@shared/services';
import { DBHOrderItemListItemDTO, KeyValueDTOOfStringAndString } from '@swagger/oms';
import { DBHOrderItemListItemDTO, KeyValueDTOOfStringAndString, ListResponseArgsOfDBHOrderItemListItemDTO } from '@swagger/oms';
import { isEmpty } from 'lodash';
import { Observable } from 'rxjs';
import { RunCheckTrigger } from './trigger';
import { OrderItemsContext } from '@domain/oms';
import { ActionHandlerService } from './services/action-handler.service';
import { Config } from '@core/config';
import { debounce } from '@utils/common';
export type GetNameForBreadcrumbData = {
processId: number;
@@ -117,31 +116,8 @@ export abstract class PickupShelfBaseComponent implements OnInit {
return queryParams;
}
regsiterFetchListResponseHandler() {
this.listStore.fetchListResponse$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(async ({ processId, queryParams, response }) => {
/**
* Wenn die Suche erfolgreich war, wird der Benutzer auf die Liste oder Detailseite des gefundenen Artikels weitergeleitet.
*/
const filterQueryParams = this.listStore.filter.getQueryParams();
// Only Update QueryParams if the user is already on the details, edit or history page
const view: string = this.activatedRoute.snapshot.data.view;
if (['filter', 'details', 'edit', 'history'].includes(view)) {
await this.router.navigate([], { queryParams: { ...queryParams, ...filterQueryParams }, skipLocationChange: true });
return;
}
if (response.hits === 1) {
const detailsPath = await this.getPathForDetail(response.result[0]).pipe(take(1)).toPromise();
await this.router.navigate(detailsPath.path, { queryParams: { ...queryParams, ...filterQueryParams, ...detailsPath.queryParams } });
} else if (response.hits > 1) {
const listPath = await this.getPathFoListBreadcrumb({ processId, queryParams });
await this.router.navigate(listPath.path, { queryParams: { ...queryParams, ...filterQueryParams, ...listPath.queryParams } });
} else {
await this.router.navigate([], { queryParams: { ...queryParams, ...filterQueryParams } });
}
});
}
// Fix Ticket #4688 Navigation behaves different based on section PickUpShelfOut and PickUpShelfIn
abstract regsiterFetchListResponseHandler(): void | Promise<void>;
/**
* Sucht die Breadcrumb anhand des Tags.

View File

@@ -43,6 +43,7 @@ export abstract class PickupShelfDetailsBaseComponent {
this.store.resetCoverItems();
}
this.store.fetchOrder({ orderId: Number(params.orderId) });
this.store.fetchOrderItems({
orderNumber: params.orderNumber ? decodeURIComponent(params.orderNumber) : undefined,
compartmentCode: params.compartmentCode ? decodeURIComponent(params.compartmentCode) : undefined,
@@ -124,6 +125,7 @@ export abstract class PickupShelfDetailsBaseComponent {
processingStatus: updatedItem.processingStatus,
compartmentCode: updatedItem.compartmentCode,
compartmentInfo: updatedItem.compartmentInfo,
quantity: updatedItem.quantity,
},
});
});

View File

@@ -36,12 +36,22 @@
<page-pickup-shelf-details-tags class="mb-px-2" *ngIf="showTagsComponent$ | async"></page-pickup-shelf-details-tags>
<page-pickup-shelf-details-covers
*ngIf="(coverOrderItems$ | async)?.length > 0"
[coverItems]="coverOrderItems$ | async"
[selectedOrderItem]="selectedItem$ | async"
(coverClick)="coverClick($event)"
></page-pickup-shelf-details-covers>
<ng-container *ngIf="fetchingCoverItems$ | async; else coverItemsTmpl">
<div class="bg-white grid grid-flow-col gap-5 justify-center items-center h-40">
<shared-skeleton-loader class="h-16 w-12"></shared-skeleton-loader>
<shared-skeleton-loader class="h-16 w-12"></shared-skeleton-loader>
<shared-skeleton-loader class="h-16 w-12"></shared-skeleton-loader>
</div>
</ng-container>
<ng-template #coverItemsTmpl>
<page-pickup-shelf-details-covers
*ngIf="(coverOrderItems$ | async)?.length > 0"
[coverItems]="coverOrderItems$ | async"
[selectedOrderItem]="selectedItem$ | async"
(coverClick)="coverClick($event)"
></page-pickup-shelf-details-covers>
</ng-template>
</div>
<div class="page-pickup-shelf-in-details__action-wrapper">

View File

@@ -10,13 +10,15 @@ import { UiSpinnerModule } from '@ui/spinner';
import { OnInitDirective } from '@shared/directives/element-lifecycle';
import { PickupShelfInNavigationService } from '@shared/services';
import { BehaviorSubject, asapScheduler, combineLatest } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { DBHOrderItemListItemDTO, KeyValueDTOOfStringAndString } from '@swagger/oms';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { ActivatedRoute } from '@angular/router';
import { RunCheckTrigger } from '../../trigger';
import { PickUpShelfDetailsItemsGroupComponent } from '../../shared/pickup-shelf-details-items-group/pickup-shelf-details-items-group.component';
import { isEqual } from 'lodash';
import { SkeletonLoaderComponent } from '@shared/components/loader';
@Component({
selector: 'page-pickup-shelf-in-details',
@@ -37,6 +39,7 @@ import { PickUpShelfDetailsItemsGroupComponent } from '../../shared/pickup-shelf
PickupShelfAddToPreviousCompartmentCodeLabelPipe,
UiSpinnerModule,
OnInitDirective,
SkeletonLoaderComponent,
],
})
export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseComponent implements OnInit, AfterViewInit {
@@ -49,16 +52,16 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
order$ = this.store.order$;
orderItems$ = this.store.orderItems$.pipe(shareReplay());
orderItems$ = this.store.orderItems$.pipe(shareReplay(1));
noOrderItemsFound$ = this.store.noOrderItemsFound$;
fetching$ = this.store.fetchingOrder$;
fetchingOrder$ = this.store.fetchingOrder$;
fetchingItems$ = this.store.fetchingOrderItems$;
fetchingCoverItems$ = this.store.fetchingCoverOrderItems$;
viewFetching$ = combineLatest([this.fetching$, this.fetchingItems$, this.fetchingCoverItems$]).pipe(
map(([fetching, fetchingItems, fetchingCoverItems]) => fetching || fetchingItems || fetchingCoverItems)
viewFetching$ = combineLatest([this.fetchingItems$, this.orderItems$]).pipe(
map(([fetchingItems, orderItems]) => fetchingItems && orderItems.length === 0)
);
selectedCompartmentInfo = this.store.selectedCompartmentInfo;
@@ -102,7 +105,7 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
}
ngOnInit() {
combineLatest([this._activatedRoute.params, this.orderItems$])
combineLatest([this._activatedRoute.params.pipe(distinctUntilChanged(isEqual)), this.orderItems$.pipe(distinctUntilChanged(isEqual))])
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(([params, items]) => {
const orderItemSubsetId = +params?.orderItemSubsetId;
@@ -114,9 +117,15 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
if (!!selectedItem && this.store.selectPreviousSelectedOrderItemSubsetId !== orderItemSubsetId) {
this.store.setPreviousSelectedOrderItemSubsetId(orderItemSubsetId); // Wichtig das die ID im Store vorhanden bleibt um z.B. für die Filter eine zurücknavigation zu ermöglichen
this.store.selectOrderItem(selectedItem, true); // Wird automatisch unselected wenn die Details Seite verlassen wird
this.store.fetchCoverOrderItems();
}
});
// Fix #4696 - Always Fetch Cover Order Items
this._activatedRoute.params.pipe(distinctUntilChanged(isEqual), takeUntilDestroyed(this.destroyRef)).subscribe((_) => {
if (!this.store.coverOrderItems || this.store.coverOrderItems.length === 0) {
this.store.fetchCoverOrderItems();
}
});
}
ngAfterViewInit() {
@@ -141,11 +150,14 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
try {
this.changeActionLoader$.next(action.command);
this.store.setDisableHeaderStatusDropdown(true);
const context = await this.execAction({ action, latestCompartmentCode, latestCompartmentInfo });
if (!!context) {
if (action.command.includes('ARRIVED') || action.command.includes('PRINT_PRICEDIFFQRCODELABEL')) {
if (
action.command.includes('ARRIVED') ||
action.command.includes('PRINT_PRICEDIFFQRCODELABEL') ||
action.command.includes('BACKTOSTOCK')
) {
asapScheduler.schedule(async () => {
await this.navigateBasedOnCurrentView();
}, 100);
@@ -210,17 +222,17 @@ export class PickupShelfInDetailsComponent extends PickupShelfDetailsBaseCompone
updateDate({ date, type }: { date: Date; type?: 'delivery' | 'pickup' | 'preferred' }) {
switch (type) {
case 'delivery':
this.store.orderItems.forEach((item) =>
this.store.selectedOrderItems.forEach((item) =>
this.store.patchOrderItemSubset({ item, changes: { estimatedShippingDate: date.toISOString() } })
);
break;
case 'pickup':
this.store.orderItems.forEach((item) =>
this.store.selectedOrderItems.forEach((item) =>
this.store.patchOrderItemSubset({ item, changes: { compartmentStop: date.toISOString() } })
);
break;
case 'preferred':
this.store.orderItems.forEach((item) =>
this.store.selectedOrderItems.forEach((item) =>
this.store.patchOrderItemSubset({ item, changes: { preferredPickUpDate: date.toISOString() } })
);
break;

View File

@@ -1,5 +1,7 @@
<shared-goods-in-out-order-edit
*ngIf="store.orderItems$ | async; let items"
(navigation)="navigateToShelfInDetailsPage($event)"
[items]="items"
></shared-goods-in-out-order-edit>
<ng-container *ngIf="store.selectedOrderItem$ | async; let item">
<shared-goods-in-out-order-edit
*ngIf="item"
(navigation)="navigateToShelfInDetailsPage($event)"
[items]="[item]"
></shared-goods-in-out-order-edit>
</ng-container>

View File

@@ -20,15 +20,23 @@ export class PickupShelfInEditComponent extends PickupShelfDetailsBaseComponent
constructor() {
super();
this.listStore;
}
async navigateToShelfInDetailsPage(changes?: Partial<DBHOrderItemListItemDTO>) {
const orderId = (await this.store.orderItems$.pipe(first()).toPromise())?.find((_) => true)?.orderId;
const orderNumber = this.activatedRoute?.snapshot?.params?.orderNumber;
const compartmentCode = changes?.compartmentCode ?? this.activatedRoute?.snapshot?.params?.compartmentCode;
const orderNumber = decodeURIComponent(this.activatedRoute?.snapshot?.params?.orderNumber);
let compartmentCode = changes?.compartmentCode ?? this.activatedRoute?.snapshot?.params?.compartmentCode;
const processingStatus = changes?.processingStatus ?? this.activatedRoute.snapshot.params.orderItemProcessingStatus;
const compartmentInfo = changes?.compartmentInfo ?? this.activatedRoute.snapshot.params.compartmentInfo;
let compartmentInfo = changes?.compartmentInfo ?? this.activatedRoute.snapshot.params.compartmentInfo;
const item = this.store?.selectedOrderItem;
if (compartmentCode) {
compartmentCode = decodeURIComponent(compartmentCode);
}
if (compartmentInfo) {
compartmentInfo = decodeURIComponent(compartmentInfo);
}
await this.router.navigate(
this.shelfInNavigation.detailRoute({
@@ -38,7 +46,7 @@ export class PickupShelfInEditComponent extends PickupShelfDetailsBaseComponent
compartmentCode,
processingStatus,
compartmentInfo,
orderItemSubsetId: this.store?.selectPreviousSelectedOrderItemSubsetId,
orderItemSubsetId: item?.orderItemSubsetId,
},
side: this.side,
}).path,

View File

@@ -79,7 +79,7 @@ export class PickUpShelfInListComponent implements OnInit, AfterViewInit {
})
);
fetching$: Observable<boolean> = this.store.fetching$;
fetching$: Observable<boolean> = this.store.fetchingList$;
searchboxHint$ = this.store.searchboxHint$;
@@ -126,7 +126,7 @@ export class PickUpShelfInListComponent implements OnInit, AfterViewInit {
.subscribe(([_, queryParams]) => {
if (!this.store.list.length || !isEqual(queryParams, this.cleanupQueryParams(this.store.filter.getQueryParams()))) {
this.store.setQueryParams(queryParams);
this.store.fetchList();
this.store.fetchList({ emitFetchListResponse: false });
}
const scrollPos = this._getScrollPositionFromCache();

View File

@@ -13,7 +13,6 @@
<div class="flex flex-row px-12 justify-center desktop-large:px-0">
<shared-filter-input-group-main
class="block w-full mr-3 desktop-large:mx-auto"
*ngIf="filter$ | async; let filter"
[inputGroup]="filter?.input | group: 'main'"
[hint]="searchboxHint$ | async"
[loading]="fetching$ | async"

View File

@@ -38,7 +38,7 @@ export class PickUpShelfInMainSideViewComponent implements OnInit {
})
);
fetching$: Observable<boolean> = this.store.fetching$;
fetching$: Observable<boolean> = this.store.fetchingList$;
searchboxHint$ = this.store.searchboxHint$;

View File

@@ -16,6 +16,7 @@ import { map, take } from 'rxjs/operators';
import { ApplicationService } from '@core/application';
import { FilterAutocompleteProvider } from '@shared/components/filter';
import { PickUpShelfInAutocompleteProvider } from './providers/pickup-shelf-in-autocomplete.provider';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'page-pickup-shelf-in',
@@ -96,6 +97,10 @@ export class PickupShelfInComponent extends PickupShelfBaseComponent {
const order = await this.detailsStore.order$.pipe(take(1)).toPromise();
if (!order) {
return;
}
if (order?.orderNumber) {
return order?.orderNumber;
}
@@ -110,6 +115,12 @@ export class PickupShelfInComponent extends PickupShelfBaseComponent {
const orderItemSubsetId = this.detailsStore.orderItemSubsetId;
const compartmentInfo = this.detailsStore.compartmentInfo;
// Ticket #4692 - Wenn keine Order vorhanden ist, dann soll Breadcrumb nicht erstellt werden
// Dies kann z.B. passieren bei Datenbankproblemen wenn das Fetchen der Order sehr lange braucht
if (!order) {
return;
}
return this._pickupShelfInNavigationService.detailRoute({
item: {
orderId: order?.id,
@@ -205,4 +216,23 @@ export class PickupShelfInComponent extends PickupShelfBaseComponent {
async getNameForHistoryBreadcrumb(data: GetNameForBreadcrumbData): Promise<string> {
return 'Historie';
}
async regsiterFetchListResponseHandler() {
this.listStore.fetchListResponse$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(async ({ processId, queryParams, response }) => {
/**
* Wenn die Suche erfolgreich war, wird der Benutzer auf die Liste oder Detailseite des gefundenen Artikels weitergeleitet.
*/
const filterQueryParams = this.listStore.filter.getQueryParams();
if (response.hits === 1) {
const detailsPath = await this.getPathForDetail(response.result[0]).pipe(take(1)).toPromise();
await this.router.navigate(detailsPath.path, { queryParams: { ...queryParams, ...filterQueryParams, ...detailsPath.queryParams } });
} else if (response.hits > 1) {
const listPath = await this.getPathFoListBreadcrumb({ processId, queryParams });
await this.router.navigate(listPath.path, { queryParams: { ...queryParams, ...filterQueryParams, ...listPath.queryParams } });
} else {
await this.router.navigate([], { queryParams: { ...queryParams, ...filterQueryParams } });
}
});
}
}

View File

@@ -46,7 +46,9 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
order$ = this.store.order$;
groupedItems$: Observable<Array<{ type: string; items: DBHOrderItemListItemDTO[] }>> = this.store.orderItems$.pipe(
orderItems$ = this.store.orderItems$;
groupedItems$: Observable<Array<{ type: string; items: DBHOrderItemListItemDTO[] }>> = this.orderItems$.pipe(
map((items) => {
const groups: Array<{ type: string; items: DBHOrderItemListItemDTO[] }> = [];
@@ -66,10 +68,12 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
})
);
fetching$ = this.store.fetchingOrder$;
fetchingOrder$ = this.store.fetchingOrder$;
fetchingItems$ = this.store.fetchingOrderItems$;
viewFetching$ = combineLatest([this.fetching$, this.fetchingItems$]).pipe(map(([fetching, fetchingItems]) => fetching || fetchingItems));
viewFetching$ = combineLatest([this.orderItems$, this.fetchingItems$]).pipe(
map(([orderItems, fetchingItems]) => orderItems?.length === 0 && fetchingItems)
);
selectedCompartmentInfo = this.store.selectedCompartmentInfo;
@@ -84,7 +88,7 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
addToPreviousCompartmentAction$ = this.store.addToPreviousCompartmentAction$;
mainActions$ = this.store.mainActions$;
mainActions$ = this.store.mainShelfOutActions$;
trackByFnGroupDBHOrderItemListItemDTO = (index: number, group: { type: string; items: DBHOrderItemListItemDTO[] }) => group.type;
@@ -105,7 +109,11 @@ export class PickupShelfOutDetailsComponent extends PickupShelfDetailsBaseCompon
const context = await this.execAction({ action });
if (!!context) {
if (action.command.includes('ARRIVED') || action.command.includes('PRINT_PRICEDIFFQRCODELABEL')) {
if (
action.command.includes('ARRIVED') ||
action.command.includes('PRINT_PRICEDIFFQRCODELABEL') ||
action.command.includes('BACKTOSTOCK')
) {
const nav = this._pickupShelfOutNavigationService.defaultRoute({ processId: this.processId });
await this.router.navigate(nav.path, { queryParams: nav.queryParams, queryParamsHandling: 'preserve' });
} else {

View File

@@ -26,10 +26,18 @@ export class PickupShelfOutEditComponent extends PickupShelfDetailsBaseComponent
async navigateToShelfOutDetailsPage(changes: Partial<DBHOrderItemListItemDTO>) {
const orderId = (await this.store.orderItems$.pipe(first()).toPromise())?.find((_) => true)?.orderId;
const orderNumber = this.activatedRoute?.snapshot?.params?.orderNumber;
const compartmentCode = changes?.compartmentCode ?? this.activatedRoute?.snapshot?.params?.compartmentCode;
const orderNumber = decodeURIComponent(this.activatedRoute?.snapshot?.params?.orderNumber);
let compartmentCode = changes?.compartmentCode ?? this.activatedRoute?.snapshot?.params?.compartmentCode;
const processingStatus = changes?.processingStatus ?? this.activatedRoute.snapshot.params.orderItemProcessingStatus;
const compartmentInfo = changes?.compartmentInfo ?? this.activatedRoute.snapshot.params.compartmentInfo;
let compartmentInfo = changes?.compartmentInfo ?? this.activatedRoute.snapshot.params.compartmentInfo;
if (compartmentCode) {
compartmentCode = decodeURIComponent(compartmentCode);
}
if (compartmentInfo) {
compartmentInfo = decodeURIComponent(compartmentInfo);
}
await this.router.navigate(
this.shelfOutNavigation.detailRoute({

View File

@@ -83,7 +83,7 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
})
);
fetching$: Observable<boolean> = this.store.fetching$;
fetching$: Observable<boolean> = this.store.fetchingList$;
searchboxHint$ = this.store.searchboxHint$;
@@ -245,7 +245,7 @@ export class PickupShelfOutListComponent implements OnInit, AfterViewInit {
action,
items: selectedItems,
});
this.store.fetchList();
this.store.fetchList({ emitFetchListResponse: false });
this.store.resetSelectedListItems();
this.loadingFetchedActionButton$.next(false);

View File

@@ -16,7 +16,6 @@
<div class="flex flex-row px-12 justify-center desktop-large:px-0">
<shared-filter-input-group-main
class="block w-full mr-3 desktop-large:mx-auto"
*ngIf="filter$ | async; let filter"
[inputGroup]="filter?.input | group: 'main'"
[hint]="searchboxHint$ | async"
[loading]="fetching$ | async"

View File

@@ -38,7 +38,7 @@ export class PickupShelfOutMainSideViewComponent implements OnInit {
})
);
fetching$: Observable<boolean> = this.store.fetching$;
fetching$: Observable<boolean> = this.store.fetchingList$;
searchboxHint$ = this.store.searchboxHint$;

View File

@@ -15,6 +15,7 @@ import { ActionHandlerServices } from '@domain/oms';
import { ActionHandlerService } from '../services/action-handler.service';
import { FilterAutocompleteProvider } from '@shared/components/filter';
import { PickUpShelfOutAutocompleteProvider } from './providers/pickup-shelf-out-autocomplete.provider';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'page-pickup-shelf-out',
@@ -203,4 +204,33 @@ export class PickupShelfOutComponent extends PickupShelfBaseComponent {
async getNameForHistoryBreadcrumb(data: GetNameForBreadcrumbData): Promise<string> {
return 'Historie';
}
async regsiterFetchListResponseHandler() {
this.listStore.fetchListResponse$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(async ({ processId, queryParams, response }) => {
/**
* Wenn die Suche erfolgreich war, wird der Benutzer auf die Liste oder Detailseite des gefundenen Artikels weitergeleitet.
*/
const filterQueryParams = this.listStore.filter.getQueryParams();
if (response.hits === 1) {
const detailsPath = await this.getPathForDetail(response.result[0]).pipe(take(1)).toPromise();
await this.router.navigate(detailsPath.path, { queryParams: { ...queryParams, ...filterQueryParams, ...detailsPath.queryParams } });
} else if (response.hits > 1) {
const listPath = await this.getPathFoListBreadcrumb({ processId, queryParams });
await this.router.navigate(listPath.path, { queryParams: { ...queryParams, ...filterQueryParams, ...listPath.queryParams } });
} else {
await this.router.navigate([], { queryParams: { ...queryParams, ...filterQueryParams } });
}
});
}
// Ticket 4720 WA // Trefferliste wird übersprungen - Bei mehreren Bestellposten pro Bestellung soll IMMER auf Trefferliste navigiert werden
// Damit werden #4684 und #4688 überflüssig
// REMOVED: Fix Ticket #4684 Navigate on Details if items contain same OrderNumber
// private _hasSameOrderNumber(response: ListResponseArgsOfDBHOrderItemListItemDTO) {
// if (response.hits === 0) return false;
// const orderNumbers = new Set(response.result.map((item) => item.orderNumber));
// return orderNumbers.size === 1;
// }
}

View File

@@ -11,7 +11,7 @@ ui-slider {
@apply mr-5;
.image {
@apply relative border-none outline-none bg-transparent;
@apply relative border-none outline-none bg-transparent flex;
width: 53px;
height: 85px;
}
@@ -27,7 +27,7 @@ ui-slider {
.thumbnail {
@apply rounded-md shadow-lg;
height: 85px;
max-height: 5.3125rem;
}
.faded {

View File

@@ -1,6 +1,7 @@
<button
type="button"
class="px-2 py-3 bg-[#C6CBD0] rounded flex flex-row items-center open:bg-[#596470] open:text-white z-dropdown"
[disabled]="!(customer$ | async)"
class="page-pickup-shelf-details-header-nav-menu__nav-button px-2 py-3 bg-[#C6CBD0] rounded flex flex-row items-center open:bg-[#596470] open:text-white z-dropdown"
[cdkMenuTriggerFor]="navMenu"
#menuTrigger="cdkMenuTriggerFor"
[class.open]="menuTrigger.isOpen()"

View File

@@ -1,3 +1,7 @@
:host {
@apply inline-block;
}
.page-pickup-shelf-details-header-nav-menu__nav-button:disabled {
@apply cursor-not-allowed text-white;
}

View File

@@ -7,11 +7,12 @@ import { ComponentStore } from '@ngrx/component-store';
import { IconComponent } from '@shared/components/icon';
import { SharedMenuModule } from '@shared/components/menu';
import { CustomerSearchNavigation } from '@shared/services';
import { CustomerInfoDTO } from '@swagger/crm';
import { combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
export interface PickUpShelfDetailsHeaderNavMenuComponentState {
customerId?: number;
customer?: CustomerInfoDTO;
showCustomerDetails: boolean;
}
@@ -25,26 +26,26 @@ export interface PickUpShelfDetailsHeaderNavMenuComponentState {
imports: [CdkMenuModule, SharedMenuModule, IconComponent, RouterLink, NgIf, AsyncPipe],
})
export class PickUpShelfDetailsHeaderNavMenuComponent extends ComponentStore<PickUpShelfDetailsHeaderNavMenuComponentState> {
@Input() set customerId(value: NumberInput) {
this.patchState({ customerId: coerceNumberProperty(value) });
@Input() set customer(value: CustomerInfoDTO) {
this.patchState({ customer: value });
}
@Input() set showCustomerDetails(value: BooleanInput) {
this.patchState({ showCustomerDetails: coerceBooleanProperty(value) });
}
readonly customerId$ = this.select((state) => state.customerId);
readonly customer$ = this.select((state) => state.customer);
readonly showCustomerDetails$ = this.select((state) => state.showCustomerDetails);
ordersRoute$ = this.customerId$.pipe(
map((customerId) => customerId && this._navigation.ordersRoute({ processId: Date.now(), customerId }))
ordersRoute$ = this.customer$.pipe(
map((customer) => !!customer && this._navigation.ordersRoute({ processId: Date.now(), customerId: customer?.id, customer }))
);
customerDetailsRoute$ = combineLatest([this.showCustomerDetails$, this.customerId$]).pipe(
customerDetailsRoute$ = combineLatest([this.showCustomerDetails$, this.customer$]).pipe(
map(
([showCustomerDetails, customerId]) =>
showCustomerDetails && customerId && this._navigation.detailsRoute({ processId: Date.now(), customerId })
([showCustomerDetails, customer]) =>
showCustomerDetails && !!customer && this._navigation.detailsRoute({ processId: Date.now(), customerId: customer?.id, customer })
)
);

View File

@@ -1,11 +1,13 @@
<ng-container *ngIf="orderItem$ | async; let orderItem">
<div class="grid grid-flow-row gap-px-2">
<div class="bg-[#F5F7FA] flex flex-row justify-between items-center p-4 rounded-t">
<div class="grid grid-flow-col gap-[0.4375rem] items-center" *ngIf="features$ | async; let features; else: featureLoading">
<shared-icon *ngIf="features?.length > 0" [size]="24" icon="person"></shared-icon>
<div class="grid grid-flow-col gap-2 items-center font-bold text-p2" *ngFor="let feature of features">
{{ feature?.description }}
</div>
<div class="grid grid-flow-col gap-[0.4375rem] items-center" *ngIf="fetchingCustomerDone$ | async; else featureLoading">
<ng-container *ngIf="features$ | async; let features">
<shared-icon *ngIf="features?.length > 0" [size]="24" icon="person"></shared-icon>
<div class="grid grid-flow-col gap-2 items-center font-bold text-p2" *ngFor="let feature of features">
{{ feature?.description }}
</div>
</ng-container>
</div>
<button
@@ -18,12 +20,13 @@
</button>
</div>
<ng-template #featureLoading>
<shared-skeleton-loader class="w-64 h-6"></shared-skeleton-loader>
</ng-template>
<div class="page-pickup-shelf-details-header__details bg-white px-4 pt-4 pb-5">
<div class="flex flex-row items-center" [class.mb-8]="!orderItem?.features?.paid && !isKulturpass">
<page-pickup-shelf-details-header-nav-menu
class="mr-2"
[customerId]="customerId$ | async"
></page-pickup-shelf-details-header-nav-menu>
<page-pickup-shelf-details-header-nav-menu class="mr-2" [customer]="customer$ | async"></page-pickup-shelf-details-header-nav-menu>
<h2 class="page-pickup-shelf-details-header__details-header items-center">
<div class="text-h2">
{{ orderItem?.organisation }}
@@ -99,7 +102,12 @@
</div>
<div class="flex flex-row page-pickup-shelf-details-header__order-source" data-detail-id="Bestellkanal">
<div class="min-w-[9rem]">Bestellkanal</div>
<div class="flex flex-row font-bold">{{ order?.features?.orderSource }}</div>
<div class="flex flex-row font-bold">
<shared-skeleton-loader class="w-32" *ngIf="fetchingOrder$ | async; else orderSourceTmpl"></shared-skeleton-loader>
<ng-template #orderSourceTmpl>
{{ order()?.features?.orderSource }}
</ng-template>
</div>
</div>
<div
class="flex flex-row page-pickup-shelf-details-header__change-date justify-between"
@@ -140,21 +148,18 @@
data-detail-id="Benachrichtigung"
>
<div class="min-w-[9rem]">Benachrichtigung</div>
<div class="flex flex-row font-bold">{{ (notificationsChannel$ | async | notificationsChannel) || '-' }}</div>
<div class="flex flex-row font-bold">
<shared-skeleton-loader class="w-32" *ngIf="fetchingOrder$ | async; else notificationsChannelTpl"></shared-skeleton-loader>
<ng-template #notificationsChannelTpl>
{{ (notificationsChannel$ | async | notificationsChannel) || '-' }}
</ng-template>
</div>
</div>
</div>
</div>
</div>
</div>
<ng-template #featureLoading>
<div class="fetch-wrapper">
<div class="fetching"></div>
<div class="fetching"></div>
<div class="fetching"></div>
</div>
</ng-template>
<ng-template #abholfrist>
<div class="min-w-[9rem]">Abholfrist</div>
<div *ngIf="!(changeDateLoader$ | async)" class="flex flex-row font-bold">

View File

@@ -2,7 +2,7 @@ import { AsyncPipe, DatePipe, NgFor, NgIf, NgSwitch, NgSwitchCase, NgTemplateOut
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, inject } from '@angular/core';
import { DBHOrderItemListItemDTO, KeyValueDTOOfStringAndString, NotificationChannel, OrderDTO } from '@swagger/oms';
import { PickupShelfDetailsStore } from '../../store';
import { map, shareReplay, withLatestFrom } from 'rxjs/operators';
import { map, shareReplay, tap, withLatestFrom } from 'rxjs/operators';
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
import { DateAdapter, UiCommonModule } from '@ui/common';
import { IconModule } from '@shared/components/icon';
@@ -12,6 +12,8 @@ import { PickupShelfNotificationsChannelPipe } from '../pipes/notifications-chan
import { PickupShelfProcessingStatusPipe } from '../pipes/processing-status.pipe';
import { UiDatepickerModule } from '@ui/datepicker';
import { PickUpShelfDetailsHeaderNavMenuComponent } from '../pickup-shelf-details-header-nav-menu/pickup-shelf-details-header-nav-menu.component';
import { SkeletonLoaderComponent } from '@shared/components/loader';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({
selector: 'page-pickup-shelf-details-header',
@@ -36,6 +38,7 @@ import { PickUpShelfDetailsHeaderNavMenuComponent } from '../pickup-shelf-detail
UiCommonModule,
UiDatepickerModule,
PickUpShelfDetailsHeaderNavMenuComponent,
SkeletonLoaderComponent,
],
})
export class PickUpShelfDetailsHeaderComponent {
@@ -50,13 +53,11 @@ export class PickUpShelfDetailsHeaderComponent {
@Output()
updateDate = new EventEmitter<{ date: Date; type?: 'delivery' | 'pickup' | 'preferred' }>();
get order$(): Observable<OrderDTO> {
return this._store.order$;
}
fetchingOrder$ = this._store.fetchingOrder$;
get order(): OrderDTO {
return this._store.order;
}
order$ = this._store.order$;
order = toSignal(this.order$);
findLatestPreferredPickUpDate$: Observable<Date> = this.order$.pipe(
withLatestFrom(this._store.selectedOrderItemIds$),
@@ -86,13 +87,17 @@ export class PickUpShelfDetailsHeaderComponent {
processId?: number;
get isKulturpass() {
return this.order?.features?.orderSource === 'KulturPass';
return this.order()?.features?.orderSource === 'KulturPass';
}
minDateDatepicker = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), -1);
today = this.dateAdapter.today();
orderItem$ = this._store.orderItems$.pipe(map((orderItems) => orderItems?.find((_) => true)));
// Daten die im Header Angezeigt werden sollen
orderItem$ = combineLatest([
this._store.selectedOrderItem$, // Wenn man im Abholfach ist muss das ausgewählte OrderItem genommen werden
this._store.selectedOrderItems$.pipe(map((orderItems) => orderItems?.find((_) => true))), // Wenn man in der Warenausgabe ist muss man das erste OrderItem nehmen
]).pipe(map(([selectedOrderItem, selectedOrderItems]) => selectedOrderItem || selectedOrderItems));
changeDateLoader$ = new BehaviorSubject<boolean>(false);
changePreferredDateLoader$ = new BehaviorSubject<boolean>(false);
@@ -101,9 +106,11 @@ export class PickUpShelfDetailsHeaderComponent {
changeDateDisabled$ = this.changeStatusDisabled$;
customerId$ = this._store.customer$.pipe(map((customer) => customer?.id));
fetchingCustomerDone$ = this._store.fetchingCustomer$.pipe(map((fetchingCustomer) => !fetchingCustomer));
features$ = this._store.customer$.pipe(
customer$ = this._store.customer$;
features$ = this.customer$.pipe(
map((customer) => customer?.features || []),
map((features) => features.filter((f) => f.enabled && !!f.description)),
shareReplay()

View File

@@ -22,7 +22,7 @@ button {
.page-pickup-shelf-details-item__thumbnail {
img {
@apply rounded shadow-cta w-[3.625rem] h-[5.9375rem];
@apply rounded shadow-cta w-[3.625rem] max-h-[5.9375rem];
}
}

View File

@@ -77,7 +77,10 @@ export class PickUpShelfDetailsItemComponent extends ComponentStore<PickUpShelfD
set orderItem(orderItem: DBHOrderItemListItemDTO) {
if (!isEqual(this.orderItem, orderItem)) {
// Remove Prev OrderItem from selected list
this._store.selectOrderItem(this.orderItem, false);
if (this.orderItem?.orderItemSubsetId !== orderItem?.orderItemSubsetId) {
this._store.selectOrderItem(this.orderItem, false);
}
this.patchState({ orderItem, quantity: orderItem?.quantity, more: false });
this.specialCommentControl.reset(orderItem?.specialComment);
// Add New OrderItem to selected list if selected was set to true by its input

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