Merged PR 918: #2244 WK Preis kann bei Archivartikeln vergeben werden

#2244 WK Preis kann bei Archivartikeln vergeben werden

Related work items: #2244
This commit is contained in:
Andreas Schickinger
2021-10-20 15:28:49 +00:00
committed by Lorenz Hilpert
parent 0115e235c2
commit 27a9d446ef
11 changed files with 174 additions and 13 deletions

View File

@@ -63,7 +63,7 @@
<div class="price">
{{ item.catalogAvailability?.price?.value?.value | currency: item.catalogAvailability?.price?.value?.currency:'code' }}
</div>
<div>{{ store.promotionPoints$ | async }} Lesepunkte</div>
<div *ngIf="store.promotionPoints$ | async; let promotionPoints">{{ promotionPoints }} Lesepunkte</div>
</div>
</div>

View File

@@ -0,0 +1,11 @@
<form *ngIf="control" [formGroup]="control">
<ui-form-control label="MwSt" variant="default">
<ui-select formControlName="vat">
<ui-select-option *ngFor="let vat of vats$ | async" [label]="vat.name + '%'" [value]="vat.vatType"></ui-select-option>
</ui-select>
</ui-form-control>
<ui-form-control class="price" label="Preis" variant="default">
<input uiInput formControlName="price" />
</ui-form-control>
</form>

View File

@@ -0,0 +1,11 @@
form {
@apply grid grid-flow-col items-center justify-end gap-4 mb-2;
}
ui-form-control {
@apply w-32;
}
::ng-deep page-purchasing-options-modal-price-input ui-form-control .input-wrapper input {
@apply w-20;
}

View File

@@ -0,0 +1,47 @@
import { ChangeDetectionStrategy, Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DomainOmsService } from '@domain/oms';
import { VATDTO } from '@swagger/oms';
import { Observable, Subscription } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
@Component({
selector: 'page-purchasing-options-modal-price-input',
templateUrl: 'purchasing-options-modal-price-input.component.html',
styleUrls: ['purchasing-options-modal-price-input.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PurchasingOptionsModalPriceInputComponent implements OnInit {
control: FormGroup;
vats$: Observable<VATDTO[]> = this._omsService.getVATs().pipe(shareReplay());
@Output()
priceChanged = new EventEmitter<number>();
@Output()
vatChanged = new EventEmitter<VATDTO>();
private _subscriptions = new Subscription();
constructor(private _omsService: DomainOmsService, private _fb: FormBuilder) {}
ngOnInit() {
this.initForm();
}
initForm() {
const fb = this._fb;
this.control = fb.group({
price: fb.control(undefined, [Validators.required, Validators.pattern(/^\d+([\,]\d{1,2})?$/), Validators.max(99999)]),
vat: fb.control(undefined, [Validators.required]),
});
this._subscriptions.add(
this.control.get('price').valueChanges.subscribe((price) => this.priceChanged.emit(Number(String(price).replace(',', '.'))))
);
this._subscriptions.add(this.control.get('vat').valueChanges.subscribe(this.vatChanged));
this.control.markAllAsTouched();
}
}

View File

@@ -0,0 +1,16 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { UiFormControlModule } from '@ui/form-control';
import { UiInputModule } from '@ui/input';
import { UiSelectModule } from '@ui/select';
import { PurchasingOptionsModalPriceInputComponent } from './purchasing-options-modal-price-input.component';
@NgModule({
imports: [CommonModule, UiFormControlModule, UiInputModule, UiSelectModule, FormsModule, ReactiveFormsModule],
exports: [PurchasingOptionsModalPriceInputComponent],
declarations: [PurchasingOptionsModalPriceInputComponent],
providers: [],
})
export class PurchasingOptionsModalPriceInputModule {}

View File

@@ -38,7 +38,7 @@
<div class="grow"></div>
<div class="format">{{ item?.product?.formatDetail }}</div>
<div class="price">
{{ item?.catalogAvailability?.price?.value?.value | currency: item?.catalogAvailability?.price?.value?.currency:'code' }}
{{ item?.catalogAvailability?.price?.value?.value | currency: item?.catalogAvailability?.price?.value?.currency || 'EUR':'code' }}
</div>
<div class="date" *ngIf="option$ | async; let option">
<ng-container *ngIf="option === 'pick-up'">Abholung ab</ng-container>
@@ -60,12 +60,21 @@
<div class="quantity-error" *ngIf="quantityError$ | async; let message">{{ message }}</div>
</div>
</div>
<div class="custom-price" *ngIf="showCustomPrice$ | async">
<page-purchasing-options-modal-price-input
(priceChanged)="changeCustomPrice($event)"
(vatChanged)="changeCustomVat($event)"
></page-purchasing-options-modal-price-input>
</div>
<hr />
<div class="summary-row" *ngIf="quantity$ | async; let quantity">
<div class="reading-points">{{ quantity }} Artikel | {{ promoPoints$ | async }} Lesepunkte</div>
<div class="reading-points">
{{ quantity }} Artikel
<ng-container *ngIf="promoPoints$ | async; let promoPoints"> | {{ promoPoints }} Lesepunkte </ng-container>
</div>
<div class="subtotal">
Zwischensumme
{{ item?.catalogAvailability?.price?.value?.value * quantity | currency: item?.catalogAvailability?.price?.value?.currency:'code' }}
{{ (price$ | async) * quantity | currency: item?.catalogAvailability?.price?.value?.currency || 'EUR':'code' }}
<div class="shipping-cost" *ngIf="showDeliveryInfo$ | async">
ohne Versandkosten
</div>
@@ -76,7 +85,7 @@
<button
*ngIf="canContinueShopping$ | async"
class="cta-continue-shopping"
[disabled]="(fetching$ | async) || (canContinueShopping$ | async) === false"
[disabled]="(fetching$ | async) || (canContinueShopping$ | async) === false || (customPriceInvalid$ | async) === true"
(click)="continue('continue-shopping')"
>
Weiter einkaufen
@@ -86,7 +95,7 @@
</button>
<button
*ngIf="showTakeAwayButton$ | async"
[disabled]="(fetching$ | async) || (canAdd$ | async) === false"
[disabled]="(fetching$ | async) || (canAdd$ | async) === false || (customPriceInvalid$ | async) === true"
class="cta-continue"
(click)="continue()"
>
@@ -96,7 +105,7 @@
*ngIf="showDefaultContinueButton$ | async"
class="cta-continue"
(click)="continue()"
[disabled]="(fetching$ | async) || (canAdd$ | async) === false"
[disabled]="(fetching$ | async) || (canAdd$ | async) === false || (customPriceInvalid$ | async) === true"
>
Fortfahren
</button>

View File

@@ -88,6 +88,10 @@ img.thumbnail {
::ng-deep.spin {
@apply text-brand;
}
&:disabled {
@apply text-inactive-branch border-inactive-branch;
}
}
.cta-continue,

View File

@@ -2,9 +2,9 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { DomainCheckoutService } from '@domain/checkout';
import { AddToShoppingCartDTO, AvailabilityDTO } from '@swagger/checkout';
import { AddToShoppingCartDTO, AvailabilityDTO, VATType } from '@swagger/checkout';
import { UiModalRef } from '@ui/modal';
import { first, map, switchMap } from 'rxjs/operators';
import { debounceTime, first, map, switchMap } from 'rxjs/operators';
import { combineLatest, Observable } from 'rxjs';
import { PurchasingOptionsModalData } from './purchasing-options-modal.data';
import { PurchasingOptions, PurchasingOptionsModalStore } from './purchasing-options-modal.store';
@@ -45,6 +45,14 @@ export class PurchasingOptionsModalComponent {
readonly quantityError$ = this.purchasingOptionsModalStore.selectQuantityError;
readonly showCustomPrice$ = this.item$.pipe(map((item) => !item?.catalogAvailability?.price?.value?.value));
readonly customPriceInvalid$ = combineLatest([
this.showCustomPrice$,
this.purchasingOptionsModalStore.selectCustomPrice,
this.purchasingOptionsModalStore.selectCustomVat,
]).pipe(map(([showCustomPrice, customPrice, customVat]) => showCustomPrice && (!customPrice || !customVat)));
readonly showTakeAwayButton$ = combineLatest([
this.option$,
this.purchasingOptionsModalStore.selectFetchingAvailability,
@@ -105,15 +113,24 @@ export class PurchasingOptionsModalComponent {
map((buyer) => buyer.source)
);
readonly promoPoints$ = combineLatest([this.item$, this.quantity$]).pipe(
switchMap(([item, quantity]) =>
price$ = combineLatest([this.item$, this.purchasingOptionsModalStore.selectCustomPrice]).pipe(
map(([item, customPrice]) => item?.catalogAvailability?.price?.value?.value ?? customPrice ?? 0)
);
vat$ = combineLatest([this.item$, this.purchasingOptionsModalStore.selectCustomVat]).pipe(
map(([item, customVat]) => item?.catalogAvailability?.price.vat ?? customVat)
);
readonly promoPoints$ = combineLatest([this.item$, this.quantity$, this.price$]).pipe(
debounceTime(250),
switchMap(([item, quantity, price]) =>
this.domainCatalogService
.getPromotionPoints({
items: [
{
id: item.id,
quantity: quantity,
price: item.catalogAvailability.price?.value?.value,
price: price,
},
],
})
@@ -145,6 +162,14 @@ export class PurchasingOptionsModalComponent {
}
}
changeCustomVat(vat: VATType) {
this.purchasingOptionsModalStore.setCustomVat(vat);
}
changeCustomPrice(price: number) {
this.purchasingOptionsModalStore.setCustomPrice(price);
}
backToSetOptions() {
this.purchasingOptionsModalStore.setOption(undefined);
}
@@ -173,6 +198,8 @@ export class PurchasingOptionsModalComponent {
const branch = await this.branch$.pipe(first()).toPromise();
const shoppingCartItem = await this.purchasingOptionsModalStore.selectShoppingCartItem.pipe(first()).toPromise();
const canAdd = await this.canAdd$.pipe(first()).toPromise();
const customPrice = await this.purchasingOptionsModalStore.selectCustomPrice.pipe(first()).toPromise();
const customVat = await this.purchasingOptionsModalStore.selectCustomVat.pipe(first()).toPromise();
if (canAdd || navigate === 'add-customer-data') {
const newItem: AddToShoppingCartDTO = {
@@ -187,6 +214,18 @@ export class PurchasingOptionsModalComponent {
newItem.product.catalogProductNumber = String(item.id);
if (!!customPrice && !!customVat) {
newItem.availability.price = {
value: {
value: customPrice,
currency: 'EUR',
},
vat: {
vatType: customVat,
},
};
}
switch (option) {
case 'take-away':
case 'pick-up':

View File

@@ -21,6 +21,7 @@ import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
import { KeyNavigationModule } from '../../shared/key-navigation/key-navigation.module';
import { RouterModule } from '@angular/router';
import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
import { PurchasingOptionsModalPriceInputModule } from './price-input/purchasing-options-modal-price-input.module';
@NgModule({
imports: [
@@ -34,6 +35,7 @@ import { UiQuantityDropdownModule } from '@ui/quantity-dropdown';
UiSpinnerModule,
KeyNavigationModule,
RouterModule,
PurchasingOptionsModalPriceInputModule,
],
exports: [PurchasingOptionsModalComponent],
declarations: [

View File

@@ -5,7 +5,7 @@ import { DomainCheckoutService } from '@domain/checkout';
import { CrmCustomerService } from '@domain/crm';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { ItemDTO } from '@swagger/cat';
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, ShoppingCartItemDTO } from '@swagger/checkout';
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, ShoppingCartItemDTO, VATType } from '@swagger/checkout';
import { isBoolean, isNullOrUndefined, isString } from '@utils/common';
import { NEVER, Observable } from 'rxjs';
import { delay, filter, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
@@ -32,6 +32,8 @@ interface PurchasingOptionsModalState {
// TODO: FilterBranch in der UI Component sortieren und filtern
filterResult?: BranchDTO[];
availabilities: { [key: string]: AvailabilityDTO };
customPrice?: number;
customVat?: VATType;
}
@Injectable()
@@ -67,6 +69,10 @@ export class PurchasingOptionsModalStore extends ComponentStore<PurchasingOption
readonly selectQuantity = this.select((s) => s.quantity);
readonly selectCustomPrice = this.select((s) => s.customPrice);
readonly selectCustomVat = this.select((s) => s.customVat);
readonly selectAvailabilities = this.select((s) => s.availabilities);
readonly selectAvailability = this.select((s) => s.availabilities[s.option]);
@@ -266,6 +272,20 @@ export class PurchasingOptionsModalStore extends ComponentStore<PurchasingOption
};
});
readonly setCustomPrice = this.updater((state, customPrice: number) => {
return {
...state,
customPrice,
};
});
readonly setCustomVat = this.updater((state, customVat: VATType) => {
return {
...state,
customVat,
};
});
readonly setAvailableOptions = this.updater((state, availableOptions: PurchasingOptions[]) => {
let option = state.option;
if (availableOptions?.length === 1 && availableOptions[0] === 'download') {

View File

@@ -11,6 +11,8 @@ export class UiFormControlFirstErrorPipe implements PipeTransform {
switch (error) {
case 'min':
return `${label} wird benötigt`; // gender validation for create (upgrade) online customer with gender min value of 2
case 'max':
return `${label} ist ungültig`;
case 'required':
return `${label} wird benötigt`;
case 'email':