| @@ -45,6 +45,7 @@ import { NewTaskNoteComponent } from './tasks/new-task-note/new-task-note.compon | |||||
| import { DocumentsDetailComponent } from './documents/documents-detail/documents-detail.component'; | import { DocumentsDetailComponent } from './documents/documents-detail/documents-detail.component'; | ||||
| import { SalesComponent } from './sales/sales.component'; | import { SalesComponent } from './sales/sales.component'; | ||||
| import { SalesDetailComponent } from './sales/sales-detail/sales-detail.component'; | import { SalesDetailComponent } from './sales/sales-detail/sales-detail.component'; | ||||
| import { NewSaleComponent } from './sales/new-sale/new-sale.component'; | |||||
| export function apiConfigFactory(): Configuration { | export function apiConfigFactory(): Configuration { | ||||
| const params: ConfigurationParameters = { | const params: ConfigurationParameters = { | ||||
| @@ -110,7 +111,8 @@ export function HttpLoaderFactory(http: HttpClient) { | |||||
| NewTaskNoteComponent, | NewTaskNoteComponent, | ||||
| DocumentsDetailComponent, | DocumentsDetailComponent, | ||||
| SalesComponent, | SalesComponent, | ||||
| SalesDetailComponent | |||||
| SalesDetailComponent, | |||||
| NewSaleComponent | |||||
| ], | ], | ||||
| providers: [ | providers: [ | ||||
| {provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true}, | {provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true}, | ||||
| @@ -16,7 +16,7 @@ | |||||
| <label for="partner" class="form-label">{{ 'form.partner' | translate }}:</label> | <label for="partner" class="form-label">{{ 'form.partner' | translate }}:</label> | ||||
| <input type="text" class="form-control" id="partner" [ngbTypeahead]="searchPartners" | <input type="text" class="form-control" id="partner" [ngbTypeahead]="searchPartners" | ||||
| [inputFormatter]="formatter" [value]="documentForm.get('partnerName')?.value" | [inputFormatter]="formatter" [value]="documentForm.get('partnerName')?.value" | ||||
| [resultFormatter]="formatter" [editable]="false" (selectItem)="onAssignedToSelectPartner($event)"/> | |||||
| [resultFormatter]="formatter" [editable]="false" (selectItem)="onPartnerSelect($event)"/> | |||||
| <input type="hidden" formControlName="partner"/> | <input type="hidden" formControlName="partner"/> | ||||
| </div> | </div> | ||||
| @@ -24,7 +24,7 @@ | |||||
| <label for="product" class="form-label">{{ 'form.product' | translate }}:</label> | <label for="product" class="form-label">{{ 'form.product' | translate }}:</label> | ||||
| <input type="text" class="form-control" id="product" [ngbTypeahead]="searchProducts" | <input type="text" class="form-control" id="product" [ngbTypeahead]="searchProducts" | ||||
| [inputFormatter]="formatter" [value]="documentForm.get('productName')?.value" | [inputFormatter]="formatter" [value]="documentForm.get('productName')?.value" | ||||
| [resultFormatter]="formatter" [editable]="false" (selectItem)="onAssignedToSelectProduct($event)"/> | |||||
| [resultFormatter]="formatter" [editable]="false" (selectItem)="onProductSelect($event)"/> | |||||
| <input type="hidden" formControlName="product"/> | <input type="hidden" formControlName="product"/> | ||||
| </div> | </div> | ||||
| @@ -82,11 +82,11 @@ export class NewDocumentComponent implements OnInit { | |||||
| ); | ); | ||||
| } | } | ||||
| protected onAssignedToSelectPartner(selectedItem: any): void { | |||||
| protected onPartnerSelect(selectedItem: any): void { | |||||
| this.documentForm.get('partner')?.setValue(selectedItem.item.id); | this.documentForm.get('partner')?.setValue(selectedItem.item.id); | ||||
| } | } | ||||
| protected onAssignedToSelectProduct(selectedItem: any): void { | |||||
| protected onProductSelect(selectedItem: any): void { | |||||
| this.documentForm.get('product')?.setValue(selectedItem.item.id); | this.documentForm.get('product')?.setValue(selectedItem.item.id); | ||||
| } | } | ||||
| @@ -0,0 +1,80 @@ | |||||
| <h2 *ngIf="!sale.id">{{ 'basic.new-sale' | translate }}</h2> | |||||
| <h2 *ngIf="sale.id">{{ 'basic.edit-sale' | translate }}</h2> | |||||
| <div class="spt-form"> | |||||
| <form [formGroup]="saleForm" (ngSubmit)="onSubmit()"> | |||||
| <div class="mb-3"> | |||||
| <label for="partner" class="form-label">{{ 'form.partner' | translate }}:</label> | |||||
| <input type="text" class="form-control" id="partner" [ngbTypeahead]="searchPartners" | |||||
| [inputFormatter]="formatter" [value]="saleForm.get('partnerName')?.value" | |||||
| [resultFormatter]="formatter" [editable]="false" (selectItem)="onPartnerSelect($event)"/> | |||||
| <input type="hidden" formControlName="partner"/> | |||||
| </div> | |||||
| <div class="mb-3"> | |||||
| <label for="product" class="form-label">{{ 'form.product' | translate }}:</label> | |||||
| <input type="text" class="form-control" id="product" [ngbTypeahead]="searchProducts" | |||||
| [inputFormatter]="formatter" [value]="saleForm.get('productName')?.value" | |||||
| [resultFormatter]="formatter" [editable]="false" (selectItem)="onProductSelect($event)"/> | |||||
| <input type="hidden" formControlName="product"/> | |||||
| </div> | |||||
| <div class="mb-3"> | |||||
| <label for="turnover" class="form-label">{{ 'form.turnover' | translate }}:</label> | |||||
| <input type="number" class="form-control" id="turnover" formControlName="turnover" min="0" step="1" /> | |||||
| <div class="form-text" *ngIf="saleForm.get('turnover')?.invalid && saleForm.get('turnover')?.touched"> | |||||
| {{ 'form.turnover' | translate }} {{ 'form.mandatory' | translate }}. | |||||
| </div> | |||||
| </div> | |||||
| <div class="mb-3"> | |||||
| <label for="profit" class="form-label">{{ 'form.profit' | translate }}:</label> | |||||
| <input type="number" class="form-control" id="profit" formControlName="profit" min="0" step="1" /> | |||||
| </div> | |||||
| <!-- <div class="mb-3">--> | |||||
| <!-- <label for="street" class="form-label">{{ 'form.street' | translate }}:</label>--> | |||||
| <!-- <input type="text" class="form-control" id="street" formControlName="street"/>--> | |||||
| <!-- </div>--> | |||||
| <!-- <div class="mb-3">--> | |||||
| <!-- <label for="streetNo" class="form-label">{{ 'form.street-no' | translate }}:</label>--> | |||||
| <!-- <input type="text" class="form-control" id="streetNo" formControlName="streetNo"/>--> | |||||
| <!-- </div>--> | |||||
| <!-- <div class="mb-3">--> | |||||
| <!-- <label for="zip" class="form-label">{{ 'form.zip' | translate }}:</label>--> | |||||
| <!-- <input type="text" class="form-control" id="zip" formControlName="zip"/>--> | |||||
| <!-- </div>--> | |||||
| <!-- <div class="mb-3">--> | |||||
| <!-- <label for="city" class="form-label">{{ 'form.city' | translate }}:</label>--> | |||||
| <!-- <input type="text" class="form-control" id="city" formControlName="city"/>--> | |||||
| <!-- </div>--> | |||||
| <!-- <div class="mb-3">--> | |||||
| <!-- <label for="country" class="form-label">{{ 'form.country' | translate }}:</label>--> | |||||
| <!-- <input type="text" class="form-control" id="country" formControlName="country"/>--> | |||||
| <!-- </div>--> | |||||
| <!-- <div class="mb-3">--> | |||||
| <!-- <label for="website" class="form-label">{{ 'form.website' | translate }}:</label>--> | |||||
| <!-- <input type="text" class="form-control" id="website" formControlName="website"/>--> | |||||
| <!-- </div>--> | |||||
| <!-- <div class="mb-3" *ngIf="partnerForm.get('logoUrl')?.value === null">--> | |||||
| <!-- <label for="logo" class="form-label">{{ 'form.upload-image' | translate }}:</label>--> | |||||
| <!-- <input type="file" class="form-control" id="logo" (change)="onFileSelected($event)" accept="image/*"/>--> | |||||
| <!-- </div>--> | |||||
| <!-- <div class="mb-3" *ngIf="partnerForm.get('logoUrl')?.value !== null">--> | |||||
| <!-- <div class="delete-image" (click)="onDeleteImage()">--> | |||||
| <!-- <img src="{{partner.logoUrl}}" width="40" height="40"/>--> | |||||
| <!-- <p class="mb-0 ms-3">{{ 'system.delete-image' | translate }}</p>--> | |||||
| <!-- </div>--> | |||||
| <!-- </div>--> | |||||
| <button type="submit" class="btn btn-primary" [disabled]="saleForm.invalid">{{ 'form.send' | translate }} | |||||
| </button> | |||||
| </form> | |||||
| </div> | |||||
| @@ -0,0 +1,23 @@ | |||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||||
| import { NewSaleComponent } from './new-sale.component'; | |||||
| describe('NewSaleComponent', () => { | |||||
| let component: NewSaleComponent; | |||||
| let fixture: ComponentFixture<NewSaleComponent>; | |||||
| beforeEach(async () => { | |||||
| await TestBed.configureTestingModule({ | |||||
| declarations: [NewSaleComponent] | |||||
| }) | |||||
| .compileComponents(); | |||||
| fixture = TestBed.createComponent(NewSaleComponent); | |||||
| component = fixture.componentInstance; | |||||
| fixture.detectChanges(); | |||||
| }); | |||||
| it('should create', () => { | |||||
| expect(component).toBeTruthy(); | |||||
| }); | |||||
| }); | |||||
| @@ -0,0 +1,126 @@ | |||||
| import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; | |||||
| import { | |||||
| MediaObjectService, | |||||
| PartnerJsonld, | |||||
| PartnerService, | |||||
| ProductService, | |||||
| SaleJsonld, | |||||
| SaleService | |||||
| } from "@app/core/api/v1"; | |||||
| import {ModalStatus} from "@app/_helpers/modal.states"; | |||||
| import {FormGroup} from "@angular/forms"; | |||||
| import {debounceTime, distinctUntilChanged, Observable, OperatorFunction, Subscription, switchMap} from "rxjs"; | |||||
| import {TranslateService} from "@ngx-translate/core"; | |||||
| import {FormGroupInitializer} from "@app/_helpers/formgroup.initializer"; | |||||
| import {saleForm} from "@app/_forms/apiForms"; | |||||
| import {ApiConverter} from "@app/_helpers/api.converter"; | |||||
| import {filter, map} from "rxjs/operators"; | |||||
| @Component({ | |||||
| selector: 'app-new-sale', | |||||
| templateUrl: './new-sale.component.html', | |||||
| styleUrl: './new-sale.component.scss' | |||||
| }) | |||||
| export class NewSaleComponent implements OnInit { | |||||
| @Input() public sale!: SaleJsonld; | |||||
| @Output() public submit: EventEmitter<ModalStatus> = new EventEmitter<ModalStatus>(); | |||||
| protected saleForm: FormGroup; | |||||
| protected saleSub: Subscription; | |||||
| protected formatter = (apiData: any) => apiData.name; | |||||
| constructor( | |||||
| private saleService: SaleService, | |||||
| private partnerService: PartnerService, | |||||
| private productService: ProductService, | |||||
| private translateService: TranslateService | |||||
| ) { | |||||
| this.saleForm = saleForm; | |||||
| this.saleSub = new Subscription(); | |||||
| } | |||||
| ngOnInit(): void { | |||||
| this.saleForm = FormGroupInitializer.initFormGroup(this.saleForm, this.sale); | |||||
| } | |||||
| protected searchPartners: OperatorFunction<string, readonly { | |||||
| id: any; | |||||
| name: any | |||||
| }[]> = (text$: Observable<string>) => | |||||
| text$.pipe( | |||||
| debounceTime(200), | |||||
| distinctUntilChanged(), | |||||
| filter((term) => term.length >= 2), | |||||
| switchMap((term) => this.fetchPartners(term)), | |||||
| map((partners) => partners.slice(0, 10)), | |||||
| ); | |||||
| protected searchProducts: OperatorFunction<string, readonly { | |||||
| id: any; | |||||
| name: any | |||||
| }[]> = (text$: Observable<string>) => | |||||
| text$.pipe( | |||||
| debounceTime(200), | |||||
| distinctUntilChanged(), | |||||
| filter((term) => term.length >= 2), | |||||
| switchMap((term) => this.fetchProducts(term)), | |||||
| map((products) => products.slice(0, 10)), | |||||
| ); | |||||
| protected fetchPartners(term: string): Observable<{ id: any; name: any }[]> { | |||||
| return this.partnerService.partnersGetCollection(1, 50, undefined, undefined, term).pipe( | |||||
| map((response) => response['hydra:member'].map(partner => ({id: partner.id, name: partner.name}))), | |||||
| ); | |||||
| } | |||||
| protected fetchProducts(term: string): Observable<{ id: any; name: any }[]> { | |||||
| return this.productService.productsGetCollection(1, 50, term).pipe( | |||||
| map((response) => response['hydra:member'].map(product => ({id: product.id, name: product.name}))), | |||||
| ); | |||||
| } | |||||
| protected onPartnerSelect(selectedItem: any): void { | |||||
| this.saleForm.get('partner')?.setValue(selectedItem.item.id); | |||||
| } | |||||
| protected onProductSelect(selectedItem: any): void { | |||||
| this.saleForm.get('product')?.setValue(selectedItem.item.id); | |||||
| } | |||||
| onSubmit() { | |||||
| if (this.saleForm.valid) { | |||||
| if (this.saleForm.get('profit')?.value > this.saleForm.get('turnover')?.value) { | |||||
| let alertMessage = ""; | |||||
| this.translateService.get('system.profit-larger-turnover').subscribe((translation: string) => { | |||||
| alertMessage = translation; | |||||
| }); | |||||
| alert(alertMessage); | |||||
| return; | |||||
| } | |||||
| if (this.sale.id === null || this.sale.id === undefined) { | |||||
| // Create new sale | |||||
| this.saleSub = this.saleService.salesPost( | |||||
| this.saleForm.value as SaleJsonld | |||||
| ).subscribe( | |||||
| data => { | |||||
| this.saleForm.reset(); | |||||
| this.submit.emit(ModalStatus.Submitted); | |||||
| } | |||||
| ); | |||||
| } else { | |||||
| // Edit sale | |||||
| this.saleSub = this.saleService.salesIdPatch( | |||||
| ApiConverter.extractId(this.sale.id), | |||||
| this.saleForm.value as SaleJsonld | |||||
| ).subscribe( | |||||
| data => { | |||||
| this.saleForm.reset(); | |||||
| this.submit.emit(ModalStatus.Submitted); | |||||
| } | |||||
| ); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -19,6 +19,7 @@ import {ApiConverter} from "@app/_helpers/api.converter"; | |||||
| import {Router} from "@angular/router"; | import {Router} from "@angular/router"; | ||||
| import {registerLocaleData} from "@angular/common"; | import {registerLocaleData} from "@angular/common"; | ||||
| import localeDe from '@angular/common/locales/de'; | import localeDe from '@angular/common/locales/de'; | ||||
| import {NewSaleComponent} from "@app/sales/new-sale/new-sale.component"; | |||||
| registerLocaleData(localeDe); | registerLocaleData(localeDe); | ||||
| @@ -163,13 +164,14 @@ export class SalesComponent implements OnInit { | |||||
| } | } | ||||
| openModalNewSale() { | openModalNewSale() { | ||||
| const modalRefContact = this.modalService.open(NewPartnerComponent, this.modalOptions); | |||||
| const modalRefSale = this.modalService.open(NewSaleComponent, this.modalOptions); | |||||
| let sale: SaleJsonld = {} as SaleJsonld; | let sale: SaleJsonld = {} as SaleJsonld; | ||||
| modalRefContact.componentInstance.partner = sale; | |||||
| modalRefContact.componentInstance.submit.subscribe((modalStatus: ModalStatus) => { | |||||
| modalRefSale.componentInstance.sale = sale; | |||||
| modalRefSale.componentInstance.submit.subscribe((modalStatus: ModalStatus) => { | |||||
| if (modalStatus === ModalStatus.Submitted) { | if (modalStatus === ModalStatus.Submitted) { | ||||
| modalRefContact.dismiss(); | |||||
| modalRefSale.dismiss(); | |||||
| this.getSalesData(); | this.getSalesData(); | ||||
| this.getSalesSummaryData(); | |||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -4,7 +4,8 @@ | |||||
| "delete-image": "Bild löschen", | "delete-image": "Bild löschen", | ||||
| "confirm-delete-image": "Möchten Sie das Bild wirklich löschen?", | "confirm-delete-image": "Möchten Sie das Bild wirklich löschen?", | ||||
| "delete-file": "Datei löschen", | "delete-file": "Datei löschen", | ||||
| "confirm-delete-file": "Möchten Sie die Datei wirklich löschen?" | |||||
| "confirm-delete-file": "Möchten Sie die Datei wirklich löschen?", | |||||
| "profit-larger-turnover": "Der Gewinn ist größer als der Umsatz!" | |||||
| }, | }, | ||||
| "basic": | "basic": | ||||
| { | { | ||||
| @@ -41,6 +42,7 @@ | |||||
| "edit-post": "Notiz bearbeiten", | "edit-post": "Notiz bearbeiten", | ||||
| "edit-comment": "Kommentar bearbeiten", | "edit-comment": "Kommentar bearbeiten", | ||||
| "edit-task-note": "Anmerkung bearbeiten", | "edit-task-note": "Anmerkung bearbeiten", | ||||
| "edit-sale": "Verkauf bearbeiten", | |||||
| "details": "Details", | "details": "Details", | ||||
| "comment-it": "Kommentieren", | "comment-it": "Kommentieren", | ||||
| "show-comments": "Kommentare anzeigen", | "show-comments": "Kommentare anzeigen", | ||||
| @@ -107,6 +109,8 @@ | |||||
| "prio-high": "hoch", | "prio-high": "hoch", | ||||
| "partner": "Partner", | "partner": "Partner", | ||||
| "product": "Produkt", | "product": "Produkt", | ||||
| "turnover": "Umsatz", | |||||
| "profit": "Gewinn", | |||||
| "send": "Abschicken" | "send": "Abschicken" | ||||
| }, | }, | ||||
| "sales": | "sales": | ||||