mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
✨ feat(isa-app): migrate to standalone component architecture
- Replace NgModule bootstrap with bootstrapApplication and ApplicationConfig - Convert app.module.ts to app.config.ts with provider functions - Convert routing module to standalone routes array - Remove domain NgModules (services now providedIn: 'root') - Remove NgRx application store in favor of signals - Update icon components and registries for modern patterns - Update page components for standalone compatibility
This commit is contained in:
@@ -1,19 +1,22 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { DevScanAdapter } from './dev.scan-adapter';
|
||||
import { NativeScanAdapter } from './native.scan-adapter';
|
||||
import { SCAN_ADAPTER } from './tokens';
|
||||
|
||||
@NgModule({})
|
||||
export class ScanAdapterModule {
|
||||
static forRoot() {
|
||||
return {
|
||||
ngModule: ScanAdapterModule,
|
||||
providers: [
|
||||
{ provide: SCAN_ADAPTER, useClass: NativeScanAdapter, multi: true },
|
||||
{ provide: SCAN_ADAPTER, useClass: DevScanAdapter, multi: true },
|
||||
],
|
||||
// Use for testing:
|
||||
// providers: [{ provide: SCAN_ADAPTER, useClass: dev ? DevScanAdapter : NativeScanAdapter, multi: true }],
|
||||
};
|
||||
}
|
||||
}
|
||||
import { NgModule } from '@angular/core';
|
||||
import { DevScanAdapter } from './dev.scan-adapter';
|
||||
import { NativeScanAdapter } from './native.scan-adapter';
|
||||
import { SCAN_ADAPTER } from './tokens';
|
||||
|
||||
/**
|
||||
* @deprecated Use '@isa/shared/scanner' instead.
|
||||
*/
|
||||
@NgModule({})
|
||||
export class ScanAdapterModule {
|
||||
static forRoot() {
|
||||
return {
|
||||
ngModule: ScanAdapterModule,
|
||||
providers: [
|
||||
{ provide: SCAN_ADAPTER, useClass: NativeScanAdapter, multi: true },
|
||||
{ provide: SCAN_ADAPTER, useClass: DevScanAdapter, multi: true },
|
||||
],
|
||||
// Use for testing:
|
||||
// providers: [{ provide: SCAN_ADAPTER, useClass: dev ? DevScanAdapter : NativeScanAdapter, multi: true }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ScanditOverlayComponent } from './scandit-overlay.component';
|
||||
import { ScanditScanAdapter } from './scandit.scan-adapter';
|
||||
import { SCAN_ADAPTER } from '../tokens';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: [ScanditOverlayComponent],
|
||||
declarations: [ScanditOverlayComponent],
|
||||
})
|
||||
export class ScanditScanAdapterModule {
|
||||
static forRoot() {
|
||||
return {
|
||||
ngModule: ScanditScanAdapterModule,
|
||||
providers: [{ provide: SCAN_ADAPTER, useClass: ScanditScanAdapter, multi: true }],
|
||||
};
|
||||
}
|
||||
}
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ScanditOverlayComponent } from './scandit-overlay.component';
|
||||
import { ScanditScanAdapter } from './scandit.scan-adapter';
|
||||
import { SCAN_ADAPTER } from '../tokens';
|
||||
|
||||
/**
|
||||
* @deprecated Use @isa/shared/scanner instead.
|
||||
*/
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: [ScanditOverlayComponent],
|
||||
declarations: [ScanditOverlayComponent],
|
||||
})
|
||||
export class ScanditScanAdapterModule {
|
||||
static forRoot() {
|
||||
return {
|
||||
ngModule: ScanditScanAdapterModule,
|
||||
providers: [
|
||||
{ provide: SCAN_ADAPTER, useClass: ScanditScanAdapter, multi: true },
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { DomainAvailabilityModule } from '@domain/availability';
|
||||
import { DomainCatalogModule } from '@domain/catalog';
|
||||
import { DomainIsaModule } from '@domain/isa';
|
||||
import { DomainCheckoutModule } from '@domain/checkout';
|
||||
import { DomainOmsModule } from '@domain/oms';
|
||||
import { DomainRemissionModule } from '@domain/remission';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
DomainIsaModule.forRoot(),
|
||||
DomainCatalogModule.forRoot(),
|
||||
DomainAvailabilityModule.forRoot(),
|
||||
DomainCheckoutModule.forRoot(),
|
||||
DomainOmsModule.forRoot(),
|
||||
DomainRemissionModule.forRoot(),
|
||||
],
|
||||
})
|
||||
export class AppDomainModule {}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { ActionReducer, MetaReducer, StoreModule } from '@ngrx/store';
|
||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
import { environment } from '../environments/environment';
|
||||
import { rootReducer } from './store/root.reducer';
|
||||
import { RootState } from './store/root.state';
|
||||
|
||||
export function storeInLocalStorage(
|
||||
reducer: ActionReducer<any>,
|
||||
): ActionReducer<any> {
|
||||
return function (state, action) {
|
||||
if (action.type === 'HYDRATE') {
|
||||
return reducer(action['payload'], action);
|
||||
}
|
||||
return reducer(state, action);
|
||||
};
|
||||
}
|
||||
|
||||
export const metaReducers: MetaReducer<RootState>[] = !environment.production
|
||||
? [storeInLocalStorage]
|
||||
: [storeInLocalStorage];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
StoreModule.forRoot(rootReducer, { metaReducers }),
|
||||
EffectsModule.forRoot([]),
|
||||
StoreDevtoolsModule.instrument({
|
||||
name: 'ISA Ngrx Application Store',
|
||||
connectInZone: true,
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class AppStoreModule {}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { HttpInterceptorFn, provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Config } from '@core/config';
|
||||
import { AvConfiguration } from '@generated/swagger/availability-api';
|
||||
import { CatConfiguration } from '@generated/swagger/cat-search-api';
|
||||
import { CheckoutConfiguration } from '@generated/swagger/checkout-api';
|
||||
import { CrmConfiguration } from '@generated/swagger/crm-api';
|
||||
import { EisConfiguration } from '@generated/swagger/eis-api';
|
||||
import { IsaConfiguration } from '@generated/swagger/isa-api';
|
||||
import { OmsConfiguration } from '@generated/swagger/oms-api';
|
||||
import { PrintConfiguration } from '@generated/swagger/print-api';
|
||||
import { RemiConfiguration } from '@generated/swagger/inventory-api';
|
||||
import { WwsConfiguration } from '@generated/swagger/wws-api';
|
||||
|
||||
export function createConfigurationFactory(name: string) {
|
||||
return function (config: Config): { rootUrl: string } {
|
||||
return config.get(`@swagger/${name}`);
|
||||
};
|
||||
}
|
||||
|
||||
const serviceWorkerInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
return next(req.clone({ setHeaders: { 'ngsw-bypass': 'true' } }));
|
||||
};
|
||||
|
||||
@NgModule({
|
||||
providers: [
|
||||
provideHttpClient(withInterceptors([serviceWorkerInterceptor])),
|
||||
{ provide: AvConfiguration, useFactory: createConfigurationFactory('av'), deps: [Config] },
|
||||
{ provide: CatConfiguration, useFactory: createConfigurationFactory('cat'), deps: [Config] },
|
||||
{ provide: CheckoutConfiguration, useFactory: createConfigurationFactory('checkout'), deps: [Config] },
|
||||
{ provide: CrmConfiguration, useFactory: createConfigurationFactory('crm'), deps: [Config] },
|
||||
{ provide: EisConfiguration, useFactory: createConfigurationFactory('eis'), deps: [Config] },
|
||||
{ provide: IsaConfiguration, useFactory: createConfigurationFactory('isa'), deps: [Config] },
|
||||
{ provide: OmsConfiguration, useFactory: createConfigurationFactory('oms'), deps: [Config] },
|
||||
{ provide: PrintConfiguration, useFactory: createConfigurationFactory('print'), deps: [Config] },
|
||||
{ provide: RemiConfiguration, useFactory: createConfigurationFactory('remi'), deps: [Config] },
|
||||
{ provide: WwsConfiguration, useFactory: createConfigurationFactory('wws'), deps: [Config] },
|
||||
],
|
||||
})
|
||||
export class AppSwaggerModule {}
|
||||
@@ -1,28 +1,28 @@
|
||||
@if ($offlineBannerVisible()) {
|
||||
<div [@fadeInOut] class="bg-brand text-white text-center fixed inset-x-0 top-0 z-tooltip p-4">
|
||||
<h3 class="font-bold grid grid-flow-col items-center justify-center text-xl gap-4">
|
||||
<div>
|
||||
<ng-icon name="matWifiOff"></ng-icon>
|
||||
</div>
|
||||
|
||||
<div>Sie sind offline, keine Verbindung zum Netzwerk.</div>
|
||||
</h3>
|
||||
<p>Bereits geladene Ihnalte werden angezeigt, Interaktionen sind aktuell nicht möglich.</p>
|
||||
</div>
|
||||
}
|
||||
@if ($onlineBannerVisible()) {
|
||||
<div [@fadeInOut] class="bg-green-500 text-white text-center fixed inset-x-0 top-0 z-tooltip p-4">
|
||||
<h3 class="font-bold grid grid-flow-col items-center justify-center text-xl gap-4">
|
||||
<div>
|
||||
<ng-icon name="matWifi"></ng-icon>
|
||||
</div>
|
||||
|
||||
<div>Sie sind wieder online.</div>
|
||||
</h3>
|
||||
<button class="fixed top-2 right-4 text-3xl w-12 h-12" type="button" (click)="$onlineBannerVisible.set(false)">
|
||||
<ng-icon name="matClose"></ng-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
<!-- @if ($offlineBannerVisible()) {
|
||||
<div [@fadeInOut] class="bg-brand text-white text-center fixed inset-x-0 top-0 z-tooltip p-4">
|
||||
<h3 class="font-bold grid grid-flow-col items-center justify-center text-xl gap-4">
|
||||
<div>
|
||||
<ng-icon name="matWifiOff"></ng-icon>
|
||||
</div>
|
||||
|
||||
<div>Sie sind offline, keine Verbindung zum Netzwerk.</div>
|
||||
</h3>
|
||||
<p>Bereits geladene Ihnalte werden angezeigt, Interaktionen sind aktuell nicht möglich.</p>
|
||||
</div>
|
||||
}
|
||||
@if ($onlineBannerVisible()) {
|
||||
<div [@fadeInOut] class="bg-green-500 text-white text-center fixed inset-x-0 top-0 z-tooltip p-4">
|
||||
<h3 class="font-bold grid grid-flow-col items-center justify-center text-xl gap-4">
|
||||
<div>
|
||||
<ng-icon name="matWifi"></ng-icon>
|
||||
</div>
|
||||
|
||||
<div>Sie sind wieder online.</div>
|
||||
</h3>
|
||||
<button class="fixed top-2 right-4 text-3xl w-12 h-12" type="button" (click)="$onlineBannerVisible.set(false)">
|
||||
<ng-icon name="matClose"></ng-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<router-outlet></router-outlet> -->
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
:host {
|
||||
@apply block;
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
import { Spectator, createComponentFactory, SpyObject, createSpyObject } from '@ngneat/spectator';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
import { Config } from '@core/config';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { of } from 'rxjs';
|
||||
import { Renderer2 } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
import { NotificationsHub } from '@hub/notifications';
|
||||
import { UserStateService } from '@generated/swagger/isa-api';
|
||||
import { UiModalService } from '@ui/modal';
|
||||
import { AuthService } from '@core/auth';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let spectator: Spectator<AppComponent>;
|
||||
let config: SpyObject<Config>;
|
||||
let renderer: SpyObject<Renderer2>;
|
||||
let applicationServiceMock: SpyObject<ApplicationService>;
|
||||
let notificationsHubMock: SpyObject<NotificationsHub>;
|
||||
let swUpdateMock: SpyObject<SwUpdate>;
|
||||
const createComponent = createComponentFactory({
|
||||
component: AppComponent,
|
||||
imports: [CommonModule, RouterTestingModule],
|
||||
providers: [],
|
||||
mocks: [Config, SwUpdate, UserStateService, UiModalService, AuthService],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
applicationServiceMock = createSpyObject(ApplicationService);
|
||||
applicationServiceMock.getSection$.and.returnValue(of('customer'));
|
||||
applicationServiceMock.getActivatedProcessId$.and.returnValue(of(undefined));
|
||||
renderer = jasmine.createSpyObj('Renderer2', ['addClass', 'removeClass']);
|
||||
|
||||
notificationsHubMock = createSpyObject(NotificationsHub);
|
||||
notificationsHubMock.notifications$ = of({});
|
||||
swUpdateMock = createSpyObject(SwUpdate);
|
||||
|
||||
spectator = createComponent({
|
||||
providers: [
|
||||
{ provide: ApplicationService, useValue: applicationServiceMock },
|
||||
{
|
||||
provide: Renderer2,
|
||||
useValue: renderer,
|
||||
},
|
||||
{ provide: NotificationsHub, useValue: notificationsHubMock },
|
||||
{ provide: SwUpdate, useValue: swUpdateMock },
|
||||
],
|
||||
});
|
||||
config = spectator.inject(Config);
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
expect(spectator.component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have a router outlet', () => {
|
||||
expect(spectator.query('router-outlet')).toExist();
|
||||
});
|
||||
|
||||
describe('ngOnInit', () => {
|
||||
it('should call setTitle', () => {
|
||||
const spy = spyOn(spectator.component, 'setTitle');
|
||||
spectator.component.ngOnInit();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call logVersion', () => {
|
||||
const spy = spyOn(spectator.component, 'logVersion');
|
||||
spectator.component.ngOnInit();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setTitle', () => {
|
||||
it('should call Title.setTitle()', () => {
|
||||
const spyTitleSetTitle = spyOn(spectator.component['_title'], 'setTitle');
|
||||
config.get.and.returnValue('test');
|
||||
spectator.component.setTitle();
|
||||
expect(spyTitleSetTitle).toHaveBeenCalledWith('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('logVersion', () => {
|
||||
it('should call console.log()', () => {
|
||||
const spyConsoleLog = spyOn(console, 'log');
|
||||
config.get.and.returnValue('test');
|
||||
spectator.component.logVersion();
|
||||
expect(spyConsoleLog).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Unit Tests Implementation for Angular Version 13.x.x
|
||||
|
||||
// describe('updateClient()', () => {
|
||||
// it('should call checkForUpdate() if SwUpdate.isEnabled is True', () => {
|
||||
// spyOn(spectator.component, 'checkForUpdate');
|
||||
// spyOn(spectator.component, 'initialCheckForUpdate');
|
||||
// (swUpdateMock as any).isEnabled = true;
|
||||
// spectator.component.updateClient();
|
||||
// expect(spectator.component.initialCheckForUpdate).toHaveBeenCalled();
|
||||
// expect(spectator.component.checkForUpdate).toHaveBeenCalled();
|
||||
// });
|
||||
|
||||
// it('should not call checkForUpdate() if SwUpdate.isEnabled is False', () => {
|
||||
// spyOn(spectator.component, 'checkForUpdate');
|
||||
// spyOn(spectator.component, 'initialCheckForUpdate');
|
||||
// (swUpdateMock as any).isEnabled = false;
|
||||
// spectator.component.updateClient();
|
||||
// expect(spectator.component.initialCheckForUpdate).not.toHaveBeenCalled();
|
||||
// expect(spectator.component.checkForUpdate).not.toHaveBeenCalled();
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('checkForUpdate', () => {
|
||||
// it('should call swUpdate.checkForUpdate() and notifications.updateNotification() every second', fakeAsync(() => {
|
||||
// swUpdateMock.checkForUpdate.and.returnValue(Promise.resolve());
|
||||
// spectator.component.checkForUpdates = 1000;
|
||||
// spectator.component.checkForUpdate();
|
||||
|
||||
// spectator.detectChanges();
|
||||
// tick(1100);
|
||||
|
||||
// expect(notificationsHubMock.updateNotification).toHaveBeenCalled();
|
||||
// discardPeriodicTasks();
|
||||
// }));
|
||||
// });
|
||||
|
||||
// describe('initialCheckForUpdate', () => {
|
||||
// it('should call swUpdate.checkForUpdate()', () => {
|
||||
// swUpdateMock.checkForUpdate.and.returnValue(new Promise(undefined));
|
||||
// spectator.component.initialCheckForUpdate();
|
||||
// expect(swUpdateMock.checkForUpdate).toHaveBeenCalled();
|
||||
// });
|
||||
// });
|
||||
});
|
||||
@@ -1,206 +1,205 @@
|
||||
|
||||
import {
|
||||
Component,
|
||||
effect,
|
||||
HostListener,
|
||||
inject,
|
||||
Inject,
|
||||
Injector,
|
||||
OnInit,
|
||||
Renderer2,
|
||||
signal,
|
||||
untracked,
|
||||
DOCUMENT
|
||||
} from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { Config } from '@core/config';
|
||||
import { NotificationsHub } from '@hub/notifications';
|
||||
import packageInfo from 'packageJson';
|
||||
import { asapScheduler, interval, Subscription } from 'rxjs';
|
||||
import { UserStateService } from '@generated/swagger/isa-api';
|
||||
import { IsaLogProvider } from './providers';
|
||||
import { EnvironmentService } from '@core/environment';
|
||||
import { AuthService, LoginStrategy } from '@core/auth';
|
||||
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
import { injectNetworkStatus } from '@isa/core/connectivity';
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
animations: [
|
||||
trigger('fadeInOut', [
|
||||
transition(':enter', [
|
||||
// :enter wird ausgelöst, wenn das Element zum DOM hinzugefügt wird
|
||||
style({ opacity: 0, transform: 'translateY(-100%)' }),
|
||||
animate('300ms', style({ opacity: 1, transform: 'translateY(0)' })),
|
||||
]),
|
||||
transition(':leave', [
|
||||
// :leave wird ausgelöst, wenn das Element aus dem DOM entfernt wird
|
||||
animate('300ms', style({ opacity: 0, transform: 'translateY(-100%)' })),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
standalone: false,
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
readonly injector = inject(Injector);
|
||||
|
||||
$networkStatus = injectNetworkStatus();
|
||||
|
||||
$offlineBannerVisible = signal(false);
|
||||
|
||||
$onlineBannerVisible = signal(false);
|
||||
|
||||
private onlineBannerDismissTimeout: any;
|
||||
|
||||
onlineEffects = effect(() => {
|
||||
const status = this.$networkStatus();
|
||||
const online = status === 'online';
|
||||
const offlineBannerVisible = this.$offlineBannerVisible();
|
||||
|
||||
untracked(() => {
|
||||
this.$offlineBannerVisible.set(!online);
|
||||
|
||||
if (!online) {
|
||||
this.$onlineBannerVisible.set(false);
|
||||
clearTimeout(this.onlineBannerDismissTimeout);
|
||||
}
|
||||
|
||||
if (offlineBannerVisible && online) {
|
||||
this.$onlineBannerVisible.set(true);
|
||||
this.onlineBannerDismissTimeout = setTimeout(() => this.$onlineBannerVisible.set(false), 5000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
private _checkForUpdates: number = this._config.get('checkForUpdates');
|
||||
|
||||
get checkForUpdates(): number {
|
||||
return this._checkForUpdates ?? 60 * 60 * 1000; // default 1 hour
|
||||
}
|
||||
|
||||
// For Unit Testing
|
||||
set checkForUpdates(time: number) {
|
||||
this._checkForUpdates = time;
|
||||
}
|
||||
|
||||
subscriptions = new Subscription();
|
||||
|
||||
constructor(
|
||||
private readonly _config: Config,
|
||||
private readonly _title: Title,
|
||||
private readonly _appService: ApplicationService,
|
||||
@Inject(DOCUMENT) private readonly _document: Document,
|
||||
private readonly _renderer: Renderer2,
|
||||
private readonly _swUpdate: SwUpdate,
|
||||
private readonly _notifications: NotificationsHub,
|
||||
private infoService: UserStateService,
|
||||
private readonly _environment: EnvironmentService,
|
||||
private readonly _authService: AuthService,
|
||||
private readonly _modal: UiModalService,
|
||||
) {
|
||||
this.updateClient();
|
||||
IsaLogProvider.InfoService = this.infoService;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.setTitle();
|
||||
this.logVersion();
|
||||
asapScheduler.schedule(() => this.determinePlatform(), 250);
|
||||
this._appService.getSection$().subscribe(this.sectionChangeHandler.bind(this));
|
||||
|
||||
this.setupSilentRefresh();
|
||||
}
|
||||
|
||||
// Setup interval for silent refresh
|
||||
setupSilentRefresh() {
|
||||
const silentRefreshInterval = this._config.get('silentRefresh.interval');
|
||||
if (silentRefreshInterval > 0) {
|
||||
interval(silentRefreshInterval).subscribe(() => {
|
||||
if (this._authService.isAuthenticated()) {
|
||||
this._authService.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setTitle() {
|
||||
this._title.setTitle(this._config.get('title'));
|
||||
}
|
||||
|
||||
logVersion() {
|
||||
console.log(
|
||||
`%c${this._config.get('title')}\r\nVersion: ${packageInfo.version}`,
|
||||
'font-weight: bold; font-size: 20px;',
|
||||
);
|
||||
}
|
||||
|
||||
determinePlatform() {
|
||||
if (this._environment.isNative()) {
|
||||
this._renderer.addClass(this._document.body, 'tablet-native');
|
||||
} else if (this._environment.isTablet()) {
|
||||
this._renderer.addClass(this._document.body, 'tablet-browser');
|
||||
}
|
||||
if (this._environment.isTablet()) {
|
||||
this._renderer.addClass(this._document.body, 'tablet');
|
||||
}
|
||||
if (this._environment.isDesktop()) {
|
||||
this._renderer.addClass(this._document.body, 'desktop');
|
||||
}
|
||||
}
|
||||
|
||||
sectionChangeHandler(section: string) {
|
||||
if (section === 'customer') {
|
||||
this._renderer.removeClass(this._document.body, 'branch');
|
||||
this._renderer.addClass(this._document.body, 'customer');
|
||||
} else if (section === 'branch') {
|
||||
this._renderer.removeClass(this._document.body, 'customer');
|
||||
this._renderer.addClass(this._document.body, 'branch');
|
||||
}
|
||||
}
|
||||
|
||||
updateClient() {
|
||||
if (!this._swUpdate.isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.initialCheckForUpdate();
|
||||
this.checkForUpdate();
|
||||
}
|
||||
|
||||
checkForUpdate() {
|
||||
interval(this._checkForUpdates).subscribe(() => {
|
||||
this._swUpdate.checkForUpdate().then((value) => {
|
||||
console.log('check for update', value);
|
||||
if (value) {
|
||||
this._notifications.updateNotification();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initialCheckForUpdate() {
|
||||
this._swUpdate.checkForUpdate().then((value) => {
|
||||
console.log('initial check for update', value);
|
||||
if (value) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@HostListener('window:visibilitychange', ['$event'])
|
||||
onVisibilityChange(event: Event) {
|
||||
// refresh token when app is in background
|
||||
if (this._document.hidden && this._authService.isAuthenticated()) {
|
||||
this._authService.refresh();
|
||||
} else if (!this._authService.isAuthenticated()) {
|
||||
const strategy = this.injector.get(LoginStrategy);
|
||||
|
||||
return strategy.login('Sie sind nicht mehr angemeldet');
|
||||
}
|
||||
}
|
||||
}
|
||||
// import {
|
||||
// Component,
|
||||
// effect,
|
||||
// HostListener,
|
||||
// inject,
|
||||
// Inject,
|
||||
// Injector,
|
||||
// OnInit,
|
||||
// Renderer2,
|
||||
// signal,
|
||||
// untracked,
|
||||
// DOCUMENT
|
||||
// } from '@angular/core';
|
||||
// import { Title } from '@angular/platform-browser';
|
||||
// import { SwUpdate } from '@angular/service-worker';
|
||||
// import { ApplicationService } from '@core/application';
|
||||
// import { Config } from '@core/config';
|
||||
// import { NotificationsHub } from '@hub/notifications';
|
||||
// import packageInfo from 'packageJson';
|
||||
// import { asapScheduler, interval, Subscription } from 'rxjs';
|
||||
// import { UserStateService } from '@generated/swagger/isa-api';
|
||||
// import { IsaLogProvider } from './providers';
|
||||
// import { EnvironmentService } from '@core/environment';
|
||||
// import { AuthService, LoginStrategy } from '@core/auth';
|
||||
// import { UiMessageModalComponent, UiModalService } from '@ui/modal';
|
||||
// import { injectNetworkStatus } from '@isa/core/connectivity';
|
||||
// import { animate, style, transition, trigger } from '@angular/animations';
|
||||
|
||||
// @Component({
|
||||
// selector: 'app-root',
|
||||
// templateUrl: './app.component.html',
|
||||
// styleUrls: ['./app.component.scss'],
|
||||
// animations: [
|
||||
// trigger('fadeInOut', [
|
||||
// transition(':enter', [
|
||||
// // :enter wird ausgelöst, wenn das Element zum DOM hinzugefügt wird
|
||||
// style({ opacity: 0, transform: 'translateY(-100%)' }),
|
||||
// animate('300ms', style({ opacity: 1, transform: 'translateY(0)' })),
|
||||
// ]),
|
||||
// transition(':leave', [
|
||||
// // :leave wird ausgelöst, wenn das Element aus dem DOM entfernt wird
|
||||
// animate('300ms', style({ opacity: 0, transform: 'translateY(-100%)' })),
|
||||
// ]),
|
||||
// ]),
|
||||
// ],
|
||||
// standalone: false,
|
||||
// })
|
||||
// export class AppComponent implements OnInit {
|
||||
// readonly injector = inject(Injector);
|
||||
|
||||
// $networkStatus = injectNetworkStatus();
|
||||
|
||||
// $offlineBannerVisible = signal(false);
|
||||
|
||||
// $onlineBannerVisible = signal(false);
|
||||
|
||||
// private onlineBannerDismissTimeout: any;
|
||||
|
||||
// onlineEffects = effect(() => {
|
||||
// const status = this.$networkStatus();
|
||||
// const online = status === 'online';
|
||||
// const offlineBannerVisible = this.$offlineBannerVisible();
|
||||
|
||||
// untracked(() => {
|
||||
// this.$offlineBannerVisible.set(!online);
|
||||
|
||||
// if (!online) {
|
||||
// this.$onlineBannerVisible.set(false);
|
||||
// clearTimeout(this.onlineBannerDismissTimeout);
|
||||
// }
|
||||
|
||||
// if (offlineBannerVisible && online) {
|
||||
// this.$onlineBannerVisible.set(true);
|
||||
// this.onlineBannerDismissTimeout = setTimeout(() => this.$onlineBannerVisible.set(false), 5000);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
// private _checkForUpdates: number = this._config.get('checkForUpdates');
|
||||
|
||||
// get checkForUpdates(): number {
|
||||
// return this._checkForUpdates ?? 60 * 60 * 1000; // default 1 hour
|
||||
// }
|
||||
|
||||
// // For Unit Testing
|
||||
// set checkForUpdates(time: number) {
|
||||
// this._checkForUpdates = time;
|
||||
// }
|
||||
|
||||
// subscriptions = new Subscription();
|
||||
|
||||
// constructor(
|
||||
// private readonly _config: Config,
|
||||
// private readonly _title: Title,
|
||||
// private readonly _appService: ApplicationService,
|
||||
// @Inject(DOCUMENT) private readonly _document: Document,
|
||||
// private readonly _renderer: Renderer2,
|
||||
// private readonly _swUpdate: SwUpdate,
|
||||
// private readonly _notifications: NotificationsHub,
|
||||
// private infoService: UserStateService,
|
||||
// private readonly _environment: EnvironmentService,
|
||||
// private readonly _authService: AuthService,
|
||||
// private readonly _modal: UiModalService,
|
||||
// ) {
|
||||
// this.updateClient();
|
||||
// IsaLogProvider.InfoService = this.infoService;
|
||||
// }
|
||||
|
||||
// ngOnInit() {
|
||||
// this.setTitle();
|
||||
// this.logVersion();
|
||||
// asapScheduler.schedule(() => this.determinePlatform(), 250);
|
||||
// this._appService.getSection$().subscribe(this.sectionChangeHandler.bind(this));
|
||||
|
||||
// this.setupSilentRefresh();
|
||||
// }
|
||||
|
||||
// // Setup interval for silent refresh
|
||||
// setupSilentRefresh() {
|
||||
// const silentRefreshInterval = this._config.get('silentRefresh.interval');
|
||||
// if (silentRefreshInterval > 0) {
|
||||
// interval(silentRefreshInterval).subscribe(() => {
|
||||
// if (this._authService.isAuthenticated()) {
|
||||
// this._authService.refresh();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// setTitle() {
|
||||
// this._title.setTitle(this._config.get('title'));
|
||||
// }
|
||||
|
||||
// logVersion() {
|
||||
// console.log(
|
||||
// `%c${this._config.get('title')}\r\nVersion: ${packageInfo.version}`,
|
||||
// 'font-weight: bold; font-size: 20px;',
|
||||
// );
|
||||
// }
|
||||
|
||||
// determinePlatform() {
|
||||
// if (this._environment.isNative()) {
|
||||
// this._renderer.addClass(this._document.body, 'tablet-native');
|
||||
// } else if (this._environment.isTablet()) {
|
||||
// this._renderer.addClass(this._document.body, 'tablet-browser');
|
||||
// }
|
||||
// if (this._environment.isTablet()) {
|
||||
// this._renderer.addClass(this._document.body, 'tablet');
|
||||
// }
|
||||
// if (this._environment.isDesktop()) {
|
||||
// this._renderer.addClass(this._document.body, 'desktop');
|
||||
// }
|
||||
// }
|
||||
|
||||
// sectionChangeHandler(section: string) {
|
||||
// if (section === 'customer') {
|
||||
// this._renderer.removeClass(this._document.body, 'branch');
|
||||
// this._renderer.addClass(this._document.body, 'customer');
|
||||
// } else if (section === 'branch') {
|
||||
// this._renderer.removeClass(this._document.body, 'customer');
|
||||
// this._renderer.addClass(this._document.body, 'branch');
|
||||
// }
|
||||
// }
|
||||
|
||||
// updateClient() {
|
||||
// if (!this._swUpdate.isEnabled) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// this.initialCheckForUpdate();
|
||||
// this.checkForUpdate();
|
||||
// }
|
||||
|
||||
// checkForUpdate() {
|
||||
// interval(this._checkForUpdates).subscribe(() => {
|
||||
// this._swUpdate.checkForUpdate().then((value) => {
|
||||
// console.log('check for update', value);
|
||||
// if (value) {
|
||||
// this._notifications.updateNotification();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// initialCheckForUpdate() {
|
||||
// this._swUpdate.checkForUpdate().then((value) => {
|
||||
// console.log('initial check for update', value);
|
||||
// if (value) {
|
||||
// location.reload();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// @HostListener('window:visibilitychange', ['$event'])
|
||||
// onVisibilityChange(event: Event) {
|
||||
// // refresh token when app is in background
|
||||
// if (this._document.hidden && this._authService.isAuthenticated()) {
|
||||
// this._authService.refresh();
|
||||
// } else if (!this._authService.isAuthenticated()) {
|
||||
// const strategy = this.injector.get(LoginStrategy);
|
||||
|
||||
// return strategy.login('Sie sind nicht mehr angemeldet');
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -2,51 +2,51 @@ import { version } from '../../../../package.json';
|
||||
import { IsaTitleStrategy } from '@isa/common/title-management';
|
||||
import {
|
||||
HTTP_INTERCEPTORS,
|
||||
HttpInterceptorFn,
|
||||
provideHttpClient,
|
||||
withInterceptors,
|
||||
withInterceptorsFromDi,
|
||||
} from '@angular/common/http';
|
||||
import {
|
||||
ApplicationConfig,
|
||||
DEFAULT_CURRENCY_CODE,
|
||||
ErrorHandler,
|
||||
importProvidersFrom,
|
||||
Injector,
|
||||
LOCALE_ID,
|
||||
NgModule,
|
||||
inject,
|
||||
provideAppInitializer,
|
||||
provideZoneChangeDetection,
|
||||
signal,
|
||||
isDevMode,
|
||||
} from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { PlatformModule } from '@angular/cdk/platform';
|
||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||
import {
|
||||
provideRouter,
|
||||
TitleStrategy,
|
||||
withComponentInputBinding,
|
||||
} from '@angular/router';
|
||||
import { ActionReducer, MetaReducer, provideStore } from '@ngrx/store';
|
||||
import { provideStoreDevtools } from '@ngrx/store-devtools';
|
||||
|
||||
import { Config } from '@core/config';
|
||||
import { AuthModule, AuthService, LoginStrategy } from '@core/auth';
|
||||
import { CoreCommandModule } from '@core/command';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import {
|
||||
ApplicationService,
|
||||
ApplicationServiceAdapter,
|
||||
CoreApplicationModule,
|
||||
} from '@core/application';
|
||||
import { AppStoreModule } from './app-store.module';
|
||||
import { routes } from './app.routes';
|
||||
|
||||
import { rootReducer } from './store/root.reducer';
|
||||
import { RootState } from './store/root.state';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
import { environment } from '../environments/environment';
|
||||
import { AppSwaggerModule } from './app-swagger.module';
|
||||
import { AppDomainModule } from './app-domain.module';
|
||||
import { UiModalModule } from '@ui/modal';
|
||||
import {
|
||||
NotificationsHubModule,
|
||||
NOTIFICATIONS_HUB_OPTIONS,
|
||||
} from '@hub/notifications';
|
||||
import { SignalRHubOptions } from '@core/signalr';
|
||||
import { CoreBreadcrumbModule } from '@core/breadcrumb';
|
||||
import { provideCoreBreadcrumb } from '@core/breadcrumb';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
|
||||
import localeDe from '@angular/common/locales/de';
|
||||
import localeDeExtra from '@angular/common/locales/extra/de';
|
||||
import { HttpErrorInterceptor } from './interceptors';
|
||||
import { CoreLoggerModule, LOG_PROVIDER } from '@core/logger';
|
||||
import { IsaLogProvider } from './providers';
|
||||
@@ -59,7 +59,6 @@ import {
|
||||
import * as Commands from './commands';
|
||||
import { NativeContainerService } from '@external/native-container';
|
||||
import { ShellModule } from '@shared/shell';
|
||||
import { MainComponent } from './main.component';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
import { NgIconsModule } from '@ng-icons/core';
|
||||
import {
|
||||
@@ -69,8 +68,7 @@ import {
|
||||
} from '@ng-icons/material-icons/baseline';
|
||||
import { NetworkStatusService } from '@isa/core/connectivity';
|
||||
import { debounceTime, filter, firstValueFrom, switchMap } from 'rxjs';
|
||||
import { provideMatomo } from 'ngx-matomo-client';
|
||||
import { withRouter, withRouteData } from 'ngx-matomo-client';
|
||||
import { provideMatomo, withRouter, withRouteData } from 'ngx-matomo-client';
|
||||
import {
|
||||
provideLogging,
|
||||
withLogLevel,
|
||||
@@ -87,15 +85,58 @@ import {
|
||||
import { Store } from '@ngrx/store';
|
||||
import { OAuthService } from 'angular-oauth2-oidc';
|
||||
import z from 'zod';
|
||||
import { TitleStrategy } from '@angular/router';
|
||||
import { provideScrollPositionRestoration } from '@isa/utils/scroll-position';
|
||||
import { TabNavigationService } from '@isa/core/tabs';
|
||||
|
||||
registerLocaleData(localeDe, localeDeExtra);
|
||||
registerLocaleData(localeDe, 'de', localeDeExtra);
|
||||
// Domain modules
|
||||
import { provideDomainCheckout } from '@domain/checkout';
|
||||
|
||||
export function _appInitializerFactory(config: Config, injector: Injector) {
|
||||
// Swagger API configurations
|
||||
import { AvConfiguration } from '@generated/swagger/availability-api';
|
||||
import { CatConfiguration } from '@generated/swagger/cat-search-api';
|
||||
import { CheckoutConfiguration } from '@generated/swagger/checkout-api';
|
||||
import { CrmConfiguration } from '@generated/swagger/crm-api';
|
||||
import { EisConfiguration } from '@generated/swagger/eis-api';
|
||||
import { IsaConfiguration } from '@generated/swagger/isa-api';
|
||||
import { OmsConfiguration } from '@generated/swagger/oms-api';
|
||||
import { PrintConfiguration } from '@generated/swagger/print-api';
|
||||
import { RemiConfiguration } from '@generated/swagger/inventory-api';
|
||||
import { WwsConfiguration } from '@generated/swagger/wws-api';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
|
||||
// --- Store Configuration ---
|
||||
|
||||
function storeHydrateMetaReducer(
|
||||
reducer: ActionReducer<RootState>,
|
||||
): ActionReducer<RootState> {
|
||||
return function (state, action) {
|
||||
if (action.type === 'HYDRATE') {
|
||||
return reducer(action['payload'], action);
|
||||
}
|
||||
return reducer(state, action);
|
||||
};
|
||||
}
|
||||
|
||||
const metaReducers: MetaReducer<RootState>[] = [storeHydrateMetaReducer];
|
||||
|
||||
// --- Swagger Configuration ---
|
||||
|
||||
const swaggerConfigSchema = z.object({ rootUrl: z.string() });
|
||||
|
||||
function createSwaggerConfigFactory(name: string) {
|
||||
return function () {
|
||||
return inject(Config).get(`@swagger/${name}`, swaggerConfigSchema);
|
||||
};
|
||||
}
|
||||
|
||||
const serviceWorkerBypassInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
return next(req.clone({ setHeaders: { 'ngsw-bypass': 'true' } }));
|
||||
};
|
||||
|
||||
// --- App Initializer ---
|
||||
|
||||
function appInitializerFactory(_config: Config, injector: Injector) {
|
||||
return async () => {
|
||||
// Get logging service for initialization logging
|
||||
const logger = loggerFactory(() => ({ service: 'AppInitializer' }));
|
||||
const statusElement = document.querySelector('#init-status');
|
||||
const laoderElement = document.querySelector('#init-loader');
|
||||
@@ -162,7 +203,6 @@ export function _appInitializerFactory(config: Config, injector: Injector) {
|
||||
await userStorage.init();
|
||||
|
||||
const store = injector.get(Store);
|
||||
// Hydrate Ngrx Store
|
||||
const state = userStorage.get('store');
|
||||
if (state && state['version'] === version) {
|
||||
store.dispatch({ type: 'HYDRATE', payload: userStorage.get('store') });
|
||||
@@ -172,7 +212,7 @@ export function _appInitializerFactory(config: Config, injector: Injector) {
|
||||
reason: state ? 'version mismatch' : 'no stored state',
|
||||
}));
|
||||
}
|
||||
// Subscribe on Store changes and save to user storage
|
||||
|
||||
auth.initialized$
|
||||
.pipe(
|
||||
filter((initialized) => initialized),
|
||||
@@ -183,7 +223,6 @@ export function _appInitializerFactory(config: Config, injector: Injector) {
|
||||
});
|
||||
|
||||
logger.info('Application initialization completed');
|
||||
// Inject tab navigation service to initialize it
|
||||
injector.get(TabNavigationService).init();
|
||||
} catch (error) {
|
||||
logger.error('Application initialization failed', error as Error, () => ({
|
||||
@@ -224,7 +263,7 @@ export function _appInitializerFactory(config: Config, injector: Injector) {
|
||||
};
|
||||
}
|
||||
|
||||
export function _notificationsHubOptionsFactory(
|
||||
function notificationsHubOptionsFactory(
|
||||
config: Config,
|
||||
auth: AuthService,
|
||||
): SignalRHubOptions {
|
||||
@@ -258,80 +297,151 @@ const USER_SUB_FACTORY = () => {
|
||||
return signal(validation.data);
|
||||
};
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, MainComponent],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
ShellModule.forRoot(),
|
||||
AppRoutingModule,
|
||||
AppSwaggerModule,
|
||||
AppDomainModule,
|
||||
CoreBreadcrumbModule.forRoot(),
|
||||
CoreCommandModule.forRoot(Object.values(Commands)),
|
||||
CoreLoggerModule.forRoot(),
|
||||
AppStoreModule,
|
||||
AuthModule.forRoot(),
|
||||
CoreApplicationModule.forRoot(),
|
||||
UiModalModule.forRoot(),
|
||||
UiCommonModule.forRoot(),
|
||||
NotificationsHubModule.forRoot(),
|
||||
ServiceWorkerModule.register('ngsw-worker.js', {
|
||||
enabled: environment.production,
|
||||
registrationStrategy: 'registerWhenStable:30000',
|
||||
}),
|
||||
ScanAdapterModule.forRoot(),
|
||||
ScanditScanAdapterModule.forRoot(),
|
||||
PlatformModule,
|
||||
IconModule.forRoot(),
|
||||
NgIconsModule.withIcons({ matWifiOff, matClose, matWifi }),
|
||||
],
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideAnimationsAsync('animations'),
|
||||
provideRouter(routes, withComponentInputBinding()),
|
||||
provideHttpClient(
|
||||
withInterceptorsFromDi(),
|
||||
withInterceptors([serviceWorkerBypassInterceptor]),
|
||||
),
|
||||
provideScrollPositionRestoration(),
|
||||
|
||||
// NgRx Store
|
||||
provideStore(rootReducer, { metaReducers }),
|
||||
provideCoreBreadcrumb(),
|
||||
provideDomainCheckout(),
|
||||
provideStoreDevtools({
|
||||
name: 'ISA Ngrx Application Store',
|
||||
connectInZone: true,
|
||||
}),
|
||||
|
||||
// Swagger API configurations
|
||||
{
|
||||
provide: AvConfiguration,
|
||||
useFactory: createSwaggerConfigFactory('av'),
|
||||
},
|
||||
{
|
||||
provide: CatConfiguration,
|
||||
useFactory: createSwaggerConfigFactory('cat'),
|
||||
},
|
||||
{
|
||||
provide: CheckoutConfiguration,
|
||||
useFactory: createSwaggerConfigFactory('checkout'),
|
||||
},
|
||||
{
|
||||
provide: CrmConfiguration,
|
||||
useFactory: createSwaggerConfigFactory('crm'),
|
||||
},
|
||||
{
|
||||
provide: EisConfiguration,
|
||||
useFactory: createSwaggerConfigFactory('eis'),
|
||||
},
|
||||
{
|
||||
provide: IsaConfiguration,
|
||||
useFactory: createSwaggerConfigFactory('isa'),
|
||||
},
|
||||
{
|
||||
provide: OmsConfiguration,
|
||||
useFactory: createSwaggerConfigFactory('oms'),
|
||||
},
|
||||
{
|
||||
provide: PrintConfiguration,
|
||||
useFactory: createSwaggerConfigFactory('print'),
|
||||
},
|
||||
{
|
||||
provide: RemiConfiguration,
|
||||
useFactory: createSwaggerConfigFactory('remi'),
|
||||
},
|
||||
{
|
||||
provide: WwsConfiguration,
|
||||
useFactory: createSwaggerConfigFactory('wws'),
|
||||
},
|
||||
|
||||
// App initializer
|
||||
provideAppInitializer(() => {
|
||||
const initializerFn = _appInitializerFactory(
|
||||
const initializerFn = appInitializerFactory(
|
||||
inject(Config),
|
||||
inject(Injector),
|
||||
);
|
||||
return initializerFn();
|
||||
}),
|
||||
|
||||
// Notifications hub
|
||||
{
|
||||
provide: NOTIFICATIONS_HUB_OPTIONS,
|
||||
useFactory: _notificationsHubOptionsFactory,
|
||||
useFactory: notificationsHubOptionsFactory,
|
||||
deps: [Config, AuthService],
|
||||
},
|
||||
|
||||
// HTTP interceptors
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: HttpErrorInterceptor,
|
||||
multi: true,
|
||||
},
|
||||
|
||||
// Logging
|
||||
{
|
||||
provide: LOG_PROVIDER,
|
||||
useClass: IsaLogProvider,
|
||||
multi: true,
|
||||
},
|
||||
provideLogging(
|
||||
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Info),
|
||||
withSink(ConsoleLogSink),
|
||||
),
|
||||
|
||||
// Error handling
|
||||
{
|
||||
provide: ErrorHandler,
|
||||
useClass: IsaErrorHandler,
|
||||
},
|
||||
{
|
||||
provide: ApplicationService,
|
||||
useClass: ApplicationServiceAdapter,
|
||||
},
|
||||
|
||||
// Locale settings
|
||||
{ provide: LOCALE_ID, useValue: 'de-DE' },
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
{ provide: DEFAULT_CURRENCY_CODE, useValue: 'EUR' },
|
||||
|
||||
// Analytics
|
||||
provideMatomo(
|
||||
{ trackerUrl: 'https://matomo.paragon-data.net', siteId: '1' },
|
||||
withRouter(),
|
||||
withRouteData(),
|
||||
),
|
||||
provideLogging(withLogLevel(LogLevel.Debug), withSink(ConsoleLogSink)),
|
||||
{
|
||||
provide: DEFAULT_CURRENCY_CODE,
|
||||
useValue: 'EUR',
|
||||
},
|
||||
|
||||
// User storage
|
||||
provideUserSubFactory(USER_SUB_FACTORY),
|
||||
|
||||
// Title strategy
|
||||
{ provide: TitleStrategy, useClass: IsaTitleStrategy },
|
||||
|
||||
// Import providers from NgModules
|
||||
importProvidersFrom(
|
||||
// Core modules
|
||||
CoreCommandModule.forRoot(Object.values(Commands)),
|
||||
CoreLoggerModule.forRoot(),
|
||||
AuthModule.forRoot(),
|
||||
|
||||
// UI modules
|
||||
UiModalModule.forRoot(),
|
||||
UiCommonModule.forRoot(),
|
||||
|
||||
// Hub modules
|
||||
NotificationsHubModule.forRoot(),
|
||||
|
||||
// Service Worker
|
||||
ServiceWorkerModule.register('ngsw-worker.js', {
|
||||
enabled: environment.production,
|
||||
registrationStrategy: 'registerWhenStable:30000',
|
||||
}),
|
||||
|
||||
// Scan adapter
|
||||
ScanAdapterModule.forRoot(),
|
||||
ScanditScanAdapterModule.forRoot(),
|
||||
|
||||
UiIconModule.forRoot(),
|
||||
IconModule.forRoot(),
|
||||
),
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
};
|
||||
0
apps/isa-app/src/app/app.css
Normal file
0
apps/isa-app/src/app/app.css
Normal file
1
apps/isa-app/src/app/app.html
Normal file
1
apps/isa-app/src/app/app.html
Normal file
@@ -0,0 +1 @@
|
||||
<router-outlet />
|
||||
@@ -1,5 +1,4 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { Routes } from '@angular/router';
|
||||
import {
|
||||
CanActivateCartGuard,
|
||||
CanActivateCartWithProcessIdGuard,
|
||||
@@ -11,13 +10,12 @@ import {
|
||||
CanActivateProductWithProcessIdGuard,
|
||||
IsAuthenticatedGuard,
|
||||
} from './guards';
|
||||
import { MainComponent } from './main.component';
|
||||
import {
|
||||
BranchSectionResolver,
|
||||
CustomerSectionResolver,
|
||||
ProcessIdResolver,
|
||||
} from './resolvers';
|
||||
import { TokenLoginComponent, TokenLoginModule } from './token-login';
|
||||
import { TokenLoginComponent } from './token-login';
|
||||
import {
|
||||
ActivateProcessIdGuard,
|
||||
ActivateProcessIdWithConfigKeyGuard,
|
||||
@@ -28,9 +26,8 @@ import {
|
||||
processResolverFn,
|
||||
hasTabIdGuard,
|
||||
} from '@isa/core/tabs';
|
||||
import { provideScrollPositionRestoration } from '@isa/utils/scroll-position';
|
||||
|
||||
const routes: Routes = [
|
||||
export const routes: Routes = [
|
||||
{ path: '', redirectTo: 'kunde/dashboard', pathMatch: 'full' },
|
||||
{
|
||||
path: 'login',
|
||||
@@ -45,7 +42,6 @@ const routes: Routes = [
|
||||
children: [
|
||||
{
|
||||
path: 'kunde',
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
@@ -72,8 +68,6 @@ const routes: Routes = [
|
||||
processId: ProcessIdResolver,
|
||||
},
|
||||
},
|
||||
// TODO: Check if order and :processId/order is still being used
|
||||
// If not, remove these routes and the related guards and resolvers
|
||||
{
|
||||
path: 'order',
|
||||
loadChildren: () =>
|
||||
@@ -122,7 +116,6 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'pickup-shelf',
|
||||
canActivate: [ActivateProcessIdGuard],
|
||||
// NOTE: This is a workaround for the canActivate guard not being called
|
||||
loadChildren: () =>
|
||||
import('@page/pickup-shelf').then((m) => m.PickupShelfOutModule),
|
||||
},
|
||||
@@ -141,7 +134,6 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'filiale',
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'task-calendar',
|
||||
@@ -154,7 +146,6 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'pickup-shelf',
|
||||
canActivate: [ActivateProcessIdWithConfigKeyGuard('pickupShelf')],
|
||||
// NOTE: This is a workaround for the canActivate guard not being called
|
||||
loadChildren: () =>
|
||||
import('@page/pickup-shelf').then((m) => m.PickupShelfInModule),
|
||||
},
|
||||
@@ -188,7 +179,6 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: ':tabId',
|
||||
component: MainComponent,
|
||||
resolve: { process: processResolverFn, tab: tabResolverFn },
|
||||
canActivate: [IsAuthenticatedGuard, hasTabIdGuard],
|
||||
children: [
|
||||
@@ -218,7 +208,6 @@ const routes: Routes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
path: 'return',
|
||||
loadChildren: () =>
|
||||
@@ -246,16 +235,3 @@ const routes: Routes = [
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot(routes, {
|
||||
bindToComponentInputs: true,
|
||||
enableTracing: false,
|
||||
}),
|
||||
TokenLoginModule,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
providers: [provideScrollPositionRestoration()],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
10
apps/isa-app/src/app/app.ts
Normal file
10
apps/isa-app/src/app/app.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.html',
|
||||
styleUrls: ['./app.css'],
|
||||
imports: [RouterOutlet],
|
||||
})
|
||||
export class App {}
|
||||
@@ -1,3 +0,0 @@
|
||||
<shell-root>
|
||||
<router-outlet></router-outlet>
|
||||
</shell-root>
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-main',
|
||||
templateUrl: 'main.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false,
|
||||
})
|
||||
export class MainComponent {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
export * from './token-login.component';
|
||||
export * from './token-login.module';
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AuthService } from '@core/auth';
|
||||
|
||||
@Component({
|
||||
selector: 'app-token-login',
|
||||
templateUrl: 'token-login.component.html',
|
||||
styleUrls: ['token-login.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false,
|
||||
})
|
||||
export class TokenLoginComponent implements OnInit {
|
||||
constructor(
|
||||
private _route: ActivatedRoute,
|
||||
private _authService: AuthService,
|
||||
private _router: Router,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (this._route.snapshot.params.token && !this._authService.isAuthenticated()) {
|
||||
this._authService.setKeyCardToken(this._route.snapshot.params.token);
|
||||
this._authService.login();
|
||||
} else if (!this._authService.isAuthenticated()) {
|
||||
this._authService.login();
|
||||
} else if (this._authService.isAuthenticated()) {
|
||||
this._router.navigate(['/']);
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AuthService } from '@core/auth';
|
||||
|
||||
@Component({
|
||||
selector: 'app-token-login',
|
||||
templateUrl: 'token-login.component.html',
|
||||
styleUrls: ['token-login.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TokenLoginComponent implements OnInit {
|
||||
constructor(
|
||||
private _route: ActivatedRoute,
|
||||
private _authService: AuthService,
|
||||
private _router: Router,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (
|
||||
this._route.snapshot.params.token &&
|
||||
!this._authService.isAuthenticated()
|
||||
) {
|
||||
this._authService.setKeyCardToken(this._route.snapshot.params.token);
|
||||
this._authService.login();
|
||||
} else if (!this._authService.isAuthenticated()) {
|
||||
this._authService.login();
|
||||
} else if (this._authService.isAuthenticated()) {
|
||||
this._router.navigate(['/']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { TokenLoginComponent } from './token-login.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: [TokenLoginComponent],
|
||||
declarations: [TokenLoginComponent],
|
||||
})
|
||||
export class TokenLoginModule {}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { applicationReducer } from './store';
|
||||
import { ApplicationService } from './application.service';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
})
|
||||
export class CoreApplicationModule {
|
||||
static forRoot(): ModuleWithProviders<CoreApplicationModule> {
|
||||
return {
|
||||
ngModule: RootCoreApplicationModule,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [StoreModule.forFeature('core-application', applicationReducer)],
|
||||
providers: [ApplicationService],
|
||||
})
|
||||
export class RootCoreApplicationModule {}
|
||||
@@ -1,337 +0,0 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, of, firstValueFrom } from 'rxjs';
|
||||
import { map, filter, withLatestFrom } from 'rxjs/operators';
|
||||
import { BranchDTO } from '@generated/swagger/checkout-api';
|
||||
import { isBoolean, isNumber } from '@utils/common';
|
||||
import { ApplicationService } from './application.service';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { ApplicationProcess } from './defs/application-process';
|
||||
import { Tab, TabMetadata } from '@isa/core/tabs';
|
||||
import { toObservable } from '@angular/core/rxjs-interop';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { removeProcess } from './store/application.actions';
|
||||
|
||||
/**
|
||||
* Adapter service that bridges the old ApplicationService interface with the new TabService.
|
||||
*
|
||||
* This adapter allows existing code that depends on ApplicationService to work with the new
|
||||
* TabService without requiring immediate code changes. It maps ApplicationProcess concepts
|
||||
* to Tab entities, storing process-specific data in tab metadata.
|
||||
*
|
||||
* Key mappings:
|
||||
* - ApplicationProcess.id <-> Tab.id
|
||||
* - ApplicationProcess.name <-> Tab.name
|
||||
* - ApplicationProcess metadata (section, type, etc.) <-> Tab.metadata with 'process_' prefix
|
||||
* - ApplicationProcess.data <-> Tab.metadata with 'data_' prefix
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Inject the adapter instead of the original service
|
||||
* constructor(private applicationService: ApplicationServiceAdapter) {}
|
||||
*
|
||||
* // Use the same API as before
|
||||
* const process = await this.applicationService.createCustomerProcess();
|
||||
* this.applicationService.activateProcess(process.id);
|
||||
* ```
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ApplicationServiceAdapter extends ApplicationService {
|
||||
#store = inject(Store);
|
||||
|
||||
#tabService = inject(TabService);
|
||||
|
||||
#activatedProcessId$ = toObservable(this.#tabService.activatedTabId);
|
||||
|
||||
#tabs$ = toObservable(this.#tabService.entities);
|
||||
|
||||
#processes$ = this.#tabs$.pipe(
|
||||
map((tabs) => tabs.map((tab) => this.mapTabToProcess(tab))),
|
||||
);
|
||||
|
||||
#section = new BehaviorSubject<'customer' | 'branch'>('customer');
|
||||
|
||||
readonly REGEX_PROCESS_NAME = /^Vorgang \d+$/;
|
||||
|
||||
get activatedProcessId() {
|
||||
return this.#tabService.activatedTabId();
|
||||
}
|
||||
|
||||
get activatedProcessId$() {
|
||||
return this.#activatedProcessId$;
|
||||
}
|
||||
|
||||
getProcesses$(
|
||||
section?: 'customer' | 'branch',
|
||||
): Observable<ApplicationProcess[]> {
|
||||
return this.#processes$.pipe(
|
||||
map((processes) =>
|
||||
processes.filter((process) =>
|
||||
section ? process.section === section : true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getProcessById$(processId: number): Observable<ApplicationProcess> {
|
||||
return this.#processes$.pipe(
|
||||
map((processes) => processes.find((process) => process.id === processId)),
|
||||
);
|
||||
}
|
||||
|
||||
getSection$(): Observable<'customer' | 'branch'> {
|
||||
return this.#section.asObservable();
|
||||
}
|
||||
|
||||
getTitle$(): Observable<'Kundenbereich' | 'Filialbereich'> {
|
||||
return this.getSection$().pipe(
|
||||
map((section) =>
|
||||
section === 'customer' ? 'Kundenbereich' : 'Filialbereich',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
getActivatedProcessId$(): Observable<number> {
|
||||
return this.activatedProcessId$;
|
||||
}
|
||||
|
||||
activateProcess(activatedProcessId: number): void {
|
||||
this.#tabService.activateTab(activatedProcessId);
|
||||
}
|
||||
|
||||
removeProcess(processId: number): void {
|
||||
this.#tabService.removeTab(processId);
|
||||
this.#store.dispatch(removeProcess({ processId }));
|
||||
}
|
||||
|
||||
patchProcess(processId: number, changes: Partial<ApplicationProcess>): void {
|
||||
const tabChanges: {
|
||||
name?: string;
|
||||
tags?: string[];
|
||||
metadata?: Record<string, unknown>;
|
||||
} = {};
|
||||
|
||||
if (changes.name) {
|
||||
tabChanges.name = changes.name;
|
||||
}
|
||||
|
||||
// Store other ApplicationProcess properties in metadata
|
||||
const metadataKeys = [
|
||||
'section',
|
||||
'type',
|
||||
'closeable',
|
||||
'confirmClosing',
|
||||
'created',
|
||||
'activated',
|
||||
'data',
|
||||
];
|
||||
metadataKeys.forEach((key) => {
|
||||
if (tabChanges.metadata === undefined) {
|
||||
tabChanges.metadata = {};
|
||||
}
|
||||
|
||||
if (changes[key as keyof ApplicationProcess] !== undefined) {
|
||||
tabChanges.metadata[`process_${key}`] =
|
||||
changes[key as keyof ApplicationProcess];
|
||||
}
|
||||
});
|
||||
|
||||
// Apply the changes to the tab
|
||||
this.#tabService.patchTab(processId, tabChanges);
|
||||
}
|
||||
|
||||
patchProcessData(processId: number, data: Record<string, unknown>): void {
|
||||
const currentProcess = this.#tabService.entityMap()[processId];
|
||||
const currentData: TabMetadata =
|
||||
(currentProcess?.metadata?.['process_data'] as TabMetadata) ?? {};
|
||||
|
||||
this.#tabService.patchTab(processId, {
|
||||
metadata: { [`process_data`]: { ...currentData, ...data } },
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedBranch$(): Observable<BranchDTO> {
|
||||
return this.#processes$.pipe(
|
||||
withLatestFrom(this.#activatedProcessId$),
|
||||
map(([processes, activatedProcessId]) =>
|
||||
processes.find((process) => process.id === activatedProcessId),
|
||||
),
|
||||
filter((process): process is ApplicationProcess => !!process),
|
||||
map((process) => process.data?.selectedBranch as BranchDTO),
|
||||
);
|
||||
}
|
||||
|
||||
async createCustomerProcess(processId?: number): Promise<ApplicationProcess> {
|
||||
const processes = await firstValueFrom(this.getProcesses$('customer'));
|
||||
|
||||
const processIds = processes
|
||||
.filter((x) => this.REGEX_PROCESS_NAME.test(x.name))
|
||||
.map((x) => +x.name.split(' ')[1]);
|
||||
|
||||
const maxId = processIds.length > 0 ? Math.max(...processIds) : 0;
|
||||
|
||||
const process: ApplicationProcess = {
|
||||
id: processId ?? Date.now(),
|
||||
type: 'cart',
|
||||
name: `Vorgang ${maxId + 1}`,
|
||||
section: 'customer',
|
||||
closeable: true,
|
||||
};
|
||||
|
||||
await this.createProcess(process);
|
||||
return process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ApplicationProcess by first creating a Tab and then storing
|
||||
* process-specific properties in the tab's metadata.
|
||||
*
|
||||
* @param process - The ApplicationProcess to create
|
||||
* @throws {Error} If process ID already exists or is invalid
|
||||
*/
|
||||
async createProcess(process: ApplicationProcess): Promise<void> {
|
||||
const existingProcess = this.#tabService.entityMap()[process.id];
|
||||
if (existingProcess?.id === process?.id) {
|
||||
throw new Error('Process Id existiert bereits');
|
||||
}
|
||||
|
||||
if (!isNumber(process.id)) {
|
||||
throw new Error('Process Id nicht gesetzt');
|
||||
}
|
||||
|
||||
if (!isBoolean(process.closeable)) {
|
||||
process.closeable = true;
|
||||
}
|
||||
|
||||
if (!isBoolean(process.confirmClosing)) {
|
||||
process.confirmClosing = true;
|
||||
}
|
||||
|
||||
process.created = this.createTimestamp();
|
||||
process.activated = 0;
|
||||
|
||||
// Create tab with process data and preserve the process ID
|
||||
this.#tabService.addTab({
|
||||
id: process.id,
|
||||
name: process.name,
|
||||
tags: [process.section, process.type].filter(Boolean),
|
||||
metadata: {
|
||||
process_section: process.section,
|
||||
process_type: process.type,
|
||||
process_closeable: process.closeable,
|
||||
process_confirmClosing: process.confirmClosing,
|
||||
process_created: process.created,
|
||||
process_activated: process.activated,
|
||||
process_data: process.data,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setSection(section: 'customer' | 'branch'): void {
|
||||
this.#section.next(section);
|
||||
}
|
||||
|
||||
getLastActivatedProcessWithSectionAndType$(
|
||||
section: 'customer' | 'branch',
|
||||
type: string,
|
||||
): Observable<ApplicationProcess> {
|
||||
return this.getProcesses$(section).pipe(
|
||||
map((processes) =>
|
||||
processes
|
||||
?.filter((process) => process.type === type)
|
||||
?.reduce((latest, current) => {
|
||||
if (!latest) {
|
||||
return current;
|
||||
}
|
||||
return latest?.activated > current?.activated ? latest : current;
|
||||
}, undefined),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getLastActivatedProcessWithSection$(
|
||||
section: 'customer' | 'branch',
|
||||
): Observable<ApplicationProcess> {
|
||||
return this.getProcesses$(section).pipe(
|
||||
map((processes) =>
|
||||
processes?.reduce((latest, current) => {
|
||||
if (!latest) {
|
||||
return current;
|
||||
}
|
||||
return latest?.activated > current?.activated ? latest : current;
|
||||
}, undefined),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps Tab entities to ApplicationProcess objects by extracting process-specific
|
||||
* metadata and combining it with tab properties.
|
||||
*
|
||||
* @param tab - The tab entity to convert
|
||||
* @returns The corresponding ApplicationProcess object
|
||||
*/
|
||||
private mapTabToProcess(tab: Tab): ApplicationProcess {
|
||||
return {
|
||||
id: tab.id,
|
||||
name: tab.name,
|
||||
created:
|
||||
this.getMetadataValue<number>(tab.metadata, 'process_created') ??
|
||||
tab.createdAt,
|
||||
activated:
|
||||
this.getMetadataValue<number>(tab.metadata, 'process_activated') ??
|
||||
tab.activatedAt ??
|
||||
0,
|
||||
section:
|
||||
this.getMetadataValue<'customer' | 'branch'>(
|
||||
tab.metadata,
|
||||
'process_section',
|
||||
) ?? 'customer',
|
||||
type: this.getMetadataValue<string>(tab.metadata, 'process_type'),
|
||||
closeable:
|
||||
this.getMetadataValue<boolean>(tab.metadata, 'process_closeable') ??
|
||||
true,
|
||||
confirmClosing:
|
||||
this.getMetadataValue<boolean>(
|
||||
tab.metadata,
|
||||
'process_confirmClosing',
|
||||
) ?? true,
|
||||
data: this.extractDataFromMetadata(tab.metadata),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts ApplicationProcess data properties from tab metadata.
|
||||
* Data properties are stored with a 'data_' prefix in tab metadata.
|
||||
*
|
||||
* @param metadata - The tab metadata object
|
||||
* @returns The extracted data object or undefined if no data properties exist
|
||||
*/
|
||||
private extractDataFromMetadata(
|
||||
metadata: TabMetadata,
|
||||
): Record<string, unknown> | undefined {
|
||||
// Return the complete data object stored under 'process_data'
|
||||
const processData = metadata?.['process_data'];
|
||||
|
||||
if (
|
||||
processData &&
|
||||
typeof processData === 'object' &&
|
||||
processData !== null
|
||||
) {
|
||||
return processData as Record<string, unknown>;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getMetadataValue<T>(
|
||||
metadata: TabMetadata,
|
||||
key: string,
|
||||
): T | undefined {
|
||||
return metadata?.[key] as T | undefined;
|
||||
}
|
||||
|
||||
private createTimestamp(): number {
|
||||
return Date.now();
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
// import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator';
|
||||
// import { Store } from '@ngrx/store';
|
||||
// import { Observable, of } from 'rxjs';
|
||||
// import { first } from 'rxjs/operators';
|
||||
// import { ApplicationProcess } from './defs';
|
||||
|
||||
// import { ApplicationService } from './application.service';
|
||||
// import * as actions from './store/application.actions';
|
||||
|
||||
// describe('ApplicationService', () => {
|
||||
// let spectator: SpectatorService<ApplicationService>;
|
||||
// let store: SpyObject<Store>;
|
||||
// const createService = createServiceFactory({
|
||||
// service: ApplicationService,
|
||||
// mocks: [Store],
|
||||
// });
|
||||
|
||||
// beforeEach(() => {
|
||||
// spectator = createService({});
|
||||
// store = spectator.inject(Store);
|
||||
// });
|
||||
|
||||
// it('should be created', () => {
|
||||
// expect(spectator.service).toBeTruthy();
|
||||
// });
|
||||
|
||||
// describe('activatedProcessId$', () => {
|
||||
// it('should return an observable', () => {
|
||||
// expect(spectator.service.activatedProcessId$).toBeInstanceOf(Observable);
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('activatedProcessId', () => {
|
||||
// it('should return the process id as a number', () => {
|
||||
// spyOnProperty(spectator.service['activatedProcessIdSubject'] as any, 'value').and.returnValue(2);
|
||||
// expect(spectator.service.activatedProcessId).toBe(2);
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('getProcesses$()', () => {
|
||||
// it('should call select on store and return all selected processes', async () => {
|
||||
// const processes: ApplicationProcess[] = [
|
||||
// { id: 1, name: 'Vorgang', type: 'cart', section: 'customer', data: { count: 1 } },
|
||||
// { id: 2, name: 'Vorgang', type: 'task-calendar', section: 'branch' },
|
||||
// ];
|
||||
// store.select.and.returnValue(of(processes));
|
||||
// const result = await spectator.service.getProcesses$().pipe(first()).toPromise();
|
||||
// expect(result).toEqual(processes);
|
||||
// expect(store.select).toHaveBeenCalled();
|
||||
// });
|
||||
|
||||
// it('should call select on store and return all section customer processes', async () => {
|
||||
// const processes: ApplicationProcess[] = [
|
||||
// { id: 1, name: 'Vorgang', type: 'cart', section: 'customer', data: { count: 1 } },
|
||||
// { id: 2, name: 'Vorgang', type: 'task-calendar', section: 'branch' },
|
||||
// ];
|
||||
// store.select.and.returnValue(of(processes));
|
||||
// const result = await spectator.service.getProcesses$('customer').pipe(first()).toPromise();
|
||||
// expect(result).toEqual([processes[0]]);
|
||||
// expect(store.select).toHaveBeenCalled();
|
||||
// });
|
||||
|
||||
// it('should call select on store and return all section branch processes', async () => {
|
||||
// const processes: ApplicationProcess[] = [
|
||||
// { id: 1, name: 'Vorgang', type: 'cart', section: 'customer', data: { count: 1 } },
|
||||
// { id: 2, name: 'Vorgang', type: 'task-calendar', section: 'branch' },
|
||||
// ];
|
||||
// store.select.and.returnValue(of(processes));
|
||||
// const result = await spectator.service.getProcesses$('branch').pipe(first()).toPromise();
|
||||
// expect(result).toEqual([processes[1]]);
|
||||
// expect(store.select).toHaveBeenCalled();
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('getProcessById$()', () => {
|
||||
// it('should return the process by id', async () => {
|
||||
// const processes: ApplicationProcess[] = [
|
||||
// { id: 1, name: 'Vorgang 1', section: 'customer' },
|
||||
// { id: 2, name: 'Vorgang 2', section: 'customer' },
|
||||
// ];
|
||||
// spyOn(spectator.service, 'getProcesses$').and.returnValue(of(processes));
|
||||
|
||||
// const process = await spectator.service.getProcessById$(1).toPromise();
|
||||
// expect(process.id).toBe(1);
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('getSection$()', () => {
|
||||
// it('should return the selected section branch', async () => {
|
||||
// const section = 'branch';
|
||||
// store.select.and.returnValue(of(section));
|
||||
// const result = await spectator.service.getSection$().pipe(first()).toPromise();
|
||||
// expect(result).toEqual(section);
|
||||
// expect(store.select).toHaveBeenCalled();
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('getActivatedProcessId$', () => {
|
||||
// it('should return the current selected activated process id', async () => {
|
||||
// const activatedProcessId = 2;
|
||||
// store.select.and.returnValue(of({ id: activatedProcessId }));
|
||||
// const result = await spectator.service.getActivatedProcessId$().pipe(first()).toPromise();
|
||||
// expect(result).toEqual(activatedProcessId);
|
||||
// expect(store.select).toHaveBeenCalled();
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('activateProcess()', () => {
|
||||
// it('should dispatch action setActivatedProcess with argument activatedProcessId and action type', () => {
|
||||
// const activatedProcessId = 2;
|
||||
// spectator.service.activateProcess(activatedProcessId);
|
||||
// expect(store.dispatch).toHaveBeenCalledWith({ activatedProcessId, type: actions.setActivatedProcess.type });
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('removeProcess()', () => {
|
||||
// it('should dispatch action removeProcess with argument processId and action type', () => {
|
||||
// const processId = 2;
|
||||
// spectator.service.removeProcess(processId);
|
||||
// expect(store.dispatch).toHaveBeenCalledWith({ processId, type: actions.removeProcess.type });
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('createProcess()', () => {
|
||||
// it('should dispatch action addProcess with process', async () => {
|
||||
// const process: ApplicationProcess = {
|
||||
// id: 1,
|
||||
// name: 'Vorgang 1',
|
||||
// section: 'customer',
|
||||
// type: 'cart',
|
||||
// };
|
||||
|
||||
// const timestamp = 100;
|
||||
// spyOn(spectator.service as any, '_createTimestamp').and.returnValue(timestamp);
|
||||
// spyOn(spectator.service, 'getProcessById$').and.returnValue(of(undefined));
|
||||
// await spectator.service.createProcess(process);
|
||||
|
||||
// expect(store.dispatch).toHaveBeenCalledWith({
|
||||
// type: actions.addProcess.type,
|
||||
// process: {
|
||||
// ...process,
|
||||
// activated: 0,
|
||||
// created: timestamp,
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
|
||||
// it('should throw an error if the process id is already existing', async () => {
|
||||
// const process: ApplicationProcess = {
|
||||
// id: 1,
|
||||
// name: 'Vorgang 1',
|
||||
// section: 'customer',
|
||||
// type: 'cart',
|
||||
// };
|
||||
// spyOn(spectator.service, 'getProcessById$').and.returnValue(of(process));
|
||||
// await expectAsync(spectator.service.createProcess(process)).toBeRejectedWithError('Process Id existiert bereits');
|
||||
// });
|
||||
|
||||
// it('should throw an error if the process id is not a number', async () => {
|
||||
// const process: ApplicationProcess = {
|
||||
// id: undefined,
|
||||
// name: 'Vorgang 1',
|
||||
// section: 'customer',
|
||||
// type: 'cart',
|
||||
// };
|
||||
// spyOn(spectator.service, 'getProcessById$').and.returnValue(of({ id: 5, name: 'Vorgang 2', section: 'customer' }));
|
||||
// await expectAsync(spectator.service.createProcess(process)).toBeRejectedWithError('Process Id nicht gesetzt');
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('patchProcess', () => {
|
||||
// it('should dispatch action patchProcess with changes', async () => {
|
||||
// const process: ApplicationProcess = {
|
||||
// id: 1,
|
||||
// name: 'Vorgang 1',
|
||||
// section: 'customer',
|
||||
// type: 'cart',
|
||||
// };
|
||||
|
||||
// await spectator.service.patchProcess(process.id, process);
|
||||
|
||||
// expect(store.dispatch).toHaveBeenCalledWith({
|
||||
// type: actions.patchProcess.type,
|
||||
// processId: process.id,
|
||||
// changes: {
|
||||
// ...process,
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('setSection()', () => {
|
||||
// it('should dispatch action setSection with argument section and action type', () => {
|
||||
// const section = 'customer';
|
||||
// spectator.service.setSection(section);
|
||||
// expect(store.dispatch).toHaveBeenCalledWith({ section, type: actions.setSection.type });
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('getLastActivatedProcessWithSectionAndType()', () => {
|
||||
// it('should return the last activated process by section and type', async () => {
|
||||
// const processes: ApplicationProcess[] = [
|
||||
// { id: 1, name: 'Vorgang 1', section: 'customer', type: 'cart', activated: 100 },
|
||||
// { id: 2, name: 'Vorgang 2', section: 'customer', type: 'cart', activated: 200 },
|
||||
// { id: 3, name: 'Vorgang 3', section: 'customer', type: 'goodsOut', activated: 300 },
|
||||
// ];
|
||||
// spyOn(spectator.service, 'getProcesses$').and.returnValue(of(processes));
|
||||
|
||||
// expect(await spectator.service.getLastActivatedProcessWithSectionAndType$('customer', 'cart').pipe(first()).toPromise()).toBe(
|
||||
// processes[1]
|
||||
// );
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('getLastActivatedProcessWithSection()', () => {
|
||||
// it('should return the last activated process by section', async () => {
|
||||
// const processes: ApplicationProcess[] = [
|
||||
// { id: 1, name: 'Vorgang 1', section: 'customer', activated: 100 },
|
||||
// { id: 2, name: 'Vorgang 2', section: 'customer', activated: 200 },
|
||||
// { id: 3, name: 'Vorgang 3', section: 'customer', activated: 300 },
|
||||
// ];
|
||||
// spyOn(spectator.service, 'getProcesses$').and.returnValue(of(processes));
|
||||
|
||||
// expect(await spectator.service.getLastActivatedProcessWithSection$('customer').pipe(first()).toPromise()).toBe(processes[2]);
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('_createTimestamp', () => {
|
||||
// it('should return the current timestamp in ms', () => {
|
||||
// expect(spectator.service['_createTimestamp']()).toBeCloseTo(Date.now());
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
@@ -1,41 +1,68 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, of, firstValueFrom } from 'rxjs';
|
||||
import { map, filter, withLatestFrom } from 'rxjs/operators';
|
||||
import { BranchDTO } from '@generated/swagger/checkout-api';
|
||||
import { isBoolean, isNumber } from '@utils/common';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { first, map, switchMap } from 'rxjs/operators';
|
||||
import { ApplicationProcess } from './defs';
|
||||
import {
|
||||
removeProcess,
|
||||
selectSection,
|
||||
selectProcesses,
|
||||
setSection,
|
||||
addProcess,
|
||||
setActivatedProcess,
|
||||
selectActivatedProcess,
|
||||
patchProcess,
|
||||
patchProcessData,
|
||||
selectTitle,
|
||||
setTitle,
|
||||
} from './store';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { ApplicationProcess } from './defs/application-process';
|
||||
import { Tab, TabMetadata } from '@isa/core/tabs';
|
||||
import { toObservable } from '@angular/core/rxjs-interop';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { removeProcess } from './store/application.actions';
|
||||
|
||||
@Injectable()
|
||||
/**
|
||||
* Adapter service that bridges the old ApplicationService interface with the new TabService.
|
||||
*
|
||||
* This adapter allows existing code that depends on ApplicationService to work with the new
|
||||
* TabService without requiring immediate code changes. It maps ApplicationProcess concepts
|
||||
* to Tab entities, storing process-specific data in tab metadata.
|
||||
*
|
||||
* Key mappings:
|
||||
* - ApplicationProcess.id <-> Tab.id
|
||||
* - ApplicationProcess.name <-> Tab.name
|
||||
* - ApplicationProcess metadata (section, type, etc.) <-> Tab.metadata with 'process_' prefix
|
||||
* - ApplicationProcess.data <-> Tab.metadata with 'data_' prefix
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Inject the adapter instead of the original service
|
||||
* constructor(private applicationService: ApplicationServiceAdapter) {}
|
||||
*
|
||||
* // Use the same API as before
|
||||
* const process = await this.applicationService.createCustomerProcess();
|
||||
* this.applicationService.activateProcess(process.id);
|
||||
* ```
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ApplicationService {
|
||||
private activatedProcessIdSubject = new BehaviorSubject<number>(undefined);
|
||||
#store = inject(Store);
|
||||
|
||||
#tabService = inject(TabService);
|
||||
|
||||
#activatedProcessId$ = toObservable(this.#tabService.activatedTabId);
|
||||
|
||||
#tabs$ = toObservable(this.#tabService.entities);
|
||||
|
||||
#processes$ = this.#tabs$.pipe(
|
||||
map((tabs) => tabs.map((tab) => this.mapTabToProcess(tab))),
|
||||
);
|
||||
|
||||
#section = new BehaviorSubject<'customer' | 'branch'>('customer');
|
||||
|
||||
readonly REGEX_PROCESS_NAME = /^Vorgang \d+$/;
|
||||
|
||||
get activatedProcessId() {
|
||||
return this.activatedProcessIdSubject.value;
|
||||
return this.#tabService.activatedTabId();
|
||||
}
|
||||
|
||||
get activatedProcessId$() {
|
||||
return this.activatedProcessIdSubject.asObservable();
|
||||
return this.#activatedProcessId$;
|
||||
}
|
||||
|
||||
constructor(private store: Store) {}
|
||||
|
||||
getProcesses$(section?: 'customer' | 'branch') {
|
||||
const processes$ = this.store.select(selectProcesses);
|
||||
return processes$.pipe(
|
||||
getProcesses$(
|
||||
section?: 'customer' | 'branch',
|
||||
): Observable<ApplicationProcess[]> {
|
||||
return this.#processes$.pipe(
|
||||
map((processes) =>
|
||||
processes.filter((process) =>
|
||||
section ? process.section === section : true,
|
||||
@@ -45,69 +72,96 @@ export class ApplicationService {
|
||||
}
|
||||
|
||||
getProcessById$(processId: number): Observable<ApplicationProcess> {
|
||||
return this.getProcesses$().pipe(
|
||||
return this.#processes$.pipe(
|
||||
map((processes) => processes.find((process) => process.id === processId)),
|
||||
);
|
||||
}
|
||||
|
||||
getSection$() {
|
||||
return this.store.select(selectSection);
|
||||
getSection$(): Observable<'customer' | 'branch'> {
|
||||
return this.#section.asObservable();
|
||||
}
|
||||
|
||||
getTitle$() {
|
||||
getTitle$(): Observable<'Kundenbereich' | 'Filialbereich'> {
|
||||
return this.getSection$().pipe(
|
||||
map((section) => {
|
||||
return section === 'customer' ? 'Kundenbereich' : 'Filialbereich';
|
||||
}),
|
||||
map((section) =>
|
||||
section === 'customer' ? 'Kundenbereich' : 'Filialbereich',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
getActivatedProcessId$() {
|
||||
return this.store
|
||||
.select(selectActivatedProcess)
|
||||
.pipe(map((process) => process?.id));
|
||||
getActivatedProcessId$(): Observable<number> {
|
||||
return this.activatedProcessId$;
|
||||
}
|
||||
|
||||
activateProcess(activatedProcessId: number) {
|
||||
this.store.dispatch(setActivatedProcess({ activatedProcessId }));
|
||||
this.activatedProcessIdSubject.next(activatedProcessId);
|
||||
activateProcess(activatedProcessId: number): void {
|
||||
this.#tabService.activateTab(activatedProcessId);
|
||||
}
|
||||
|
||||
removeProcess(processId: number) {
|
||||
this.store.dispatch(removeProcess({ processId }));
|
||||
removeProcess(processId: number): void {
|
||||
this.#tabService.removeTab(processId);
|
||||
this.#store.dispatch(removeProcess({ processId }));
|
||||
}
|
||||
|
||||
patchProcess(processId: number, changes: Partial<ApplicationProcess>) {
|
||||
this.store.dispatch(patchProcess({ processId, changes }));
|
||||
}
|
||||
patchProcess(processId: number, changes: Partial<ApplicationProcess>): void {
|
||||
const tabChanges: {
|
||||
name?: string;
|
||||
tags?: string[];
|
||||
metadata?: Record<string, unknown>;
|
||||
} = {};
|
||||
|
||||
patchProcessData(processId: number, data: Record<string, any>) {
|
||||
this.store.dispatch(patchProcessData({ processId, data }));
|
||||
}
|
||||
|
||||
getSelectedBranch$(processId?: number): Observable<BranchDTO> {
|
||||
if (!processId) {
|
||||
return this.activatedProcessId$.pipe(
|
||||
switchMap((processId) =>
|
||||
this.getProcessById$(processId).pipe(
|
||||
map((process) => process?.data?.selectedBranch),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (changes.name) {
|
||||
tabChanges.name = changes.name;
|
||||
}
|
||||
|
||||
return this.getProcessById$(processId).pipe(
|
||||
map((process) => process?.data?.selectedBranch),
|
||||
// Store other ApplicationProcess properties in metadata
|
||||
const metadataKeys = [
|
||||
'section',
|
||||
'type',
|
||||
'closeable',
|
||||
'confirmClosing',
|
||||
'created',
|
||||
'activated',
|
||||
'data',
|
||||
];
|
||||
metadataKeys.forEach((key) => {
|
||||
if (tabChanges.metadata === undefined) {
|
||||
tabChanges.metadata = {};
|
||||
}
|
||||
|
||||
if (changes[key as keyof ApplicationProcess] !== undefined) {
|
||||
tabChanges.metadata[`process_${key}`] =
|
||||
changes[key as keyof ApplicationProcess];
|
||||
}
|
||||
});
|
||||
|
||||
// Apply the changes to the tab
|
||||
this.#tabService.patchTab(processId, tabChanges);
|
||||
}
|
||||
|
||||
patchProcessData(processId: number, data: Record<string, unknown>): void {
|
||||
const currentProcess = this.#tabService.entityMap()[processId];
|
||||
const currentData: TabMetadata =
|
||||
(currentProcess?.metadata?.['process_data'] as TabMetadata) ?? {};
|
||||
|
||||
this.#tabService.patchTab(processId, {
|
||||
metadata: { [`process_data`]: { ...currentData, ...data } },
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedBranch$(): Observable<BranchDTO> {
|
||||
return this.#processes$.pipe(
|
||||
withLatestFrom(this.#activatedProcessId$),
|
||||
map(([processes, activatedProcessId]) =>
|
||||
processes.find((process) => process.id === activatedProcessId),
|
||||
),
|
||||
filter((process): process is ApplicationProcess => !!process),
|
||||
map((process) => process.data?.selectedBranch as BranchDTO),
|
||||
);
|
||||
}
|
||||
|
||||
readonly REGEX_PROCESS_NAME = /^Vorgang \d+$/;
|
||||
|
||||
async createCustomerProcess(processId?: number): Promise<ApplicationProcess> {
|
||||
const processes = await this.getProcesses$('customer')
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
const processes = await firstValueFrom(this.getProcesses$('customer'));
|
||||
|
||||
const processIds = processes
|
||||
.filter((x) => this.REGEX_PROCESS_NAME.test(x.name))
|
||||
@@ -124,14 +178,18 @@ export class ApplicationService {
|
||||
};
|
||||
|
||||
await this.createProcess(process);
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
async createProcess(process: ApplicationProcess) {
|
||||
const existingProcess = await this.getProcessById$(process?.id)
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
/**
|
||||
* Creates a new ApplicationProcess by first creating a Tab and then storing
|
||||
* process-specific properties in the tab's metadata.
|
||||
*
|
||||
* @param process - The ApplicationProcess to create
|
||||
* @throws {Error} If process ID already exists or is invalid
|
||||
*/
|
||||
async createProcess(process: ApplicationProcess): Promise<void> {
|
||||
const existingProcess = this.#tabService.entityMap()[process.id];
|
||||
if (existingProcess?.id === process?.id) {
|
||||
throw new Error('Process Id existiert bereits');
|
||||
}
|
||||
@@ -148,13 +206,28 @@ export class ApplicationService {
|
||||
process.confirmClosing = true;
|
||||
}
|
||||
|
||||
process.created = this._createTimestamp();
|
||||
process.created = this.createTimestamp();
|
||||
process.activated = 0;
|
||||
this.store.dispatch(addProcess({ process }));
|
||||
|
||||
// Create tab with process data and preserve the process ID
|
||||
this.#tabService.addTab({
|
||||
id: process.id,
|
||||
name: process.name,
|
||||
tags: [process.section, process.type].filter(Boolean),
|
||||
metadata: {
|
||||
process_section: process.section,
|
||||
process_type: process.type,
|
||||
process_closeable: process.closeable,
|
||||
process_confirmClosing: process.confirmClosing,
|
||||
process_created: process.created,
|
||||
process_activated: process.activated,
|
||||
process_data: process.data,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setSection(section: 'customer' | 'branch') {
|
||||
this.store.dispatch(setSection({ section }));
|
||||
setSection(section: 'customer' | 'branch'): void {
|
||||
this.#section.next(section);
|
||||
}
|
||||
|
||||
getLastActivatedProcessWithSectionAndType$(
|
||||
@@ -190,7 +263,74 @@ export class ApplicationService {
|
||||
);
|
||||
}
|
||||
|
||||
private _createTimestamp() {
|
||||
/**
|
||||
* Maps Tab entities to ApplicationProcess objects by extracting process-specific
|
||||
* metadata and combining it with tab properties.
|
||||
*
|
||||
* @param tab - The tab entity to convert
|
||||
* @returns The corresponding ApplicationProcess object
|
||||
*/
|
||||
private mapTabToProcess(tab: Tab): ApplicationProcess {
|
||||
return {
|
||||
id: tab.id,
|
||||
name: tab.name,
|
||||
created:
|
||||
this.getMetadataValue<number>(tab.metadata, 'process_created') ??
|
||||
tab.createdAt,
|
||||
activated:
|
||||
this.getMetadataValue<number>(tab.metadata, 'process_activated') ??
|
||||
tab.activatedAt ??
|
||||
0,
|
||||
section:
|
||||
this.getMetadataValue<'customer' | 'branch'>(
|
||||
tab.metadata,
|
||||
'process_section',
|
||||
) ?? 'customer',
|
||||
type: this.getMetadataValue<string>(tab.metadata, 'process_type'),
|
||||
closeable:
|
||||
this.getMetadataValue<boolean>(tab.metadata, 'process_closeable') ??
|
||||
true,
|
||||
confirmClosing:
|
||||
this.getMetadataValue<boolean>(
|
||||
tab.metadata,
|
||||
'process_confirmClosing',
|
||||
) ?? true,
|
||||
data: this.extractDataFromMetadata(tab.metadata),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts ApplicationProcess data properties from tab metadata.
|
||||
* Data properties are stored with a 'data_' prefix in tab metadata.
|
||||
*
|
||||
* @param metadata - The tab metadata object
|
||||
* @returns The extracted data object or undefined if no data properties exist
|
||||
*/
|
||||
private extractDataFromMetadata(
|
||||
metadata: TabMetadata,
|
||||
): Record<string, unknown> | undefined {
|
||||
// Return the complete data object stored under 'process_data'
|
||||
const processData = metadata?.['process_data'];
|
||||
|
||||
if (
|
||||
processData &&
|
||||
typeof processData === 'object' &&
|
||||
processData !== null
|
||||
) {
|
||||
return processData as Record<string, unknown>;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getMetadataValue<T>(
|
||||
metadata: TabMetadata,
|
||||
key: string,
|
||||
): T | undefined {
|
||||
return metadata?.[key] as T | undefined;
|
||||
}
|
||||
|
||||
private createTimestamp(): number {
|
||||
return Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export * from './application.module';
|
||||
export * from './application.service';
|
||||
export * from './application.service-adapter';
|
||||
export * from './defs';
|
||||
export * from './store';
|
||||
export * from './application.service';
|
||||
export * from './defs';
|
||||
export * from './store/application.actions';
|
||||
|
||||
@@ -1,27 +1,8 @@
|
||||
import { createAction, props } from '@ngrx/store';
|
||||
import { ApplicationProcess } from '..';
|
||||
|
||||
const prefix = '[CORE-APPLICATION]';
|
||||
|
||||
export const setTitle = createAction(`${prefix} Set Title`, props<{ title: string }>());
|
||||
|
||||
export const setSection = createAction(`${prefix} Set Section`, props<{ section: 'customer' | 'branch' }>());
|
||||
|
||||
export const addProcess = createAction(`${prefix} Add Process`, props<{ process: ApplicationProcess }>());
|
||||
|
||||
export const removeProcess = createAction(`${prefix} Remove Process`, props<{ processId: number }>());
|
||||
|
||||
export const setActivatedProcess = createAction(
|
||||
`${prefix} Set Activated Process`,
|
||||
props<{ activatedProcessId: number }>(),
|
||||
);
|
||||
|
||||
export const patchProcess = createAction(
|
||||
`${prefix} Patch Process`,
|
||||
props<{ processId: number; changes: Partial<ApplicationProcess> }>(),
|
||||
);
|
||||
|
||||
export const patchProcessData = createAction(
|
||||
`${prefix} Patch Process Data`,
|
||||
props<{ processId: number; data: Record<string, any> }>(),
|
||||
);
|
||||
import { createAction, props } from '@ngrx/store';
|
||||
|
||||
const prefix = '[CORE-APPLICATION]';
|
||||
|
||||
export const removeProcess = createAction(
|
||||
`${prefix} Remove Process`,
|
||||
props<{ processId: number }>(),
|
||||
);
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
import { INITIAL_APPLICATION_STATE } from './application.state';
|
||||
import * as actions from './application.actions';
|
||||
import { applicationReducer } from './application.reducer';
|
||||
import { ApplicationProcess } from '../defs';
|
||||
import { ApplicationState } from './application.state';
|
||||
|
||||
describe('applicationReducer', () => {
|
||||
describe('setSection()', () => {
|
||||
it('should return modified state with section customer', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
|
||||
const action = actions.setSection({ section: 'customer' });
|
||||
const state = applicationReducer(initialState, action);
|
||||
|
||||
expect(state).toEqual({
|
||||
...initialState,
|
||||
section: 'customer',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return modified state with section branch', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
|
||||
const action = actions.setSection({ section: 'branch' });
|
||||
const state = applicationReducer(initialState, action);
|
||||
|
||||
expect(state).toEqual({
|
||||
...initialState,
|
||||
section: 'branch',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addProcess()', () => {
|
||||
it('should return modified state with new process if no processes are registered in the state', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
|
||||
const process: ApplicationProcess = {
|
||||
id: 1,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
data: {},
|
||||
};
|
||||
|
||||
const action = actions.addProcess({ process });
|
||||
const state = applicationReducer(initialState, action);
|
||||
expect(state.processes[0]).toEqual(process);
|
||||
});
|
||||
});
|
||||
|
||||
describe('patchProcess()', () => {
|
||||
it('should return modified state with updated process when id is found', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
|
||||
const process: ApplicationProcess = {
|
||||
id: 1,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
};
|
||||
|
||||
const action = actions.patchProcess({ processId: process.id, changes: { ...process, name: 'Test' } });
|
||||
const state = applicationReducer(
|
||||
{
|
||||
...initialState,
|
||||
processes: [process],
|
||||
},
|
||||
action,
|
||||
);
|
||||
expect(state.processes[0].name).toEqual('Test');
|
||||
});
|
||||
|
||||
it('should return unmodified state when id is not existing', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
|
||||
const process: ApplicationProcess = {
|
||||
id: 1,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
};
|
||||
|
||||
const action = actions.patchProcess({ processId: process.id, changes: { ...process, id: 2 } });
|
||||
const state = applicationReducer(
|
||||
{
|
||||
...initialState,
|
||||
processes: [process],
|
||||
},
|
||||
action,
|
||||
);
|
||||
expect(state.processes).toEqual([process]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeProcess()', () => {
|
||||
it('should return initial state if no processes are registered in the state', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
|
||||
const action = actions.removeProcess({ processId: 2 });
|
||||
const state = applicationReducer(initialState, action);
|
||||
expect(state).toEqual(initialState);
|
||||
});
|
||||
|
||||
it('should return the unmodified state if processId not found', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
const modifiedState: ApplicationState = {
|
||||
...initialState,
|
||||
section: 'customer',
|
||||
processes: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'goods-out',
|
||||
},
|
||||
] as ApplicationProcess[],
|
||||
};
|
||||
|
||||
const action = actions.removeProcess({ processId: 2 });
|
||||
const state = applicationReducer(modifiedState, action);
|
||||
expect(state).toEqual(modifiedState);
|
||||
});
|
||||
|
||||
it('should return modified state, after process gets removed', () => {
|
||||
const initialState = INITIAL_APPLICATION_STATE;
|
||||
const modifiedState: ApplicationState = {
|
||||
...initialState,
|
||||
section: 'customer',
|
||||
processes: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'goods-out',
|
||||
},
|
||||
] as ApplicationProcess[],
|
||||
};
|
||||
|
||||
const action = actions.removeProcess({ processId: 2 });
|
||||
const state = applicationReducer(modifiedState, action);
|
||||
expect(state.processes).toEqual([
|
||||
{
|
||||
id: 1,
|
||||
name: 'Vorgang',
|
||||
section: 'customer',
|
||||
type: 'cart',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setActivatedProcess()', () => {
|
||||
it('should return modified state with process.activated value', () => {
|
||||
const process: ApplicationProcess = {
|
||||
id: 3,
|
||||
name: 'Vorgang 3',
|
||||
section: 'customer',
|
||||
};
|
||||
const initialState: ApplicationState = {
|
||||
...INITIAL_APPLICATION_STATE,
|
||||
processes: [process],
|
||||
};
|
||||
|
||||
const action = actions.setActivatedProcess({ activatedProcessId: 3 });
|
||||
const state = applicationReducer(initialState, action);
|
||||
|
||||
expect(state.processes[0].activated).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return modified state without process.activated value when activatedProcessId doesnt exist', () => {
|
||||
const process: ApplicationProcess = {
|
||||
id: 1,
|
||||
name: 'Vorgang 3',
|
||||
section: 'customer',
|
||||
};
|
||||
const initialState: ApplicationState = {
|
||||
...INITIAL_APPLICATION_STATE,
|
||||
processes: [process],
|
||||
};
|
||||
|
||||
const action = actions.setActivatedProcess({ activatedProcessId: 3 });
|
||||
const state = applicationReducer(initialState, action);
|
||||
|
||||
expect(state.processes[0].activated).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import { Action, createReducer, on } from '@ngrx/store';
|
||||
import {
|
||||
setSection,
|
||||
addProcess,
|
||||
removeProcess,
|
||||
setActivatedProcess,
|
||||
patchProcess,
|
||||
patchProcessData,
|
||||
setTitle,
|
||||
} from './application.actions';
|
||||
import { ApplicationState, INITIAL_APPLICATION_STATE } from './application.state';
|
||||
|
||||
const _applicationReducer = createReducer(
|
||||
INITIAL_APPLICATION_STATE,
|
||||
on(setTitle, (state, { title }) => ({ ...state, title })),
|
||||
on(setSection, (state, { section }) => ({ ...state, section })),
|
||||
on(addProcess, (state, { process }) => ({ ...state, processes: [...state.processes, { data: {}, ...process }] })),
|
||||
on(removeProcess, (state, { processId }) => {
|
||||
const processes = state?.processes?.filter((process) => process.id !== processId) || [];
|
||||
return { ...state, processes };
|
||||
}),
|
||||
on(setActivatedProcess, (state, { activatedProcessId }) => {
|
||||
const processes = state.processes.map((process) => {
|
||||
if (process.id === activatedProcessId) {
|
||||
return { ...process, activated: Date.now() };
|
||||
}
|
||||
return process;
|
||||
});
|
||||
|
||||
return { ...state, processes };
|
||||
}),
|
||||
on(patchProcess, (state, { processId, changes }) => {
|
||||
const processes = state.processes.map((process) => {
|
||||
if (process.id === processId) {
|
||||
return { ...process, ...changes, id: processId };
|
||||
}
|
||||
return process;
|
||||
});
|
||||
|
||||
return { ...state, processes };
|
||||
}),
|
||||
on(patchProcessData, (state, { processId, data }) => {
|
||||
const processes = state.processes.map((process) => {
|
||||
if (process.id === processId) {
|
||||
return { ...process, data: { ...(process.data || {}), ...data } };
|
||||
}
|
||||
return process;
|
||||
});
|
||||
|
||||
return { ...state, processes };
|
||||
}),
|
||||
);
|
||||
|
||||
export function applicationReducer(state: ApplicationState, action: Action) {
|
||||
return _applicationReducer(state, action);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// import { ApplicationState } from './application.state';
|
||||
// import { ApplicationProcess } from '../defs';
|
||||
// import * as selectors from './application.selectors';
|
||||
|
||||
// describe('applicationSelectors', () => {
|
||||
// it('should select the processes', () => {
|
||||
// const processes: ApplicationProcess[] = [{ id: 1, name: 'Vorgang 1', section: 'customer' }];
|
||||
// const state: ApplicationState = {
|
||||
// processes,
|
||||
// section: 'customer',
|
||||
// };
|
||||
// expect(selectors.selectProcesses.projector(state)).toEqual(processes);
|
||||
// });
|
||||
|
||||
// it('should select the section', () => {
|
||||
// const state: ApplicationState = {
|
||||
// processes: [],
|
||||
// section: 'customer',
|
||||
// };
|
||||
// expect(selectors.selectSection.projector(state)).toEqual('customer');
|
||||
// });
|
||||
|
||||
// it('should select the activatedProcess', () => {
|
||||
// const processes: ApplicationProcess[] = [
|
||||
// { id: 1, name: 'Vorgang 1', section: 'customer', activated: 100 },
|
||||
// { id: 2, name: 'Vorgang 2', section: 'customer', activated: 300 },
|
||||
// { id: 3, name: 'Vorgang 3', section: 'customer', activated: 200 },
|
||||
// ];
|
||||
// const state: ApplicationState = {
|
||||
// processes,
|
||||
// section: 'customer',
|
||||
// };
|
||||
// expect(selectors.selectActivatedProcess.projector(state)).toEqual(processes[1]);
|
||||
// });
|
||||
// });
|
||||
@@ -1,18 +0,0 @@
|
||||
import { createFeatureSelector, createSelector } from '@ngrx/store';
|
||||
import { ApplicationState } from './application.state';
|
||||
export const selectApplicationState = createFeatureSelector<ApplicationState>('core-application');
|
||||
|
||||
export const selectTitle = createSelector(selectApplicationState, (s) => s.title);
|
||||
|
||||
export const selectSection = createSelector(selectApplicationState, (s) => s.section);
|
||||
|
||||
export const selectProcesses = createSelector(selectApplicationState, (s) => s.processes);
|
||||
|
||||
export const selectActivatedProcess = createSelector(selectApplicationState, (s) =>
|
||||
s?.processes?.reduce((process, current) => {
|
||||
if (!process) {
|
||||
return current;
|
||||
}
|
||||
return process.activated > current.activated ? process : current;
|
||||
}, undefined),
|
||||
);
|
||||
@@ -1,13 +0,0 @@
|
||||
import { ApplicationProcess } from '../defs';
|
||||
|
||||
export interface ApplicationState {
|
||||
title: string;
|
||||
processes: ApplicationProcess[];
|
||||
section: 'customer' | 'branch';
|
||||
}
|
||||
|
||||
export const INITIAL_APPLICATION_STATE: ApplicationState = {
|
||||
title: '',
|
||||
processes: [],
|
||||
section: 'customer',
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
// start:ng42.barrel
|
||||
export * from './application.actions';
|
||||
export * from './application.reducer';
|
||||
export * from './application.selectors';
|
||||
export * from './application.state';
|
||||
// end:ng42.barrel
|
||||
@@ -1,22 +1,15 @@
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { BreadcrumbService } from './breadcrumb.service';
|
||||
import { BreadcrumbEffects } from './store/breadcrumb.effect';
|
||||
import { breadcrumbReducer } from './store/breadcrumb.reducer';
|
||||
import { featureName } from './store/breadcrumb.state';
|
||||
|
||||
@NgModule()
|
||||
export class CoreBreadcrumbModule {
|
||||
static forRoot(): ModuleWithProviders<CoreBreadcrumbModule> {
|
||||
return {
|
||||
ngModule: CoreBreadcrumbForRootModule,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [StoreModule.forFeature(featureName, breadcrumbReducer), EffectsModule.forFeature([BreadcrumbEffects])],
|
||||
providers: [BreadcrumbService],
|
||||
})
|
||||
export class CoreBreadcrumbForRootModule {}
|
||||
import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';
|
||||
import { provideEffects } from '@ngrx/effects';
|
||||
import { provideState } from '@ngrx/store';
|
||||
import { BreadcrumbService } from './breadcrumb.service';
|
||||
import { BreadcrumbEffects } from './store/breadcrumb.effect';
|
||||
import { breadcrumbReducer } from './store/breadcrumb.reducer';
|
||||
import { featureName } from './store/breadcrumb.state';
|
||||
|
||||
export function provideCoreBreadcrumb(): EnvironmentProviders {
|
||||
return makeEnvironmentProviders([
|
||||
provideState({ name: featureName, reducer: breadcrumbReducer }),
|
||||
provideEffects(BreadcrumbEffects),
|
||||
BreadcrumbService,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { DomainAvailabilityService } from './availability.service';
|
||||
|
||||
@NgModule()
|
||||
export class DomainAvailabilityModule {
|
||||
static forRoot(): ModuleWithProviders<DomainAvailabilityModule> {
|
||||
return {
|
||||
ngModule: DomainAvailabilityModule,
|
||||
providers: [DomainAvailabilityService],
|
||||
};
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
||||
export * from './availability.module';
|
||||
export * from './availability.service';
|
||||
export * from './defs';
|
||||
export * from './in-stock.service';
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { DomainCatalogService } from './catalog.service';
|
||||
import { ThumbnailUrlPipe } from './thumbnail-url.pipe';
|
||||
import { DomainCatalogThumbnailService } from './thumbnail.service';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ThumbnailUrlPipe],
|
||||
imports: [],
|
||||
exports: [ThumbnailUrlPipe],
|
||||
})
|
||||
export class DomainCatalogModule {
|
||||
static forRoot(): ModuleWithProviders<DomainCatalogModule> {
|
||||
return {
|
||||
ngModule: DomainCatalogModule,
|
||||
providers: [DomainCatalogService, DomainCatalogThumbnailService],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,105 +1,111 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import {
|
||||
AutocompleteTokenDTO,
|
||||
PromotionService,
|
||||
QueryTokenDTO,
|
||||
SearchService,
|
||||
} from '@generated/swagger/cat-search-api';
|
||||
import { memorize } from '@utils/common';
|
||||
import { map, share, shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class DomainCatalogService {
|
||||
constructor(
|
||||
private searchService: SearchService,
|
||||
private promotionService: PromotionService,
|
||||
private applicationService: ApplicationService,
|
||||
) {}
|
||||
|
||||
@memorize()
|
||||
getFilters() {
|
||||
return this.searchService.SearchSearchFilter().pipe(
|
||||
map((res) => res.result),
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
|
||||
@memorize()
|
||||
getOrderBy() {
|
||||
return this.searchService.SearchSearchSort().pipe(
|
||||
map((res) => res.result),
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
|
||||
getSearchHistory({ take }: { take: number }) {
|
||||
return this.searchService.SearchHistory(take ?? 5).pipe(map((res) => res.result));
|
||||
}
|
||||
|
||||
@memorize({ ttl: 120000 })
|
||||
search({ queryToken }: { queryToken: QueryTokenDTO }) {
|
||||
return this.searchService
|
||||
.SearchSearch({
|
||||
...queryToken,
|
||||
stockId: null,
|
||||
})
|
||||
.pipe(share());
|
||||
}
|
||||
|
||||
@memorize({ ttl: 120000 })
|
||||
searchWithStockId({ queryToken }: { queryToken: QueryTokenDTO }) {
|
||||
return this.searchService
|
||||
.SearchSearch2({
|
||||
queryToken,
|
||||
stockId: queryToken?.stockId ?? null,
|
||||
})
|
||||
.pipe(share());
|
||||
}
|
||||
|
||||
getDetailsById({ id }: { id: number }) {
|
||||
return this.searchService.SearchDetail({
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
getDetailsByEan({ ean }: { ean: string }) {
|
||||
return this.searchService.SearchDetailByEAN(ean);
|
||||
}
|
||||
|
||||
searchByIds({ ids }: { ids: number[] }) {
|
||||
return this.searchService.SearchById(ids);
|
||||
}
|
||||
|
||||
searchByEans({ eans }: { eans: string[] }) {
|
||||
return this.searchService.SearchByEAN(eans);
|
||||
}
|
||||
|
||||
searchTop({ queryToken }: { queryToken: QueryTokenDTO }) {
|
||||
return this.searchService.SearchTop(queryToken);
|
||||
}
|
||||
|
||||
searchComplete({ queryToken }: { queryToken: AutocompleteTokenDTO }) {
|
||||
return this.searchService.SearchAutocomplete(queryToken);
|
||||
}
|
||||
|
||||
@memorize()
|
||||
getPromotionPoints({ items }: { items: { id: number; quantity: number; price?: number }[] }) {
|
||||
return this.promotionService.PromotionLesepunkte(items).pipe(shareReplay());
|
||||
}
|
||||
|
||||
@memorize()
|
||||
getSettings() {
|
||||
return this.searchService.SearchSettings().pipe(
|
||||
map((res) => res.result),
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
|
||||
getRecommendations({ digId }: { digId: number }) {
|
||||
return this.searchService.SearchGetRecommendations({
|
||||
digId: digId + '',
|
||||
sessionId: this.applicationService.activatedProcessId + '',
|
||||
});
|
||||
}
|
||||
}
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import {
|
||||
AutocompleteTokenDTO,
|
||||
PromotionService,
|
||||
QueryTokenDTO,
|
||||
SearchService,
|
||||
} from '@generated/swagger/cat-search-api';
|
||||
import { memorize } from '@utils/common';
|
||||
import { map, share, shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DomainCatalogService {
|
||||
constructor(
|
||||
private searchService: SearchService,
|
||||
private promotionService: PromotionService,
|
||||
private applicationService: ApplicationService,
|
||||
) {}
|
||||
|
||||
@memorize()
|
||||
getFilters() {
|
||||
return this.searchService.SearchSearchFilter().pipe(
|
||||
map((res) => res.result),
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
|
||||
@memorize()
|
||||
getOrderBy() {
|
||||
return this.searchService.SearchSearchSort().pipe(
|
||||
map((res) => res.result),
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
|
||||
getSearchHistory({ take }: { take: number }) {
|
||||
return this.searchService
|
||||
.SearchHistory(take ?? 5)
|
||||
.pipe(map((res) => res.result));
|
||||
}
|
||||
|
||||
@memorize({ ttl: 120000 })
|
||||
search({ queryToken }: { queryToken: QueryTokenDTO }) {
|
||||
return this.searchService
|
||||
.SearchSearch({
|
||||
...queryToken,
|
||||
stockId: null,
|
||||
})
|
||||
.pipe(share());
|
||||
}
|
||||
|
||||
@memorize({ ttl: 120000 })
|
||||
searchWithStockId({ queryToken }: { queryToken: QueryTokenDTO }) {
|
||||
return this.searchService
|
||||
.SearchSearch2({
|
||||
queryToken,
|
||||
stockId: queryToken?.stockId ?? null,
|
||||
})
|
||||
.pipe(share());
|
||||
}
|
||||
|
||||
getDetailsById({ id }: { id: number }) {
|
||||
return this.searchService.SearchDetail({
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
getDetailsByEan({ ean }: { ean: string }) {
|
||||
return this.searchService.SearchDetailByEAN(ean);
|
||||
}
|
||||
|
||||
searchByIds({ ids }: { ids: number[] }) {
|
||||
return this.searchService.SearchById(ids);
|
||||
}
|
||||
|
||||
searchByEans({ eans }: { eans: string[] }) {
|
||||
return this.searchService.SearchByEAN(eans);
|
||||
}
|
||||
|
||||
searchTop({ queryToken }: { queryToken: QueryTokenDTO }) {
|
||||
return this.searchService.SearchTop(queryToken);
|
||||
}
|
||||
|
||||
searchComplete({ queryToken }: { queryToken: AutocompleteTokenDTO }) {
|
||||
return this.searchService.SearchAutocomplete(queryToken);
|
||||
}
|
||||
|
||||
@memorize()
|
||||
getPromotionPoints({
|
||||
items,
|
||||
}: {
|
||||
items: { id: number; quantity: number; price?: number }[];
|
||||
}) {
|
||||
return this.promotionService.PromotionLesepunkte(items).pipe(shareReplay());
|
||||
}
|
||||
|
||||
@memorize()
|
||||
getSettings() {
|
||||
return this.searchService.SearchSettings().pipe(
|
||||
map((res) => res.result),
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
|
||||
getRecommendations({ digId }: { digId: number }) {
|
||||
return this.searchService.SearchGetRecommendations({
|
||||
digId: digId + '',
|
||||
sessionId: this.applicationService.activatedProcessId + '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './catalog.module';
|
||||
export * from './catalog.service';
|
||||
export * from './thumbnail-url.pipe';
|
||||
export * from './thumbnail.service';
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DomainCatalogThumbnailService } from './thumbnail.service';
|
||||
@Pipe({
|
||||
name: 'thumbnailUrl',
|
||||
pure: false,
|
||||
standalone: false,
|
||||
standalone: true,
|
||||
})
|
||||
export class ThumbnailUrlPipe implements PipeTransform, OnDestroy {
|
||||
private input$ = new BehaviorSubject<{ width?: number; height?: number; ean?: string }>(undefined);
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { memorize } from '@utils/common';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { DomainCatalogService } from './catalog.service';
|
||||
|
||||
@Injectable()
|
||||
export class DomainCatalogThumbnailService {
|
||||
constructor(private domainCatalogService: DomainCatalogService) {}
|
||||
|
||||
@memorize()
|
||||
getThumnaulUrl({ ean, height, width }: { width?: number; height?: number; ean?: string }) {
|
||||
return this.domainCatalogService.getSettings().pipe(
|
||||
map((settings) => {
|
||||
let thumbnailUrl = settings.imageUrl.replace(/{ean}/, ean);
|
||||
return thumbnailUrl;
|
||||
}),
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
}
|
||||
import { Injectable } from '@angular/core';
|
||||
import { memorize } from '@utils/common';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { DomainCatalogService } from './catalog.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DomainCatalogThumbnailService {
|
||||
constructor(private domainCatalogService: DomainCatalogService) {}
|
||||
|
||||
@memorize()
|
||||
getThumnaulUrl({
|
||||
ean,
|
||||
height,
|
||||
width,
|
||||
}: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
ean?: string;
|
||||
}) {
|
||||
return this.domainCatalogService.getSettings().pipe(
|
||||
map((settings) => {
|
||||
const thumbnailUrl = settings.imageUrl.replace(/{ean}/, ean);
|
||||
return thumbnailUrl;
|
||||
}),
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,15 @@
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { DomainCheckoutService } from './checkout.service';
|
||||
import { domainCheckoutReducer } from './store/domain-checkout.reducer';
|
||||
import { storeFeatureName } from './store/domain-checkout.state';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { DomainCheckoutEffects } from './store/domain-checkout.effects';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [StoreModule.forFeature(storeFeatureName, domainCheckoutReducer)],
|
||||
providers: [DomainCheckoutService],
|
||||
})
|
||||
export class DomainCheckoutModule {
|
||||
static forRoot(): ModuleWithProviders<DomainCheckoutModule> {
|
||||
return {
|
||||
ngModule: RootDomainCheckoutModule,
|
||||
providers: [DomainCheckoutService],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
StoreModule.forFeature(storeFeatureName, domainCheckoutReducer),
|
||||
EffectsModule.forFeature([DomainCheckoutEffects]),
|
||||
],
|
||||
})
|
||||
export class RootDomainCheckoutModule {}
|
||||
import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';
|
||||
import { provideEffects } from '@ngrx/effects';
|
||||
import { provideState } from '@ngrx/store';
|
||||
import { DomainCheckoutService } from './checkout.service';
|
||||
import { DomainCheckoutEffects } from './store/domain-checkout.effects';
|
||||
import { domainCheckoutReducer } from './store/domain-checkout.reducer';
|
||||
import { storeFeatureName } from './store/domain-checkout.state';
|
||||
|
||||
export function provideDomainCheckout(): EnvironmentProviders {
|
||||
return makeEnvironmentProviders([
|
||||
provideState({ name: storeFeatureName, reducer: domainCheckoutReducer }),
|
||||
provideEffects(DomainCheckoutEffects),
|
||||
DomainCheckoutService,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1071,7 +1071,7 @@ export class DomainCheckoutService {
|
||||
});
|
||||
} else if (orderType === 'B2B-Versand') {
|
||||
const branch = await this.applicationService
|
||||
.getSelectedBranch$(processId)
|
||||
.getSelectedBranch$()
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
availability$ =
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { InfoService } from '@generated/swagger/isa-api';
|
||||
|
||||
@Injectable()
|
||||
export class DomainDashboardService {
|
||||
constructor(private readonly _infoService: InfoService) {}
|
||||
|
||||
feed() {
|
||||
return this._infoService.InfoInfo({});
|
||||
}
|
||||
}
|
||||
import { Injectable } from '@angular/core';
|
||||
import { InfoService } from '@generated/swagger/isa-api';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DomainDashboardService {
|
||||
constructor(private readonly _infoService: InfoService) {}
|
||||
|
||||
feed() {
|
||||
return this._infoService.InfoInfo({});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { DomainDashboardService } from './dashboard.service';
|
||||
|
||||
@NgModule({})
|
||||
export class DomainIsaModule {
|
||||
static forRoot(): ModuleWithProviders<DomainIsaModule> {
|
||||
return {
|
||||
ngModule: DomainIsaModule,
|
||||
providers: [DomainDashboardService],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './dashboard.service';
|
||||
export * from './defs';
|
||||
export * from './domain-isa.module';
|
||||
|
||||
@@ -1,116 +1,130 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AbholfachService, AutocompleteTokenDTO, QueryTokenDTO } from '@generated/swagger/oms-api';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { memorize } from '@utils/common';
|
||||
import { shareReplay } from 'rxjs/operators';
|
||||
@Injectable()
|
||||
export class DomainGoodsService {
|
||||
constructor(
|
||||
private abholfachService: AbholfachService,
|
||||
private dateAdapter: DateAdapter,
|
||||
) {}
|
||||
|
||||
searchWareneingang(queryToken: QueryTokenDTO) {
|
||||
return this.abholfachService.AbholfachWareneingang(queryToken);
|
||||
}
|
||||
|
||||
searchWarenausgabe(queryToken: QueryTokenDTO) {
|
||||
return this.abholfachService.AbholfachWarenausgabe(queryToken);
|
||||
}
|
||||
|
||||
wareneingangComplete(autocompleteToken: AutocompleteTokenDTO) {
|
||||
return this.abholfachService.AbholfachWareneingangAutocomplete(autocompleteToken);
|
||||
}
|
||||
|
||||
warenausgabeComplete(autocompleteToken: AutocompleteTokenDTO) {
|
||||
return this.abholfachService.AbholfachWarenausgabeAutocomplete(autocompleteToken);
|
||||
}
|
||||
|
||||
getWareneingangItemByOrderNumber(orderNumber: string) {
|
||||
return this.abholfachService.AbholfachWareneingang({
|
||||
filter: { all_branches: 'true', archive: 'true' },
|
||||
input: {
|
||||
qs: orderNumber,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getWarenausgabeItemByOrderNumber(orderNumber: string, archive: boolean) {
|
||||
return this.abholfachService.AbholfachWarenausgabe({
|
||||
filter: { all_branches: 'true', archive: `${archive}` },
|
||||
input: {
|
||||
qs: orderNumber,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getWarenausgabeItemByCompartment(compartmentCode: string, archive: boolean) {
|
||||
return this.abholfachService.AbholfachWarenausgabe({
|
||||
filter: { all_branches: 'true', archive: `${archive}` },
|
||||
input: {
|
||||
qs: compartmentCode,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getWareneingangItemByCustomerNumber(customerNumber: string) {
|
||||
// Suche anhand der Kundennummer mit Status Bestellt, nachbestellt, eingetroffen, weitergeleitet intern
|
||||
return this.abholfachService.AbholfachWareneingang({
|
||||
filter: { orderitemprocessingstatus: '16;128;8192;1048576' },
|
||||
input: {
|
||||
customer_no: customerNumber,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
list() {
|
||||
const base = this.dateAdapter.today();
|
||||
const startDate = this.dateAdapter.addCalendarDays(base, -5);
|
||||
const endDate = this.dateAdapter.addCalendarDays(base, 1);
|
||||
const queryToken: QueryTokenDTO = {
|
||||
filter: {
|
||||
orderitemprocessingstatus: '16;8192;1024;512;2048',
|
||||
estimatedshippingdate: `"${startDate.toJSON()}"-"${endDate.toJSON()}"`,
|
||||
},
|
||||
orderBy: [{ by: 'estimatedshippingdate' }],
|
||||
skip: 0,
|
||||
take: 20,
|
||||
};
|
||||
return this.searchWareneingang(queryToken);
|
||||
}
|
||||
|
||||
@memorize()
|
||||
goodsInQuerySettings() {
|
||||
return this.abholfachService.AbholfachWareneingangQuerySettings().pipe(shareReplay());
|
||||
}
|
||||
|
||||
@memorize()
|
||||
goodsOutQuerySettings() {
|
||||
return this.abholfachService.AbholfachWarenausgabeQuerySettings().pipe(shareReplay());
|
||||
}
|
||||
|
||||
goodsInList(queryToken: QueryTokenDTO) {
|
||||
return this.abholfachService.AbholfachWareneingangsliste(queryToken);
|
||||
}
|
||||
|
||||
@memorize()
|
||||
goodsInListQuerySettings() {
|
||||
return this.abholfachService.AbholfachWareneingangslisteQuerySettings().pipe(shareReplay());
|
||||
}
|
||||
|
||||
goodsInCleanupList() {
|
||||
return this.abholfachService.AbholfachAbholfachbereinigungsliste();
|
||||
}
|
||||
|
||||
goodsInReservationList(queryToken: QueryTokenDTO) {
|
||||
return this.abholfachService.AbholfachReservierungen(queryToken);
|
||||
}
|
||||
|
||||
goodsInRemissionPreviewList() {
|
||||
return this.abholfachService.AbholfachAbholfachremissionsvorschau();
|
||||
}
|
||||
|
||||
createGoodsInRemissionFromPreviewList() {
|
||||
return this.abholfachService.AbholfachCreateAbholfachremission();
|
||||
}
|
||||
}
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
AbholfachService,
|
||||
AutocompleteTokenDTO,
|
||||
QueryTokenDTO,
|
||||
} from '@generated/swagger/oms-api';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { memorize } from '@utils/common';
|
||||
import { shareReplay } from 'rxjs/operators';
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DomainGoodsService {
|
||||
constructor(
|
||||
private abholfachService: AbholfachService,
|
||||
private dateAdapter: DateAdapter,
|
||||
) {}
|
||||
|
||||
searchWareneingang(queryToken: QueryTokenDTO) {
|
||||
return this.abholfachService.AbholfachWareneingang(queryToken);
|
||||
}
|
||||
|
||||
searchWarenausgabe(queryToken: QueryTokenDTO) {
|
||||
return this.abholfachService.AbholfachWarenausgabe(queryToken);
|
||||
}
|
||||
|
||||
wareneingangComplete(autocompleteToken: AutocompleteTokenDTO) {
|
||||
return this.abholfachService.AbholfachWareneingangAutocomplete(
|
||||
autocompleteToken,
|
||||
);
|
||||
}
|
||||
|
||||
warenausgabeComplete(autocompleteToken: AutocompleteTokenDTO) {
|
||||
return this.abholfachService.AbholfachWarenausgabeAutocomplete(
|
||||
autocompleteToken,
|
||||
);
|
||||
}
|
||||
|
||||
getWareneingangItemByOrderNumber(orderNumber: string) {
|
||||
return this.abholfachService.AbholfachWareneingang({
|
||||
filter: { all_branches: 'true', archive: 'true' },
|
||||
input: {
|
||||
qs: orderNumber,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getWarenausgabeItemByOrderNumber(orderNumber: string, archive: boolean) {
|
||||
return this.abholfachService.AbholfachWarenausgabe({
|
||||
filter: { all_branches: 'true', archive: `${archive}` },
|
||||
input: {
|
||||
qs: orderNumber,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getWarenausgabeItemByCompartment(compartmentCode: string, archive: boolean) {
|
||||
return this.abholfachService.AbholfachWarenausgabe({
|
||||
filter: { all_branches: 'true', archive: `${archive}` },
|
||||
input: {
|
||||
qs: compartmentCode,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getWareneingangItemByCustomerNumber(customerNumber: string) {
|
||||
// Suche anhand der Kundennummer mit Status Bestellt, nachbestellt, eingetroffen, weitergeleitet intern
|
||||
return this.abholfachService.AbholfachWareneingang({
|
||||
filter: { orderitemprocessingstatus: '16;128;8192;1048576' },
|
||||
input: {
|
||||
customer_no: customerNumber,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
list() {
|
||||
const base = this.dateAdapter.today();
|
||||
const startDate = this.dateAdapter.addCalendarDays(base, -5);
|
||||
const endDate = this.dateAdapter.addCalendarDays(base, 1);
|
||||
const queryToken: QueryTokenDTO = {
|
||||
filter: {
|
||||
orderitemprocessingstatus: '16;8192;1024;512;2048',
|
||||
estimatedshippingdate: `"${startDate.toJSON()}"-"${endDate.toJSON()}"`,
|
||||
},
|
||||
orderBy: [{ by: 'estimatedshippingdate' }],
|
||||
skip: 0,
|
||||
take: 20,
|
||||
};
|
||||
return this.searchWareneingang(queryToken);
|
||||
}
|
||||
|
||||
@memorize()
|
||||
goodsInQuerySettings() {
|
||||
return this.abholfachService
|
||||
.AbholfachWareneingangQuerySettings()
|
||||
.pipe(shareReplay());
|
||||
}
|
||||
|
||||
@memorize()
|
||||
goodsOutQuerySettings() {
|
||||
return this.abholfachService
|
||||
.AbholfachWarenausgabeQuerySettings()
|
||||
.pipe(shareReplay());
|
||||
}
|
||||
|
||||
goodsInList(queryToken: QueryTokenDTO) {
|
||||
return this.abholfachService.AbholfachWareneingangsliste(queryToken);
|
||||
}
|
||||
|
||||
@memorize()
|
||||
goodsInListQuerySettings() {
|
||||
return this.abholfachService
|
||||
.AbholfachWareneingangslisteQuerySettings()
|
||||
.pipe(shareReplay());
|
||||
}
|
||||
|
||||
goodsInCleanupList() {
|
||||
return this.abholfachService.AbholfachAbholfachbereinigungsliste();
|
||||
}
|
||||
|
||||
goodsInReservationList(queryToken: QueryTokenDTO) {
|
||||
return this.abholfachService.AbholfachReservierungen(queryToken);
|
||||
}
|
||||
|
||||
goodsInRemissionPreviewList() {
|
||||
return this.abholfachService.AbholfachAbholfachremissionsvorschau();
|
||||
}
|
||||
|
||||
createGoodsInRemissionFromPreviewList() {
|
||||
return this.abholfachService.AbholfachCreateAbholfachremission();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,5 @@ export * from './action-handler-services';
|
||||
export * from './action-handlers';
|
||||
export * from './customer-order.service';
|
||||
export * from './goods.service';
|
||||
export * from './oms.module';
|
||||
export * from './oms.service';
|
||||
export * from './receipt.service';
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { DomainGoodsService } from './goods.service';
|
||||
import { DomainOmsService } from './oms.service';
|
||||
import { DomainReceiptService } from './receipt.service';
|
||||
|
||||
@NgModule()
|
||||
export class DomainOmsModule {
|
||||
static forRoot(): ModuleWithProviders<DomainOmsModule> {
|
||||
return {
|
||||
ngModule: DomainOmsModule,
|
||||
providers: [DomainOmsService, DomainGoodsService, DomainReceiptService],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,316 +1,381 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
BranchService,
|
||||
BuyerDTO,
|
||||
ChangeStockStatusCodeValues,
|
||||
HistoryDTO,
|
||||
NotificationChannel,
|
||||
OrderCheckoutService,
|
||||
OrderDTO,
|
||||
OrderItemDTO,
|
||||
OrderItemSubsetDTO,
|
||||
OrderListItemDTO,
|
||||
OrderService,
|
||||
ReceiptService,
|
||||
StatusValues,
|
||||
StockStatusCodeService,
|
||||
ValueTupleOfLongAndReceiptTypeAndEntityDTOContainerOfReceiptDTO,
|
||||
ValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO,
|
||||
VATService,
|
||||
} from '@generated/swagger/oms-api';
|
||||
import { memorize } from '@utils/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class DomainOmsService {
|
||||
constructor(
|
||||
private orderService: OrderService,
|
||||
private receiptService: ReceiptService,
|
||||
private branchService: BranchService,
|
||||
private vatService: VATService,
|
||||
private stockStatusCodeService: StockStatusCodeService,
|
||||
private _orderCheckoutService: OrderCheckoutService,
|
||||
) {}
|
||||
|
||||
getOrderItemsByCustomerNumber(customerNumber: string, skip: number): Observable<OrderListItemDTO[]> {
|
||||
return this.orderService
|
||||
.OrderGetOrdersByBuyerNumber({ buyerNumber: customerNumber, take: 20, skip })
|
||||
.pipe(map((orders) => orders.result));
|
||||
}
|
||||
|
||||
getOrder(orderId: number): Observable<OrderDTO> {
|
||||
return this.orderService.OrderGetOrder(orderId).pipe(map((o) => o.result));
|
||||
}
|
||||
|
||||
getBranches() {
|
||||
return this.branchService.BranchGetBranches({});
|
||||
}
|
||||
|
||||
getHistory(orderItemSubsetId: number): Observable<HistoryDTO[]> {
|
||||
return this.orderService
|
||||
.OrderGetOrderItemStatusHistory({ orderItemSubsetId })
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
getReceipts(
|
||||
orderItemSubsetIds: number[],
|
||||
): Observable<ValueTupleOfLongAndReceiptTypeAndEntityDTOContainerOfReceiptDTO[]> {
|
||||
return this.receiptService
|
||||
.ReceiptGetReceiptsByOrderItemSubset({
|
||||
payload: {
|
||||
receiptType: 65 as unknown as any,
|
||||
ids: orderItemSubsetIds,
|
||||
eagerLoading: 1,
|
||||
},
|
||||
})
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
getReorderReasons() {
|
||||
return this._orderCheckoutService.OrderCheckoutGetReorderReasons().pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
@memorize()
|
||||
getVATs() {
|
||||
return this.vatService.VATGetVATs({}).pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
// ttl 4 Stunden
|
||||
@memorize({ ttl: 14400000 })
|
||||
getStockStatusCodes({ supplierId, eagerLoading = 0 }: { supplierId: number; eagerLoading?: number }) {
|
||||
return this.stockStatusCodeService.StockStatusCodeGetStockStatusCodes({ supplierId, eagerLoading }).pipe(
|
||||
map((response) => response.result),
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
|
||||
patchOrderItem(payload: { orderItemId: number; orderId: number; orderItem: Partial<OrderItemDTO> }) {
|
||||
return this.orderService.OrderPatchOrderItem(payload).pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
patchOrderItemSubset(payload: {
|
||||
orderItemSubsetId: number;
|
||||
orderItemId: number;
|
||||
orderId: number;
|
||||
orderItemSubset: Partial<OrderItemSubsetDTO>;
|
||||
}) {
|
||||
return this.orderService.OrderPatchOrderItemSubset(payload).pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
patchComment({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
specialComment,
|
||||
}: {
|
||||
orderId: number;
|
||||
orderItemId: number;
|
||||
orderItemSubsetId: number;
|
||||
specialComment: string;
|
||||
}) {
|
||||
return this.orderService
|
||||
.OrderPatchOrderItemSubset({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
orderItemSubset: {
|
||||
specialComment,
|
||||
},
|
||||
})
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
changeOrderStatus(
|
||||
orderId: number,
|
||||
orderItemId: number,
|
||||
orderItemSubsetId: number,
|
||||
data: StatusValues,
|
||||
): Observable<ValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO> {
|
||||
return this.orderService
|
||||
.OrderChangeStatus({
|
||||
data,
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
})
|
||||
.pipe(map((o) => o.result));
|
||||
}
|
||||
|
||||
setEstimatedShippingDate(
|
||||
orderId: number,
|
||||
orderItemId: number,
|
||||
orderItemSubsetId: number,
|
||||
estimatedShippingDate: Date | string,
|
||||
) {
|
||||
return this.orderService
|
||||
.OrderPatchOrderItemSubset({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
orderItemSubset: {
|
||||
estimatedShippingDate:
|
||||
estimatedShippingDate instanceof Date ? estimatedShippingDate.toJSON() : estimatedShippingDate,
|
||||
},
|
||||
})
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
setPickUpDeadline(orderId: number, orderItemId: number, orderItemSubsetId: number, pickUpDeadline: string) {
|
||||
return this.orderService
|
||||
.OrderPatchOrderItemSubset({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
orderItemSubset: {
|
||||
compartmentStop: pickUpDeadline,
|
||||
},
|
||||
})
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
setPreferredPickUpDate({ data }: { data: { [key: string]: string } }) {
|
||||
return this.orderService.OrderSetPreferredPickUpDate({ data });
|
||||
}
|
||||
|
||||
changeOrderItemStatus(data: OrderService.OrderChangeStatusParams) {
|
||||
return this.orderService.OrderChangeStatus(data);
|
||||
}
|
||||
|
||||
changeStockStatusCode(payload: ChangeStockStatusCodeValues[]) {
|
||||
return this.orderService.OrderChangeStockStatusCode(payload).pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
orderAtSupplier({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
}: {
|
||||
orderId: number;
|
||||
orderItemId: number;
|
||||
orderItemSubsetId: number;
|
||||
}) {
|
||||
return this._orderCheckoutService.OrderCheckoutOrderSubsetItemAtSupplier({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
});
|
||||
}
|
||||
|
||||
getNotifications(orderId: number): Observable<{ selected: NotificationChannel; email: string; mobile: string }> {
|
||||
return this.getOrder(orderId).pipe(
|
||||
map((order) => ({
|
||||
selected: order.notificationChannels,
|
||||
email: order.buyer?.communicationDetails?.email,
|
||||
mobile: order.buyer?.communicationDetails?.mobile,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
getOrderSource(orderId: number): Observable<string> {
|
||||
return this.getOrder(orderId).pipe(map((order) => order?.features?.orderSource));
|
||||
}
|
||||
|
||||
updateNotifications(orderId: number, changes: { selected: NotificationChannel; email: string; mobile: string }) {
|
||||
const communicationDetails = {
|
||||
email: changes.email,
|
||||
mobile: changes.mobile,
|
||||
};
|
||||
|
||||
if (!(changes.selected & 1)) {
|
||||
delete communicationDetails.email;
|
||||
}
|
||||
if (!(changes.selected & 2)) {
|
||||
delete communicationDetails.mobile;
|
||||
}
|
||||
|
||||
return this.updateOrder({ orderId, notificationChannels: changes.selected, communicationDetails });
|
||||
}
|
||||
|
||||
updateOrder({
|
||||
orderId,
|
||||
notificationChannels,
|
||||
communicationDetails,
|
||||
firstName,
|
||||
lastName,
|
||||
organisation,
|
||||
}: {
|
||||
orderId: number;
|
||||
notificationChannels?: NotificationChannel;
|
||||
communicationDetails?: { email?: string; mobile?: string };
|
||||
lastName?: string;
|
||||
firstName?: string;
|
||||
organisation?: string;
|
||||
}) {
|
||||
const buyer: BuyerDTO = {};
|
||||
|
||||
if (communicationDetails) {
|
||||
buyer.communicationDetails = { ...communicationDetails };
|
||||
}
|
||||
|
||||
if (!!lastName || !!firstName) {
|
||||
buyer.lastName = lastName;
|
||||
buyer.firstName = firstName;
|
||||
}
|
||||
|
||||
if (!!organisation && !!buyer.organisation) {
|
||||
buyer.organisation = {
|
||||
name: organisation,
|
||||
};
|
||||
}
|
||||
|
||||
return this.orderService
|
||||
.OrderPatchOrder({
|
||||
orderId: orderId,
|
||||
order: {
|
||||
notificationChannels,
|
||||
buyer,
|
||||
},
|
||||
})
|
||||
.pipe(map((res) => res.result));
|
||||
}
|
||||
|
||||
generateNotifications({ orderId, taskTypes }: { orderId: number; taskTypes: string[] }) {
|
||||
return this.orderService.OrderRegenerateOrderItemStatusTasks({
|
||||
orderId,
|
||||
taskTypes,
|
||||
});
|
||||
}
|
||||
|
||||
getCompletedTasks({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
take,
|
||||
skip,
|
||||
}: {
|
||||
orderId: number;
|
||||
orderItemId: number;
|
||||
orderItemSubsetId: number;
|
||||
take?: number;
|
||||
skip?: number;
|
||||
}): Observable<Record<string, Date[]>> {
|
||||
return this.orderService
|
||||
.OrderGetOrderItemSubsetTasks({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
completed: new Date(0).toISOString(),
|
||||
take,
|
||||
skip,
|
||||
})
|
||||
.pipe(
|
||||
map((res) =>
|
||||
res.result
|
||||
.sort((a, b) => new Date(b.completed).getTime() - new Date(a.completed).getTime())
|
||||
.reduce(
|
||||
(data, result) => {
|
||||
(data[result.name] = data[result.name] || []).push(new Date(result.completed));
|
||||
return data;
|
||||
},
|
||||
{} as Record<string, Date[]>,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
BranchService,
|
||||
BuyerDTO,
|
||||
ChangeStockStatusCodeValues,
|
||||
HistoryDTO,
|
||||
NotificationChannel,
|
||||
OrderCheckoutService,
|
||||
OrderDTO,
|
||||
OrderItemDTO,
|
||||
OrderItemSubsetDTO,
|
||||
OrderListItemDTO,
|
||||
OrderService,
|
||||
ReceiptService,
|
||||
StatusValues,
|
||||
StockStatusCodeService,
|
||||
ValueTupleOfLongAndReceiptTypeAndEntityDTOContainerOfReceiptDTO,
|
||||
ValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO,
|
||||
VATService,
|
||||
} from '@generated/swagger/oms-api';
|
||||
import { memorize } from '@utils/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DomainOmsService {
|
||||
constructor(
|
||||
private orderService: OrderService,
|
||||
private receiptService: ReceiptService,
|
||||
private branchService: BranchService,
|
||||
private vatService: VATService,
|
||||
private stockStatusCodeService: StockStatusCodeService,
|
||||
private _orderCheckoutService: OrderCheckoutService,
|
||||
) {}
|
||||
|
||||
getOrderItemsByCustomerNumber(
|
||||
customerNumber: string,
|
||||
skip: number,
|
||||
): Observable<OrderListItemDTO[]> {
|
||||
return this.orderService
|
||||
.OrderGetOrdersByBuyerNumber({
|
||||
buyerNumber: customerNumber,
|
||||
take: 20,
|
||||
skip,
|
||||
})
|
||||
.pipe(map((orders) => orders.result));
|
||||
}
|
||||
|
||||
getOrder(orderId: number): Observable<OrderDTO> {
|
||||
return this.orderService.OrderGetOrder(orderId).pipe(map((o) => o.result));
|
||||
}
|
||||
|
||||
getBranches() {
|
||||
return this.branchService.BranchGetBranches({});
|
||||
}
|
||||
|
||||
getHistory(orderItemSubsetId: number): Observable<HistoryDTO[]> {
|
||||
return this.orderService
|
||||
.OrderGetOrderItemStatusHistory({ orderItemSubsetId })
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
getReceipts(
|
||||
orderItemSubsetIds: number[],
|
||||
): Observable<
|
||||
ValueTupleOfLongAndReceiptTypeAndEntityDTOContainerOfReceiptDTO[]
|
||||
> {
|
||||
return this.receiptService
|
||||
.ReceiptGetReceiptsByOrderItemSubset({
|
||||
payload: {
|
||||
receiptType: 65 as unknown as any,
|
||||
ids: orderItemSubsetIds,
|
||||
eagerLoading: 1,
|
||||
},
|
||||
})
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
getReorderReasons() {
|
||||
return this._orderCheckoutService
|
||||
.OrderCheckoutGetReorderReasons()
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
@memorize()
|
||||
getVATs() {
|
||||
return this.vatService
|
||||
.VATGetVATs({})
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
// ttl 4 Stunden
|
||||
@memorize({ ttl: 14400000 })
|
||||
getStockStatusCodes({
|
||||
supplierId,
|
||||
eagerLoading = 0,
|
||||
}: {
|
||||
supplierId: number;
|
||||
eagerLoading?: number;
|
||||
}) {
|
||||
return this.stockStatusCodeService
|
||||
.StockStatusCodeGetStockStatusCodes({ supplierId, eagerLoading })
|
||||
.pipe(
|
||||
map((response) => response.result),
|
||||
shareReplay(),
|
||||
);
|
||||
}
|
||||
|
||||
patchOrderItem(payload: {
|
||||
orderItemId: number;
|
||||
orderId: number;
|
||||
orderItem: Partial<OrderItemDTO>;
|
||||
}) {
|
||||
return this.orderService
|
||||
.OrderPatchOrderItem(payload)
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
patchOrderItemSubset(payload: {
|
||||
orderItemSubsetId: number;
|
||||
orderItemId: number;
|
||||
orderId: number;
|
||||
orderItemSubset: Partial<OrderItemSubsetDTO>;
|
||||
}) {
|
||||
return this.orderService
|
||||
.OrderPatchOrderItemSubset(payload)
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
patchComment({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
specialComment,
|
||||
}: {
|
||||
orderId: number;
|
||||
orderItemId: number;
|
||||
orderItemSubsetId: number;
|
||||
specialComment: string;
|
||||
}) {
|
||||
return this.orderService
|
||||
.OrderPatchOrderItemSubset({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
orderItemSubset: {
|
||||
specialComment,
|
||||
},
|
||||
})
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
changeOrderStatus(
|
||||
orderId: number,
|
||||
orderItemId: number,
|
||||
orderItemSubsetId: number,
|
||||
data: StatusValues,
|
||||
): Observable<ValueTupleOfOrderItemSubsetDTOAndOrderItemSubsetDTO> {
|
||||
return this.orderService
|
||||
.OrderChangeStatus({
|
||||
data,
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
})
|
||||
.pipe(map((o) => o.result));
|
||||
}
|
||||
|
||||
setEstimatedShippingDate(
|
||||
orderId: number,
|
||||
orderItemId: number,
|
||||
orderItemSubsetId: number,
|
||||
estimatedShippingDate: Date | string,
|
||||
) {
|
||||
return this.orderService
|
||||
.OrderPatchOrderItemSubset({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
orderItemSubset: {
|
||||
estimatedShippingDate:
|
||||
estimatedShippingDate instanceof Date
|
||||
? estimatedShippingDate.toJSON()
|
||||
: estimatedShippingDate,
|
||||
},
|
||||
})
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
setPickUpDeadline(
|
||||
orderId: number,
|
||||
orderItemId: number,
|
||||
orderItemSubsetId: number,
|
||||
pickUpDeadline: string,
|
||||
) {
|
||||
return this.orderService
|
||||
.OrderPatchOrderItemSubset({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
orderItemSubset: {
|
||||
compartmentStop: pickUpDeadline,
|
||||
},
|
||||
})
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
setPreferredPickUpDate({ data }: { data: { [key: string]: string } }) {
|
||||
return this.orderService.OrderSetPreferredPickUpDate({ data });
|
||||
}
|
||||
|
||||
changeOrderItemStatus(data: OrderService.OrderChangeStatusParams) {
|
||||
return this.orderService.OrderChangeStatus(data);
|
||||
}
|
||||
|
||||
changeStockStatusCode(payload: ChangeStockStatusCodeValues[]) {
|
||||
return this.orderService
|
||||
.OrderChangeStockStatusCode(payload)
|
||||
.pipe(map((response) => response.result));
|
||||
}
|
||||
|
||||
orderAtSupplier({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
}: {
|
||||
orderId: number;
|
||||
orderItemId: number;
|
||||
orderItemSubsetId: number;
|
||||
}) {
|
||||
return this._orderCheckoutService.OrderCheckoutOrderSubsetItemAtSupplier({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
});
|
||||
}
|
||||
|
||||
getNotifications(
|
||||
orderId: number,
|
||||
): Observable<{
|
||||
selected: NotificationChannel;
|
||||
email: string;
|
||||
mobile: string;
|
||||
}> {
|
||||
return this.getOrder(orderId).pipe(
|
||||
map((order) => ({
|
||||
selected: order.notificationChannels,
|
||||
email: order.buyer?.communicationDetails?.email,
|
||||
mobile: order.buyer?.communicationDetails?.mobile,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
getOrderSource(orderId: number): Observable<string> {
|
||||
return this.getOrder(orderId).pipe(
|
||||
map((order) => order?.features?.orderSource),
|
||||
);
|
||||
}
|
||||
|
||||
updateNotifications(
|
||||
orderId: number,
|
||||
changes: { selected: NotificationChannel; email: string; mobile: string },
|
||||
) {
|
||||
const communicationDetails = {
|
||||
email: changes.email,
|
||||
mobile: changes.mobile,
|
||||
};
|
||||
|
||||
if (!(changes.selected & 1)) {
|
||||
delete communicationDetails.email;
|
||||
}
|
||||
if (!(changes.selected & 2)) {
|
||||
delete communicationDetails.mobile;
|
||||
}
|
||||
|
||||
return this.updateOrder({
|
||||
orderId,
|
||||
notificationChannels: changes.selected,
|
||||
communicationDetails,
|
||||
});
|
||||
}
|
||||
|
||||
updateOrder({
|
||||
orderId,
|
||||
notificationChannels,
|
||||
communicationDetails,
|
||||
firstName,
|
||||
lastName,
|
||||
organisation,
|
||||
}: {
|
||||
orderId: number;
|
||||
notificationChannels?: NotificationChannel;
|
||||
communicationDetails?: { email?: string; mobile?: string };
|
||||
lastName?: string;
|
||||
firstName?: string;
|
||||
organisation?: string;
|
||||
}) {
|
||||
const buyer: BuyerDTO = {};
|
||||
|
||||
if (communicationDetails) {
|
||||
buyer.communicationDetails = { ...communicationDetails };
|
||||
}
|
||||
|
||||
if (!!lastName || !!firstName) {
|
||||
buyer.lastName = lastName;
|
||||
buyer.firstName = firstName;
|
||||
}
|
||||
|
||||
if (!!organisation && !!buyer.organisation) {
|
||||
buyer.organisation = {
|
||||
name: organisation,
|
||||
};
|
||||
}
|
||||
|
||||
return this.orderService
|
||||
.OrderPatchOrder({
|
||||
orderId: orderId,
|
||||
order: {
|
||||
notificationChannels,
|
||||
buyer,
|
||||
},
|
||||
})
|
||||
.pipe(map((res) => res.result));
|
||||
}
|
||||
|
||||
generateNotifications({
|
||||
orderId,
|
||||
taskTypes,
|
||||
}: {
|
||||
orderId: number;
|
||||
taskTypes: string[];
|
||||
}) {
|
||||
return this.orderService.OrderRegenerateOrderItemStatusTasks({
|
||||
orderId,
|
||||
taskTypes,
|
||||
});
|
||||
}
|
||||
|
||||
getCompletedTasks({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
take,
|
||||
skip,
|
||||
}: {
|
||||
orderId: number;
|
||||
orderItemId: number;
|
||||
orderItemSubsetId: number;
|
||||
take?: number;
|
||||
skip?: number;
|
||||
}): Observable<Record<string, Date[]>> {
|
||||
return this.orderService
|
||||
.OrderGetOrderItemSubsetTasks({
|
||||
orderId,
|
||||
orderItemId,
|
||||
orderItemSubsetId,
|
||||
completed: new Date(0).toISOString(),
|
||||
take,
|
||||
skip,
|
||||
})
|
||||
.pipe(
|
||||
map((res) =>
|
||||
res.result
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b.completed).getTime() -
|
||||
new Date(a.completed).getTime(),
|
||||
)
|
||||
.reduce(
|
||||
(data, result) => {
|
||||
(data[result.name] = data[result.name] || []).push(
|
||||
new Date(result.completed),
|
||||
);
|
||||
return data;
|
||||
},
|
||||
{} as Record<string, Date[]>,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReceiptOrderItemSubsetReferenceValues, ReceiptService } from '@generated/swagger/oms-api';
|
||||
import { memorize } from '@utils/common';
|
||||
import { shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class DomainReceiptService {
|
||||
constructor(private receiptService: ReceiptService) {}
|
||||
|
||||
createShippingNotes(params: ReceiptService.ReceiptCreateShippingNote2Params) {
|
||||
return this.receiptService.ReceiptCreateShippingNote2(params);
|
||||
}
|
||||
|
||||
@memorize({ ttl: 1000 })
|
||||
getReceipts(payload: ReceiptOrderItemSubsetReferenceValues) {
|
||||
return this.receiptService
|
||||
.ReceiptGetReceiptsByOrderItemSubset({
|
||||
payload: payload,
|
||||
})
|
||||
.pipe(shareReplay(1));
|
||||
}
|
||||
}
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ReceiptOrderItemSubsetReferenceValues,
|
||||
ReceiptService,
|
||||
} from '@generated/swagger/oms-api';
|
||||
import { memorize } from '@utils/common';
|
||||
import { shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DomainReceiptService {
|
||||
constructor(private receiptService: ReceiptService) {}
|
||||
|
||||
createShippingNotes(params: ReceiptService.ReceiptCreateShippingNote2Params) {
|
||||
return this.receiptService.ReceiptCreateShippingNote2(params);
|
||||
}
|
||||
|
||||
@memorize({ ttl: 1000 })
|
||||
getReceipts(payload: ReceiptOrderItemSubsetReferenceValues) {
|
||||
return this.receiptService
|
||||
.ReceiptGetReceiptsByOrderItemSubset({
|
||||
payload: payload,
|
||||
})
|
||||
.pipe(shareReplay(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './defs';
|
||||
export * from './mappings';
|
||||
export * from './remission.module';
|
||||
export * from './remission.service';
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { DomainRemissionService } from './remission.service';
|
||||
|
||||
@NgModule({})
|
||||
export class DomainRemissionModule {
|
||||
static forRoot(): ModuleWithProviders<DomainRemissionModule> {
|
||||
return {
|
||||
ngModule: RootDomainRemissionModule,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
providers: [DomainRemissionService],
|
||||
})
|
||||
export class RootDomainRemissionModule {}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,32 +1,43 @@
|
||||
import { enableProdMode, isDevMode } from "@angular/core";
|
||||
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
|
||||
import { CONFIG_DATA } from "@isa/core/config";
|
||||
import { setDefaultOptions } from "date-fns";
|
||||
import { de } from "date-fns/locale";
|
||||
import * as moment from "moment";
|
||||
import "moment/locale/de";
|
||||
|
||||
setDefaultOptions({ locale: de });
|
||||
moment.locale("de");
|
||||
|
||||
import { AppModule } from "./app/app.module";
|
||||
|
||||
if (!isDevMode()) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
const configRes = await fetch("/config/config.json");
|
||||
|
||||
const config = await configRes.json();
|
||||
|
||||
platformBrowserDynamic([
|
||||
{ provide: CONFIG_DATA, useValue: config },
|
||||
]).bootstrapModule(AppModule);
|
||||
}
|
||||
|
||||
try {
|
||||
bootstrap();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
import { enableProdMode, isDevMode } from '@angular/core';
|
||||
import { CONFIG_DATA } from '@isa/core/config';
|
||||
import { setDefaultOptions } from 'date-fns';
|
||||
import { de } from 'date-fns/locale';
|
||||
import localeDe from '@angular/common/locales/de';
|
||||
import localeDeExtra from '@angular/common/locales/extra/de';
|
||||
import * as moment from 'moment';
|
||||
import 'moment/locale/de';
|
||||
|
||||
setDefaultOptions({ locale: de });
|
||||
moment.locale('de');
|
||||
|
||||
registerLocaleData(localeDe, localeDeExtra);
|
||||
registerLocaleData(localeDe, 'de', localeDeExtra);
|
||||
|
||||
import { App } from './app/app';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
|
||||
if (!isDevMode()) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
const configRes = await fetch('/config/config.json');
|
||||
|
||||
const config = await configRes.json();
|
||||
|
||||
await bootstrapApplication(App, {
|
||||
...appConfig,
|
||||
providers: [
|
||||
{ provide: CONFIG_DATA, useValue: config },
|
||||
...appConfig.providers,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
bootstrap();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export class ModalAvailabilitiesComponent {
|
||||
item = this.modalRef.data.item;
|
||||
itemId = this.modalRef.data.itemId || this.modalRef.data.item.id;
|
||||
userbranch$ = combineLatest([
|
||||
this.applicationService.getSelectedBranch$(this.applicationService.activatedProcessId),
|
||||
this.applicationService.getSelectedBranch$(),
|
||||
this.domainAvailabilityService.getDefaultBranch(),
|
||||
]).pipe(map(([selectedBranch, defaultBranch]) => selectedBranch || defaultBranch));
|
||||
|
||||
|
||||
@@ -192,11 +192,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
}),
|
||||
);
|
||||
|
||||
selectedBranchId$ = this.applicationService.activatedProcessId$.pipe(
|
||||
switchMap((processId) =>
|
||||
this.applicationService.getSelectedBranch$(processId),
|
||||
),
|
||||
);
|
||||
selectedBranchId$ = this.applicationService.getSelectedBranch$();
|
||||
|
||||
get isTablet$() {
|
||||
return this._environment.matchTablet$;
|
||||
@@ -328,7 +324,7 @@ export class ArticleDetailsComponent implements OnInit, OnDestroy {
|
||||
debounceTime(0),
|
||||
switchMap((params) =>
|
||||
this.applicationService
|
||||
.getSelectedBranch$(Number(params.processId))
|
||||
.getSelectedBranch$()
|
||||
.pipe(map((selectedBranch) => ({ params, selectedBranch }))),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -98,11 +98,9 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
this.subscriptions.add(
|
||||
this.application.activatedProcessId$
|
||||
.pipe(
|
||||
debounceTime(0),
|
||||
switchMap((processId) => this.application.getSelectedBranch$(processId)),
|
||||
)
|
||||
this.application
|
||||
.getSelectedBranch$()
|
||||
.pipe(debounceTime(0))
|
||||
.subscribe((selectedBranch) => {
|
||||
const branchChanged = selectedBranch?.id !== this.searchService?.selectedBranch?.id;
|
||||
if (branchChanged) {
|
||||
@@ -143,7 +141,7 @@ export class ArticleSearchMainComponent implements OnInit, OnDestroy {
|
||||
const clean = { ...params };
|
||||
|
||||
for (const key in clean) {
|
||||
if (key === 'main_qs' || key?.includes('order_by')) {
|
||||
if (key === 'main_qs') {
|
||||
clean[key] = undefined;
|
||||
} else if (key?.includes('order_by')) {
|
||||
delete clean[key];
|
||||
|
||||
@@ -40,7 +40,7 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
|
||||
readonly item$ = this.select((s) => s.item);
|
||||
|
||||
@Input() selected: boolean = false;
|
||||
@Input() selected = false;
|
||||
|
||||
@Input()
|
||||
get selectable() {
|
||||
@@ -91,9 +91,7 @@ export class SearchResultItemComponent extends ComponentStore<SearchResultItemCo
|
||||
|
||||
defaultBranch$ = this._availability.getDefaultBranch();
|
||||
|
||||
selectedBranchId$ = this.applicationService.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this.applicationService.getSelectedBranch$(processId)),
|
||||
);
|
||||
selectedBranchId$ = this.applicationService.getSelectedBranch$();
|
||||
|
||||
isOrderBranch$ = combineLatest([this.defaultBranch$, this.selectedBranchId$]).pipe(
|
||||
map(([defaultBranch, selectedBranch]) => {
|
||||
|
||||
@@ -157,7 +157,7 @@ export class ArticleSearchResultsComponent
|
||||
.pipe(
|
||||
debounceTime(0),
|
||||
switchMap(([processId, queryParams]) =>
|
||||
this.application.getSelectedBranch$(processId).pipe(
|
||||
this.application.getSelectedBranch$().pipe(
|
||||
map((selectedBranch) => ({
|
||||
processId,
|
||||
queryParams,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { DomainCatalogModule } from '@domain/catalog';
|
||||
import { ThumbnailUrlPipe } from '@domain/catalog';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiSelectBulletModule } from '@ui/select-bullet';
|
||||
@@ -26,7 +26,7 @@ import { MatomoModule } from 'ngx-matomo-client';
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
RouterModule,
|
||||
DomainCatalogModule,
|
||||
ThumbnailUrlPipe,
|
||||
UiCommonModule,
|
||||
UiIconModule,
|
||||
UiSelectBulletModule,
|
||||
|
||||
@@ -77,9 +77,7 @@ export class PageCatalogComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
ngOnInit() {
|
||||
this.activatedProcessId$ = this.application.activatedProcessId$.pipe(map((processId) => String(processId)));
|
||||
|
||||
this.selectedBranch$ = this.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this.application.getSelectedBranch$(Number(processId))),
|
||||
);
|
||||
this.selectedBranch$ = this.application.getSelectedBranch$();
|
||||
|
||||
this.stockTooltipText$ = combineLatest([this.defaultBranch$, this.selectedBranch$]).pipe(
|
||||
map(([defaultBranch, selectedBranch]) => {
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BranchSelectorComponent } from '@shared/components/branch-selector';
|
||||
import { BreadcrumbModule } from '@shared/components/breadcrumb';
|
||||
import { ArticleDetailsModule } from './article-details/article-details.module';
|
||||
import { ArticleSearchModule } from './article-search/article-search.module';
|
||||
import { PageCatalogRoutingModule } from './page-catalog-routing.module';
|
||||
import { PageCatalogComponent } from './page-catalog.component';
|
||||
import { SharedSplitscreenComponent } from '@shared/components/splitscreen';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiTooltipModule } from '@ui/tooltip';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
PageCatalogRoutingModule,
|
||||
ArticleSearchModule,
|
||||
ArticleDetailsModule,
|
||||
BreadcrumbModule,
|
||||
BranchSelectorComponent,
|
||||
SharedSplitscreenComponent,
|
||||
UiCommonModule,
|
||||
UiTooltipModule,
|
||||
],
|
||||
exports: [],
|
||||
declarations: [PageCatalogComponent],
|
||||
})
|
||||
export class PageCatalogModule {}
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BranchSelectorComponent } from '@shared/components/branch-selector';
|
||||
import { BreadcrumbModule } from '@shared/components/breadcrumb';
|
||||
import { ArticleDetailsModule } from './article-details/article-details.module';
|
||||
import { ArticleSearchModule } from './article-search/article-search.module';
|
||||
import { PageCatalogRoutingModule } from './page-catalog-routing.module';
|
||||
import { PageCatalogComponent } from './page-catalog.component';
|
||||
import { SharedSplitscreenComponent } from '@shared/components/splitscreen';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiTooltipModule } from '@ui/tooltip';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
PageCatalogRoutingModule,
|
||||
ArticleDetailsModule,
|
||||
ArticleSearchModule,
|
||||
BreadcrumbModule,
|
||||
BranchSelectorComponent,
|
||||
SharedSplitscreenComponent,
|
||||
UiCommonModule,
|
||||
UiTooltipModule,
|
||||
],
|
||||
exports: [],
|
||||
declarations: [PageCatalogComponent],
|
||||
})
|
||||
export class PageCatalogModule {}
|
||||
|
||||
@@ -73,11 +73,9 @@ export class CustomerOrderSearchMainComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
this._subscriptions.add(
|
||||
this._application.activatedProcessId$
|
||||
.pipe(
|
||||
debounceTime(0),
|
||||
switchMap((processId) => this._application.getSelectedBranch$(processId)),
|
||||
)
|
||||
this._application
|
||||
.getSelectedBranch$()
|
||||
.pipe(debounceTime(0))
|
||||
.subscribe((selectedBranch) => {
|
||||
const branchChanged = selectedBranch?.id !== this._customerOrderSearchStore?.selectedBranch?.id;
|
||||
if (branchChanged) {
|
||||
|
||||
@@ -183,7 +183,7 @@ export class CustomerOrderSearchResultsComponent
|
||||
debounceTime(150),
|
||||
switchMap(([processId, params]) =>
|
||||
this._application
|
||||
.getSelectedBranch$(processId)
|
||||
.getSelectedBranch$()
|
||||
.pipe(map((selectedBranch) => ({ processId, params, selectedBranch }))),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -49,9 +49,7 @@ export class CustomerOrderComponent implements OnInit, AfterViewInit, OnDestroy
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.selectedBranch$ = this.application.activatedProcessId$.pipe(
|
||||
switchMap((processId) => this.application.getSelectedBranch$(Number(processId))),
|
||||
);
|
||||
this.selectedBranch$ = this.application.getSelectedBranch$();
|
||||
|
||||
/* Ticket #4544 - Suchrequest abbrechen bei Prozesswechsel
|
||||
/ um zu verhindern, dass die Suche in einen anderen Kundenbestellungen Prozess übernommen wird
|
||||
|
||||
@@ -1,110 +1,113 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Icon, IconAlias, IconConfig } from './interfaces';
|
||||
import { IconLoader } from './loader';
|
||||
import { Observable, Subject, isObservable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class IconRegistry {
|
||||
private _icons = new Map<string, Icon>();
|
||||
private _aliases = new Map<string, string>();
|
||||
private _fallback: string;
|
||||
private _viewBox: string;
|
||||
|
||||
updated = new Subject<void>();
|
||||
|
||||
private _initComplete = false;
|
||||
|
||||
constructor(private _iconLoader: IconLoader) {
|
||||
this._loadIcons();
|
||||
}
|
||||
|
||||
private async _loadIcons(): Promise<void> {
|
||||
const load = this._iconLoader.getIcons();
|
||||
|
||||
if (load instanceof Promise) {
|
||||
const config = await load;
|
||||
this._init(config);
|
||||
} else if (isObservable(load)) {
|
||||
load.subscribe((config) => {
|
||||
this._init(config);
|
||||
});
|
||||
} else {
|
||||
this._init(load);
|
||||
}
|
||||
}
|
||||
|
||||
private _init(config: IconConfig): void {
|
||||
this.register(...config.icons);
|
||||
this.alias(...config.aliases);
|
||||
this.setViewBox(config.viewBox);
|
||||
this.setFallback(config.fallback);
|
||||
|
||||
this._initComplete = true;
|
||||
|
||||
this.updated.next();
|
||||
}
|
||||
|
||||
register(...icons: Icon[]): IconRegistry {
|
||||
icons?.forEach((icon) => {
|
||||
this._icons.set(icon.name, icon);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setViewBox(viewBox: string): void {
|
||||
this._viewBox = viewBox;
|
||||
}
|
||||
|
||||
alias(...aliases: IconAlias[]): IconRegistry {
|
||||
aliases?.forEach((alias) => {
|
||||
this._aliases.set(alias.alias, alias.name);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setFallback(name: string): void {
|
||||
this._fallback = name;
|
||||
}
|
||||
|
||||
get(name: string): Icon | undefined {
|
||||
const alias = this._aliases.get(name);
|
||||
let iconName = name;
|
||||
if (alias) {
|
||||
iconName = alias;
|
||||
}
|
||||
|
||||
let icon = this._icons.get(iconName);
|
||||
|
||||
if (!icon && this._initComplete) {
|
||||
if (alias) {
|
||||
console.warn(`Not found: Icon with name ${name} (${iconName})`);
|
||||
} else {
|
||||
console.warn(`Unable to find icon: '${name}'`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!icon && this._fallback) {
|
||||
icon = this._icons.get(this._fallback);
|
||||
}
|
||||
|
||||
return { ...icon, viewBox: icon?.viewBox || this._viewBox };
|
||||
}
|
||||
|
||||
get$(name: string): Observable<Icon | undefined> {
|
||||
return new Observable<Icon | undefined>((subscriber) => {
|
||||
let icon = this.get(name);
|
||||
subscriber.next(icon);
|
||||
subscriber.complete();
|
||||
|
||||
const sub = this.updated.subscribe(() => {
|
||||
icon = this.get(name);
|
||||
subscriber.next(icon);
|
||||
subscriber.complete();
|
||||
});
|
||||
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
}
|
||||
}
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Icon, IconAlias, IconConfig } from './interfaces';
|
||||
import { IconLoader } from './loader';
|
||||
import { Observable, Subject, isObservable } from 'rxjs';
|
||||
|
||||
/**
|
||||
* @deprecated Use UiIconModule from '@isa/ui/icon' instead.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class IconRegistry {
|
||||
private _icons = new Map<string, Icon>();
|
||||
private _aliases = new Map<string, string>();
|
||||
private _fallback: string;
|
||||
private _viewBox: string;
|
||||
|
||||
updated = new Subject<void>();
|
||||
|
||||
private _initComplete = false;
|
||||
|
||||
constructor(private _iconLoader: IconLoader) {
|
||||
this._loadIcons();
|
||||
}
|
||||
|
||||
private async _loadIcons(): Promise<void> {
|
||||
const load = this._iconLoader.getIcons();
|
||||
|
||||
if (load instanceof Promise) {
|
||||
const config = await load;
|
||||
this._init(config);
|
||||
} else if (isObservable(load)) {
|
||||
load.subscribe((config) => {
|
||||
this._init(config);
|
||||
});
|
||||
} else {
|
||||
this._init(load);
|
||||
}
|
||||
}
|
||||
|
||||
private _init(config: IconConfig): void {
|
||||
this.register(...config.icons);
|
||||
this.alias(...config.aliases);
|
||||
this.setViewBox(config.viewBox);
|
||||
this.setFallback(config.fallback);
|
||||
|
||||
this._initComplete = true;
|
||||
|
||||
this.updated.next();
|
||||
}
|
||||
|
||||
register(...icons: Icon[]): IconRegistry {
|
||||
icons?.forEach((icon) => {
|
||||
this._icons.set(icon.name, icon);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setViewBox(viewBox: string): void {
|
||||
this._viewBox = viewBox;
|
||||
}
|
||||
|
||||
alias(...aliases: IconAlias[]): IconRegistry {
|
||||
aliases?.forEach((alias) => {
|
||||
this._aliases.set(alias.alias, alias.name);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setFallback(name: string): void {
|
||||
this._fallback = name;
|
||||
}
|
||||
|
||||
get(name: string): Icon | undefined {
|
||||
const alias = this._aliases.get(name);
|
||||
let iconName = name;
|
||||
if (alias) {
|
||||
iconName = alias;
|
||||
}
|
||||
|
||||
let icon = this._icons.get(iconName);
|
||||
|
||||
if (!icon && this._initComplete) {
|
||||
if (alias) {
|
||||
console.warn(`Not found: Icon with name ${name} (${iconName})`);
|
||||
} else {
|
||||
console.warn(`Unable to find icon: '${name}'`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!icon && this._fallback) {
|
||||
icon = this._icons.get(this._fallback);
|
||||
}
|
||||
|
||||
return { ...icon, viewBox: icon?.viewBox || this._viewBox };
|
||||
}
|
||||
|
||||
get$(name: string): Observable<Icon | undefined> {
|
||||
return new Observable<Icon | undefined>((subscriber) => {
|
||||
let icon = this.get(name);
|
||||
subscriber.next(icon);
|
||||
subscriber.complete();
|
||||
|
||||
const sub = this.updated.subscribe(() => {
|
||||
icon = this.get(name);
|
||||
subscriber.next(icon);
|
||||
subscriber.complete();
|
||||
});
|
||||
|
||||
return () => sub.unsubscribe();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,75 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { IconRegistry } from './icon-registry';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'shared-icon',
|
||||
template: `
|
||||
<svg [style.width.rem]="size / 16" [style.height.rem]="size / 16" [attr.viewBox]="viewBox">
|
||||
<path fill="currentColor" [attr.d]="data" />
|
||||
</svg>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
})
|
||||
export class IconComponent implements OnInit, OnDestroy, OnChanges {
|
||||
@Input()
|
||||
icon: string;
|
||||
|
||||
data: string;
|
||||
|
||||
viewBox: string;
|
||||
|
||||
@Input()
|
||||
size: number = 24;
|
||||
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private readonly _iconRegistry: IconRegistry,
|
||||
private readonly _cdr: ChangeDetectorRef,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._iconRegistry.updated.pipe(takeUntil(this._onDestroy$)).subscribe(() => {
|
||||
this.updateIcon();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.icon) {
|
||||
this.updateIcon();
|
||||
}
|
||||
}
|
||||
|
||||
updateIcon(): void {
|
||||
const icon = this._iconRegistry.get(this.icon);
|
||||
this.data = icon?.data;
|
||||
this.viewBox = icon?.viewBox;
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
}
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { IconRegistry } from './icon-registry';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* @deprecated Use UiIconModule from '@isa/ui/icon' instead.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'shared-icon',
|
||||
template: `
|
||||
<svg
|
||||
[style.width.rem]="size / 16"
|
||||
[style.height.rem]="size / 16"
|
||||
[attr.viewBox]="viewBox"
|
||||
>
|
||||
<path fill="currentColor" [attr.d]="data" />
|
||||
</svg>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
})
|
||||
export class IconComponent implements OnInit, OnDestroy, OnChanges {
|
||||
@Input()
|
||||
icon: string;
|
||||
|
||||
data: string;
|
||||
|
||||
viewBox: string;
|
||||
|
||||
@Input()
|
||||
size = 24;
|
||||
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private readonly _iconRegistry: IconRegistry,
|
||||
private readonly _cdr: ChangeDetectorRef,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._iconRegistry.updated
|
||||
.pipe(takeUntil(this._onDestroy$))
|
||||
.subscribe(() => {
|
||||
this.updateIcon();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._onDestroy$.next();
|
||||
this._onDestroy$.complete();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.icon) {
|
||||
this.updateIcon();
|
||||
}
|
||||
}
|
||||
|
||||
updateIcon(): void {
|
||||
const icon = this._iconRegistry.get(this.icon);
|
||||
this.data = icon?.data;
|
||||
this.viewBox = icon?.viewBox;
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,37 @@
|
||||
import { NgModule, Provider } from '@angular/core';
|
||||
import { IconComponent } from './icon.component';
|
||||
import { IconLoader, JsonIconLoader } from './loader';
|
||||
import { IconRegistry } from './icon-registry';
|
||||
|
||||
export function provideIcon(loaderProvider?: Provider) {
|
||||
const providers: Provider[] = [IconRegistry];
|
||||
if (!loaderProvider) {
|
||||
providers.push({
|
||||
provide: IconLoader,
|
||||
useClass: JsonIconLoader,
|
||||
});
|
||||
} else {
|
||||
providers.push(loaderProvider);
|
||||
}
|
||||
|
||||
return providers;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [IconComponent],
|
||||
exports: [IconComponent],
|
||||
})
|
||||
export class IconModule {
|
||||
static forRoot(loaderProvider?: Provider) {
|
||||
return {
|
||||
ngModule: IconModule,
|
||||
providers: provideIcon(loaderProvider),
|
||||
};
|
||||
}
|
||||
}
|
||||
import { NgModule, Provider } from '@angular/core';
|
||||
import { IconComponent } from './icon.component';
|
||||
import { IconLoader, JsonIconLoader } from './loader';
|
||||
import { IconRegistry } from './icon-registry';
|
||||
|
||||
/**
|
||||
* @deprecated Use UiIconModule from '@isa/ui/icon' instead.
|
||||
*/
|
||||
export function provideIcon(loaderProvider?: Provider) {
|
||||
const providers: Provider[] = [IconRegistry];
|
||||
if (!loaderProvider) {
|
||||
providers.push({
|
||||
provide: IconLoader,
|
||||
useClass: JsonIconLoader,
|
||||
});
|
||||
} else {
|
||||
providers.push(loaderProvider);
|
||||
}
|
||||
|
||||
return providers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use UiIconModule from '@isa/ui/icon' instead.
|
||||
*/
|
||||
@NgModule({
|
||||
imports: [IconComponent],
|
||||
exports: [IconComponent],
|
||||
})
|
||||
export class IconModule {
|
||||
static forRoot(loaderProvider?: Provider) {
|
||||
return {
|
||||
ngModule: IconModule,
|
||||
providers: provideIcon(loaderProvider),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ui-icon-badge',
|
||||
templateUrl: 'icon-badge.component.html',
|
||||
styleUrls: ['icon-badge.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false,
|
||||
})
|
||||
export class UiIconBadgeComponent {
|
||||
@Input()
|
||||
icon: string;
|
||||
|
||||
@Input()
|
||||
alt: string;
|
||||
|
||||
@Input()
|
||||
area: 'customer' | 'branch' = 'customer';
|
||||
}
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
|
||||
/**
|
||||
* @deprecated Use UiIconModule from '@isa/ui/icon' instead.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ui-icon-badge',
|
||||
templateUrl: 'icon-badge.component.html',
|
||||
styleUrls: ['icon-badge.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false,
|
||||
})
|
||||
export class UiIconBadgeComponent {
|
||||
@Input()
|
||||
icon: string;
|
||||
|
||||
@Input()
|
||||
alt: string;
|
||||
|
||||
@Input()
|
||||
area: 'customer' | 'branch' = 'customer';
|
||||
}
|
||||
|
||||
@@ -1,59 +1,62 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { SvgIcon } from './defs';
|
||||
import { IconAlias } from './defs/icon-alias';
|
||||
|
||||
@Injectable()
|
||||
export class IconRegistry {
|
||||
private _icons = new Map<string, SvgIcon>();
|
||||
private _aliases = new Map<string, string>();
|
||||
private _fallback: string;
|
||||
private _viewBox: string;
|
||||
|
||||
register(...icons: SvgIcon[]): IconRegistry {
|
||||
icons?.forEach((icon) => {
|
||||
this._icons.set(icon.name, icon);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setViewBox(viewBox: string): void {
|
||||
this._viewBox = viewBox;
|
||||
}
|
||||
|
||||
alias(...aliases: IconAlias[]): IconRegistry {
|
||||
aliases?.forEach((alias) => {
|
||||
this._aliases.set(alias.alias, alias.name);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setFallback(name: string): void {
|
||||
this._fallback = name;
|
||||
}
|
||||
|
||||
get(name: string): SvgIcon | undefined {
|
||||
const alias = this._aliases.get(name);
|
||||
let iconName = name;
|
||||
if (alias) {
|
||||
iconName = alias;
|
||||
}
|
||||
|
||||
let icon = this._icons.get(iconName);
|
||||
|
||||
if (!icon) {
|
||||
if (alias) {
|
||||
console.warn(`Not found: Icon with name ${name} (${iconName})`);
|
||||
} else {
|
||||
console.warn(`Unable to find icon: '${name}'`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!icon && this._fallback) {
|
||||
icon = this._icons.get(this._fallback);
|
||||
}
|
||||
|
||||
return { ...icon, viewBox: icon?.viewBox || this._viewBox };
|
||||
}
|
||||
}
|
||||
import { Injectable } from '@angular/core';
|
||||
import { SvgIcon } from './defs';
|
||||
import { IconAlias } from './defs/icon-alias';
|
||||
|
||||
/**
|
||||
* @deprecated Use UiIconModule from '@isa/ui/icon' instead.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class IconRegistry {
|
||||
private _icons = new Map<string, SvgIcon>();
|
||||
private _aliases = new Map<string, string>();
|
||||
private _fallback: string;
|
||||
private _viewBox: string;
|
||||
|
||||
register(...icons: SvgIcon[]): IconRegistry {
|
||||
icons?.forEach((icon) => {
|
||||
this._icons.set(icon.name, icon);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setViewBox(viewBox: string): void {
|
||||
this._viewBox = viewBox;
|
||||
}
|
||||
|
||||
alias(...aliases: IconAlias[]): IconRegistry {
|
||||
aliases?.forEach((alias) => {
|
||||
this._aliases.set(alias.alias, alias.name);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
setFallback(name: string): void {
|
||||
this._fallback = name;
|
||||
}
|
||||
|
||||
get(name: string): SvgIcon | undefined {
|
||||
const alias = this._aliases.get(name);
|
||||
let iconName = name;
|
||||
if (alias) {
|
||||
iconName = alias;
|
||||
}
|
||||
|
||||
let icon = this._icons.get(iconName);
|
||||
|
||||
if (!icon) {
|
||||
if (alias) {
|
||||
console.warn(`Not found: Icon with name ${name} (${iconName})`);
|
||||
} else {
|
||||
console.warn(`Unable to find icon: '${name}'`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!icon && this._fallback) {
|
||||
icon = this._icons.get(this._fallback);
|
||||
}
|
||||
|
||||
return { ...icon, viewBox: icon?.viewBox || this._viewBox };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,39 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, Optional, Inject, HostBinding } from '@angular/core';
|
||||
import { UI_ICON_HREF, UI_ICON_VIEW_BOX } from './tokens';
|
||||
|
||||
@Component({
|
||||
selector: 'ui-icon',
|
||||
templateUrl: 'icon.component.html',
|
||||
styleUrls: ['icon.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false,
|
||||
})
|
||||
export class UiIconComponent {
|
||||
@Input()
|
||||
@HostBinding('attr.icon')
|
||||
icon: string;
|
||||
|
||||
@Input()
|
||||
size = '1em';
|
||||
|
||||
@Input()
|
||||
rotate = '0deg';
|
||||
|
||||
constructor(
|
||||
@Optional() @Inject(UI_ICON_HREF) public iconHref: string,
|
||||
@Optional() @Inject(UI_ICON_VIEW_BOX) public viewBox: string,
|
||||
) {
|
||||
this.iconHref = this.iconHref || '/assets/icons.svg';
|
||||
this.viewBox = this.viewBox || '0 0 32 32';
|
||||
}
|
||||
}
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
Input,
|
||||
Optional,
|
||||
Inject,
|
||||
HostBinding,
|
||||
} from '@angular/core';
|
||||
import { UI_ICON_HREF, UI_ICON_VIEW_BOX } from './tokens';
|
||||
|
||||
/**
|
||||
* @deprecated Use UiIconModule from '@isa/ui/icon' instead.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ui-icon',
|
||||
templateUrl: 'icon.component.html',
|
||||
styleUrls: ['icon.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false,
|
||||
})
|
||||
export class UiIconComponent {
|
||||
@Input()
|
||||
@HostBinding('attr.icon')
|
||||
icon: string;
|
||||
|
||||
@Input()
|
||||
size = '1em';
|
||||
|
||||
@Input()
|
||||
rotate = '0deg';
|
||||
|
||||
constructor(
|
||||
@Optional() @Inject(UI_ICON_HREF) public iconHref: string,
|
||||
@Optional() @Inject(UI_ICON_VIEW_BOX) public viewBox: string,
|
||||
) {
|
||||
this.iconHref = this.iconHref || '/assets/icons.svg';
|
||||
this.viewBox = this.viewBox || '0 0 32 32';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,52 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { IconRegistry } from './icon-registry';
|
||||
|
||||
@Component({
|
||||
selector: 'ui-svg-icon',
|
||||
template: `
|
||||
<svg [style.width.rem]="size / 16" [style.height.rem]="size / 16" [attr.viewBox]="viewBox">
|
||||
<path fill="currentColor" [attr.d]="data" />
|
||||
</svg>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false,
|
||||
})
|
||||
export class UISvgIconComponent implements OnChanges {
|
||||
@Input()
|
||||
icon: string;
|
||||
|
||||
data: string;
|
||||
|
||||
viewBox: string;
|
||||
|
||||
@Input()
|
||||
size: number = 24;
|
||||
|
||||
constructor(
|
||||
private readonly _iconRegistry: IconRegistry,
|
||||
private readonly _cdr: ChangeDetectorRef,
|
||||
) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.icon) {
|
||||
const icon = this._iconRegistry.get(this.icon);
|
||||
this.data = icon?.data;
|
||||
this.viewBox = icon?.viewBox;
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { IconRegistry } from './icon-registry';
|
||||
|
||||
/**
|
||||
* @deprecated Use UiIconModule from '@isa/ui/icon' instead.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ui-svg-icon',
|
||||
template: `
|
||||
<svg
|
||||
[style.width.rem]="size / 16"
|
||||
[style.height.rem]="size / 16"
|
||||
[attr.viewBox]="viewBox"
|
||||
>
|
||||
<path fill="currentColor" [attr.d]="data" />
|
||||
</svg>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false,
|
||||
})
|
||||
export class UISvgIconComponent implements OnChanges {
|
||||
@Input()
|
||||
icon: string;
|
||||
|
||||
data: string;
|
||||
|
||||
viewBox: string;
|
||||
|
||||
@Input()
|
||||
size = 24;
|
||||
|
||||
constructor(
|
||||
private readonly _iconRegistry: IconRegistry,
|
||||
private readonly _cdr: ChangeDetectorRef,
|
||||
) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.icon) {
|
||||
const icon = this._iconRegistry.get(this.icon);
|
||||
this.data = icon?.data;
|
||||
this.viewBox = icon?.viewBox;
|
||||
this._cdr.markForCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,63 @@
|
||||
import { ModuleWithProviders, NgModule, Provider } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { UiIconComponent } from './icon.component';
|
||||
import { UiIconBadgeComponent } from './icon-badge/icon-badge.component';
|
||||
import { UISvgIconComponent } from './svg-icon.component';
|
||||
import { IconRegistry } from './icon-registry';
|
||||
import { UI_ICON_CFG } from './tokens';
|
||||
import { UiIconConfig } from './icon-config';
|
||||
|
||||
export function _rootIconRegistryFactory(config: UiIconConfig): IconRegistry {
|
||||
const registry = new IconRegistry();
|
||||
|
||||
if (config?.fallback) {
|
||||
registry.setFallback(config.fallback);
|
||||
}
|
||||
if (config?.aliases) {
|
||||
registry.alias(...config.aliases);
|
||||
}
|
||||
if (config?.icons) {
|
||||
registry.register(...config.icons);
|
||||
}
|
||||
|
||||
if (config?.viewBox) {
|
||||
registry.setViewBox(config.viewBox);
|
||||
}
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
declarations: [UiIconComponent, UiIconBadgeComponent, UISvgIconComponent],
|
||||
exports: [UiIconComponent, UiIconBadgeComponent, UISvgIconComponent],
|
||||
})
|
||||
export class UiIconModule {
|
||||
static forRoot(config?: UiIconConfig): ModuleWithProviders<UiIconModule> {
|
||||
const providers: Provider[] = [
|
||||
{
|
||||
provide: IconRegistry,
|
||||
useFactory: _rootIconRegistryFactory,
|
||||
deps: [UI_ICON_CFG],
|
||||
},
|
||||
];
|
||||
|
||||
if (config) {
|
||||
providers.push({
|
||||
provide: UI_ICON_CFG,
|
||||
useValue: config,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
ngModule: UiIconModule,
|
||||
providers,
|
||||
};
|
||||
}
|
||||
}
|
||||
import { ModuleWithProviders, NgModule, Provider } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { UiIconComponent } from './icon.component';
|
||||
import { UiIconBadgeComponent } from './icon-badge/icon-badge.component';
|
||||
import { UISvgIconComponent } from './svg-icon.component';
|
||||
import { IconRegistry } from './icon-registry';
|
||||
import { UI_ICON_CFG } from './tokens';
|
||||
import { UiIconConfig } from './icon-config';
|
||||
|
||||
/**
|
||||
* @deprecated Use UiIconModule from '@isa/ui/icon' instead.
|
||||
*/
|
||||
export function _rootIconRegistryFactory(config: UiIconConfig): IconRegistry {
|
||||
const registry = new IconRegistry();
|
||||
|
||||
if (config?.fallback) {
|
||||
registry.setFallback(config.fallback);
|
||||
}
|
||||
if (config?.aliases) {
|
||||
registry.alias(...config.aliases);
|
||||
}
|
||||
if (config?.icons) {
|
||||
registry.register(...config.icons);
|
||||
}
|
||||
|
||||
if (config?.viewBox) {
|
||||
registry.setViewBox(config.viewBox);
|
||||
}
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use UiIconModule from '@isa/ui/icon' instead.
|
||||
*/
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
declarations: [UiIconComponent, UiIconBadgeComponent, UISvgIconComponent],
|
||||
exports: [UiIconComponent, UiIconBadgeComponent, UISvgIconComponent],
|
||||
})
|
||||
export class UiIconModule {
|
||||
static forRoot(config?: UiIconConfig): ModuleWithProviders<UiIconModule> {
|
||||
const providers: Provider[] = [
|
||||
{
|
||||
provide: IconRegistry,
|
||||
useFactory: _rootIconRegistryFactory,
|
||||
deps: [UI_ICON_CFG],
|
||||
},
|
||||
];
|
||||
|
||||
if (config) {
|
||||
providers.push({
|
||||
provide: UI_ICON_CFG,
|
||||
useValue: config,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
ngModule: UiIconModule,
|
||||
providers,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user