Merge pull request 'develop' (#8) from develop into master
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details

Reviewed-on: #8
This commit is contained in:
William Nolin 2022-04-20 23:13:53 -04:00
commit 3c630914b0
35 changed files with 246 additions and 268 deletions

View File

@ -1,12 +1,13 @@
--- ---
global-variables: global-variables:
release: &release ${DRONE_BRANCH##**/} release: &release ${DRONE_TAG}
environment: &environment environment: &environment
CRE_REGISTRY_IMAGE: registry.fyloz.dev:5443/colorrecipesexplorer/frontend CRE_REGISTRY_IMAGE: registry.fyloz.dev/colorrecipesexplorer/frontend
CRE_PORT: 9102 CRE_PORT: 9102
CRE_RELEASE: *release CRE_RELEASE: *release
alpine-image: &alpine-image alpine:latest alpine-image: &alpine-image alpine:latest
docker-registry-repo: &docker-registry-repo registry.fyloz.dev:5443/colorrecipesexplorer/frontend docker-registry: &docker-registry registry.fyloz.dev
docker-registry-repo: &docker-registry-repo registry.fyloz.dev/colorrecipesexplorer/frontend
kind: pipeline kind: pipeline
name: default name: default
@ -21,6 +22,9 @@ steps:
- echo -n "latest" > .tags - echo -n "latest" > .tags
when: when:
branch: develop branch: develop
event:
exclude:
- pull_request
- name: set-docker-tags-release - name: set-docker-tags-release
image: *alpine-image image: *alpine-image
@ -29,18 +33,40 @@ steps:
commands: commands:
- echo -n "latest-release,$CRE_RELEASE" > .tags - echo -n "latest-release,$CRE_RELEASE" > .tags
when: when:
branch: release/** event:
- tag
- name: containerize - name: containerize-dev
image: plugins/docker image: plugins/docker
environment: environment:
<<: *environment <<: *environment
settings: settings:
repo: *docker-registry-repo repo: *docker-registry-repo
registry: *docker-registry
username:
from_secret: docker_username
password:
from_secret: docker_password
when: when:
branch: branch: develop
- develop event:
- release/** exclude:
- pull_request
- name: containerize-release
image: plugins/docker
environment:
<<: *environment
settings:
repo: *docker-registry-repo
registry: *docker-registry
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
event:
- tag
- name: deploy - name: deploy
image: alpine:latest image: alpine:latest
@ -70,10 +96,5 @@ steps:
- ssh -p $DEPLOY_SERVER_SSH_PORT $DEPLOY_SERVER_USERNAME@$DEPLOY_SERVER "docker pull $CRE_REGISTRY_IMAGE:$CRE_RELEASE" - ssh -p $DEPLOY_SERVER_SSH_PORT $DEPLOY_SERVER_USERNAME@$DEPLOY_SERVER "docker pull $CRE_REGISTRY_IMAGE:$CRE_RELEASE"
- ssh -p $DEPLOY_SERVER_SSH_PORT $DEPLOY_SERVER_USERNAME@$DEPLOY_SERVER "docker run -d -p $CRE_PORT:80 --name=$DEPLOY_CONTAINER_NAME $CRE_REGISTRY_IMAGE:$CRE_RELEASE" - ssh -p $DEPLOY_SERVER_SSH_PORT $DEPLOY_SERVER_USERNAME@$DEPLOY_SERVER "docker run -d -p $CRE_PORT:80 --name=$DEPLOY_CONTAINER_NAME $CRE_REGISTRY_IMAGE:$CRE_RELEASE"
when: when:
branch: release/** event:
- tag
trigger:
branch:
- develop
- release/**
- master

View File

@ -1,14 +1,13 @@
import {NgModule} from '@angular/core' import {NgModule} from '@angular/core'
import {RouterModule, Routes} from '@angular/router' import {RouterModule, Routes} from '@angular/router'
import {LogoutComponent} from './pages/logout/logout.component' import {Login, Logout} from './accounts'
import {Login} from './accounts'
const routes: Routes = [{ const routes: Routes = [{
path: 'login', path: 'login',
component: Login component: Login
}, { }, {
path: 'logout', path: 'logout',
component: LogoutComponent component: Logout
}, { }, {
path: '', path: '',
redirectTo: 'login' redirectTo: 'login'

View File

@ -1,19 +1,16 @@
import {NgModule} from '@angular/core' import {NgModule} from '@angular/core'
import {AccountsRoutingModule} from './accounts-routing.module' import {AccountsRoutingModule} from './accounts-routing.module'
import {LoginComponent} from './pages/login/login.component'
import {SharedModule} from '../shared/shared.module' import {SharedModule} from '../shared/shared.module'
import {LogoutComponent} from './pages/logout/logout.component' import {Login, Logout} from './accounts'
import {Login} from './accounts'
import {CreInputsModule} from '../shared/components/inputs/inputs.module' import {CreInputsModule} from '../shared/components/inputs/inputs.module'
import {CreButtonsModule} from '../shared/components/buttons/buttons.module' import {CreButtonsModule} from '../shared/components/buttons/buttons.module'
@NgModule({ @NgModule({
declarations: [ declarations: [
LoginComponent, Login,
LogoutComponent, Logout
Login
], ],
imports: [ imports: [
SharedModule, SharedModule,

View File

@ -1,12 +1,11 @@
import {Component, HostListener, ViewChild} from '@angular/core' import {Component, HostListener, ViewChild} from '@angular/core'
import {FormControl, Validators} from '@angular/forms' import {FormControl, Validators} from '@angular/forms'
import {ErrorHandlingComponent} from '../shared/components/subscribing.component' import {ErrorHandlingComponent, SubscribingComponent} from '../shared/components/subscribing.component'
import {AccountService} from './services/account.service' import {AccountService} from './services/account.service'
import {AppState} from '../shared/app-state' import {AppState} from '../shared/app-state'
import {ErrorHandler, ErrorService} from '../shared/service/error.service' import {ErrorHandler, ErrorService} from '../shared/service/error.service'
import {ActivatedRoute, Router} from '@angular/router' import {ActivatedRoute, Router} from '@angular/router'
import {CreForm, ICreForm} from "../shared/components/forms/forms"; import {CreForm, ICreForm} from "../shared/components/forms/forms";
import {take, takeUntil} from "rxjs/operators";
import {AlertService} from "../shared/service/alert.service"; import {AlertService} from "../shared/service/alert.service";
@Component({ @Component({
@ -61,3 +60,32 @@ export class Login extends ErrorHandlingComponent {
} }
} }
} }
@Component({
selector: 'cre-logout',
template: ''
})
export class Logout extends SubscribingComponent {
constructor(
private accountService: AccountService,
private alertService: AlertService,
private appState: AppState,
errorService: ErrorService,
router: Router,
activatedRoute: ActivatedRoute
) {
super(errorService, activatedRoute, router)
this.appState.title = 'Connexion'
}
ngOnInit(): void {
if (!this.appState.isAuthenticated) {
this.urlUtils.navigateTo('/account/login')
}
this.subscribeAndNavigate(
this.accountService.logout(),
'/account/login'
)
}
}

View File

@ -1,36 +0,0 @@
<form [formGroup]="form">
<mat-card class="x-centered y-centered">
<mat-card-header>
<mat-card-title>Connexion au système</mat-card-title>
</mat-card-header>
<mat-card-content>
<mat-form-field>
<mat-label>Numéro d'utilisateur</mat-label>
<input matInput [formControl]="idFormControl" type="text"/>
<mat-icon matSuffix>person</mat-icon>
<mat-error *ngIf="idFormControl.invalid">
<span *ngIf="idFormControl.errors.required">Un numéro d'utilisateur est requis</span>
<span *ngIf="idFormControl.errors.pattern">Le numéro d'utilisateur doit être un nombre</span>
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-label>Mot de passe</mat-label>
<input matInput [formControl]="passwordFormControl" type="password"/>
<mat-icon matSuffix>lock</mat-icon>
<mat-error *ngIf="passwordFormControl.invalid">
<span *ngIf="passwordFormControl.errors.required">Un mot de passe est requis</span>
</mat-error>
</mat-form-field>
</mat-card-content>
<mat-card-actions class="justify-content-end">
<button
mat-raised-button
type="submit"
color="accent"
[disabled]="form.invalid"
(click)="submit()">
Connexion
</button>
</mat-card-actions>
</mat-card>
</form>

View File

@ -1,8 +0,0 @@
mat-card
width: 25rem
.alert p
margin: 0
mat-form-field
width: 100%

View File

@ -1,55 +0,0 @@
import {Component, OnInit} from '@angular/core'
import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'
import {AccountService} from '../../services/account.service'
import {ActivatedRoute, Router} from '@angular/router'
import {ErrorService} from '../../../shared/service/error.service'
import {ErrorHandlingComponent} from '../../../shared/components/subscribing.component'
import {AppState} from '../../../shared/app-state'
@Component({
selector: 'cre-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.sass']
})
export class LoginComponent extends ErrorHandlingComponent implements OnInit {
form: FormGroup
idFormControl: FormControl
passwordFormControl: FormControl
constructor(
private formBuilder: FormBuilder,
private accountService: AccountService,
private appState: AppState,
errorService: ErrorService,
router: Router,
activatedRoute: ActivatedRoute
) {
super(errorService, activatedRoute, router)
this.appState.title = 'Connexion'
}
ngOnInit(): void {
this.errorService.activeErrorHandler = this
if (this.appState.isAuthenticated) {
this.router.navigate(['/color'])
}
this.idFormControl = this.formBuilder.control(null, Validators.compose([Validators.required, Validators.pattern(new RegExp('^[0-9]+$'))]))
this.passwordFormControl = this.formBuilder.control(null, Validators.required)
this.form = this.formBuilder.group({
id: this.idFormControl,
password: this.passwordFormControl
})
}
submit() {
this.subscribe(
this.accountService.login(
this.idFormControl.value,
this.passwordFormControl.value
),
response => console.log(response)
)
}
}

View File

@ -1,35 +0,0 @@
import {Component} from '@angular/core';
import {AccountService} from "../../services/account.service";
import {ActivatedRoute, Router} from "@angular/router";
import {AppState} from "../../../shared/app-state";
import {SubscribingComponent} from "../../../shared/components/subscribing.component";
import {ErrorService} from "../../../shared/service/error.service";
@Component({
selector: 'cre-logout',
templateUrl: './logout.component.html',
styleUrls: ['./logout.component.sass']
})
export class LogoutComponent extends SubscribingComponent {
constructor(
private accountService: AccountService,
private appState: AppState,
errorService: ErrorService,
router: Router,
activatedRoute: ActivatedRoute
) {
super(errorService, activatedRoute, router)
}
ngOnInit(): void {
if (!this.appState.isAuthenticated) {
this.urlUtils.navigateTo('/account/login')
}
this.subscribeAndNavigate(
this.accountService.logout(),
'/account/login'
)
}
}

View File

@ -16,8 +16,8 @@
<button <button
mat-raised-button mat-raised-button
color="primary" color="primary"
[disabled]="!hasSimdut" [disabled]="!material.hasSimdut"
[attr.title]="!hasSimdut ? 'Ce produit n\'a pas de fiche signalitique' : null" [attr.title]="!material.hasSimdut ? 'Ce produit n\'a pas de fiche signalitique' : null"
(click)="openSimdut()"> (click)="openSimdut()">
Voir la fiche signalitique Voir la fiche signalitique
</button> </button>

View File

@ -119,10 +119,6 @@ export class EditComponent extends ErrorHandlingComponent {
) )
} }
get hasSimdut(): boolean {
return this.material.simdutUrl != null
}
openSimdut() { openSimdut() {
openSimdut(this.material) openSimdut(this.material)
} }

View File

@ -98,7 +98,7 @@
<ng-container matColumnDef="simdutIcon"> <ng-container matColumnDef="simdutIcon">
<th mat-header-cell *matHeaderCellDef></th> <th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let material" [class.disabled]="materialHasSimdut(material)"> <td mat-cell *matCellDef="let material" [class.disabled]="material.hasSimdut">
<mat-icon <mat-icon
svgIcon="text-box-remove" svgIcon="text-box-remove"
color="warn" color="warn"
@ -123,7 +123,7 @@
<td mat-cell *matCellDef="let material; let i = index" [class.disabled]="canEditMaterial"> <td mat-cell *matCellDef="let material; let i = index" [class.disabled]="canEditMaterial">
<cre-accent-button <cre-accent-button
[creInteractiveCell]="i" [creInteractiveCell]="i"
[disabled]="!materialHasSimdut(material)" [disabled]="!material.hasSimdut"
(click)="openSimdut(material)"> (click)="openSimdut(material)">
Fiche signalitique Fiche signalitique
</cre-accent-button> </cre-accent-button>

View File

@ -64,7 +64,7 @@ export class InventoryComponent extends ErrorHandlingComponent {
super.ngOnInit() super.ngOnInit()
this.subscribe( this.subscribe(
this.materialService.allNotMixType, this.materialService.all,
materials => this.materials = materials, materials => this.materials = materials,
true, true,
1 1
@ -94,10 +94,6 @@ export class InventoryComponent extends ErrorHandlingComponent {
return round(convertQuantity(material.inventoryQuantity, UNIT_MILLILITER, this.units), 2) return round(convertQuantity(material.inventoryQuantity, UNIT_MILLILITER, this.units), 2)
} }
materialHasSimdut(material: Material): boolean {
return material.simdutUrl != null
}
openSimdut(material: Material) { openSimdut(material: Material) {
openSimdut(material) openSimdut(material)
} }

View File

@ -17,10 +17,6 @@ export class MaterialService {
return this.api.get<Material[]>('/material') return this.api.get<Material[]>('/material')
} }
get allNotMixType(): Observable<Material[]> {
return this.api.get<Material[]>('/material/notmixtype')
}
getAllForMixCreation(recipeId: number): Observable<Material[]> { getAllForMixCreation(recipeId: number): Observable<Material[]> {
return this.api.get<Material[]>(`/material/mix/create/${recipeId}`) return this.api.get<Material[]>(`/material/mix/create/${recipeId}`)
} }
@ -33,14 +29,6 @@ export class MaterialService {
return this.api.get<Material>(`/material/${id}`) return this.api.get<Material>(`/material/${id}`)
} }
hasSimdut(id: number): Observable<boolean> {
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> { save(name: string, inventoryQuantity: number, materialType: number, simdutFile: FileInput): Observable<void> {
const body = new FormData() const body = new FormData()
body.append('name', name) body.append('name', name)

View File

@ -1,17 +1,17 @@
<mat-card *ngIf="editionMode || imagesUrls"> <mat-card *ngIf="editionMode || imagesIds">
<mat-card-header> <mat-card-header>
<mat-card-title>Images</mat-card-title> <mat-card-title>Images</mat-card-title>
</mat-card-header> </mat-card-header>
<mat-card-content [class.no-action]="!editionMode"> <mat-card-content [class.no-action]="!editionMode">
<div class="d-flex flex-row justify-content-around flex-wrap"> <div class="d-flex flex-row justify-content-around flex-wrap">
<p *ngIf="imagesUrls.length <= 0" class="light-text text-center mb-0">Aucune image n'est associée à cette couleur</p> <p *ngIf="imagesIds.length <= 0" class="light-text text-center mb-0">Aucune image n'est associée à cette couleur</p>
<div *ngFor="let imageUrl of imagesUrls" class="d-flex flex-column align-self-center m-3"> <div *ngFor="let imageId of imagesIds" class="d-flex flex-column align-self-center m-3">
<div class="image-wrapper"> <div class="image-wrapper">
<img [src]="imageUrl" width="300px"/> <img [src]="getImageUrl(imageId)" width="300px"/>
<div class="d-flex flex-row justify-content-end mt-2" [class.justify-content-between]="editionMode"> <div class="d-flex flex-row justify-content-end mt-2" [class.justify-content-between]="editionMode">
<button mat-raised-button color="primary" (click)="openImage(imageUrl)">Afficher</button> <button mat-raised-button color="primary" (click)="openImage(imageId)">Afficher</button>
<button *ngIf="editionMode" mat-raised-button color="warn" (click)="delete(imageUrl)">Retirer</button> <button *ngIf="editionMode" mat-raised-button color="warn" (click)="delete(imageId)">Retirer</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,7 +4,8 @@ import {SubscribingComponent} from '../../../shared/components/subscribing.compo
import {ActivatedRoute, Router} from '@angular/router' import {ActivatedRoute, Router} from '@angular/router'
import {RecipeImageService} from '../../services/recipe-image.service' import {RecipeImageService} from '../../services/recipe-image.service'
import {ErrorService} from '../../../shared/service/error.service' import {ErrorService} from '../../../shared/service/error.service'
import {openJpg} from '../../../shared/utils/utils' import {getImageUrl, openJpg} from '../../../shared/utils/utils'
import {RecipeService} from "../../services/recipe.service";
@Component({ @Component({
selector: 'cre-images-editor', selector: 'cre-images-editor',
@ -15,10 +16,11 @@ export class ImagesEditorComponent extends SubscribingComponent {
@Input() recipe: Recipe @Input() recipe: Recipe
@Input() editionMode = false @Input() editionMode = false
imagesUrls: string[] imagesIds: string[] = []
constructor( constructor(
private recipeImageService: RecipeImageService, private recipeImageService: RecipeImageService,
private recipeService: RecipeService,
errorService: ErrorService, errorService: ErrorService,
router: Router, router: Router,
activatedRoute: ActivatedRoute activatedRoute: ActivatedRoute
@ -29,19 +31,26 @@ export class ImagesEditorComponent extends SubscribingComponent {
ngOnInit() { ngOnInit() {
super.ngOnInit() super.ngOnInit()
this.imagesUrls = this.recipe.imagesUrls this.subscribe(
this.recipeService.getImagesIds(this.recipe.id),
imagesIds => this.imagesIds = imagesIds ?? []
)
} }
submit(event) { submit(event) {
const image = event.target.files[0] const image = event.target.files[0]
this.subscribe( this.subscribe(
this.recipeImageService.save(image, this.recipe.id), this.recipeImageService.save(image, this.recipe.id),
r => this.imagesUrls = r.imagesUrls imageId => this.imagesIds.push(imageId)
) )
} }
openImage(url: string) { getImageUrl(id: string): string {
openJpg(url) return getImageUrl(this.getImagePath(id))
}
openImage(id: string) {
openJpg(this.getImagePath(id))
} }
delete(url: string) { delete(url: string) {
@ -52,6 +61,10 @@ export class ImagesEditorComponent extends SubscribingComponent {
} }
private removeUrl(url: string) { private removeUrl(url: string) {
this.imagesUrls = this.imagesUrls.filter(u => u !== url) this.imagesIds = this.imagesIds.filter(u => u !== url)
}
private getImagePath(id: string): string {
return `recipes/${this.recipe.id}/${id}`
} }
} }

View File

@ -126,7 +126,7 @@
<button <button
mat-raised-button mat-raised-button
color="accent" color="accent"
[disabled]="!hasSimdut(getMixMaterialFromDto(mixMaterial).material)" [disabled]="!getMixMaterialFromDto(mixMaterial).material.hasSimdut"
(click)="openSimdut(getMixMaterialFromDto(mixMaterial))"> (click)="openSimdut(getMixMaterialFromDto(mixMaterial))">
Fiche signalitique Fiche signalitique
</button> </button>

View File

@ -1,5 +1,11 @@
import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core' import {Component, EventEmitter, Input, Output, ViewChild} from '@angular/core'
import {Mix, MixMaterial, MixMaterialDto, mixMaterialsToMixMaterialsDto, Recipe} from '../../../shared/model/recipe.model' import {
Mix,
MixQuantity,
MixMaterialDto,
mixMaterialsToMixMaterialsDto,
Recipe
} from '../../../shared/model/recipe.model'
import {Subject} from 'rxjs' import {Subject} from 'rxjs'
import {SubscribingComponent} from '../../../shared/components/subscribing.component' import {SubscribingComponent} from '../../../shared/components/subscribing.component'
import {convertMixMaterialQuantity, UNIT_MILLILITER} from '../../../shared/units' import {convertMixMaterialQuantity, UNIT_MILLILITER} from '../../../shared/units'
@ -37,7 +43,7 @@ export class MixTableComponent extends SubscribingComponent {
mixColumns = this.COLUMNS mixColumns = this.COLUMNS
units = UNIT_MILLILITER units = UNIT_MILLILITER
mixMaterials: MixMaterialDto[] = [] mixMaterials: MixMaterialDto[] = []
hoveredMixMaterial: MixMaterial | null hoveredMixMaterial: MixQuantity | null
// BPac printer // BPac printer
printer: PtouchPrinter | null printer: PtouchPrinter | null
@ -68,11 +74,7 @@ export class MixTableComponent extends SubscribingComponent {
) )
} }
hasSimdut(material: Material): boolean { openSimdut(mixMaterial: MixQuantity) {
return material.simdutUrl != null
}
openSimdut(mixMaterial: MixMaterial) {
openSimdut(mixMaterial.material) openSimdut(mixMaterial.material)
} }
@ -102,8 +104,8 @@ export class MixTableComponent extends SubscribingComponent {
}) })
} }
getMixMaterialFromDto(mixMaterialDto: MixMaterialDto): MixMaterial { getMixMaterialFromDto(mixMaterialDto: MixMaterialDto): MixQuantity {
return this.mix.mixMaterials.find(m => m.material.id === mixMaterialDto.materialId) return this.mix.mixQuantities.find(m => m.material.id === mixMaterialDto.materialId)
} }
getMixMaterialQuantityRounded(mixMaterial: MixMaterialDto): number { getMixMaterialQuantityRounded(mixMaterial: MixMaterialDto): number {
@ -121,7 +123,7 @@ export class MixTableComponent extends SubscribingComponent {
return totalQuantity return totalQuantity
} }
getCalculatedQuantityHtml(mixMaterial: MixMaterial, index: number): string { getCalculatedQuantityHtml(mixMaterial: MixQuantity, index: number): string {
const totalQuantity = this.round(this.getTotalQuantity(index)) const totalQuantity = this.round(this.getTotalQuantity(index))
const addedQuantity = this.round(this.calculateQuantity(index)) const addedQuantity = this.round(this.calculateQuantity(index))
return `<span class="mix-calculated-quantity">+${addedQuantity}</span> <span class="mix-calculated-quantity">(${totalQuantity})</span>` return `<span class="mix-calculated-quantity">+${addedQuantity}</span> <span class="mix-calculated-quantity">(${totalQuantity})</span>`
@ -137,7 +139,7 @@ export class MixTableComponent extends SubscribingComponent {
} }
async print() { async print() {
const base = this.mix.mixMaterials const base = this.mix.mixQuantities
.map(ma => ma.material) .map(ma => ma.material)
.filter(m => m.materialType.name === 'Base')[0] .filter(m => m.materialType.name === 'Base')[0]
if (!base) { if (!base) {
@ -192,7 +194,8 @@ export class MixTableComponent extends SubscribingComponent {
quantity: this.calculateQuantity(index), quantity: this.calculateQuantity(index),
isPercents: quantity.isPercents, isPercents: quantity.isPercents,
position: quantity.position, position: quantity.position,
units: UNIT_MILLILITER units: UNIT_MILLILITER,
isMixType: false // TODO
}) })
} }

View File

@ -39,7 +39,7 @@
</div> </div>
<!-- Images --> <!-- Images -->
<div *ngIf="recipe.imagesUrls"> <div>
<cre-images-editor [recipe]="recipe" [editionMode]="false"></cre-images-editor> <cre-images-editor [recipe]="recipe" [editionMode]="false"></cre-images-editor>
</div> </div>
</div> </div>

View File

@ -143,7 +143,7 @@ export class CreRecipeExplore extends ErrorHandlingComponent {
} }
deductMix() { deductMix() {
const firstMixMaterial = this.recipe.mixes.filter(m => m.id === this.deductedMixId)[0].mixMaterials[0] const firstMixMaterial = this.recipe.mixes.filter(m => m.id === this.deductedMixId)[0].mixQuantities[0]
if (this.quantitiesChanges.has(this.deductedMixId) && this.quantitiesChanges.get(this.deductedMixId).has(firstMixMaterial.material.id)) { if (this.quantitiesChanges.has(this.deductedMixId) && this.quantitiesChanges.get(this.deductedMixId).has(firstMixMaterial.material.id)) {
const originalQuantity = firstMixMaterial.quantity const originalQuantity = firstMixMaterial.quantity
const currentQuantity = this.quantitiesChanges.get(this.deductedMixId).get(firstMixMaterial.material.id) const currentQuantity = this.quantitiesChanges.get(this.deductedMixId).get(firstMixMaterial.material.id)

View File

@ -4,11 +4,8 @@
<cre-primary-button routerLink="/color/edit/{{recipe.id}}">Retour</cre-primary-button> <cre-primary-button routerLink="/color/edit/{{recipe.id}}">Retour</cre-primary-button>
</cre-action-group> </cre-action-group>
<cre-action-group> <cre-action-group>
<cre-accent-button <cre-warn-button (click)="deleteConfirmBox.show()">Supprimer</cre-warn-button>
[disabled]="!form.valid" <cre-accent-button [disabled]="!form.valid" (click)="submit(form.formValues)">Enregistrer</cre-accent-button>
(click)="submit(form.formValues)">
Enregistrer
</cre-accent-button>
</cre-action-group> </cre-action-group>
</cre-action-bar> </cre-action-bar>
@ -22,4 +19,10 @@
Modification du mélange {{mix.mixType.name}} de la recette {{recipe.company.name}} - {{recipe.name}} Modification du mélange {{mix.mixType.name}} de la recette {{recipe.company.name}} - {{recipe.name}}
</cre-form-title> </cre-form-title>
</cre-mix-form> </cre-mix-form>
<cre-confirm-box
#deleteConfirmBox
message="Voulez-vous vraiment supprimer le mélange {{mix.mixType.name}} de la recette {{recipe.company.name}} - {{recipe.name}}?"
(confirm)="delete()">
</cre-confirm-box>
</ng-container> </ng-container>

View File

@ -33,7 +33,7 @@
[mix]="mix" [mix]="mix"
[mixMaterials]="mixMaterials" [mixMaterials]="mixMaterials"
[control]="getControls(mixMaterial.position).materialId" [control]="getControls(mixMaterial.position).materialId"
[materials]="allMaterials" [materials]="allMaterialsValues"
[position]="mixMaterial.position"> [position]="mixMaterial.position">
</cre-mix-materials-form-combo-box> </cre-mix-materials-form-combo-box>
</td> </td>

View File

@ -1,4 +1,13 @@
import {AfterViewInit, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild, ViewChildren} from '@angular/core' import {
AfterViewInit,
ChangeDetectorRef,
Component,
Input,
OnDestroy,
OnInit,
ViewChild,
ViewChildren
} from '@angular/core'
import {CreTable} from '../../shared/components/tables/tables' import {CreTable} from '../../shared/components/tables/tables'
import {Mix, MixMaterialDto, mixMaterialsToMixMaterialsDto, sortMixMaterialsDto} from '../../shared/model/recipe.model' import {Mix, MixMaterialDto, mixMaterialsToMixMaterialsDto, sortMixMaterialsDto} from '../../shared/model/recipe.model'
import {Observable, Subject} from 'rxjs' import {Observable, Subject} from 'rxjs'
@ -38,10 +47,6 @@ export class MixMaterialsFormComboBox implements OnInit {
private filterMaterials(): CreInputEntry[] { private filterMaterials(): CreInputEntry[] {
return this.materials return this.materials
.filter(material => { .filter(material => {
if (this.mix && this.mix.mixType.material.id === material.id) {
return false
}
// Prevent use of percents in first position // Prevent use of percents in first position
if (material.materialType.usePercentages && this.mixMaterial.position <= 1) { if (material.materialType.usePercentages && this.mixMaterial.position <= 1) {
return false return false
@ -54,7 +59,18 @@ export class MixMaterialsFormComboBox implements OnInit {
return this.mixMaterials.filter(x => x.materialId === material.id).length <= 0 return this.mixMaterials.filter(x => x.materialId === material.id).length <= 0
}) })
.sort(materialComparator) .sort(materialComparator)
.map(material => new CreInputEntry(material.id, material.name, material.materialType.prefix ? `[${material.materialType.prefix}] ${material.name}` : material.name)) .map(this.materialAsInputEntry)
}
private materialAsInputEntry(material: Material): CreInputEntry {
return new CreInputEntry(
material.id,
material.name,
material.materialType.prefix ? `[${material.materialType.prefix}] ${material.name}` : material.name,
{
bold: material.isMixType
}
)
} }
} }
@ -71,7 +87,8 @@ export class MixMaterialsForm implements AfterViewInit, OnDestroy {
mixMaterials: MixMaterialDto[] = [] mixMaterials: MixMaterialDto[] = []
columns = ['position', 'positionButtons', 'material', 'quantity', 'units', 'endButton'] columns = ['position', 'positionButtons', 'material', 'quantity', 'units', 'endButton']
allMaterials: Material[] allMaterials = new Map<number, Material>();
allMaterialsValues = [];
private _controls: ControlsByPosition[] = [] private _controls: ControlsByPosition[] = []
private _destroy$ = new Subject<boolean>() private _destroy$ = new Subject<boolean>()
@ -85,7 +102,9 @@ export class MixMaterialsForm implements AfterViewInit, OnDestroy {
ngAfterViewInit() { ngAfterViewInit() {
this.materials.subscribe({ this.materials.subscribe({
next: materials => { next: materials => {
this.allMaterials = materials this.allMaterials.clear()
this.allMaterialsValues = materials
materials.forEach(m => this.allMaterials.set(m.id, m))
if (!this.mix) { if (!this.mix) {
this.addRow() this.addRow()
@ -105,7 +124,7 @@ export class MixMaterialsForm implements AfterViewInit, OnDestroy {
} }
addRow() { addRow() {
const mixMaterial = new MixMaterialDto(null, 0, false, this.nextPosition, UNIT_MILLILITER) const mixMaterial = new MixMaterialDto(null, 0, false, this.nextPosition, UNIT_MILLILITER, false)
this.insertRow(mixMaterial) this.insertRow(mixMaterial)
this.table.renderRows() this.table.renderRows()
} }
@ -170,7 +189,7 @@ export class MixMaterialsForm implements AfterViewInit, OnDestroy {
return false return false
} }
return mixMaterial.materialId ? this.allMaterials?.filter(x => x.id === mixMaterial.materialId)[0].materialType.usePercentages : false return mixMaterial.materialId ? this.allMaterials.get(mixMaterial.materialId)?.materialType.usePercentages : false
} }
isDecreasePositionButtonDisabled(mixMaterial: MixMaterialDto): boolean { isDecreasePositionButtonDisabled(mixMaterial: MixMaterialDto): boolean {
@ -195,19 +214,23 @@ export class MixMaterialsForm implements AfterViewInit, OnDestroy {
} }
get materialCount(): number { get materialCount(): number {
return this.allMaterials ? this.allMaterials.length : 0 return this.allMaterials.size
} }
get updatedMixMaterials(): MixMaterialDto[] { get updatedMixMaterials(): MixMaterialDto[] {
const updatedMixMaterials: MixMaterialDto[] = [] const updatedMixMaterials: MixMaterialDto[] = []
this.mixMaterials.forEach(mixMaterial => { this.mixMaterials.forEach(mixMaterial => {
const controls = this.getControlsByPosition(mixMaterial.position).controls const controls = this.getControlsByPosition(mixMaterial.position).controls
const materialId = controls.materialId.value;
const material = this.allMaterials.get(materialId)
updatedMixMaterials.push({ updatedMixMaterials.push({
materialId: controls.materialId.value, ...mixMaterial,
materialId,
quantity: controls.quantity.value, quantity: controls.quantity.value,
position: mixMaterial.position,
units: controls.units.value, units: controls.units.value,
isPercents: this.areUnitsPercents(mixMaterial) isPercents: material.materialType.usePercentages,
isMixType: material.isMixType
}) })
}) })
return updatedMixMaterials return updatedMixMaterials

View File

@ -27,7 +27,7 @@ abstract class _BaseMixPage extends SubscribingComponent {
protected mixService: MixService, protected mixService: MixService,
private recipeService: RecipeService, private recipeService: RecipeService,
private materialTypeService: MaterialTypeService, private materialTypeService: MaterialTypeService,
private materialService: MaterialService, protected materialService: MaterialService,
errorService: ErrorService, errorService: ErrorService,
router: Router, router: Router,
activatedRoute: ActivatedRoute activatedRoute: ActivatedRoute
@ -50,13 +50,15 @@ abstract class _BaseMixPage extends SubscribingComponent {
set recipe(recipe: Recipe) { set recipe(recipe: Recipe) {
this._recipe = recipe this._recipe = recipe
this.materials$ = this.materialService.getAllForMixCreation(recipe.id) this.materials$ = this.fetchMaterials(recipe.id)
} }
get recipe(): Recipe { get recipe(): Recipe {
return this._recipe return this._recipe
} }
protected abstract fetchMaterials(recipeId: number): Observable<Material[]>
abstract submit(dto: MixSaveDto) abstract submit(dto: MixSaveDto)
} }
@ -65,6 +67,10 @@ abstract class _BaseMixPage extends SubscribingComponent {
templateUrl: 'add.html' templateUrl: 'add.html'
}) })
export class MixAdd extends _BaseMixPage { export class MixAdd extends _BaseMixPage {
protected fetchMaterials(recipeId: number): Observable<Material[]> {
return this.materialService.getAllForMixCreation(recipeId)
}
submit(dto: MixSaveDto) { submit(dto: MixSaveDto) {
this.subscribeAndNavigate( this.subscribeAndNavigate(
this.mixService.saveDto(dto), this.mixService.saveDto(dto),
@ -80,27 +86,40 @@ export class MixAdd extends _BaseMixPage {
export class MixEdit extends _BaseMixPage { export class MixEdit extends _BaseMixPage {
mix: Mix mix: Mix
private mixId: number
ngOnInit() { ngOnInit() {
super.ngOnInit() super.ngOnInit()
this.mixId = this.urlUtils.parseIntUrlParam('id')
this.fetchMix() this.fetchMix()
} }
private fetchMix() { private fetchMix() {
const mixId = this.urlUtils.parseIntUrlParam('id')
this.subscribe( this.subscribe(
this.mixService.getById(mixId), this.mixService.getById(this.mixId),
mix => this.mix = mix mix => this.mix = mix
) )
} }
protected fetchMaterials(recipeId: number): Observable<Material[]> {
return this.materialService.getAllForMixUpdate(this.mixId)
}
submit(dto: MixSaveDto) { submit(dto: MixSaveDto) {
this.subscribeAndNavigate( this.subscribeAndNavigate(
this.mixService.updateDto({...dto, id: this.mix.id}), this.mixService.updateDto({...dto, id: this.mix.id}),
`/color/edit/${this.recipe.id}` `/color/edit/${this.recipe.id}`
) )
} }
delete() {
this.subscribeAndNavigate(
this.mixService.delete(this.mixId),
'/color/edit/' + this.recipe.id
)
}
} }
@Component({ @Component({
@ -126,7 +145,7 @@ export class MixInfoForm implements OnInit {
this.controls = { this.controls = {
name: new FormControl(this.mix?.mixType.name, Validators.required), name: new FormControl(this.mix?.mixType.name, Validators.required),
materialType: new FormControl(this.mix?.mixType.material.materialType.id, Validators.required) materialType: new FormControl(this.mix?.mixType.materialType.id, Validators.required)
} }
} }
@ -161,7 +180,7 @@ export class MixForm {
name: this.infoForm.mixName, name: this.infoForm.mixName,
recipeId: this.recipe.id, recipeId: this.recipe.id,
materialTypeId: this.infoForm.mixMaterialTypeId, materialTypeId: this.infoForm.mixMaterialTypeId,
mixMaterials: this.mixMaterialsForm.updatedMixMaterials mixQuantities: this.mixMaterialsForm.updatedMixMaterials
} }
} }

View File

@ -26,7 +26,7 @@ export class MixService {
dto.name, dto.name,
dto.recipeId, dto.recipeId,
dto.materialTypeId, dto.materialTypeId,
dto.mixMaterials, dto.mixQuantities,
) )
} }
@ -39,7 +39,7 @@ export class MixService {
name, name,
recipeId, recipeId,
materialTypeId, materialTypeId,
mixMaterials: [] mixQuantities: []
} }
this.appendMixMaterialsToBody(mixMaterials, body) this.appendMixMaterialsToBody(mixMaterials, body)
return this.api.post('/recipe/mix', body) return this.api.post('/recipe/mix', body)
@ -50,7 +50,7 @@ export class MixService {
dto.id, dto.id,
dto.name, dto.name,
dto.materialTypeId, dto.materialTypeId,
dto.mixMaterials dto.mixQuantities
) )
} }
@ -63,7 +63,7 @@ export class MixService {
id, id,
name, name,
materialTypeId, materialTypeId,
mixMaterials: [] mixQuantities: []
} }
this.appendMixMaterialsToBody(mixMaterials, body) this.appendMixMaterialsToBody(mixMaterials, body)
@ -84,10 +84,11 @@ export class MixService {
private appendMixMaterialsToBody(mixMaterials: MixMaterialDto[], body: any) { private appendMixMaterialsToBody(mixMaterials: MixMaterialDto[], body: any) {
mixMaterials.filter(m => m.materialId != null && m.quantity != null).forEach(m => { mixMaterials.filter(m => m.materialId != null && m.quantity != null).forEach(m => {
body.mixMaterials.push({ body.mixQuantities.push({
materialId: m.materialId, materialId: m.materialId,
quantity: m.quantity, quantity: m.quantity,
position: m.position position: m.position,
isMixType: m.isMixType
}) })
}) })
} }
@ -97,13 +98,13 @@ export interface MixSaveDto {
name: string name: string
recipeId: number recipeId: number
materialTypeId: number materialTypeId: number
mixMaterials: MixMaterialDto[] mixQuantities: MixMaterialDto[]
} }
export interface MixUpdateDto { export interface MixUpdateDto {
id: number id: number
name: string name: string
materialTypeId: number materialTypeId: number
mixMaterials: MixMaterialDto[] mixQuantities: MixMaterialDto[]
} }

View File

@ -12,10 +12,10 @@ export class RecipeImageService {
) { ) {
} }
save(image: File, recipeId: number): Observable<Recipe> { save(image: File, recipeId: number): Observable<string> {
const body = new FormData() const body = new FormData()
body.append('image', image) body.append('image', image)
return this.api.put<Recipe>(`/recipe/${recipeId}/image`, body) return this.api.put<string>(`/recipe/${recipeId}/image`, body)
} }
delete(url: string, recipeId: number): Observable<void> { delete(url: string, recipeId: number): Observable<void> {

View File

@ -84,4 +84,8 @@ export class RecipeService {
delete(id: number): Observable<void> { delete(id: number): Observable<void> {
return this.api.delete<void>(`/recipe/${id}`) return this.api.delete<void>(`/recipe/${id}`)
} }
getImagesIds(id: number): Observable<string[]> {
return this.api.get<string[]>(`/recipe/${id}/image`)
}
} }

View File

@ -16,7 +16,11 @@
</mat-error> </mat-error>
<mat-autocomplete #auto="matAutocomplete"> <mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let entry of filteredEntries" [value]="entry.value"> <mat-option
*ngFor="let entry of filteredEntries"
[value]="entry.value"
[class.font-weight-bold]="entry.styleOptions?.bold"
[title]="entry.value">
{{entry.display ? entry.display : entry.value}} {{entry.display ? entry.display : entry.value}}
</mat-option> </mat-option>
</mat-autocomplete> </mat-autocomplete>

View File

@ -167,7 +167,6 @@ export class CreComboBoxComponent {
internalControl: FormControl internalControl: FormControl
filteredEntries: CreInputEntry[] filteredEntries: CreInputEntry[]
validValue = false
private _destroy$ = new Subject<boolean>() private _destroy$ = new Subject<boolean>()
private _entries: CreInputEntry[] private _entries: CreInputEntry[]
@ -466,11 +465,16 @@ export class CreInputEntry {
constructor( constructor(
public key: any, public key: any,
public value: any, public value: any,
public display?: any public display?: any,
public styleOptions?: CreInputEntryStyleOptions
) { ) {
} }
} }
export interface CreInputEntryStyleOptions {
bold?: boolean
}
export function chipListRequired(): ValidatorFn { export function chipListRequired(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => { return (control: AbstractControl): ValidationErrors | null => {
return !control.value || control.value.length <= 0 ? {required: true} : null return !control.value || control.value.length <= 0 ? {required: true} : null

View File

@ -7,13 +7,14 @@ export class Material {
public name: string, public name: string,
public inventoryQuantity: number, public inventoryQuantity: number,
public materialType: MaterialType, public materialType: MaterialType,
public simdutUrl: string public isMixType: boolean,
public hasSimdut: boolean
) { ) {
} }
} }
export function openSimdut(material: Material) { export function openSimdut(material: Material) {
openPdf(material.simdutUrl) openPdf(`simdut/${material.name}`)
} }
export const materialComparator = (a: Material, b: Material): number => { export const materialComparator = (a: Material, b: Material): number => {

View File

@ -2,6 +2,7 @@ import {Material} from './material.model'
import {Company} from './company.model' import {Company} from './company.model'
import {Group} from './user' import {Group} from './user'
import {UNIT_MILLILITER} from "../units"; import {UNIT_MILLILITER} from "../units";
import {MaterialType} from "./materialtype.model";
export class Recipe { export class Recipe {
public id: number public id: number
@ -16,7 +17,6 @@ export class Recipe {
public mixes: Mix[] public mixes: Mix[]
public approbationExpired: boolean public approbationExpired: boolean
public groupsInformation: RecipeGroupInformation[] public groupsInformation: RecipeGroupInformation[]
public imagesUrls: string[]
} }
export class RecipeGroupInformation { export class RecipeGroupInformation {
@ -33,13 +33,13 @@ export class Mix {
constructor( constructor(
public id: number, public id: number,
public mixType: MixType, public mixType: MixType,
public mixMaterials: MixMaterial[], public mixQuantities: MixQuantity[],
public location: string, public location: string,
) { ) {
} }
} }
export class MixMaterial { export class MixQuantity {
constructor( constructor(
public id: number, public id: number,
public material: Material, public material: Material,
@ -55,7 +55,8 @@ export class MixMaterialDto {
public quantity: number, public quantity: number,
public isPercents: boolean, public isPercents: boolean,
public position: number, public position: number,
public units: string public units: string,
public isMixType: boolean
) { ) {
} }
} }
@ -64,7 +65,7 @@ class MixType {
constructor( constructor(
public id: number, public id: number,
public name: string, public name: string,
public material: Material public materialType: MaterialType
) { ) {
} }
} }
@ -101,12 +102,13 @@ export function sortRecipeSteps(steps: RecipeStep[]): RecipeStep[] {
} }
export function mixMaterialsToMixMaterialsDto(mix: Mix): MixMaterialDto[] { export function mixMaterialsToMixMaterialsDto(mix: Mix): MixMaterialDto[] {
return sortMixMaterialsDto(mix.mixMaterials.map(m => new MixMaterialDto( return sortMixMaterialsDto(mix.mixQuantities.map(m => new MixMaterialDto(
m.material.id, m.material.id,
m.quantity, m.quantity,
m.material.materialType.usePercentages, m.material.materialType.usePercentages,
m.position, m.position,
UNIT_MILLILITER UNIT_MILLILITER,
m.material.isMixType
))) )))
} }

View File

@ -10,20 +10,28 @@ export function valueOr<T>(value: T, or: T): T {
const MEDIA_TYPE_PDF = 'application/pdf' const MEDIA_TYPE_PDF = 'application/pdf'
const MEDIA_TYPE_JPG = 'image/jpeg' const MEDIA_TYPE_JPG = 'image/jpeg'
export function openPdf(url: string) { export function getImageUrl(path: string): string {
openUrl(url, MEDIA_TYPE_PDF) return getFileUri(`images/${path}`, MEDIA_TYPE_JPG)
} }
export function openJpg(url: string) { export function getFileUri(path: string, mediaType: string): string {
openUrl(url, MEDIA_TYPE_JPG) return `${environment.apiUrl}/file?path=${encodeURIComponent(path)}&mediaType=${encodeURIComponent(mediaType)}`
}
export function openPdf(path: string) {
openFileUri(`pdf/${path}.pdf`, MEDIA_TYPE_PDF)
}
export function openJpg(path: string) {
openFileUri(`images/${path}`, MEDIA_TYPE_JPG)
} }
export function openTouchUpKit(touchUpKit: TouchUpKit) { export function openTouchUpKit(touchUpKit: TouchUpKit) {
openRawUrl(`${environment.apiUrl}/touchupkit/pdf?project=${touchUpKit.project}`) openRawUrl(`${environment.apiUrl}/touchupkit/pdf?project=${touchUpKit.project}`)
} }
export function openUrl(url: string, mediaType: string) { export function openFileUri(path: string, mediaType: string) {
openRawUrl(`${url}&mediaType=${encodeURIComponent(mediaType)}`) openRawUrl(getFileUri(path, mediaType))
} }
export function openRawUrl(url: string) { export function openRawUrl(url: string) {

View File

@ -1,4 +1,5 @@
export const environment = { export const environment = {
production: true, production: true,
apiBaseUrl: window.location.origin,
apiUrl: window.location.origin + '/api' apiUrl: window.location.origin + '/api'
}; };

View File

@ -4,6 +4,7 @@
export const environment = { export const environment = {
production: false, production: false,
apiBaseUrl: 'http://localhost:9090',
apiUrl: 'http://localhost:9090/api' apiUrl: 'http://localhost:9090/api'
}; };