Materials and Material types frontend
This commit is contained in:
parent
c81f046804
commit
f98a0064ca
|
@ -50,6 +50,7 @@ dependencies {
|
|||
// testImplementation("io.mockk:mockk:1.10.2")
|
||||
|
||||
runtimeOnly("com.h2database:h2:1.4.199")
|
||||
runtimeOnly("mysql:mysql-connector-java:8.0.22")
|
||||
compileOnly("org.projectlombok:lombok:1.18.10")
|
||||
}
|
||||
|
||||
|
|
|
@ -7597,6 +7597,11 @@
|
|||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"ngx-material-file-input": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ngx-material-file-input/-/ngx-material-file-input-2.1.1.tgz",
|
||||
"integrity": "sha512-FbaIjiJnL6BZtZYWLvMSn9aSaM62AZaJegloTUphmLz5jopXPzE5W+3aC+dsf9h1IIqHSCLcyv0w+qH0ypBhMA=="
|
||||
},
|
||||
"nice-try": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"@mdi/angular-material": "^5.7.55",
|
||||
"bootstrap": "^4.5.2",
|
||||
"copy-webpack-plugin": "^6.2.1",
|
||||
"ngx-material-file-input": "^2.1.1",
|
||||
"rxjs": "~6.5.4",
|
||||
"tslib": "^1.10.0",
|
||||
"zone.js": "~0.10.2"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {Routes, RouterModule} from '@angular/router';
|
||||
import {InventoryPageComponent} from "./pages/inventory-page/inventory-page.component";
|
||||
|
||||
|
||||
const routes: Routes = [{
|
||||
|
@ -16,8 +17,24 @@ const routes: Routes = [{
|
|||
loadChildren: () => import('./modules/groups/groups.module').then(m => m.GroupsModule)
|
||||
}, {
|
||||
path: 'inventory',
|
||||
loadChildren: () => import('./modules/inventory/inventory.module').then(m => m.InventoryModule)
|
||||
}];
|
||||
component: InventoryPageComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'materialtype',
|
||||
loadChildren: () => import('./modules/material-type/material-type.module').then(m => m.MaterialTypeModule),
|
||||
},
|
||||
{
|
||||
path: 'material',
|
||||
loadChildren: () => import('./modules/material/material.module').then(m => m.MaterialModule)
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'materialtype'
|
||||
}
|
||||
]
|
||||
},
|
||||
{path: 'material', loadChildren: () => import('./modules/material/material.module').then(m => m.MaterialModule)}];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
<cre-header></cre-header>
|
||||
<div>
|
||||
<router-outlet *ngIf="isOnline; else isOffline"></router-outlet>
|
||||
<ng-template #isOffline>
|
||||
<div>
|
||||
<p>Aucune connexion</p>
|
||||
</div>
|
||||
</ng-template>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<div class="offline-server-card-wrapper" [hidden]="isServerOnline">
|
||||
<div class="dark-background"></div>
|
||||
<mat-card class="x-centered y-centered">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Erreur de connexion</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p>Le serveur est présentement hors ligne. Réessayez plus tard.</p>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-raised-button color="accent" (click)="reload()">Réessayer</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
<!-- FOOTER -->
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
.offline-server-card-wrapper
|
||||
position: fixed
|
||||
top: 0
|
||||
z-index: 100
|
||||
|
||||
.dark-background
|
||||
position: fixed
|
||||
top: 0
|
||||
opacity: .5
|
||||
|
||||
mat-card
|
||||
left: 50vw
|
||||
transform: translate(-50%, -50%)
|
|
@ -1,17 +1,38 @@
|
|||
import {Component, Inject, PLATFORM_ID} from '@angular/core';
|
||||
import {isPlatformBrowser} from "@angular/common";
|
||||
import {AppState} from "./modules/shared/app-state";
|
||||
import {Observable} from "rxjs";
|
||||
import {SubscribingComponent} from "./modules/shared/components/subscribing.component";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.sass']
|
||||
})
|
||||
export class AppComponent {
|
||||
isOnline: boolean;
|
||||
export class AppComponent extends SubscribingComponent {
|
||||
isOnline: boolean
|
||||
isServerOnline = true
|
||||
|
||||
constructor(
|
||||
@Inject(PLATFORM_ID) private platformId: object
|
||||
@Inject(PLATFORM_ID) private platformId: object,
|
||||
private appState: AppState
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.isOnline = isPlatformBrowser(this.platformId)
|
||||
super.ngOnInit();
|
||||
|
||||
this.subscribe(
|
||||
this.appState.serverOnline$,
|
||||
{
|
||||
next: online => this.isServerOnline = online
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
reload() {
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,12 @@ import {AppComponent} from './app.component';
|
|||
import {MatIconRegistry} from "@angular/material/icon";
|
||||
import {SharedModule} from "./modules/shared/shared.module";
|
||||
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
|
||||
import { InventoryPageComponent } from './pages/inventory-page/inventory-page.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
AppComponent,
|
||||
InventoryPageComponent
|
||||
],
|
||||
imports: [
|
||||
AppRoutingModule,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {Injectable, OnDestroy} from '@angular/core';
|
||||
import {Subject} from "rxjs";
|
||||
import {take, takeUntil, tap} from "rxjs/operators";
|
||||
import {take, takeUntil} from "rxjs/operators";
|
||||
import {AppState} from "../../shared/app-state";
|
||||
import {HttpClient, HttpResponse} from "@angular/common/http";
|
||||
import {environment} from "../../../../environments/environment";
|
||||
|
@ -39,11 +39,16 @@ export class AccountService implements OnDestroy {
|
|||
).subscribe({
|
||||
next: employee => this.appState.authenticatedEmployee = employee,
|
||||
error: err => {
|
||||
if (err.status === 404) {
|
||||
console.error('No default user is defined on this computer')
|
||||
if (err.status === 0 && err.statusText === "Unknown Error") {
|
||||
this.appState.isServerOnline = false
|
||||
} else {
|
||||
console.error('An error occurred while authenticating the default user')
|
||||
console.error(err)
|
||||
this.appState.isServerOnline = true
|
||||
if (err.status === 404 || err.status === 403) {
|
||||
console.error('No default user is defined on this computer')
|
||||
} else {
|
||||
console.error('An error occurred while authenticating the default user')
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -67,7 +72,14 @@ export class AccountService implements OnDestroy {
|
|||
this.setLoggedInEmployeeFromApi()
|
||||
success()
|
||||
},
|
||||
error: err => error(err)
|
||||
error: err => {
|
||||
if (err.status === 0 && err.statusText === "Unknown Error") {
|
||||
this.appState.isServerOnline = false
|
||||
} else {
|
||||
this.appState.isServerOnline = true
|
||||
error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -89,7 +101,7 @@ export class AccountService implements OnDestroy {
|
|||
}
|
||||
|
||||
hasPermission(permission: EmployeePermission): boolean {
|
||||
return this.appState.authenticatedEmployee.permissions.indexOf(permission) >= 0
|
||||
return this.appState.authenticatedEmployee && this.appState.authenticatedEmployee.permissions.indexOf(permission) >= 0
|
||||
}
|
||||
|
||||
private setLoggedInEmployeeFromApi() {
|
||||
|
|
|
@ -1,41 +1,33 @@
|
|||
import {Injectable, OnDestroy} from '@angular/core';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ApiService} from "../../shared/service/api.service";
|
||||
import {Employee, EmployeePermission} from "../../shared/model/employee";
|
||||
import {Observable, Subject} from "rxjs";
|
||||
import {takeUntil} from "rxjs/operators";
|
||||
import {Observable} from "rxjs";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EmployeeService implements OnDestroy {
|
||||
private _destroy$ = new Subject<boolean>()
|
||||
|
||||
export class EmployeeService {
|
||||
constructor(
|
||||
private api: ApiService
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._destroy$.next(true)
|
||||
this._destroy$.complete()
|
||||
}
|
||||
|
||||
get all(): Observable<Employee[]> {
|
||||
return this.api.get<Employee[]>('/employee', true)
|
||||
return this.api.get<Employee[]>('/employee')
|
||||
}
|
||||
|
||||
get(id: number): Observable<Employee> {
|
||||
return this.api.get<Employee>(`/employee/${id}`).pipe(takeUntil(this._destroy$))
|
||||
return this.api.get<Employee>(`/employee/${id}`)
|
||||
}
|
||||
|
||||
save(id: number, firstName: string, lastName: string, password: string, group: number, permissions: EmployeePermission[]): Observable<Employee> {
|
||||
const employee = {id, firstName, lastName, password, group, permissions}
|
||||
return this.api.post<Employee>('/employee', employee).pipe(takeUntil(this._destroy$))
|
||||
return this.api.post<Employee>('/employee', employee)
|
||||
}
|
||||
|
||||
update(id: number, firstName: string, lastName: string, permissions: EmployeePermission[]): Observable<void> {
|
||||
const employee = {id, firstName, lastName, permissions}
|
||||
return this.api.put<void>('/employee', employee).pipe(takeUntil(this._destroy$))
|
||||
return this.api.put<void>('/employee', employee)
|
||||
}
|
||||
|
||||
updatePassword(id: number, password: string): Observable<void> {
|
||||
|
|
|
@ -36,7 +36,14 @@ export class ListComponent extends SubscribingComponent {
|
|||
this.groups$ = this.groupService.all.pipe(takeUntil(this.destroy$))
|
||||
this.subscribe(
|
||||
this.groupService.defaultGroup,
|
||||
{next: g => this.defaultGroup = g}
|
||||
{
|
||||
next: g => this.defaultGroup = g,
|
||||
error: err => {
|
||||
if (err.status === 404) {
|
||||
console.error('No default group is defined on this computer')
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
|
||||
import {InventoryComponent} from './inventory.component';
|
||||
|
||||
const routes: Routes = [{
|
||||
path: '',
|
||||
component: InventoryComponent
|
||||
}, {
|
||||
path: 'materialtype',
|
||||
loadChildren: () => import('./modules/materialtype/materialtype.module').then(m => m.MaterialtypeModule)
|
||||
}];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class InventoryRoutingModule {
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
<cre-nav [links]="navLinks"></cre-nav>
|
||||
test
|
|
@ -1,20 +0,0 @@
|
|||
import {Component, OnInit} from '@angular/core';
|
||||
import {NavLink} from "../shared/components/nav/nav.component";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-inventory',
|
||||
templateUrl: './inventory.component.html',
|
||||
styleUrls: ['./inventory.component.sass']
|
||||
})
|
||||
export class InventoryComponent implements OnInit {
|
||||
navLinks: NavLink[] = [
|
||||
{route: 'materialtype', title: 'Types de produit', enabled: true}
|
||||
]
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { InventoryRoutingModule } from './inventory-routing.module';
|
||||
import { InventoryComponent } from './inventory.component';
|
||||
import {SharedModule} from "../shared/shared.module";
|
||||
import { MaterialtypeModule } from './modules/materialtype/materialtype.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [InventoryComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
InventoryRoutingModule,
|
||||
SharedModule,
|
||||
MaterialtypeModule
|
||||
]
|
||||
})
|
||||
export class InventoryModule { }
|
|
@ -1,11 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
|
||||
const routes: Routes = [];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class MaterialtypeRoutingModule { }
|
|
@ -1,15 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { MaterialtypeRoutingModule } from './materialtype-routing.module';
|
||||
import { ListComponent } from './pages/list/list.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [ListComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MaterialtypeRoutingModule
|
||||
]
|
||||
})
|
||||
export class MaterialtypeModule { }
|
|
@ -0,0 +1,32 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {ListComponent} from "./pages/list/list.component";
|
||||
import {AddComponent} from "./pages/add/add.component";
|
||||
import {EditComponent} from "./pages/edit/edit.component";
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'list',
|
||||
component: ListComponent
|
||||
},
|
||||
{
|
||||
path: 'add',
|
||||
component: AddComponent
|
||||
},
|
||||
{
|
||||
path: 'edit/:id',
|
||||
component: EditComponent
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'list'
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class MaterialTypeRoutingModule {
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { MaterialTypeRoutingModule } from './material-type-routing.module';
|
||||
import { ListComponent } from './pages/list/list.component';
|
||||
import {SharedModule} from "../shared/shared.module";
|
||||
import { AddComponent } from './pages/add/add.component';
|
||||
import { EditComponent } from './pages/edit/edit.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [ListComponent, AddComponent, EditComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MaterialTypeRoutingModule,
|
||||
SharedModule
|
||||
]
|
||||
})
|
||||
export class MaterialTypeModule { }
|
|
@ -0,0 +1,8 @@
|
|||
<cre-entity-add
|
||||
title="Création d'un type de produit"
|
||||
backButtonLink="/inventory/materialtype/list"
|
||||
[unknownError]="unknownError"
|
||||
[customError]="errorMessage"
|
||||
[formFields]="formFields"
|
||||
(submit)="submit($event)">
|
||||
</cre-entity-add>
|
|
@ -0,0 +1,74 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {FormField} from "../../../shared/components/entity-add/entity-add.component";
|
||||
import {Validators} from "@angular/forms";
|
||||
import {MaterialTypeService} from "../../service/material-type.service";
|
||||
import {SubscribingComponent} from "../../../shared/components/subscribing.component";
|
||||
import {Router} from "@angular/router";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-add',
|
||||
templateUrl: './add.component.html',
|
||||
styleUrls: ['./add.component.sass']
|
||||
})
|
||||
export class AddComponent extends SubscribingComponent {
|
||||
formFields: FormField[] = [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Nom',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un nom est requis'},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'prefix',
|
||||
label: 'Préfixe',
|
||||
icon: 'label-variant',
|
||||
type: 'text',
|
||||
validator: Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(3)]),
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un préfixe est requis'},
|
||||
{
|
||||
conditionFn: (errors) => errors.minlength || errors.maxlength,
|
||||
message: 'Le préfixe doit faire exactement 3 caractères'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'usePercentages',
|
||||
label: 'Utiliser les pourcentages',
|
||||
icon: 'percent',
|
||||
type: 'checkbox'
|
||||
}
|
||||
]
|
||||
unknownError = false
|
||||
errorMessage: string | null
|
||||
|
||||
constructor(
|
||||
private materialTypeService: MaterialTypeService,
|
||||
private router: Router
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
submit(values) {
|
||||
this.subscribe(
|
||||
this.materialTypeService.save(values.name, values.prefix, values.usePercentages),
|
||||
{
|
||||
next: () => this.router.navigate(['/inventory/materialtype/list']),
|
||||
error: err => {
|
||||
if (err.status == 409 && err.error.id === values.name) {
|
||||
this.errorMessage = `Un type de produit avec le nom '${values.name}' existe déjà`
|
||||
} else if (err.status == 409 && err.error.id === values.prefix) {
|
||||
this.errorMessage = `Un type de produit avec le préfixe '${values.prefix}' exists déjà`
|
||||
} else {
|
||||
this.unknownError = true
|
||||
}
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<cre-entity-edit
|
||||
*ngIf="materialType"
|
||||
title="Modifier le group {{materialType.name}}"
|
||||
deleteConfirmMessage="Voulez-vous vraiment supprimer le type de produit {{materialType.name}}?"
|
||||
backButtonLink="/inventory/materialtype/list"
|
||||
deletePermission="REMOVE_MATERIAL_TYPE"
|
||||
[entity]="materialType"
|
||||
[formFields]="formFields"
|
||||
[unknownError]="unknownError"
|
||||
[customError]="errorMessage"
|
||||
(submit)="submit($event)"
|
||||
(delete)="delete()">
|
||||
</cre-entity-edit>
|
|
@ -0,0 +1,104 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {MaterialType} from "../../../shared/model/materialtype.model";
|
||||
import {ActivatedRoute, Router} from "@angular/router";
|
||||
import {SubscribingComponent} from "../../../shared/components/subscribing.component";
|
||||
import {MaterialTypeService} from "../../service/material-type.service";
|
||||
import {FormField} from "../../../shared/components/entity-add/entity-add.component";
|
||||
import {Validators} from "@angular/forms";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-edit',
|
||||
templateUrl: './edit.component.html',
|
||||
styleUrls: ['./edit.component.sass']
|
||||
})
|
||||
export class EditComponent extends SubscribingComponent {
|
||||
materialType: MaterialType | null
|
||||
formFields: FormField[] = [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Nom',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un nom est requis'},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'prefix',
|
||||
label: 'Préfixe',
|
||||
icon: 'label-variant',
|
||||
type: 'text',
|
||||
validator: Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(3)]),
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un préfixe est requis'},
|
||||
{
|
||||
conditionFn: (errors) => errors.minlength || errors.maxlength,
|
||||
message: 'Le préfixe doit faire exactement 3 caractères'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
unknownError = false
|
||||
errorMessage: string | null
|
||||
|
||||
constructor(
|
||||
private materialTypeService: MaterialTypeService,
|
||||
private router: Router,
|
||||
private activatedRoute: ActivatedRoute
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit()
|
||||
|
||||
const id = parseInt(this.activatedRoute.snapshot.paramMap.get('id'))
|
||||
this.subscribe(
|
||||
this.materialTypeService.get(id),
|
||||
{
|
||||
next: materialType => this.materialType = materialType,
|
||||
error: err => {
|
||||
if (err.status === 404) {
|
||||
this.router.navigate(['/employee/list'])
|
||||
} else {
|
||||
this.unknownError = true
|
||||
}
|
||||
}
|
||||
},
|
||||
1
|
||||
)
|
||||
}
|
||||
|
||||
submit(values) {
|
||||
this.subscribe(
|
||||
this.materialTypeService.update(this.materialType.id, values.name, values.prefix),
|
||||
{
|
||||
next: () => this.router.navigate(['/inventory/materialtype/list']),
|
||||
error: err => {
|
||||
if (err.status == 409 && err.error.id === values.name) {
|
||||
this.errorMessage = `Un type de produit avec le nom '${values.name}' existe déjà`
|
||||
} else if (err.status == 409 && err.error.id === values.prefix) {
|
||||
this.errorMessage = `Un type de produit avec le préfixe '${values.prefix}' exists déjà`
|
||||
} else {
|
||||
this.unknownError = true
|
||||
}
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.subscribe(
|
||||
this.materialTypeService.delete(this.materialType.id),
|
||||
{
|
||||
next: () => this.router.navigate(['/inventory/materialtype/list']),
|
||||
error: err => {
|
||||
this.unknownError = true
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<cre-entity-list
|
||||
[entities$]="materialTypes$"
|
||||
[columns]="columns"
|
||||
[buttons]="buttons"
|
||||
addLink="/inventory/materialtype/add">
|
||||
</cre-entity-list>
|
|
@ -0,0 +1,32 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {MaterialTypeService} from "../../service/material-type.service";
|
||||
import {SubscribingComponent} from "../../../shared/components/subscribing.component";
|
||||
import {EmployeePermission} from "../../../shared/model/employee";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-list',
|
||||
templateUrl: './list.component.html',
|
||||
styleUrls: ['./list.component.sass']
|
||||
})
|
||||
export class ListComponent extends SubscribingComponent {
|
||||
materialTypes$ = this.materialTypeService.all
|
||||
columns = [
|
||||
{def: 'name', title: 'Nom', valueFn: t => t.name},
|
||||
{def: 'prefix', title: 'Préfixe', valueFn: t => t.prefix},
|
||||
{def: 'usePercentages', title: 'Utilise les pourcentages', valueFn: t => t.usePercentages ? 'Oui' : 'Non'}
|
||||
]
|
||||
buttons = [
|
||||
{
|
||||
text: 'Modifier',
|
||||
linkFn: t => `/inventory/materialtype/edit/${t.id}`,
|
||||
permission: EmployeePermission.EDIT_MATERIAL_TYPE,
|
||||
disabledFn: t => t.systemType
|
||||
}
|
||||
]
|
||||
|
||||
constructor(
|
||||
private materialTypeService: MaterialTypeService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {ApiService} from "../../shared/service/api.service";
|
||||
import {Observable} from "rxjs";
|
||||
import {MaterialType} from "../../shared/model/materialtype.model";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MaterialTypeService {
|
||||
constructor(
|
||||
private api: ApiService
|
||||
) {
|
||||
}
|
||||
|
||||
get all(): Observable<MaterialType[]> {
|
||||
return this.api.get<MaterialType[]>('/materialtype')
|
||||
}
|
||||
|
||||
get(id: number): Observable<MaterialType> {
|
||||
return this.api.get<MaterialType>(`/materialtype/${id}`)
|
||||
}
|
||||
|
||||
save(name: string, prefix: string, usePercentages: boolean): Observable<MaterialType> {
|
||||
const materialType = {name, prefix, usePercentages}
|
||||
return this.api.post<MaterialType>('/materialtype', materialType)
|
||||
}
|
||||
|
||||
update(id: number, name: string, prefix: string): Observable<void> {
|
||||
const materialType = {id, name, prefix}
|
||||
return this.api.put<void>('/materialtype', materialType)
|
||||
}
|
||||
|
||||
delete(id: number): Observable<void> {
|
||||
return this.api.delete<void>(`/materialtype/${id}`)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {ListComponent} from "./pages/list/list.component";
|
||||
import {AddComponent} from "./pages/add/add.component";
|
||||
import {EditComponent} from "./pages/edit/edit.component";
|
||||
|
||||
const routes: Routes = [{
|
||||
path: 'list',
|
||||
component: ListComponent
|
||||
}, {
|
||||
path: 'add',
|
||||
component: AddComponent
|
||||
}, {
|
||||
path: 'edit/:id',
|
||||
component: EditComponent
|
||||
}, {
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'list'
|
||||
}];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class MaterialRoutingModule {
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
|
||||
import {MaterialRoutingModule} from './material-routing.module';
|
||||
import {ListComponent} from './pages/list/list.component';
|
||||
import {SharedModule} from "../shared/shared.module";
|
||||
import {AddComponent} from './pages/add/add.component';
|
||||
import {EditComponent} from './pages/edit/edit.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [ListComponent, AddComponent, EditComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MaterialRoutingModule,
|
||||
SharedModule
|
||||
]
|
||||
})
|
||||
export class MaterialModule {
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<cre-entity-add
|
||||
title="Création d'un produit"
|
||||
backButtonLink="/inventory/material/list"
|
||||
[unknownError]="unknownError"
|
||||
[customError]="errorMessage"
|
||||
[formFields]="formFields"
|
||||
(submit)="submit($event)">
|
||||
</cre-entity-add>
|
|
@ -0,0 +1,87 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {FormField} from "../../../shared/components/entity-add/entity-add.component";
|
||||
import {Validators} from "@angular/forms";
|
||||
import {MaterialService} from "../../service/material.service";
|
||||
import {MaterialTypeService} from "../../../material-type/service/material-type.service";
|
||||
import {Router} from "@angular/router";
|
||||
import {SubscribingComponent} from "../../../shared/components/subscribing.component";
|
||||
import {map} from "rxjs/operators";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-add',
|
||||
templateUrl: './add.component.html',
|
||||
styleUrls: ['./add.component.sass']
|
||||
})
|
||||
export class AddComponent extends SubscribingComponent {
|
||||
formFields: FormField[] = [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Code',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un code est requis'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'inventoryQuantity',
|
||||
label: 'Quantité en inventaire',
|
||||
icon: 'beaker-outline',
|
||||
type: 'number',
|
||||
validator: Validators.compose([Validators.required, Validators.min(0)]),
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Une quantité en inventaire est requise'},
|
||||
{conditionFn: errors => errors.min, message: 'La quantité doit être supérieure ou égale à 0'}
|
||||
],
|
||||
step: '0.01'
|
||||
},
|
||||
{
|
||||
name: 'materialType',
|
||||
label: 'Type de produit',
|
||||
icon: 'shape-outline',
|
||||
type: 'select',
|
||||
validator: Validators.required,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un type de produit est requis'}
|
||||
],
|
||||
options$: this.materialTypeService.all.pipe(map(types => types.map(t => {
|
||||
return {value: t.id, label: t.name}
|
||||
})))
|
||||
},
|
||||
{
|
||||
name: 'simdutFile',
|
||||
label: 'Fiche signalitique',
|
||||
icon: 'file-outline',
|
||||
type: 'file',
|
||||
fileType: 'application/pdf'
|
||||
}
|
||||
]
|
||||
unknownError = false
|
||||
errorMessage: string | null
|
||||
|
||||
constructor(
|
||||
private materialService: MaterialService,
|
||||
private materialTypeService: MaterialTypeService,
|
||||
private router: Router
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
submit(values) {
|
||||
this.subscribe(
|
||||
this.materialService.save(values.name, values.inventoryQuantity, values.materialType, values.simdutFile),
|
||||
{
|
||||
next: () => this.router.navigate(['/inventory/material/list']),
|
||||
error: err => {
|
||||
if (err.status == 409) {
|
||||
this.errorMessage = `Un produit avec le nom '${values.name}' existe déjà`
|
||||
} else {
|
||||
this.unknownError = true
|
||||
}
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<cre-entity-edit
|
||||
*ngIf="material"
|
||||
title="Modifier le produit {{material.name}}"
|
||||
deleteConfirmMessage="Voulez-vous vraiment supprimer le produit {{material.name}}?"
|
||||
backButtonLink="/inventory/material/list"
|
||||
deletePermission="REMOVE_MATERIAL"
|
||||
[entity]="material"
|
||||
[formFields]="formFields"
|
||||
[unknownError]="unknownError"
|
||||
[customError]="errorMessage"
|
||||
(submit)="submit($event)"
|
||||
(delete)="delete()">
|
||||
</cre-entity-edit>
|
||||
|
||||
|
||||
<ng-template
|
||||
#simdutTemplate
|
||||
let-control="control"
|
||||
let-field="field">
|
||||
<div class="simdut-file w-100 d-flex justify-content-between">
|
||||
<button mat-raised-button color="primary" [disabled]="!hasSimdut"
|
||||
[attr.title]="!hasSimdut ? 'Ce produit n\'a pas de fiche signalitique' : null" (click)="openSimdutUrl()">
|
||||
Voir la fiche signalitique
|
||||
</button>
|
||||
<div class="edit-simdut-file-input">
|
||||
<button mat-raised-button color="accent" type="button">Modifier la fiche
|
||||
signalitique
|
||||
</button>
|
||||
<mat-form-field>
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<ngx-mat-file-input #simdutFileInput [accept]="field.fileType" [formControl]="control"></ngx-mat-file-input>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
|
@ -0,0 +1,14 @@
|
|||
.simdut-file
|
||||
button
|
||||
height: 43px
|
||||
|
||||
.edit-simdut-file-input
|
||||
width: 250px
|
||||
|
||||
mat-form-field
|
||||
z-index: 10
|
||||
margin-top: 10px
|
||||
opacity: 0
|
||||
|
||||
button
|
||||
position: absolute
|
|
@ -0,0 +1,141 @@
|
|||
import {Component, ViewChild} from '@angular/core';
|
||||
import {FormField} from "../../../shared/components/entity-add/entity-add.component";
|
||||
import {Validators} from "@angular/forms";
|
||||
import {map} from "rxjs/operators";
|
||||
import {MaterialTypeService} from "../../../material-type/service/material-type.service";
|
||||
import {MaterialService} from "../../service/material.service";
|
||||
import {ActivatedRoute, Router} from "@angular/router";
|
||||
import {SubscribingComponent} from "../../../shared/components/subscribing.component";
|
||||
import {Material} from "../../../shared/model/material.model";
|
||||
import {environment} from "../../../../../environments/environment";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-edit',
|
||||
templateUrl: './edit.component.html',
|
||||
styleUrls: ['./edit.component.sass']
|
||||
})
|
||||
export class EditComponent extends SubscribingComponent {
|
||||
@ViewChild('simdutTemplate', {static: true}) simdutTemplateRef
|
||||
@ViewChild('simdutFileInput') simdutFileInput
|
||||
|
||||
material: Material | null
|
||||
formFields: FormField[] = [
|
||||
{
|
||||
name: 'name',
|
||||
label: 'Code',
|
||||
icon: 'form-textbox',
|
||||
type: 'text',
|
||||
validator: Validators.required,
|
||||
errorMessages: [
|
||||
{conditionFn: (errors) => errors.required, message: 'Un code est requis'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'inventoryQuantity',
|
||||
label: 'Quantité en inventaire',
|
||||
icon: 'beaker-outline',
|
||||
type: 'number',
|
||||
validator: Validators.compose([Validators.required, Validators.min(0)]),
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Une quantité en inventaire est requise'},
|
||||
{conditionFn: errors => errors.min, message: 'La quantité doit être supérieure ou égale à 0'}
|
||||
],
|
||||
step: '0.01'
|
||||
},
|
||||
{
|
||||
name: 'materialType',
|
||||
label: 'Type de produit',
|
||||
icon: 'shape-outline',
|
||||
type: 'select',
|
||||
validator: Validators.required,
|
||||
errorMessages: [
|
||||
{conditionFn: errors => errors.required, message: 'Un type de produit est requis'}
|
||||
],
|
||||
valueFn: material => material.materialType.id,
|
||||
options$: this.materialTypeService.all.pipe(map(types => types.map(t => {
|
||||
return {value: t.id, label: t.name}
|
||||
})))
|
||||
},
|
||||
{
|
||||
name: 'simdutFile',
|
||||
label: 'Fiche signalitique',
|
||||
icon: 'file-outline',
|
||||
type: 'file',
|
||||
fileType: 'application/pdf'
|
||||
}
|
||||
]
|
||||
unknownError = false
|
||||
errorMessage: string | null
|
||||
hasSimdut = false
|
||||
|
||||
constructor(
|
||||
private materialService: MaterialService,
|
||||
private materialTypeService: MaterialTypeService,
|
||||
private router: Router,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.formFields[3].template = this.simdutTemplateRef
|
||||
|
||||
const id = parseInt(this.activatedRoute.snapshot.paramMap.get('id'))
|
||||
this.subscribe(
|
||||
this.materialService.getById(id),
|
||||
{
|
||||
next: material => this.material = material,
|
||||
error: err => {
|
||||
if (err.status === 404) {
|
||||
this.router.navigate(['/inventory/material/list'])
|
||||
} else {
|
||||
this.unknownError = true
|
||||
}
|
||||
}
|
||||
},
|
||||
1
|
||||
)
|
||||
|
||||
this.subscribe(
|
||||
this.materialService.hasSimdut(id),
|
||||
{next: b => this.hasSimdut = b}
|
||||
)
|
||||
}
|
||||
|
||||
submit(values) {
|
||||
this.subscribe(
|
||||
this.materialService.update(this.material.id, values.name, values.inventoryQuantity, values.materialType, values.simdutFile),
|
||||
{
|
||||
next: () => this.router.navigate(['/inventory/material/list']),
|
||||
error: err => {
|
||||
if (err.status == 409) {
|
||||
this.errorMessage = `Un produit avec le nom '${values.name}' existe déjà`
|
||||
} else {
|
||||
this.unknownError = true
|
||||
}
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.subscribe(
|
||||
this.materialService.delete(this.material.id),
|
||||
{
|
||||
next: () => this.router.navigate(['/inventory/material/list']),
|
||||
error: err => {
|
||||
this.unknownError = true
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
openSimdutUrl() {
|
||||
const simdutUrl = environment.apiUrl + `/material/${this.material.id}/simdut`
|
||||
window.open(simdutUrl, "_blank")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<cre-entity-list
|
||||
[entities$]="materials$"
|
||||
[columns]="columns"
|
||||
[icons]="icons"
|
||||
[buttons]="buttons"
|
||||
addLink="/inventory/material/add">
|
||||
</cre-entity-list>
|
|
@ -0,0 +1,65 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {SubscribingComponent} from "../../../shared/components/subscribing.component";
|
||||
import {MaterialService} from "../../service/material.service";
|
||||
import {EmployeePermission} from "../../../shared/model/employee";
|
||||
import {environment} from "../../../../../environments/environment";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-list',
|
||||
templateUrl: './list.component.html',
|
||||
styleUrls: ['./list.component.sass']
|
||||
})
|
||||
export class ListComponent extends SubscribingComponent {
|
||||
materials$ = this.materialService.all
|
||||
columns = [
|
||||
{def: 'name', title: 'Code', valueFn: t => t.name},
|
||||
{def: 'inventoryQuantity', title: 'Quantité', valueFn: t => t.inventoryQuantity},
|
||||
{def: 'materialType', title: 'Type de produit', valueFn: t => t.materialType.name}
|
||||
]
|
||||
icons = [{
|
||||
icon: 'text-box-remove',
|
||||
color: 'warn',
|
||||
title: 'Ce produit n\'a pas de fiche signalitique',
|
||||
disabledFn: t => this.hasSimdutMap[t.id]
|
||||
}]
|
||||
buttons = [{
|
||||
text: 'Modifier',
|
||||
linkFn: t => `/inventory/material/edit/${t.id}`,
|
||||
permission: EmployeePermission.EDIT_MATERIAL
|
||||
}, {
|
||||
text: 'Fiche signalitique',
|
||||
linkFn: t => {
|
||||
return {
|
||||
externalLink: environment.apiUrl + `/material/${t.id}/simdut`
|
||||
}
|
||||
},
|
||||
disabledFn: t => !this.hasSimdutMap[t.id]
|
||||
}]
|
||||
|
||||
private hasSimdutMap: any = {}
|
||||
|
||||
constructor(
|
||||
private materialService: MaterialService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
|
||||
this.subscribe(
|
||||
this.materials$,
|
||||
{
|
||||
next: mArray => {
|
||||
mArray.forEach(m => {
|
||||
this.subscribe(
|
||||
this.materialService.hasSimdut(m.id),
|
||||
{ next: b => this.hasSimdutMap[m.id] = b }
|
||||
)
|
||||
})
|
||||
}
|
||||
},
|
||||
1
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {ApiService} from "../../shared/service/api.service";
|
||||
import {Observable} from "rxjs";
|
||||
import {Material} from "../../shared/model/material.model";
|
||||
import {FileInput} from "ngx-material-file-input";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MaterialService {
|
||||
constructor(
|
||||
private api: ApiService
|
||||
) {
|
||||
}
|
||||
|
||||
get all(): Observable<Material[]> {
|
||||
return this.api.get<Material[]>('/material')
|
||||
}
|
||||
|
||||
getById(id: number): Observable<Material> {
|
||||
return this.api.get<Material>(`/material/${id}`)
|
||||
}
|
||||
|
||||
hasSimdut(id: number): Observable<boolean> {
|
||||
return this.api.get<boolean>(`/material/${id}/simdut/exists`)
|
||||
}
|
||||
|
||||
save(name: string, inventoryQuantity: number, materialType: number, simdutFile: FileInput): Observable<void> {
|
||||
const body = new FormData()
|
||||
body.append('name', name)
|
||||
body.append('inventoryQuantity', inventoryQuantity.toString())
|
||||
body.append('materialType', materialType.toString())
|
||||
if (simdutFile && simdutFile.files) {
|
||||
body.append('simdutFile', simdutFile.files[0])
|
||||
}
|
||||
return this.api.post<void>('/material/', body)
|
||||
}
|
||||
|
||||
update(id: number, name: string, inventoryQuantity: number, materialType: number, simdutFile: FileInput): Observable<void> {
|
||||
const body = new FormData()
|
||||
body.append('id', id.toString())
|
||||
body.append('name', name)
|
||||
body.append('inventoryQuantity', inventoryQuantity.toString())
|
||||
body.append('materialType', materialType.toString())
|
||||
if (simdutFile && simdutFile.files) {
|
||||
body.append('simdutFile', simdutFile.files[0])
|
||||
}
|
||||
return this.api.put<void>('/material/', body)
|
||||
}
|
||||
|
||||
delete(id: number): Observable<void> {
|
||||
return this.api.delete<void>(`/material/${id}`)
|
||||
}
|
||||
}
|
|
@ -11,6 +11,12 @@ export class AppState {
|
|||
private readonly KEY_LOGGED_IN_EMPLOYEE = "logged-in-employee"
|
||||
|
||||
authenticatedUser$ = new Subject<{ authenticated: boolean, authenticatedUser: Employee }>()
|
||||
serverOnline$ = new Subject<boolean>()
|
||||
|
||||
set isServerOnline(isOnline: boolean) {
|
||||
if (!isOnline) this.authenticatedEmployee = null
|
||||
this.serverOnline$.next(isOnline);
|
||||
}
|
||||
|
||||
get isAuthenticated(): boolean {
|
||||
return sessionStorage.getItem(this.KEY_AUTHENTICATED) === "true"
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<mat-card class="x-centered mt-5">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{title}}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div *ngIf="unknownError || customError" class="alert alert-danger">
|
||||
<p *ngIf="unknownError">Une erreur est survenue</p>
|
||||
<p *ngIf="customError">{{customError}}</p>
|
||||
</div>
|
||||
|
||||
<form *ngIf="form" [formGroup]="form">
|
||||
<ng-container *ngFor="let field of formFields">
|
||||
<ng-container
|
||||
*ngIf="field.type != 'checkbox' && field.type != 'select' && field.type != 'file'"
|
||||
[ngTemplateOutlet]="fieldTemplate"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="field.type == 'checkbox'"
|
||||
[ngTemplateOutlet]="checkboxTemplate"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="field.type == 'select'"
|
||||
[ngTemplateOutlet]="selectTemplate"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="field.type == 'file'"
|
||||
[ngTemplateOutlet]="fileTemplate"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-raised-button color="primary" [routerLink]="backButtonLink">Retour</button>
|
||||
<button mat-raised-button color="accent" [disabled]="form.invalid" (click)="submitForm()">Créer</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
<ng-template
|
||||
#fieldTemplate
|
||||
let-control="control" let-field="field">
|
||||
<mat-form-field>
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<input matInput [type]="field.type" [formControl]="control" [step]="field.step ? field.step : null"/>
|
||||
<mat-icon [svgIcon]="field.icon" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="control.invalid && field.errorMessages">
|
||||
<ng-container *ngFor="let errorMessage of field.errorMessages">
|
||||
<span *ngIf="errorMessage.conditionFn(control.errors)">{{errorMessage.message}}</span>
|
||||
</ng-container>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
|
||||
<ng-template
|
||||
#checkboxTemplate
|
||||
let-control="control" let-field="field">
|
||||
<mat-checkbox [formControl]="control">
|
||||
{{field.label}}
|
||||
</mat-checkbox>
|
||||
</ng-template>
|
||||
|
||||
<ng-template
|
||||
#selectTemplate
|
||||
let-control="control" let-field="field">
|
||||
<mat-form-field *ngIf="field.options$ | async as options">
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<mat-select [formControl]="control">
|
||||
<mat-option *ngFor="let option of options" [value]="option.value">
|
||||
{{option.label}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-icon [svgIcon]="field.icon" matSuffix></mat-icon>
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
|
||||
<ng-template
|
||||
#fileTemplate
|
||||
let-control="control" let-field="field">
|
||||
<mat-form-field>
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<ngx-mat-file-input [accept]="field.fileType" [formControl]="control"></ngx-mat-file-input>
|
||||
</mat-form-field>
|
||||
</ng-template>
|
|
@ -0,0 +1,78 @@
|
|||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {SubscribingComponent} from "../subscribing.component";
|
||||
import {FormBuilder, FormControl, FormGroup, ValidatorFn} from "@angular/forms";
|
||||
import {Observable} from "rxjs";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-entity-add',
|
||||
templateUrl: './entity-add.component.html',
|
||||
styleUrls: ['./entity-add.component.sass']
|
||||
})
|
||||
export class EntityAddComponent extends SubscribingComponent {
|
||||
@Input() title: string
|
||||
@Input() backButtonLink: string
|
||||
@Input() unknownError: boolean = false
|
||||
@Input() customError: string | null
|
||||
@Input() formFields: FormField[]
|
||||
@Output() submit = new EventEmitter<any>()
|
||||
|
||||
form: FormGroup | null
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const formGroup = {}
|
||||
this.formFields.forEach(f => {
|
||||
formGroup[f.name] = new FormControl(null, f.validator)
|
||||
})
|
||||
this.form = this.formBuilder.group(formGroup)
|
||||
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
const values = {}
|
||||
this.formFields.forEach(f => {
|
||||
values[f.name] = this.getControl(f.name).value
|
||||
})
|
||||
this.submit.emit(values)
|
||||
}
|
||||
|
||||
getControl(controlName: string): FormControl {
|
||||
return this.form.controls[controlName] as FormControl
|
||||
}
|
||||
|
||||
test(any) {
|
||||
console.log(any)
|
||||
}
|
||||
}
|
||||
|
||||
export class FormField {
|
||||
constructor(
|
||||
public name: string,
|
||||
public label?: string,
|
||||
public icon?: string,
|
||||
public type?: string,
|
||||
public validator?: ValidatorFn,
|
||||
public errorMessages?: FormErrorMessage[],
|
||||
public valueFn?: (any) => any,
|
||||
public template?: any,
|
||||
// Specifics to some types
|
||||
public step?: string,
|
||||
public options$?: Observable<{ value: any, label: string }[]>,
|
||||
public fileType?: string
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
export class FormErrorMessage {
|
||||
constructor(
|
||||
public conditionFn: (any) => boolean,
|
||||
public message: string
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<mat-card *ngIf="entity" class="mt-5 x-centered">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{title}}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div *ngIf="unknownError || customError" class="alert alert-danger">
|
||||
<p *ngIf="unknownError">Une erreur est survenue</p>
|
||||
<p *ngIf="customError">{{customError}}</p>
|
||||
</div>
|
||||
|
||||
<form [formGroup]="form">
|
||||
<ng-container *ngFor="let field of formFields">
|
||||
<ng-container
|
||||
*ngIf="!field.template && field.type != 'checkbox' && field.type != 'select' && field.type != 'file'"
|
||||
[ngTemplateOutlet]="fieldTemplate"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="field.type == 'select' && !field.template"
|
||||
[ngTemplateOutlet]="selectTemplate"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="field.type == 'file' && !field.template"
|
||||
[ngTemplateOutlet]="fileTemplate"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="field.template"
|
||||
[ngTemplateOutletContext]="{control: getControl(field.name), field: field}">
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-raised-button color="primary" [routerLink]="backButtonLink">Retour</button>
|
||||
<button mat-raised-button color="warn" *ngIf="canDelete" (click)="confirmBoxComponent.show()">Supprimer</button>
|
||||
<button mat-raised-button color="accent" [disabled]="form.invalid" (click)="submitForm()">Enregistrer</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
<ng-template
|
||||
#fieldTemplate
|
||||
let-control="control" let-field="field">
|
||||
<mat-form-field>
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<input matInput [type]="field.type" [formControl]="control"/>
|
||||
<mat-icon [svgIcon]="field.icon" matSuffix></mat-icon>
|
||||
<mat-error *ngIf="control.invalid && field.errorMessages">
|
||||
<ng-container *ngFor="let errorMessage of field.errorMessages">
|
||||
<span *ngIf="errorMessage.conditionFn(control.errors)">{{errorMessage.message}}</span>
|
||||
</ng-container>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
|
||||
<ng-template
|
||||
#selectTemplate
|
||||
let-control="control" let-field="field">
|
||||
<mat-form-field *ngIf="field.options$ | async as options">
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<mat-select [formControl]="control">
|
||||
<mat-option *ngFor="let option of options" [value]="option.value">
|
||||
{{option.label}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-icon [svgIcon]="field.icon" matSuffix></mat-icon>
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
|
||||
<ng-template
|
||||
#fileTemplate
|
||||
let-control="control" let-field="field">
|
||||
<mat-form-field>
|
||||
<mat-label>{{field.label}}</mat-label>
|
||||
<ngx-mat-file-input [accept]="field.fileType" [formControl]="control"></ngx-mat-file-input>
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
|
||||
|
||||
<cre-confirm-box #confirmBoxComponent [message]="deleteConfirmMessage" (confirm)="delete.emit()"></cre-confirm-box>
|
|
@ -0,0 +1,59 @@
|
|||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {FormBuilder, FormControl, FormGroup} from "@angular/forms";
|
||||
import {SubscribingComponent} from "../subscribing.component";
|
||||
import {FormField} from "../entity-add/entity-add.component";
|
||||
import {EmployeePermission} from "../../model/employee";
|
||||
import {AccountService} from "../../../accounts/services/account.service";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-entity-edit',
|
||||
templateUrl: './entity-edit.component.html',
|
||||
styleUrls: ['./entity-edit.component.sass']
|
||||
})
|
||||
export class EntityEditComponent extends SubscribingComponent {
|
||||
@Input() entity: any
|
||||
@Input() title: string
|
||||
@Input() deleteConfirmMessage: string
|
||||
@Input() backButtonLink: string
|
||||
@Input() formFields: FormField[]
|
||||
@Input() deletePermission: EmployeePermission
|
||||
@Input() unknownError = false
|
||||
@Input() customError: string | null
|
||||
@Output() submit = new EventEmitter<any>()
|
||||
@Output() delete = new EventEmitter<void>()
|
||||
|
||||
form: FormGroup | null
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
private formBuilder: FormBuilder
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const formGroup = {}
|
||||
this.formFields.forEach(f => {
|
||||
formGroup[f.name] = new FormControl(f.valueFn ? f.valueFn(this.entity) : this.entity[f.name], f.validator)
|
||||
})
|
||||
this.form = this.formBuilder.group(formGroup)
|
||||
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
const values = {}
|
||||
this.formFields.forEach(f => {
|
||||
values[f.name] = this.getControl(f.name).value
|
||||
})
|
||||
this.submit.emit(values)
|
||||
}
|
||||
|
||||
getControl(controlName: string): FormControl {
|
||||
return this.form.controls[controlName] as FormControl
|
||||
}
|
||||
|
||||
get canDelete(): boolean {
|
||||
return this.accountService.hasPermission(this.deletePermission)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<div class="action-bar">
|
||||
<button mat-raised-button color="accent" [routerLink]="addLink">Ajouter</button>
|
||||
</div>
|
||||
|
||||
<table class="mx-auto" *ngIf="entities$ | async as entities" mat-table [dataSource]="entities">
|
||||
<!-- Columns -->
|
||||
<ng-container *ngFor="let column of columns" [matColumnDef]="column.def">
|
||||
<th mat-header-cell *matHeaderCellDef>{{column.title}}</th>
|
||||
<td mat-cell *matCellDef="let entity">{{column.valueFn(entity)}}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Icons -->
|
||||
<ng-container *ngFor="let icon of icons; let iconIndex = index" matColumnDef="icon{{iconIndex}}">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let entity" [class.disabled]="icon.disabledFn && icon.disabledFn(entity)">
|
||||
<mat-icon [svgIcon]="icon.icon" [color]="icon.color" [title]="icon.title"></mat-icon>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Buttons -->
|
||||
<ng-container *ngFor="let button of buttons; let buttonIndex = index" matColumnDef="button{{buttonIndex}}">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell [class.disabled]="!hasPermissionToUseButton(button)" *matCellDef="let entity">
|
||||
<button
|
||||
mat-raised-button
|
||||
color="accent"
|
||||
[routerLink]="button.link ? button.link.externalLink ? undefined : button.link : button.linkFn(entity).externalLink ? undefined : button.linkFn(entity)"
|
||||
[disabled]="button.disabledFn && button.disabledFn(entity)"
|
||||
(click)="openExternalLink(button, entity)">
|
||||
{{button.text}}
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="tableCols"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: tableCols"></tr>
|
||||
</table>
|
|
@ -0,0 +1,87 @@
|
|||
import {Component, Input} from '@angular/core';
|
||||
import {Observable} from "rxjs";
|
||||
import {SubscribingComponent} from "../subscribing.component";
|
||||
import {AccountService} from "../../../accounts/services/account.service";
|
||||
import {EmployeePermission} from "../../model/employee";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-entity-list',
|
||||
templateUrl: './entity-list.component.html',
|
||||
styleUrls: ['./entity-list.component.sass']
|
||||
})
|
||||
export class EntityListComponent<T> extends SubscribingComponent {
|
||||
@Input() entities$: Observable<T>
|
||||
@Input() columns: TableColumn[]
|
||||
@Input() icons: TableIcon[]
|
||||
@Input() buttons?: TableButton[]
|
||||
@Input() addLink: string
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
hasPermissionToUseButton(button: TableButton): boolean {
|
||||
return !button.permission || this.accountService.hasPermission(button.permission)
|
||||
}
|
||||
|
||||
openExternalLink(button: TableButton, entity: T) {
|
||||
let externalLink = null
|
||||
// @ts-ignore
|
||||
if (button.link && button.link.externalLink) {
|
||||
// @ts-ignore
|
||||
externalLink = button.link.externalLink
|
||||
} else {
|
||||
const linkFnResult = button.linkFn(entity)
|
||||
// @ts-ignore
|
||||
if (linkFnResult && linkFnResult.externalLink) {
|
||||
// @ts-ignore
|
||||
externalLink = linkFnResult.externalLink
|
||||
}
|
||||
}
|
||||
|
||||
if (externalLink) window.open(externalLink, "_blank")
|
||||
}
|
||||
|
||||
get tableCols(): string[] {
|
||||
const cols = this.columns.map(c => c.def)
|
||||
if (this.icons) {
|
||||
this.icons.forEach((_, i) => cols.push(`icon${i}`))
|
||||
}
|
||||
if (this.buttons) {
|
||||
this.buttons.forEach((_, i) => cols.push(`button${i}`))
|
||||
}
|
||||
return cols
|
||||
}
|
||||
}
|
||||
|
||||
export class TableColumn {
|
||||
constructor(
|
||||
public def: string,
|
||||
public title: string,
|
||||
public valueFn: (T) => string
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
export class TableIcon {
|
||||
constructor(
|
||||
public icon: string,
|
||||
public color: string,
|
||||
public title: string,
|
||||
public disabledFn: (T) => boolean
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
export class TableButton {
|
||||
constructor(
|
||||
public text: string,
|
||||
public link: string | { externalLink: string } | null,
|
||||
public linkFn: (T) => string | { externalLink: string } | null,
|
||||
public permission: EmployeePermission | null,
|
||||
public disabledFn: (T) => boolean | null
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -6,8 +6,8 @@
|
|||
<a
|
||||
*ngIf="link.enabled"
|
||||
mat-tab-link
|
||||
(click)="activeLink = link.route"
|
||||
[active]="activeLink == link.route">
|
||||
[active]="activeLink.startsWith(link.route)"
|
||||
(click)="activeLink = link.route">
|
||||
{{ link.title }}
|
||||
</a>
|
||||
</ng-container>
|
||||
|
|
|
@ -1,53 +1,64 @@
|
|||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Router} from "@angular/router";
|
||||
import {ResolveEnd, Router} from "@angular/router";
|
||||
import {AppState} from "../../app-state";
|
||||
import {Employee, EmployeePermission} from "../../model/employee";
|
||||
import {AccountService} from "../../../accounts/services/account.service";
|
||||
import {Subject} from "rxjs";
|
||||
import {takeUntil} from "rxjs/operators";
|
||||
import {SubscribingComponent} from "../subscribing.component";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-header',
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.sass']
|
||||
})
|
||||
export class HeaderComponent implements OnInit, OnDestroy {
|
||||
export class HeaderComponent extends SubscribingComponent {
|
||||
links: HeaderLink[] = [
|
||||
// {route: 'color', title: 'Couleurs', enabled: true},
|
||||
{route: 'inventory', title: 'Inventaire', enabled: true},
|
||||
new HeaderLink('employee', 'Employés', EmployeePermission.VIEW_EMPLOYEE),
|
||||
new HeaderLink('group', 'Groupes', EmployeePermission.VIEW_EMPLOYEE_GROUP),
|
||||
{route: 'account/login', title: 'Connexion', enabled: true},
|
||||
{route: 'account/logout', title: 'Déconnexion', enabled: false},
|
||||
{route: '/inventory', title: 'Inventaire', enabled: true},
|
||||
new HeaderLink('/employee', 'Employés', EmployeePermission.VIEW_EMPLOYEE),
|
||||
new HeaderLink('/group', 'Groupes', EmployeePermission.VIEW_EMPLOYEE_GROUP),
|
||||
{route: '/account/login', title: 'Connexion', enabled: true},
|
||||
{route: '/account/logout', title: 'Déconnexion', enabled: false},
|
||||
];
|
||||
_activeLink = this.links[0].route;
|
||||
|
||||
private destroy$ = new Subject<boolean>()
|
||||
_activeLink = this.links[0].route
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
private router: Router,
|
||||
private appState: AppState
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Gets the current route
|
||||
this.subscribe(
|
||||
this.router.events,
|
||||
{
|
||||
next: data => {
|
||||
if (data instanceof ResolveEnd) this._activeLink = data.url
|
||||
}
|
||||
},
|
||||
1
|
||||
)
|
||||
|
||||
// Auth status
|
||||
this.accountService.checkAuthenticationStatus()
|
||||
this.updateEnabledLinks(this.appState.isAuthenticated, this.appState.authenticatedEmployee)
|
||||
this.subscribe(
|
||||
this.appState.authenticatedUser$,
|
||||
{next: authentication => this.updateEnabledLinks(authentication.authenticated, authentication.authenticatedUser)}
|
||||
)
|
||||
|
||||
this.appState.authenticatedUser$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: authentication => this.updateEnabledLinks(authentication.authenticated, authentication.authenticatedUser)
|
||||
})
|
||||
super.ngOnInit()
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next(true)
|
||||
this.destroy$.complete()
|
||||
this.accountService.logout(() => {
|
||||
console.log("Successfully logged out")
|
||||
})
|
||||
|
||||
super.ngOnDestroy()
|
||||
}
|
||||
|
||||
set activeLink(link: string) {
|
||||
|
@ -60,8 +71,8 @@ export class HeaderComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
private updateEnabledLinks(authenticated: boolean, employee: Employee) {
|
||||
this.link('account/login').enabled = !authenticated
|
||||
this.link('account/logout').enabled = authenticated
|
||||
this.link('/account/login').enabled = !authenticated
|
||||
this.link('/account/logout').enabled = authenticated
|
||||
|
||||
this.links.forEach(l => {
|
||||
if (l.requiredPermission) {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<nav mat-tab-nav-bar backgroundColor="primary">
|
||||
<ng-container *ngFor="let link of links">
|
||||
<a
|
||||
*ngIf="link.enabled"
|
||||
*ngIf="link.enabled && hasPermission(link)"
|
||||
mat-tab-link
|
||||
[active]="activeLink == link.route"
|
||||
[active]="activeLink.startsWith(link.route)"
|
||||
(click)="activeLink = link.route">
|
||||
{{ link.title }}
|
||||
</a>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Employee, EmployeePermission} from "../../model/employee";
|
||||
import {AccountService} from "../../../accounts/services/account.service";
|
||||
import {Router} from "@angular/router";
|
||||
import {ActivatedRoute, Router} from "@angular/router";
|
||||
import {takeUntil} from "rxjs/operators";
|
||||
import {AppState} from "../../app-state";
|
||||
import {Subject} from "rxjs";
|
||||
|
@ -16,19 +16,17 @@ export class NavComponent implements OnInit, OnDestroy {
|
|||
|
||||
@Input() links: NavLink [] = []
|
||||
|
||||
// links: NavLink[] = [
|
||||
// {route: 'materialtype', title: 'Types de produit', enabled: true}
|
||||
// ]
|
||||
_activeLink: string | null;
|
||||
_activeLink: string;
|
||||
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
private router: Router,
|
||||
private appState: AppState
|
||||
private appState: AppState,
|
||||
private router: Router
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._activeLink = this.router.url
|
||||
this.updateEnabledLinks(this.appState.authenticatedEmployee)
|
||||
|
||||
this.appState.authenticatedUser$
|
||||
|
@ -43,6 +41,10 @@ export class NavComponent implements OnInit, OnDestroy {
|
|||
this._destroy$.complete()
|
||||
}
|
||||
|
||||
hasPermission(link: NavLink): boolean {
|
||||
return !link.permission || this.accountService.hasPermission(link.permission)
|
||||
}
|
||||
|
||||
set activeLink(link: string) {
|
||||
this._activeLink = link
|
||||
this.router.navigate([link])
|
||||
|
@ -54,8 +56,8 @@ export class NavComponent implements OnInit, OnDestroy {
|
|||
|
||||
private updateEnabledLinks(employee: Employee) {
|
||||
this.links.forEach(l => {
|
||||
if (l.requiredPermission) {
|
||||
l.enabled = employee && employee.permissions.indexOf(l.requiredPermission) >= 0;
|
||||
if (l.permission) {
|
||||
l.enabled = employee && employee.permissions.indexOf(l.permission) >= 0;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -65,8 +67,8 @@ export class NavLink {
|
|||
constructor(
|
||||
public route: string,
|
||||
public title: string,
|
||||
public requiredPermission?: EmployeePermission,
|
||||
public enabled = false
|
||||
public permission?: EmployeePermission,
|
||||
public enabled?
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,18 +21,24 @@ export class EmployeeGroup {
|
|||
}
|
||||
|
||||
export enum EmployeePermission {
|
||||
VIEW_MATERIAL = 'VIEW_MATERIAL',
|
||||
VIEW_MATERIAL_TYPE = 'VIEW_MATERIAL_TYPE',
|
||||
VIEW = 'VIEW',
|
||||
VIEW_EMPLOYEE = 'VIEW_EMPLOYEE',
|
||||
VIEW_EMPLOYEE_GROUP = 'VIEW_EMPLOYEE_GROUP',
|
||||
VIEW = 'VIEW',
|
||||
|
||||
EDIT_MATERIAL = 'EDIT_MATERIAL',
|
||||
EDIT_MATERIAL_TYPE = 'EDIT_MATERIAL_TYPE',
|
||||
EDIT = 'EDIT',
|
||||
EDIT_EMPLOYEE = 'EDIT_EMPLOYEE',
|
||||
EDIT_EMPLOYEE_PASSWORD = 'EDIT_EMPLOYEE_PASSWORD',
|
||||
EDIT_EMPLOYEE_GROUP = 'EDIT_EMPLOYEE_GROUP',
|
||||
EDIT = 'EDIT',
|
||||
|
||||
REMOVE_MATERIAL = 'REMOVE_MATERIAL',
|
||||
REMOVE_MATERIAL_TYPE = 'REMOVE_MATERIAL_TYPE',
|
||||
REMOVE = 'REMOVE',
|
||||
REMOVE_EMPLOYEE = 'REMOVE_EMPLOYEE',
|
||||
REMOVE_EMPLOYEE_GROUP = 'REMOVE_EMPLOYEE_GROUP',
|
||||
REMOVE = 'REMOVE',
|
||||
|
||||
SET_BROWSER_DEFAULT_GROUP = 'SET_BROWSER_DEFAULT_GROUP',
|
||||
ADMIN = 'ADMIN'
|
||||
|
@ -40,23 +46,89 @@ export enum EmployeePermission {
|
|||
|
||||
export const mapped_permissions = {
|
||||
view: [
|
||||
{permission: EmployeePermission.VIEW_MATERIAL, description: 'Voir les produits', impliedPermissions: []},
|
||||
{
|
||||
permission: EmployeePermission.VIEW_MATERIAL_TYPE,
|
||||
description: 'Voir les types de produit',
|
||||
impliedPermissions: []
|
||||
},
|
||||
{
|
||||
permission: EmployeePermission.VIEW,
|
||||
description: 'Voir',
|
||||
impliedPermissions: [EmployeePermission.VIEW_MATERIAL, EmployeePermission.VIEW_MATERIAL_TYPE]
|
||||
},
|
||||
{permission: EmployeePermission.VIEW_EMPLOYEE, description: 'Voir les employés', impliedPermissions: []},
|
||||
{permission: EmployeePermission.VIEW_EMPLOYEE_GROUP, description: 'Voir les groupes', impliedPermissions: []},
|
||||
{permission: EmployeePermission.VIEW, description: 'Voir', impliedPermissions: []},
|
||||
],
|
||||
edit: [
|
||||
{permission: EmployeePermission.EDIT_EMPLOYEE, description: 'Modifier les employés', impliedPermissions: [EmployeePermission.VIEW_EMPLOYEE]},
|
||||
{permission: EmployeePermission.EDIT_EMPLOYEE_PASSWORD, description: 'Modifier le mot de passe des employés', impliedPermissions: [EmployeePermission.EDIT_EMPLOYEE]},
|
||||
{permission: EmployeePermission.EDIT_EMPLOYEE_GROUP, description: 'Modifier les groupes', impliedPermissions: [EmployeePermission.VIEW_EMPLOYEE_GROUP]},
|
||||
{permission: EmployeePermission.EDIT, description: 'Modifier', impliedPermissions: [EmployeePermission.VIEW]},
|
||||
{
|
||||
permission: EmployeePermission.EDIT_MATERIAL,
|
||||
description: 'Modifier les produits',
|
||||
impliedPermissions: [EmployeePermission.VIEW_MATERIAL_TYPE]
|
||||
},
|
||||
{
|
||||
permission: EmployeePermission.EDIT_MATERIAL_TYPE,
|
||||
description: 'Modifier les types de produit',
|
||||
impliedPermissions: [EmployeePermission.VIEW_MATERIAL_TYPE]
|
||||
},
|
||||
{
|
||||
permission: EmployeePermission.EDIT,
|
||||
description: 'Modifier',
|
||||
impliedPermissions: [EmployeePermission.EDIT_MATERIAL, EmployeePermission.EDIT_MATERIAL_TYPE, EmployeePermission.VIEW]
|
||||
},
|
||||
{
|
||||
permission: EmployeePermission.EDIT_EMPLOYEE,
|
||||
description: 'Modifier les employés',
|
||||
impliedPermissions: [EmployeePermission.EDIT_EMPLOYEE]
|
||||
},
|
||||
{
|
||||
permission: EmployeePermission.EDIT_EMPLOYEE_PASSWORD,
|
||||
description: 'Modifier le mot de passe des employés',
|
||||
impliedPermissions: [EmployeePermission.EDIT_EMPLOYEE]
|
||||
},
|
||||
{
|
||||
permission: EmployeePermission.EDIT_EMPLOYEE_GROUP,
|
||||
description: 'Modifier les groupes',
|
||||
impliedPermissions: [EmployeePermission.VIEW_EMPLOYEE_GROUP]
|
||||
},
|
||||
],
|
||||
remove: [
|
||||
{permission: EmployeePermission.REMOVE_EMPLOYEE, description: 'Supprimer les employés', impliedPermissions: [EmployeePermission.EDIT_EMPLOYEE]},
|
||||
{permission: EmployeePermission.REMOVE_EMPLOYEE_GROUP, description: 'Supprimer les groupes', impliedPermissions: [EmployeePermission.EDIT_EMPLOYEE_GROUP]},
|
||||
{permission: EmployeePermission.REMOVE, description: 'Supprimer', impliedPermissions: [EmployeePermission.EDIT]},
|
||||
{
|
||||
permission: EmployeePermission.REMOVE_MATERIAL,
|
||||
description: 'Supprimer des produits',
|
||||
impliedPermissions: [EmployeePermission.EDIT_MATERIAL]
|
||||
},
|
||||
{
|
||||
permission: EmployeePermission.REMOVE_MATERIAL_TYPE,
|
||||
description: 'Supprimer des types de produit',
|
||||
impliedPermissions: [EmployeePermission.EDIT_MATERIAL_TYPE]
|
||||
},
|
||||
{
|
||||
permission: EmployeePermission.REMOVE,
|
||||
description: 'Supprimer',
|
||||
impliedPermissions: [EmployeePermission.REMOVE_MATERIAL, EmployeePermission.REMOVE_MATERIAL_TYPE, EmployeePermission.EDIT]
|
||||
},
|
||||
{
|
||||
permission: EmployeePermission.REMOVE_EMPLOYEE,
|
||||
description: 'Supprimer des employés',
|
||||
impliedPermissions: [EmployeePermission.EDIT_EMPLOYEE]
|
||||
},
|
||||
{
|
||||
permission: EmployeePermission.REMOVE_EMPLOYEE_GROUP,
|
||||
description: 'Supprimer des groupes',
|
||||
impliedPermissions: [EmployeePermission.EDIT_EMPLOYEE_GROUP]
|
||||
},
|
||||
],
|
||||
other: [
|
||||
{permission: EmployeePermission.SET_BROWSER_DEFAULT_GROUP, description: 'Définir le groupe par défaut', impliedPermissions: [EmployeePermission.VIEW_EMPLOYEE_GROUP]},
|
||||
{permission: EmployeePermission.ADMIN, description: 'Administrateur', impliedPermissions: [EmployeePermission.REMOVE, EmployeePermission.SET_BROWSER_DEFAULT_GROUP, EmployeePermission.REMOVE_EMPLOYEE, EmployeePermission.EDIT_EMPLOYEE_PASSWORD, EmployeePermission.REMOVE_EMPLOYEE_GROUP]}
|
||||
{
|
||||
permission: EmployeePermission.SET_BROWSER_DEFAULT_GROUP,
|
||||
description: 'Définir le groupe par défaut',
|
||||
impliedPermissions: [EmployeePermission.VIEW_EMPLOYEE_GROUP]
|
||||
},
|
||||
{
|
||||
permission: EmployeePermission.ADMIN,
|
||||
description: 'Administrateur',
|
||||
impliedPermissions: [EmployeePermission.REMOVE, EmployeePermission.SET_BROWSER_DEFAULT_GROUP, EmployeePermission.REMOVE_EMPLOYEE, EmployeePermission.EDIT_EMPLOYEE_PASSWORD, EmployeePermission.REMOVE_EMPLOYEE_GROUP]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import {MaterialType} from "./materialtype.model";
|
||||
|
||||
export class Material {
|
||||
constructor(
|
||||
public id: number,
|
||||
public name: string,
|
||||
public inventoryQuantity: number,
|
||||
public materialType: MaterialType
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -3,7 +3,8 @@ export class MaterialType {
|
|||
public id: number,
|
||||
public name: string,
|
||||
public prefix: string,
|
||||
public usePercentages: boolean
|
||||
public usePercentages: boolean,
|
||||
public systemType: boolean
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {Injectable, OnDestroy} from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {HttpClient, HttpHeaders, HttpParams} from "@angular/common/http";
|
||||
import {Observable, Subject} from "rxjs";
|
||||
import {environment} from "../../../../environments/environment";
|
||||
import {AppState} from "../app-state";
|
||||
import {Router} from "@angular/router";
|
||||
import {takeUntil} from "rxjs/operators";
|
||||
import {share, takeUntil} from "rxjs/operators";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -25,42 +25,77 @@ export class ApiService implements OnDestroy {
|
|||
}
|
||||
|
||||
get<T>(url: string, needAuthentication = true, options: any = {}): Observable<T> {
|
||||
if (this.checkAuthenticated(needAuthentication, options)) {
|
||||
// @ts-ignore
|
||||
return this.http.get<string>(environment.apiUrl + url, options).pipe(takeUntil(this._destroy$))
|
||||
}
|
||||
return this.executeHttpRequest(
|
||||
httpOptions => this.http.get<T>(environment.apiUrl + url, httpOptions),
|
||||
needAuthentication,
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
post<T>(url: string, body: any = {}, needAuthentication = true, options: any = {}): Observable<T> {
|
||||
if (this.checkAuthenticated(needAuthentication, options)) {
|
||||
// @ts-ignore
|
||||
return this.http.post<T>(environment.apiUrl + url, body, options).pipe(takeUntil(this._destroy$))
|
||||
}
|
||||
return this.executeHttpRequest(
|
||||
httpOptions => this.http.post<T>(environment.apiUrl + url, body, httpOptions),
|
||||
needAuthentication,
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
put<T>(url: string, body: any = {}, needAuthentication = true, options: any = {}): Observable<T> {
|
||||
if (this.checkAuthenticated(needAuthentication, options)) {
|
||||
// @ts-ignore
|
||||
return this.http.put<T>(environment.apiUrl + url, body, options).pipe(takeUntil(this._destroy$))
|
||||
}
|
||||
return this.executeHttpRequest(
|
||||
httpOptions => this.http.put<T>(environment.apiUrl + url, body, httpOptions),
|
||||
needAuthentication,
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
delete<T>(url: string, needAuthentication = true, options: any = {}): Observable<T> {
|
||||
if (this.checkAuthenticated(needAuthentication, options)) {
|
||||
// @ts-ignore
|
||||
return this.http.delete<T>(environment.apiUrl + url, options).pipe(takeUntil(this._destroy$))
|
||||
}
|
||||
return this.executeHttpRequest(
|
||||
httpOptions => this.http.delete<T>(environment.apiUrl + url, httpOptions),
|
||||
needAuthentication,
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
private checkAuthenticated(needAuthentication: boolean, httpOptions: any): boolean {
|
||||
private executeHttpRequest<T>(requestFn: (httpOptions?: {
|
||||
headers?: HttpHeaders | {
|
||||
[header: string]: string | string[];
|
||||
};
|
||||
observe?: 'body';
|
||||
params?: HttpParams | {
|
||||
[param: string]: string | string[];
|
||||
};
|
||||
reportProgress?: boolean;
|
||||
responseType?: 'json';
|
||||
withCredentials?: boolean;
|
||||
}) => Observable<T>, needAuthentication = true, httpOptions: any = {}): Observable<T> {
|
||||
if (needAuthentication) {
|
||||
if (!this.appState.isAuthenticated || Date.now() > this.appState.authenticationExpiration) {
|
||||
if (this.checkAuthenticated()) {
|
||||
if (httpOptions) {
|
||||
httpOptions.withCredentials = true
|
||||
} else {
|
||||
console.error("httpOptions need to be specified to use credentials in HTTP methods.")
|
||||
}
|
||||
} else {
|
||||
this.navigateToLogin()
|
||||
return false
|
||||
}
|
||||
httpOptions.withCredentials = true
|
||||
}
|
||||
return true
|
||||
|
||||
const result$ = requestFn(httpOptions)
|
||||
.pipe(takeUntil(this._destroy$), share())
|
||||
|
||||
const errorCheckSubscription = result$.subscribe({
|
||||
next: () => this.appState.isServerOnline = true,
|
||||
error: err => {
|
||||
errorCheckSubscription.unsubscribe()
|
||||
this.appState.isServerOnline = !(err.status === 0 && err.statusText === "Unknown Error");
|
||||
}
|
||||
})
|
||||
|
||||
return result$
|
||||
}
|
||||
|
||||
private checkAuthenticated(): boolean {
|
||||
return this.appState.isAuthenticated && Date.now() <= this.appState.authenticationExpiration
|
||||
}
|
||||
|
||||
private navigateToLogin() {
|
||||
|
|
|
@ -18,30 +18,43 @@ import {ConfirmBoxComponent} from './components/confirm-box/confirm-box.componen
|
|||
import {PermissionsListComponent} from './components/permissions-list/permissions-list.component';
|
||||
import {MatChipsModule} from "@angular/material/chips";
|
||||
import {PermissionsFieldComponent} from "./components/permissions-field/permissions-field.component";
|
||||
import { NavComponent } from './components/nav/nav.component';
|
||||
import {NavComponent} from './components/nav/nav.component';
|
||||
import {EntityListComponent} from './components/entity-list/entity-list.component';
|
||||
import {RouterModule} from "@angular/router";
|
||||
import {EntityAddComponent} from './components/entity-add/entity-add.component';
|
||||
import {EntityEditComponent} from './components/entity-edit/entity-edit.component';
|
||||
import {MatSelectModule} from "@angular/material/select";
|
||||
import {MatOptionModule} from "@angular/material/core";
|
||||
import {MaterialFileInputModule} from "ngx-material-file-input";
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [HeaderComponent, EmployeeInfoComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent],
|
||||
exports: [
|
||||
CommonModule,
|
||||
HttpClientModule,
|
||||
HeaderComponent,
|
||||
MatCardModule,
|
||||
MatButtonModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatIconModule,
|
||||
MatTableModule,
|
||||
MatCheckboxModule,
|
||||
MatListModule,
|
||||
ReactiveFormsModule,
|
||||
LabeledIconComponent,
|
||||
ConfirmBoxComponent,
|
||||
PermissionsListComponent,
|
||||
PermissionsFieldComponent,
|
||||
NavComponent
|
||||
],
|
||||
declarations: [HeaderComponent, EmployeeInfoComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent],
|
||||
exports: [
|
||||
CommonModule,
|
||||
HttpClientModule,
|
||||
HeaderComponent,
|
||||
MatCardModule,
|
||||
MatButtonModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatIconModule,
|
||||
MatTableModule,
|
||||
MatCheckboxModule,
|
||||
MatListModule,
|
||||
MatSelectModule,
|
||||
MatOptionModule,
|
||||
MaterialFileInputModule,
|
||||
ReactiveFormsModule,
|
||||
LabeledIconComponent,
|
||||
ConfirmBoxComponent,
|
||||
PermissionsListComponent,
|
||||
PermissionsFieldComponent,
|
||||
NavComponent,
|
||||
EntityListComponent,
|
||||
EntityAddComponent,
|
||||
EntityEditComponent
|
||||
],
|
||||
imports: [
|
||||
MatTabsModule,
|
||||
MatIconModule,
|
||||
|
@ -50,7 +63,13 @@ import { NavComponent } from './components/nav/nav.component';
|
|||
MatChipsModule,
|
||||
MatCheckboxModule,
|
||||
MatFormFieldModule,
|
||||
MaterialFileInputModule,
|
||||
MatTableModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatOptionModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
CommonModule
|
||||
]
|
||||
})
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<cre-nav [links]="links"></cre-nav>
|
||||
<router-outlet></router-outlet>
|
|
@ -0,0 +1,15 @@
|
|||
import {Component} from '@angular/core';
|
||||
import {NavLink} from "../../modules/shared/components/nav/nav.component";
|
||||
import {EmployeePermission} from "../../modules/shared/model/employee";
|
||||
|
||||
@Component({
|
||||
selector: 'cre-inventory-page',
|
||||
templateUrl: './inventory-page.component.html',
|
||||
styleUrls: ['./inventory-page.component.sass']
|
||||
})
|
||||
export class InventoryPageComponent {
|
||||
links: NavLink[] = [
|
||||
{route: '/inventory/materialtype', title: 'Types de produit', permission: EmployeePermission.VIEW_MATERIAL_TYPE},
|
||||
{route: '/inventory/material', title: 'Produits', permission: EmployeePermission.VIEW_MATERIAL}
|
||||
]
|
||||
}
|
|
@ -69,7 +69,7 @@ table
|
|||
overflow: hidden
|
||||
display: flex
|
||||
|
||||
.disabled button
|
||||
.disabled *
|
||||
display: none
|
||||
|
||||
button
|
||||
|
|
|
@ -113,7 +113,7 @@ public class FilesService {
|
|||
|
||||
return file;
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException("Impossible de créer un fichier: " + ex.getMessage());
|
||||
throw new RuntimeException("Impossible de créer un fichier: ", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -257,6 +257,18 @@ private enum class ControllerAuthorizations(
|
|||
val antMatcher: String,
|
||||
val permissions: Map<HttpMethod, EmployeePermission>
|
||||
) {
|
||||
MATERIALS("/api/material/**", mapOf(
|
||||
HttpMethod.GET to EmployeePermission.VIEW_MATERIAL,
|
||||
HttpMethod.POST to EmployeePermission.EDIT_MATERIAL,
|
||||
HttpMethod.PUT to EmployeePermission.EDIT_MATERIAL,
|
||||
HttpMethod.DELETE to EmployeePermission.REMOVE_MATERIAL
|
||||
)),
|
||||
MATERIAL_TYPES("/api/materialtype/**", mapOf(
|
||||
HttpMethod.GET to EmployeePermission.VIEW_MATERIAL_TYPE,
|
||||
HttpMethod.POST to EmployeePermission.EDIT_MATERIAL_TYPE,
|
||||
HttpMethod.PUT to EmployeePermission.EDIT_MATERIAL_TYPE,
|
||||
HttpMethod.DELETE to EmployeePermission.REMOVE_MATERIAL_TYPE
|
||||
)),
|
||||
SET_BROWSER_DEFAULT_GROUP("/api/employee/group/default/**", mapOf(
|
||||
HttpMethod.GET to EmployeePermission.VIEW_EMPLOYEE_GROUP,
|
||||
HttpMethod.POST to EmployeePermission.SET_BROWSER_DEFAULT_GROUP
|
||||
|
|
|
@ -163,26 +163,37 @@ data class EmployeeLoginRequest(val id: Long, val password: String)
|
|||
|
||||
enum class EmployeePermission(val impliedPermissions: List<EmployeePermission> = listOf()) {
|
||||
// View
|
||||
VIEW_MATERIAL,
|
||||
VIEW_MATERIAL_TYPE,
|
||||
VIEW(listOf(
|
||||
VIEW_MATERIAL,
|
||||
VIEW_MATERIAL_TYPE
|
||||
)),
|
||||
VIEW_EMPLOYEE,
|
||||
VIEW_EMPLOYEE_GROUP,
|
||||
VIEW(listOf(
|
||||
|
||||
)),
|
||||
|
||||
// Edit
|
||||
EDIT_MATERIAL(listOf(VIEW_MATERIAL)),
|
||||
EDIT_MATERIAL_TYPE(listOf(VIEW_MATERIAL_TYPE)),
|
||||
EDIT(listOf(
|
||||
EDIT_MATERIAL,
|
||||
EDIT_MATERIAL_TYPE,
|
||||
VIEW
|
||||
)),
|
||||
EDIT_EMPLOYEE(listOf(VIEW_EMPLOYEE)),
|
||||
EDIT_EMPLOYEE_PASSWORD(listOf(EDIT_EMPLOYEE)),
|
||||
EDIT_EMPLOYEE_GROUP(listOf(VIEW_EMPLOYEE_GROUP)),
|
||||
EDIT(listOf(
|
||||
VIEW
|
||||
)),
|
||||
|
||||
// Remove
|
||||
REMOVE_EMPLOYEE(listOf(EDIT_EMPLOYEE)),
|
||||
REMOVE_EMPLOYEE_GROUP(listOf(EDIT_EMPLOYEE_GROUP)),
|
||||
REMOVE_MATERIAL(listOf(EDIT_MATERIAL)),
|
||||
REMOVE_MATERIAL_TYPE(listOf(EDIT_MATERIAL_TYPE)),
|
||||
REMOVE(listOf(
|
||||
REMOVE_MATERIAL,
|
||||
REMOVE_MATERIAL_TYPE,
|
||||
EDIT
|
||||
)),
|
||||
REMOVE_EMPLOYEE(listOf(EDIT_EMPLOYEE)),
|
||||
REMOVE_EMPLOYEE_GROUP(listOf(EDIT_EMPLOYEE_GROUP)),
|
||||
|
||||
// Others
|
||||
SET_BROWSER_DEFAULT_GROUP(listOf(
|
|
@ -6,6 +6,7 @@ import org.springframework.util.Assert
|
|||
import org.springframework.web.multipart.MultipartFile
|
||||
import java.util.*
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.Min
|
||||
import javax.validation.constraints.NotBlank
|
||||
import javax.validation.constraints.NotNull
|
||||
import javax.validation.constraints.Size
|
||||
|
@ -44,7 +45,7 @@ open class MaterialSaveDto(
|
|||
val name: String,
|
||||
|
||||
@field:NotNull(message = MATERIAL_INVENTORY_QUANTITY_NULL_MESSAGE)
|
||||
@field:Size(min = 0, message = MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE)
|
||||
@field:Min(value = 0, message = MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE)
|
||||
val inventoryQuantity: Float,
|
||||
|
||||
@field:NotNull(message = MATERIAL_TYPE_NULL_MESSAGE)
|
||||
|
|
|
@ -2,6 +2,7 @@ package dev.fyloz.trial.colorrecipesexplorer.rest
|
|||
|
||||
import dev.fyloz.trial.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.trial.colorrecipesexplorer.service.MaterialService
|
||||
import org.jetbrains.annotations.Nullable
|
||||
import org.springframework.context.annotation.Profile
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpStatus
|
||||
|
@ -9,6 +10,7 @@ import org.springframework.http.MediaType
|
|||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import javax.validation.Valid
|
||||
|
||||
private const val MATERIAL_CONTROLLER_PATH = "api/material"
|
||||
|
||||
|
@ -34,7 +36,7 @@ class MaterialController(materialService: MaterialService) : AbstractRestModelAp
|
|||
}
|
||||
|
||||
@PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
fun save(entity: MaterialSaveDto, simdutFile: MultipartFile): ResponseEntity<Material> =
|
||||
fun saveTest(@Valid entity: MaterialSaveDto, simdutFile: MultipartFile?): ResponseEntity<Material> =
|
||||
super.save(materialSaveDto(name = entity.name, inventoryQuantity = entity.inventoryQuantity, materialType = entity.materialType, simdutFile = simdutFile))
|
||||
|
||||
@PostMapping("oldsave")
|
||||
|
@ -42,8 +44,8 @@ class MaterialController(materialService: MaterialService) : AbstractRestModelAp
|
|||
ResponseEntity.notFound().build()
|
||||
|
||||
@PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
fun update(entity: MaterialUpdateDto, simdutFile: MultipartFile): ResponseEntity<Void> =
|
||||
super.update(materialUpdateDto(name = entity.name, inventoryQuantity = entity.inventoryQuantity, materialType = entity.materialType, simdutFile = simdutFile))
|
||||
fun update(@Valid entity: MaterialUpdateDto, simdutFile: MultipartFile?): ResponseEntity<Void> =
|
||||
super.update(materialUpdateDto(id = entity.id, name = entity.name, inventoryQuantity = entity.inventoryQuantity, materialType = entity.materialType, simdutFile = simdutFile))
|
||||
|
||||
@PutMapping("oldupdate")
|
||||
override fun update(entity: MaterialUpdateDto): ResponseEntity<Void> =
|
||||
|
|
|
@ -40,14 +40,10 @@ class MaterialServiceImpl(materialRepository: MaterialRepository, val mixQuantit
|
|||
if (entity.simdutFile != null && !entity.simdutFile.isEmpty) simdutService.write(this, entity.simdutFile)
|
||||
}
|
||||
|
||||
override fun save(entity: Material): Material {
|
||||
if (existsByName(entity.name))
|
||||
throw EntityAlreadyExistsRestException(entity.name)
|
||||
return super.save(entity)
|
||||
}
|
||||
|
||||
override fun update(entity: MaterialUpdateDto): Material =
|
||||
update(entity.toMaterial()).apply { simdutService.update(entity.simdutFile, this) }
|
||||
update(entity.toMaterial()).apply {
|
||||
if (entity.simdutFile != null && !entity.simdutFile.isEmpty) simdutService.update(entity.simdutFile, this)
|
||||
}
|
||||
|
||||
override fun update(entity: Material): Material {
|
||||
Assert.notNull(entity.id, "MaterialService.update() was called with a null identifier")
|
||||
|
|
|
@ -10,6 +10,7 @@ import dev.fyloz.trial.colorrecipesexplorer.repository.NamedJpaRepository
|
|||
import io.jsonwebtoken.lang.Assert
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import java.lang.RuntimeException
|
||||
|
||||
/**
|
||||
* A service implementing the basics CRUD operations.
|
||||
|
@ -92,7 +93,7 @@ abstract class AbstractModelService<E : Model, S : EntityDto<E>, U : EntityDto<E
|
|||
return super.update(entity)
|
||||
}
|
||||
|
||||
override fun deleteById(id: Long) = repository.deleteById(id)
|
||||
override fun deleteById(id: Long) = delete(getById(id)) // Use delete(entity) to prevent code duplication
|
||||
}
|
||||
|
||||
abstract class AbstractNamedModelService<E : NamedModel, S : EntityDto<E>, U : EntityDto<E>, R : NamedJpaRepository<E>>(repository: R) :
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
spring.datasource.url=jdbc:h2:file:./workdir/recipes
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=LWK4Y7TvEbNyhu1yCoG3
|
||||
spring.h2.console.path=/dbconsole
|
||||
spring.h2.console.enabled=true
|
||||
spring.h2.console.settings.trace=false
|
||||
spring.h2.console.settings.web-allow-others=false
|
||||
spring.datasource.driver-class-name=org.h2.Driver
|
||||
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
|
@ -0,0 +1,5 @@
|
|||
spring.datasource.url=jdbc:mysql://172.20.0.2/cre
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=pass
|
||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
|
|
@ -1,9 +1,3 @@
|
|||
# BDD
|
||||
spring.datasource.url=jdbc:h2:file:./workdir/recipes
|
||||
spring.datasource.username=sa
|
||||
spring.datasource.password=LWK4Y7TvEbNyhu1yCoG3
|
||||
# CONSOLE DE LA BDD
|
||||
spring.h2.console.path=/dbconsole
|
||||
# PORT
|
||||
server.port=9090
|
||||
# CRE
|
||||
|
@ -17,8 +11,8 @@ cre.security.jwt-duration=18000000
|
|||
cre.security.root.id=9999
|
||||
cre.security.root.password=password
|
||||
# Common user
|
||||
cre.security.common.id=9998
|
||||
cre.security.common.password=common
|
||||
#cre.security.common.id=9998
|
||||
#cre.security.common.password=common
|
||||
# TYPES DE PRODUIT PAR DÉFAUT
|
||||
entities.material-types.systemTypes[0].name=Aucun
|
||||
entities.material-types.systemTypes[0].prefix=
|
||||
|
@ -29,20 +23,12 @@ entities.material-types.systemTypes[1].usepercentages=false
|
|||
entities.material-types.baseName=Base
|
||||
# DEBUG
|
||||
spring.jpa.show-sql=true
|
||||
spring.h2.console.enabled=true
|
||||
# Permet d'accéder à la console de la BDD à distance
|
||||
spring.h2.console.settings.trace=false
|
||||
spring.h2.console.settings.web-allow-others=false
|
||||
# NE PAS MODIFIER
|
||||
spring.datasource.driver-class-name=org.h2.Driver
|
||||
spring.messages.fallback-to-system-locale=true
|
||||
spring.servlet.multipart.max-file-size=10MB
|
||||
spring.servlet.multipart.max-request-size=15MB
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
|
||||
spring.jpa.open-in-view=true
|
||||
server.http2.enabled=true
|
||||
server.error.whitelabel.enabled=false
|
||||
#spring.redis.host=localhost
|
||||
#spring.redis.port=6379
|
||||
spring.profiles.active=@spring.profiles.active@
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
== Icônes pour recettes non-approuvés / quantité faible ==
|
||||
== Texte SIMDUT inexistant (fiche signalitique) pour les matériaux ==
|
||||
|
||||
|
||||
|
||||
== Comptes ==
|
||||
No employé - Permissions - Employés
|
||||
|
||||
|
||||
|
||||
== Kits de retouche ==
|
||||
No Job - No Dossier - Qté - Description - Case à cocher - Note
|
||||
Bouton compléter si tout est coché/imprimé ?
|
||||
|
||||
Enregistrer localdatetime/personne pendant une certaine durée
|
Loading…
Reference in New Issue