Inventaire
This commit is contained in:
parent
e693625ab7
commit
83d8b30207
|
@ -21,13 +21,16 @@ import { MixesCardComponent } from './components/mixes-card/mixes-card.component
|
|||
|
||||
|
||||
@NgModule({
|
||||
declarations: [ListComponent, AddComponent, EditComponent, ExploreComponent, RecipeInfoComponent, MixTableComponent, StepListComponent, StepTableComponent, MixEditorComponent, UnitSelectorComponent, MixAddComponent, MixEditComponent, ImagesEditorComponent, MixesCardComponent],
|
||||
imports: [
|
||||
ColorsRoutingModule,
|
||||
SharedModule,
|
||||
MatExpansionModule,
|
||||
FormsModule
|
||||
]
|
||||
declarations: [ListComponent, AddComponent, EditComponent, ExploreComponent, RecipeInfoComponent, MixTableComponent, StepListComponent, StepTableComponent, MixEditorComponent, UnitSelectorComponent, MixAddComponent, MixEditComponent, ImagesEditorComponent, MixesCardComponent],
|
||||
exports: [
|
||||
UnitSelectorComponent
|
||||
],
|
||||
imports: [
|
||||
ColorsRoutingModule,
|
||||
SharedModule,
|
||||
MatExpansionModule,
|
||||
FormsModule
|
||||
]
|
||||
})
|
||||
export class ColorsModule {
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<button mat-raised-button color="accent" (click)="printingConfirmBox.show()">Imprimer</button>
|
||||
</div>
|
||||
<div>
|
||||
<button mat-raised-button color="accent" (click)="deduct.emit()" disabled title="WIP">Déduire</button>
|
||||
<button mat-raised-button color="accent" (click)="deduct.emit()">Déduire</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="editionMode">
|
||||
|
@ -115,7 +115,7 @@
|
|||
<tr mat-header-row *matHeaderRowDef="mixColumns"></tr>
|
||||
<tr mat-row
|
||||
*matRowDef="let mixMaterial; columns: mixColumns"
|
||||
[class.low-quantity]="!editionMode && isInLowQuantity(mixMaterial.id)"
|
||||
[class.low-quantity]="!editionMode && isInLowQuantity(mixMaterial.material.id)"
|
||||
(mouseover)="hoveredMixMaterial = mixMaterial">
|
||||
</tr>
|
||||
<ng-container *ngIf="!editionMode">
|
||||
|
|
|
@ -119,7 +119,8 @@ export class MixTableComponent extends SubscribingComponent {
|
|||
}
|
||||
|
||||
isInLowQuantity(materialId: number): boolean {
|
||||
return this.deductErrorBody[this.mix.id] && this.deductErrorBody[this.mix.id].indexOf(materialId) >= 0
|
||||
return this.deductErrorBody.mix === this.mix.id &&
|
||||
this.deductErrorBody.lowQuantities.filter(l => l.material === materialId).length > 0
|
||||
}
|
||||
|
||||
round(quantity: number): number {
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
<button mat-raised-button color="accent" (click)="saveModifications()" [disabled]="!hasModifications">
|
||||
Enregistrer
|
||||
</button>
|
||||
<button mat-raised-button color="accent" (click)="deductQuantities()" disabled title="WIP">Déduire</button>
|
||||
</div>
|
||||
<cre-unit-selector (unitChange)="changeUnits($event)"></cre-unit-selector>
|
||||
</div>
|
||||
|
@ -31,7 +30,7 @@
|
|||
[units$]="units$"
|
||||
(quantityChange)="changeQuantity($event)"
|
||||
(locationChange)="changeMixLocation($event)"
|
||||
(deduct)="deductMixQuantities($event)">
|
||||
(deduct)="deductMix($event)">
|
||||
</cre-mixes-card>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -23,9 +23,10 @@ export class ExploreComponent extends ErrorHandlingComponent {
|
|||
quantitiesChanges = new Map<number, Map<number, number>>()
|
||||
mixesLocationChanges = new Map<number, string>()
|
||||
|
||||
deductedMixId: number | null
|
||||
handledErrorModels: ErrorModel[] = [{
|
||||
filter: error => error.status === 409,
|
||||
consumer: error => this.deductErrorBody = error.error,
|
||||
consumer: error => this.deductErrorBody = {mix: this.deductedMixId, lowQuantities: error.error.lowQuantities},
|
||||
messageProducer: () => 'Certains produit ne sont pas en quantité suffisante dans l\'inventaire'
|
||||
}]
|
||||
|
||||
|
@ -52,7 +53,7 @@ export class ExploreComponent extends ErrorHandlingComponent {
|
|||
this.note = r.note
|
||||
|
||||
if (this.recipe.mixes.length <= 0 || this.recipe.steps.length <= 0) {
|
||||
this.alertService.pushWarning("Cette recette n'est pas complète")
|
||||
this.alertService.pushWarning('Cette recette n\'est pas complète')
|
||||
}
|
||||
},
|
||||
'/colors/list'
|
||||
|
@ -60,7 +61,7 @@ export class ExploreComponent extends ErrorHandlingComponent {
|
|||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
super.ngOnDestroy()
|
||||
GlobalAlertHandlerComponent.extraTopMarginMultiplier = 0
|
||||
}
|
||||
|
||||
|
@ -93,18 +94,22 @@ export class ExploreComponent extends ErrorHandlingComponent {
|
|||
)
|
||||
}
|
||||
|
||||
deductQuantities() {
|
||||
this.performDeductQuantities(this.recipeService.deductQuantities(this.recipe, this.quantitiesChanges))
|
||||
deductMix(mixId: number) {
|
||||
this.deductedMixId = mixId
|
||||
const firstMixMaterial = this.recipe.mixes.filter(m => m.id === mixId)[0].mixMaterials[0]
|
||||
if (this.quantitiesChanges.has(mixId) && this.quantitiesChanges.get(mixId).has(firstMixMaterial.material.id)) {
|
||||
const originalQuantity = firstMixMaterial.quantity
|
||||
const currentQuantity = this.quantitiesChanges.get(mixId).get(firstMixMaterial.material.id)
|
||||
this.subscribeDeductMix(this.recipeService.deductMixFromQuantities(mixId, originalQuantity, currentQuantity))
|
||||
} else {
|
||||
this.subscribeDeductMix(this.recipeService.deductMix(mixId, 1))
|
||||
}
|
||||
}
|
||||
|
||||
deductMixQuantities(mixId: number) {
|
||||
this.performDeductQuantities(this.recipeService.deductMixQuantities(this.recipe, mixId, this.quantitiesChanges.get(mixId)))
|
||||
}
|
||||
|
||||
performDeductQuantities(observable: Observable<void>) {
|
||||
subscribeDeductMix(observable: Observable<any>) {
|
||||
this.subscribe(
|
||||
observable,
|
||||
() => this.alertService.pushSuccess('Les quantités des produits utilisés ont été déduites de l\'inventaire'),
|
||||
() => this.alertService.pushSuccess('Les quantités quantités ont été déduites de l\'inventaire'),
|
||||
true
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import {ApiService} from '../../shared/service/api.service'
|
|||
import {Observable} from 'rxjs'
|
||||
import {Recipe, RecipeStep} from '../../shared/model/recipe.model'
|
||||
import {map} from 'rxjs/operators'
|
||||
import {MaterialQuantity} from '../../material/service/inventory.service'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -63,46 +64,12 @@ export class RecipeService {
|
|||
return this.api.put<void>('/recipe/public', body)
|
||||
}
|
||||
|
||||
deductMixQuantities(recipe: Recipe, mixId: number, quantities: Map<number, number>): Observable<void> {
|
||||
return this.sendDeductBody(this.buildDeductMixBody(recipe, mixId, quantities))
|
||||
deductMixFromQuantities(mixId: number, originalQuantity: number, currentQuantity: number): Observable<MaterialQuantity[]> {
|
||||
return this.deductMix(mixId, currentQuantity / originalQuantity)
|
||||
}
|
||||
|
||||
deductQuantities(recipe: Recipe, quantities: Map<number, Map<number, number>>): Observable<void> {
|
||||
return this.sendDeductBody(this.buildDeductBody(recipe, quantities))
|
||||
}
|
||||
|
||||
delete(id: number): Observable<void> {
|
||||
return this.api.delete<void>(`/recipe/${id}`)
|
||||
}
|
||||
|
||||
private buildDeductMixBody(recipe: Recipe, mixId: number, quantities: Map<number, number>): any {
|
||||
const mix = recipe.mixes.filter(m => m.id === mixId)[0]
|
||||
const body = {id: recipe.id, quantities: {}}
|
||||
body.quantities[mixId] = {}
|
||||
const firstMaterial = mix.mixMaterials[0].material.id
|
||||
mix.mixMaterials.forEach(m => {
|
||||
if (quantities && quantities.has(m.material.id)) {
|
||||
body.quantities[mix.id][m.material.id] = quantities.get(m.material.id)
|
||||
} else {
|
||||
let quantity = m.quantity
|
||||
if (m.material.materialType.usePercentages) {
|
||||
quantity = body.quantities[mix.id][firstMaterial] * (quantity / 100)
|
||||
}
|
||||
body.quantities[mix.id][m.material.id] = quantity
|
||||
}
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
||||
private buildDeductBody(recipe: Recipe, quantities: Map<number, Map<number, number>>): any {
|
||||
const body = {id: recipe.id, quantities: {}}
|
||||
recipe.mixes.forEach(mix => {
|
||||
body.quantities[mix.id] = this.buildDeductMixBody(recipe, mix.id, quantities.get(mix.id)).quantities[mix.id]
|
||||
})
|
||||
return body
|
||||
}
|
||||
|
||||
private sendDeductBody(body: any): Observable<void> {
|
||||
return this.api.put('/recipe/deduct', body)
|
||||
deductMix(mixId: number, ratio: number): Observable<MaterialQuantity[]> {
|
||||
const body = {id: mixId, ratio}
|
||||
return this.api.put<MaterialQuantity[]>('/inventory/deduct/mix', body)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,13 +94,14 @@ export class AddComponent extends ErrorHandlingComponent {
|
|||
submit(values) {
|
||||
const permissionsField = currentPermissionsFieldComponent
|
||||
if (permissionsField.valid()) {
|
||||
const groupId = values.groupId >= 0 ? values.groupId : null
|
||||
this.subscribeAndNavigate(
|
||||
this.employeeService.save(
|
||||
values.id,
|
||||
values.firstName,
|
||||
values.lastName,
|
||||
values.password,
|
||||
values.groupId,
|
||||
groupId,
|
||||
permissionsField.allEnabledPermissions
|
||||
),
|
||||
'/employee/list'
|
||||
|
|
|
@ -92,22 +92,23 @@ export class EditComponent extends ErrorHandlingComponent {
|
|||
permissionsField.allEnabledPermissions
|
||||
),
|
||||
() => {
|
||||
const group = values.groupId
|
||||
if (group >= 0) {
|
||||
this.subscribeAndNavigate(
|
||||
this.groupService.addEmployeeToGroup(group, this.employee),
|
||||
'/employee/list'
|
||||
)
|
||||
} else {
|
||||
if (this.employee.group) {
|
||||
this.subscribeAndNavigate(
|
||||
this.groupService.removeEmployeeFromGroup(this.employee),
|
||||
'/employee/list'
|
||||
)
|
||||
} else {
|
||||
// TODO de-comment when backend will be ready
|
||||
// const group = values.groupId
|
||||
// if (group >= 0) {
|
||||
// this.subscribeAndNavigate(
|
||||
// this.groupService.addEmployeeToGroup(group, this.employee),
|
||||
// '/employee/list'
|
||||
// )
|
||||
// } else {
|
||||
// if (this.employee.group) {
|
||||
// this.subscribeAndNavigate(
|
||||
// this.groupService.removeEmployeeFromGroup(this.employee),
|
||||
// '/employee/list'
|
||||
// )
|
||||
// } else {
|
||||
this.urlUtils.navigateTo('/employee/list')
|
||||
}
|
||||
}
|
||||
// }
|
||||
// }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -18,8 +18,7 @@ export class ListComponent extends ErrorHandlingComponent {
|
|||
defaultGroup: EmployeeGroup = null
|
||||
columns = [
|
||||
{def: 'name', title: 'Nom', valueFn: g => g.name},
|
||||
{def: 'permissionCount', title: 'Nombre de permissions', valueFn: g => g.permissions.length},
|
||||
{def: 'employeeCount', title: 'Nombre d\'utilisateurs', valueFn: g => g.employeeCount}
|
||||
{def: 'permissionCount', title: 'Nombre de permissions', valueFn: g => g.permissions.length}
|
||||
]
|
||||
buttons = [{
|
||||
text: 'Définir par défaut',
|
||||
|
|
|
@ -18,8 +18,7 @@ export class GroupService {
|
|||
.pipe(tap(groups => groups.unshift({
|
||||
id: -1,
|
||||
name: 'Aucun',
|
||||
permissions: [],
|
||||
employeeCount: 0
|
||||
permissions: []
|
||||
})))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {ListComponent} from "./pages/list/list.component";
|
||||
import {InventoryComponent} from "./pages/inventory/inventory.component";
|
||||
import {AddComponent} from "./pages/add/add.component";
|
||||
import {EditComponent} from "./pages/edit/edit.component";
|
||||
|
||||
const routes: Routes = [{
|
||||
path: 'list',
|
||||
component: ListComponent
|
||||
component: InventoryComponent
|
||||
}, {
|
||||
path: 'add',
|
||||
component: AddComponent
|
||||
|
|
|
@ -2,18 +2,24 @@ import {NgModule} from '@angular/core';
|
|||
import {CommonModule} from '@angular/common';
|
||||
|
||||
import {MaterialRoutingModule} from './material-routing.module';
|
||||
import {ListComponent} from './pages/list/list.component';
|
||||
import {InventoryComponent} from './pages/inventory/inventory.component';
|
||||
import {SharedModule} from "../shared/shared.module";
|
||||
import {AddComponent} from './pages/add/add.component';
|
||||
import {EditComponent} from './pages/edit/edit.component';
|
||||
import {ColorsModule} from '../colors/colors.module'
|
||||
import {MatSortModule} from '@angular/material/sort'
|
||||
import {FormsModule} from '@angular/forms'
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [ListComponent, AddComponent, EditComponent],
|
||||
declarations: [InventoryComponent, AddComponent, EditComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MaterialRoutingModule,
|
||||
SharedModule
|
||||
SharedModule,
|
||||
ColorsModule,
|
||||
MatSortModule,
|
||||
FormsModule
|
||||
]
|
||||
})
|
||||
export class MaterialModule {
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
<div class="action-bar backward">
|
||||
<!-- Left -->
|
||||
<div class="d-flex flex-row">
|
||||
<mat-form-field class="mr-4">
|
||||
<mat-label>Recherche par code...</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[(ngModel)]="materialNameFilter"
|
||||
(keyup)="filterDataSource()"/>
|
||||
</mat-form-field>
|
||||
<mat-form-field *ngIf="materialTypes$ | async as materialTypes">
|
||||
<mat-label>Recherche par type de produit</mat-label>
|
||||
<mat-select
|
||||
[(value)]="materialTypeFilter"
|
||||
(valueChange)="filterDataSource()">
|
||||
<mat-option
|
||||
*ngFor="let materialType of materialTypes"
|
||||
[value]="materialType.id">
|
||||
{{materialType.name}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Right -->
|
||||
<div class="ml-auto">
|
||||
<mat-form-field class="mr-4">
|
||||
<mat-label>Quantité faible</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="number"
|
||||
step="0.01"
|
||||
[(ngModel)]="lowQuantityThreshold"/>
|
||||
</mat-form-field>
|
||||
<cre-unit-selector [(unit)]="units"></cre-unit-selector>
|
||||
<button
|
||||
*ngIf="canEditMaterial"
|
||||
class="ml-3"
|
||||
mat-raised-button
|
||||
color="accent"
|
||||
routerLink="/material/add">
|
||||
Ajouter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table
|
||||
mat-table
|
||||
matSort
|
||||
class="mx-auto"
|
||||
[dataSource]="dataSource">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Code</th>
|
||||
<td mat-cell *matCellDef="let material">{{material.name}}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="materialType">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Type de produit</th>
|
||||
<td mat-cell *matCellDef="let material">{{material.materialType.name}}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="quantity">
|
||||
<th mat-header-cell *matHeaderCellDef>Quantité</th>
|
||||
<td mat-cell *matCellDef="let material">{{getQuantity(material)}} {{units}}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="addQuantity">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell [class.disabled]="!canEditMaterial" *matCellDef="let material; let i = index">
|
||||
<div *ngIf="(!hoveredMaterial && i === 0) || hoveredMaterial === material" class="input-group">
|
||||
<input
|
||||
#addQuantityInput
|
||||
class="form-control"
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="0"/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
mat-flat-button
|
||||
color="accent"
|
||||
(click)="addQuantity(material, addQuantityInput)">
|
||||
Ajouter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="lowQuantityIcon">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header></th>
|
||||
<td mat-cell *matCellDef="let material" [class.disabled]="!isLowQuantity(material)">
|
||||
<mat-icon
|
||||
svgIcon="format-color-fill"
|
||||
class="color-warning"
|
||||
title="Il y a moins que {{lowQuantityThreshold}} {{units}} de {{material.name}} en inventaire">
|
||||
</mat-icon>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="simdutIcon">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let material" [class.disabled]="materialHasSimdut(material)">
|
||||
<mat-icon
|
||||
svgIcon="text-box-remove"
|
||||
color="warn"
|
||||
title="Ce produit n'a pas fiche signalitique">
|
||||
</mat-icon>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="editButton">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let material; let i = index" [class.disabled]="!canEditMaterial">
|
||||
<ng-container *ngIf="(!hoveredMaterial && i === 0) || hoveredMaterial === material">
|
||||
<button
|
||||
mat-raised-button
|
||||
color="accent"
|
||||
routerLink="/material/edit/{{material.id}}">
|
||||
Modifier
|
||||
</button>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="openSimdutButton">
|
||||
<th mat-header-cell *matHeaderCellDef></th>
|
||||
<td mat-cell *matCellDef="let material; let i = index" [class.disabled]="canEditMaterial">
|
||||
<ng-container *ngIf="(!hoveredMaterial && i === 0) || hoveredMaterial === material">
|
||||
<button
|
||||
mat-raised-button
|
||||
color="accent"
|
||||
[disabled]="!materialHasSimdut(material)"
|
||||
(click)="openSimdut(material)">
|
||||
Fiche signalitique
|
||||
</button>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columns"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
class="entity-row"
|
||||
*matRowDef="let material; columns: columns"
|
||||
(mouseover)="hoveredMaterial = material">
|
||||
</tr>
|
||||
</table>
|
|
@ -0,0 +1,8 @@
|
|||
.input-group-append button
|
||||
border-radius: 0 4px 4px 0
|
||||
|
||||
mat-select
|
||||
margin-top: 4px
|
||||
|
||||
.form-control
|
||||
width: 6rem
|
|
@ -0,0 +1,128 @@
|
|||
import {Component, ViewChild} from '@angular/core'
|
||||
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {MaterialService} from '../../service/material.service'
|
||||
import {EmployeePermission} from '../../../shared/model/employee'
|
||||
import {ActivatedRoute, Router} from '@angular/router'
|
||||
import {ErrorService} from '../../../shared/service/error.service'
|
||||
import {Material} from '../../../shared/model/material.model'
|
||||
import {AccountService} from '../../../accounts/services/account.service'
|
||||
import {convertQuantity, UNIT_MILLILITER} from '../../../shared/units'
|
||||
import {MatSort} from '@angular/material/sort'
|
||||
import {MatTableDataSource} from '@angular/material/table'
|
||||
import {MaterialTypeService} from '../../../material-type/service/material-type.service'
|
||||
import {InventoryService} from '../../service/inventory.service'
|
||||
import {environment} from '../../../../../environments/environment'
|
||||
|
||||
@Component({
|
||||
selector: 'cre-list',
|
||||
templateUrl: './inventory.component.html',
|
||||
styleUrls: ['./inventory.component.sass']
|
||||
})
|
||||
export class InventoryComponent extends ErrorHandlingComponent {
|
||||
@ViewChild(MatSort) sort: MatSort
|
||||
|
||||
materials: Material[] | null
|
||||
materialTypes$ = this.materialTypeService.all
|
||||
dataSource: MatTableDataSource<Material>
|
||||
hasSimdut: any
|
||||
|
||||
columns = ['name', 'materialType', 'quantity', 'addQuantity', 'lowQuantityIcon', 'simdutIcon', 'editButton', 'openSimdutButton']
|
||||
hoveredMaterial: Material | null
|
||||
|
||||
units = UNIT_MILLILITER
|
||||
lowQuantityThreshold = 100 // TEMPORARY will be in the application settings
|
||||
materialTypeFilter = 1
|
||||
materialNameFilter = ''
|
||||
|
||||
constructor(
|
||||
private materialService: MaterialService,
|
||||
private materialTypeService: MaterialTypeService,
|
||||
private inventoryService: InventoryService,
|
||||
private accountService: AccountService,
|
||||
errorService: ErrorService,
|
||||
router: Router,
|
||||
activatedRoute: ActivatedRoute
|
||||
) {
|
||||
super(errorService, activatedRoute, router)
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit()
|
||||
|
||||
this.subscribe(
|
||||
this.materialService.allNotMixType,
|
||||
materials => {
|
||||
this.materials = materials
|
||||
this.dataSource = this.setupDataSource()
|
||||
},
|
||||
true,
|
||||
1
|
||||
)
|
||||
|
||||
this.subscribe(
|
||||
this.materialService.getSimduts(),
|
||||
ids => this.hasSimdut = ids,
|
||||
false,
|
||||
1
|
||||
)
|
||||
}
|
||||
|
||||
setupDataSource(): MatTableDataSource<Material> {
|
||||
this.dataSource = new MatTableDataSource<Material>(this.materials)
|
||||
this.dataSource.sortingDataAccessor = (material, header) => {
|
||||
switch (header) {
|
||||
case 'materialType':
|
||||
return material[header].name
|
||||
case 'lowQuantityIcon':
|
||||
return this.isLowQuantity(material)
|
||||
default:
|
||||
return material[header]
|
||||
}
|
||||
}
|
||||
this.dataSource.filterPredicate = (material, filter) => {
|
||||
return (!this.materialTypeFilter || this.materialTypeFilter === 1 || material.materialType.id === this.materialTypeFilter) &&
|
||||
(!this.materialNameFilter || material.name.toLowerCase().includes(this.materialNameFilter.toLowerCase()))
|
||||
}
|
||||
|
||||
this.dataSource.sort = this.sort
|
||||
return this.dataSource
|
||||
}
|
||||
|
||||
filterDataSource() {
|
||||
this.dataSource.filter = 'filter'
|
||||
}
|
||||
|
||||
isLowQuantity(material: Material) {
|
||||
return this.getQuantity(material) < this.lowQuantityThreshold
|
||||
}
|
||||
|
||||
getQuantity(material: Material): number {
|
||||
return Math.round(convertQuantity(material.inventoryQuantity, UNIT_MILLILITER, this.units) * 100) / 100
|
||||
}
|
||||
|
||||
materialHasSimdut(material: Material): boolean {
|
||||
return this.hasSimdut && this.hasSimdut.filter(i => i === material.id).length > 0
|
||||
}
|
||||
|
||||
openSimdut(material: Material) {
|
||||
window.open(`${environment.apiUrl}/material/${material.id}/simdut`, '_blank')
|
||||
}
|
||||
|
||||
addQuantity(material: Material, input: HTMLInputElement) {
|
||||
const quantity = input.valueAsNumber
|
||||
this.subscribe(
|
||||
this.inventoryService.add(material.id, quantity),
|
||||
materialQuantities => {
|
||||
// Reset the input value
|
||||
input.value = null
|
||||
|
||||
material.inventoryQuantity = parseInt(materialQuantities.filter(mq => mq.material === material.id)[0].quantity)
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
get canEditMaterial(): boolean {
|
||||
return this.accountService.hasPermission(EmployeePermission.EDIT_MATERIAL)
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
<cre-entity-list
|
||||
[entities$]="materials$"
|
||||
[columns]="columns"
|
||||
[icons]="icons"
|
||||
[buttons]="buttons"
|
||||
addLink="/catalog/material/add"
|
||||
addPermission="EDIT_MATERIAL">
|
||||
</cre-entity-list>
|
|
@ -1,69 +0,0 @@
|
|||
import {Component} from '@angular/core'
|
||||
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
|
||||
import {MaterialService} from '../../service/material.service'
|
||||
import {EmployeePermission} from '../../../shared/model/employee'
|
||||
import {environment} from '../../../../../environments/environment'
|
||||
import {ActivatedRoute, Router} from '@angular/router'
|
||||
import {ErrorService} from '../../../shared/service/error.service'
|
||||
|
||||
@Component({
|
||||
selector: 'cre-list',
|
||||
templateUrl: './list.component.html',
|
||||
styleUrls: ['./list.component.sass']
|
||||
})
|
||||
export class ListComponent extends ErrorHandlingComponent {
|
||||
materials$ = this.materialService.allNotMixType
|
||||
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 => `/catalog/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,
|
||||
errorService: ErrorService,
|
||||
router: Router,
|
||||
activatedRoute: ActivatedRoute
|
||||
) {
|
||||
super(errorService, activatedRoute, router)
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit()
|
||||
|
||||
this.subscribe(
|
||||
this.materials$,
|
||||
mArray => {
|
||||
mArray.forEach(m => {
|
||||
this.subscribe(
|
||||
this.materialService.hasSimdut(m.id),
|
||||
b => this.hasSimdutMap[m.id] = b
|
||||
)
|
||||
})
|
||||
},
|
||||
false,
|
||||
1
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import {Injectable} from '@angular/core'
|
||||
import {ApiService} from '../../shared/service/api.service'
|
||||
import {Observable} from 'rxjs'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class InventoryService {
|
||||
constructor(
|
||||
private api: ApiService
|
||||
) {
|
||||
}
|
||||
|
||||
add(material: number, quantity: number): Observable<MaterialQuantity[]> {
|
||||
return this.api.put<MaterialQuantity[]>('/material/inventory/add', [{material, quantity}])
|
||||
}
|
||||
|
||||
deduct(materialQuantities: [{material: number, quantity: number}]): Observable<MaterialQuantity[]> {
|
||||
return this.api.put<MaterialQuantity[]>('/material/inventory/deduct', materialQuantities)
|
||||
}
|
||||
}
|
||||
|
||||
export class MaterialQuantity {
|
||||
constructor(
|
||||
public material: number,
|
||||
public quantity: number
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -37,6 +37,10 @@ export class MaterialService {
|
|||
return this.api.get<boolean>(`/material/${id}/simdut/exists`)
|
||||
}
|
||||
|
||||
getSimduts(): Observable<number[]> {
|
||||
return this.api.get<number[]>('/material/simdut')
|
||||
}
|
||||
|
||||
save(name: string, inventoryQuantity: number, materialType: number, simdutFile: FileInput): Observable<void> {
|
||||
const body = new FormData()
|
||||
body.append('name', name)
|
||||
|
|
|
@ -15,8 +15,7 @@ export class EmployeeGroup {
|
|||
constructor(
|
||||
public id: number,
|
||||
public name: string,
|
||||
public permissions: EmployeePermission[],
|
||||
public employeeCount: number
|
||||
public permissions: EmployeePermission[]
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import {GlobalAlertHandlerComponent} from '../../modules/shared/components/globa
|
|||
export class CatalogComponent implements OnInit, OnDestroy {
|
||||
links: NavLink[] = [
|
||||
{route: '/catalog/materialtype', title: 'Types de produit', permission: EmployeePermission.VIEW_MATERIAL_TYPE},
|
||||
{route: '/catalog/material', title: 'Produits', permission: EmployeePermission.VIEW_MATERIAL},
|
||||
{route: '/catalog/material', title: 'Inventaire', permission: EmployeePermission.VIEW_MATERIAL},
|
||||
{route: '/catalog/company', title: 'Bannières', permission: EmployeePermission.VIEW_COMPANY}
|
||||
]
|
||||
|
||||
|
|
Loading…
Reference in New Issue