Merged PR 1555: #4074 Implemented Changes from ISA-Integration to ISA-Test

#4074 Implemented Changes from ISA-Integration to ISA-Test
This commit is contained in:
Nino Righi
2023-06-07 13:30:14 +00:00
committed by Andreas Schickinger
parent 74bf2133c6
commit d86f595b1f
7 changed files with 212 additions and 25 deletions

View File

@@ -47,3 +47,10 @@
.fancy-checkbox:checked::after {
@apply opacity-100;
}
::ng-deep shared-purchase-options-list-item ui-select .ui-input-wrapper {
@apply h-full;
}
::ng-deep shared-purchase-options-list-item ui-select .ui-select-toggle {
@apply ml-2;
}

View File

@@ -64,8 +64,46 @@
</div>
</div>
<div class="shared-purchase-options-list-item__price text-right ml-4 flex flex-col items-end">
<div class="shared-purchase-options-list-item__price-value font-bold text-xl" *ngIf="!(canEditPrice$ | async)">
{{ priceValue$ | async | currency: 'EUR':'code' }}
<div
class="shared-purchase-options-list-item__price-value font-bold text-xl flex flex-row items-center"
*ngIf="!(canEditPrice$ | async)"
>
<ng-container *ngIf="!(setManualPrice$ | async); else setManualPrice">
{{ priceValue$ | async | currency: 'EUR':'code' }}
</ng-container>
<ng-template #setManualPrice>
<div class="relative flex flex-row items-start">
<ui-select
class="w-[6.5rem] min-h-[3.4375rem] p-4 rounded border border-solid border-[#AEB7C1] mr-4"
tabindex="-1"
[formControl]="manualVatFormControl"
[defaultLabel]="'MwSt'"
>
<ui-select-option *ngFor="let vat of vats$ | async" [label]="vat.name + '%'" [value]="vat.vatType"></ui-select-option>
</ui-select>
<shared-input-control [class.ml-8]="manualPriceFormControl?.invalid && manualPriceFormControl?.dirty">
<shared-input-control-indicator>
<ui-svg-icon *ngIf="manualPriceFormControl?.invalid && manualPriceFormControl?.dirty" icon="mat-info"></ui-svg-icon>
</shared-input-control-indicator>
<input
triggerOn="init"
#quantityInput
sharedInputControlInput
type="string"
class="w-24"
[formControl]="manualPriceFormControl"
placeholder="00,00"
(sharedOnInit)="quantityInput.focus()"
sharedNumberValue
/>
<shared-input-control-suffix>EUR</shared-input-control-suffix>
<shared-input-control-error error="required">Preis ist ungültig</shared-input-control-error>
<shared-input-control-error error="pattern">Preis ist ungültig</shared-input-control-error>
<shared-input-control-error error="max">Preis ist ungültig</shared-input-control-error>
</shared-input-control>
</div>
</ng-template>
</div>
<div class="shared-purchase-options-list-item__price-value font-bold text-xl" *ngIf="canEditPrice$ | async">
<div class="relative flex flex-col">

View File

@@ -21,9 +21,11 @@ import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
import { UiSpinnerModule } from '@ui/spinner';
import { UiTooltipModule } from '@ui/tooltip';
import { combineLatest, ReplaySubject, Subscription } from 'rxjs';
import { map, startWith, switchMap } from 'rxjs/operators';
import { map, take, shareReplay, startWith, switchMap, withLatestFrom } from 'rxjs/operators';
import { GIFT_CARD_MAX_PRICE, PRICE_PATTERN } from '../constants';
import { Item, PurchaseOptionsStore } from '../store';
import { UiSelectModule } from '@ui/select';
import { KeyValueDTOOfStringAndString } from '@swagger/cat';
@Component({
selector: 'scale-content, [scaleContent]',
@@ -78,6 +80,7 @@ export class ScaleContentComponent implements AfterContentInit {
imports: [
CommonModule,
UiQuantityDropdownModule,
UiSelectModule,
ProductImageModule,
UiIconModule,
UiSpinnerModule,
@@ -108,7 +111,15 @@ export class PurchaseOptionsListItemComponent implements OnInit, OnDestroy, OnCh
quantityFormControl = new FormControl<number>(null);
priceFormControl = new FormControl<string>(null, [Validators.required, Validators.min(1), Validators.max(GIFT_CARD_MAX_PRICE)]);
priceFormControl = new FormControl<string>(null, [
Validators.required,
Validators.min(1),
Validators.max(GIFT_CARD_MAX_PRICE),
Validators.pattern(PRICE_PATTERN),
]);
manualPriceFormControl = new FormControl<string>(null, [Validators.required, Validators.max(999.99), Validators.pattern(PRICE_PATTERN)]);
manualVatFormControl = new FormControl<string>('', [Validators.required]);
selectedFormControl = new FormControl<boolean>(false);
@@ -123,7 +134,25 @@ export class PurchaseOptionsListItemComponent implements OnInit, OnDestroy, OnCh
priceValue$ = this.price$.pipe(map((price) => price?.value?.value));
priceVat$ = this.price$.pipe(map((price) => price?.vat?.value));
// Ticket #4074 analog zu Ticket #2244
// take(2) um die Response des Katalogpreises und danach um die Response der OLAs abzuwarten
// Logik gilt ausschließlich für Archivartikel
setManualPrice$ = this.price$.pipe(
take(2),
map((price) => {
// Logik nur beim Hinzufügen über Kaufoptionen, da über Ändern im Warenkorb die Info fehlt ob das jeweilige ShoppingCartItem ein Archivartikel ist oder nicht
const features = this.item?.features as KeyValueDTOOfStringAndString[];
if (!!features && Array.isArray(features)) {
const isArchive = !!features?.find((feature) => feature?.enabled === true && feature?.key === 'ARC') ?? false;
return isArchive ? !price?.value?.value || price?.vat === undefined : false;
}
return false;
})
);
vats$ = this._store.vats$.pipe(shareReplay());
priceVat$ = this.price$.pipe(map((price) => price?.vat?.vatType));
canEditPrice$ = this.item$.pipe(switchMap((item) => this._store.getCanEditPrice$(item.id)));
@@ -174,7 +203,6 @@ export class PurchaseOptionsListItemComponent implements OnInit, OnDestroy, OnCh
if (PRICE_PATTERN.test(value)) {
return parseFloat(value.replace(',', '.'));
}
this.priceFormControl.setErrors({ pattern: true });
}
stringifyPrice(value: number) {
@@ -193,6 +221,7 @@ export class PurchaseOptionsListItemComponent implements OnInit, OnDestroy, OnCh
this.initQuantitySubscription();
this.initPriceSubscription();
this.initSelectedSubscription();
this.initManualPriceSubscriptions();
}
ngOnChanges({ item }: SimpleChanges) {
@@ -206,6 +235,19 @@ export class PurchaseOptionsListItemComponent implements OnInit, OnDestroy, OnCh
this._subscriptions.unsubscribe();
}
// Ticket #4074 analog zu Ticket #2244
// Logik gilt ausschließlich für Archivartikel und über die Kaufoptionen. Nicht über den Warenkorb
initManualPriceSubscriptions() {
const features = this.item?.features as KeyValueDTOOfStringAndString[];
if (!!features && Array.isArray(features)) {
const isArchive = !!features?.find((feature) => feature?.enabled === true && feature?.key === 'ARC') ?? false;
if (isArchive) {
this.initManualPriceSubscription();
this.initManualVatSubscription();
}
}
}
initQuantitySubscription() {
const sub = this.item$.subscribe((item) => {
if (this.quantityFormControl.value !== item.quantity) {
@@ -250,6 +292,51 @@ export class PurchaseOptionsListItemComponent implements OnInit, OnDestroy, OnCh
this._subscriptions.add(valueChangesSub);
}
initManualPriceSubscription() {
const sub = this.price$.subscribe((price) => {
const priceStr = this.stringifyPrice(price?.value?.value);
if (priceStr === '') return;
if (this.parsePrice(this.manualPriceFormControl.value) !== price?.value?.value) {
this.manualPriceFormControl.setValue(priceStr);
}
});
const valueChangesSub = this.manualPriceFormControl.valueChanges.subscribe((value) => {
const price = this._store.getPrice(this.item.id);
const parsedPrice = this.parsePrice(value);
if (!parsedPrice) {
this._store.setPrice(this.item.id, null);
return;
}
if (price[this.item.id] !== parsedPrice) {
this._store.setPrice(this.item.id, this.parsePrice(value));
}
});
this._subscriptions.add(sub);
this._subscriptions.add(valueChangesSub);
}
initManualVatSubscription() {
const valueChangesSub = this.manualVatFormControl.valueChanges.pipe(withLatestFrom(this.vats$)).subscribe(([formVatType, vats]) => {
const price = this._store.getPrice(this.item.id);
const vat = vats.find((vat) => vat?.vatType === Number(formVatType));
if (!vat) {
this._store.setVat(this.item.id, null);
return;
}
if (price[this.item.id]?.vat?.vatType !== vat?.vatType) {
this._store.setVat(this.item.id, vat);
}
});
this._subscriptions.add(valueChangesSub);
}
initSelectedSubscription() {
const sub = this.item$
.pipe(switchMap((item) => this._store.selectedItemIds$.pipe(map((ids) => ids.includes(item.id)))))

View File

@@ -3,15 +3,6 @@
Wie möchten Sie die Artikel erhalten?
</p>
<div class="rounded p-4 shadow-card mt-4 grid grid-flow-col gap-4 justify-center items-center relative">
<!-- <ng-container *ngFor="let option of purchasingOptions$ | async">
<ng-container [ngSwitch]="option">
<app-delivery-purchase-options-tile *ngSwitchCase="'delivery'"> </app-delivery-purchase-options-tile>
<app-in-store-purchase-options-tile *ngSwitchCase="'in-store'"> </app-in-store-purchase-options-tile>
<app-pickup-purchase-options-tile *ngSwitchCase="'pickup'"> </app-pickup-purchase-options-tile>
<app-download-purchase-options-tile *ngSwitchCase="'download'"> </app-download-purchase-options-tile>
</ng-container>
</ng-container> -->
<ng-container *ngIf="!(isDownloadOnly$ | async)">
<ng-container *ngIf="!(isGiftCardOnly$ | async)">
<app-in-store-purchase-options-tile> </app-in-store-purchase-options-tile>
@@ -32,13 +23,18 @@
</div>
<div class="text-center -mx-4 border-t border-gray-200 p-4 border-solid">
<ng-container *ngIf="type === 'add'">
<button type="button" class="isa-cta-button" [disabled]="!(canContinue$ | async) || saving" (click)="save('continue-shopping')">
<button
type="button"
class="isa-cta-button"
[disabled]="!(canContinue$ | async) || saving || !(hasPrice$ | async)"
(click)="save('continue-shopping')"
>
Weiter einkaufen
</button>
<button
type="button"
class="ml-4 isa-cta-button isa-button-primary"
[disabled]="!(canContinue$ | async) || saving"
[disabled]="!(canContinue$ | async) || saving || !(hasPrice$ | async)"
(click)="save('continue')"
>
Fortfahren
@@ -48,7 +44,7 @@
<button
type="button"
class="ml-4 isa-cta-button isa-button-primary"
[disabled]="!(canContinue$ | async) || saving"
[disabled]="!(canContinue$ | async) || saving || !(hasPrice$ | async)"
(click)="save('continue')"
>
Fortfahren

View File

@@ -5,7 +5,7 @@ import { PurchaseOptionsModalData } from './purchase-options-modal.data';
import { PurchaseOptionsListHeaderComponent } from './purchase-options-list-header';
import { PurchaseOptionsListItemComponent } from './purchase-options-list-item';
import { CommonModule } from '@angular/common';
import { Subject } from 'rxjs';
import { Subject, zip } from 'rxjs';
import {
DeliveryPurchaseOptionTileComponent,
DownloadPurchaseOptionTileComponent,
@@ -13,7 +13,8 @@ import {
PickupPurchaseOptionTileComponent,
} from './purchase-options-tile';
import { isGiftCard, Item, PurchaseOptionsStore } from './store';
import { delay, map, shareReplay, skip, takeUntil } from 'rxjs/operators';
import { delay, map, shareReplay, skip, switchMap, takeUntil } from 'rxjs/operators';
import { KeyValueDTOOfStringAndString } from '@swagger/cat';
@Component({
selector: 'shared-purchase-options-modal',
@@ -44,6 +45,39 @@ export class PurchaseOptionsModalComponent implements OnInit, OnDestroy {
items$ = this.store.items$;
hasPrice$ = this.items$.pipe(
switchMap((items) =>
items.map((item) => {
let isArchive = false;
const features = item?.features as KeyValueDTOOfStringAndString[];
// Ticket #4074 analog zu Ticket #2244
// Ob Archivartikel kann nur über Kaufoptionen herausgefunden werden, nicht über Ändern im Warenkorb da am ShoppingCartItem das Archivartikel Feature fehlt
if (!!features && Array.isArray(features)) {
isArchive = !!features?.find((feature) => feature?.enabled === true && feature?.key === 'ARC') ?? false;
}
return zip(
this.store
?.getPrice$(item?.id)
.pipe(
map((price) =>
isArchive
? !!price?.value?.value &&
price?.vat !== undefined &&
price?.vat?.vatType !== undefined &&
price?.vat?.value !== undefined
: !!price?.value?.value
)
)
);
})
),
switchMap((hasPrices) => hasPrices),
map((hasPrices) => {
const containsItemWithNoPrice = hasPrices?.filter((hasPrice) => hasPrice === false) ?? [];
return containsItemWithNoPrice?.length === 0;
})
);
purchasingOptions$ = this.store.getPurchaseOptionsInAvailabilities$;
isDownloadOnly$ = this.purchasingOptions$.pipe(

View File

@@ -11,21 +11,27 @@ import {
UpdateShoppingCartItemDTO,
} from '@swagger/checkout';
import { Observable } from 'rxjs';
import { map, shareReplay, switchMap, take, tap } from 'rxjs/operators';
import { map, shareReplay, take } from 'rxjs/operators';
import { Branch, ItemData } from './purchase-options.types';
import { memorize } from '@utils/common';
import { AuthService } from '@core/auth';
import { ApplicationService } from '@core/application';
import { DomainOmsService } from '@domain/oms';
@Injectable({ providedIn: 'root' })
export class PurchaseOptionsService {
constructor(
private _availabilityService: DomainAvailabilityService,
private _checkoutService: DomainCheckoutService,
private _omsService: DomainOmsService,
private _auth: AuthService,
private _app: ApplicationService
) {}
getVats$() {
return this._omsService.getVATs();
}
getSelectedBranchForProcess(processId: number): Observable<Branch> {
return this._app.getSelectedBranch$(processId).pipe(take(1), shareReplay(1));
}

View File

@@ -26,9 +26,10 @@ import {
} from './purchase-options.helpers';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { DEFAULT_PRICE_DTO, DEFAULT_PRICE_VALUE } from '../constants';
import { DEFAULT_PRICE_DTO, DEFAULT_PRICE_VALUE, DEFAULT_VAT_VALUE } from '../constants';
import { AddToShoppingCartDTO, EntityDTOContainerOfDestinationDTO, UpdateShoppingCartItemDTO } from '@swagger/checkout';
import { isEqual, uniqueId } from 'lodash';
import { uniqueId } from 'lodash';
import { VATDTO } from '@swagger/oms';
@Injectable()
export class PurchaseOptionsStore extends ComponentStore<PurchaseOptionsState> {
@@ -128,6 +129,10 @@ export class PurchaseOptionsStore extends ComponentStore<PurchaseOptionsState> {
fetchingAvailabilities$ = this.select(Selectors.getFetchingAvailabilities);
get vats$() {
return this._service.getVats$();
}
constructor(private _service: PurchaseOptionsService) {
super({
defaultBranch: undefined,
@@ -641,9 +646,23 @@ export class PurchaseOptionsStore extends ComponentStore<PurchaseOptionsState> {
let price = prices[itemId];
if (price?.value?.value !== value) {
if (!price) {
price = { ...DEFAULT_PRICE_DTO, value: { ...DEFAULT_PRICE_VALUE, value } };
price = { ...DEFAULT_PRICE_DTO, value: { ...DEFAULT_PRICE_VALUE, value }, vat: { ...DEFAULT_VAT_VALUE, ...price?.vat } };
} else {
price = { ...price, value: { ...price.value, value } };
price = { ...price, value: { ...price.value, value }, vat: price?.vat };
}
this.patchState({ prices: { ...prices, [itemId]: price } });
}
}
setVat(itemId: number, vat: VATDTO) {
const prices = this.prices;
let price = prices[itemId];
if (price?.vat?.vatType !== vat?.vatType) {
if (!price) {
price = { ...DEFAULT_PRICE_DTO, value: { ...DEFAULT_PRICE_VALUE, ...price?.value }, vat: { ...DEFAULT_VAT_VALUE, ...vat } };
} else {
price = { ...price, value: price.value, vat: { ...price.vat, ...vat } };
}
this.patchState({ prices: { ...prices, [itemId]: price } });