Merge branch 'main' of github.com:Poomipat-Ch/taobin_recipe_manager
This commit is contained in:
commit
c20d24c6ac
18 changed files with 1477 additions and 397 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,3 +2,5 @@ server/services/logger/serverlog.log
|
|||
.idea/
|
||||
.DS_Store
|
||||
server/cofffeemachineConfig
|
||||
node_modules
|
||||
server/cofffeemachineConfig.diff
|
||||
|
|
|
|||
|
|
@ -166,26 +166,31 @@ export interface ToppingSet {
|
|||
}
|
||||
|
||||
export interface MaterialSetting {
|
||||
StringParam: string,
|
||||
materialName: string;
|
||||
materialId: number;
|
||||
materialOtherName: string;
|
||||
RawMaterialUnit: string;
|
||||
IceScreamBingsuChannel: boolean;
|
||||
StringParam: string;
|
||||
AlarmIDWhenOffline: string;
|
||||
BeanChannel: string;
|
||||
BeanChannel: boolean;
|
||||
CanisterType: string;
|
||||
DrainTimer: string;
|
||||
IsEquipment: string;
|
||||
LeavesChannel: string;
|
||||
IsEquipment: boolean;
|
||||
LeavesChannel: boolean;
|
||||
LowToOffline: string;
|
||||
MaterialStatus: string;
|
||||
PowderChannel: string;
|
||||
PowderChannel: boolean;
|
||||
RefillUnitGram: string;
|
||||
RefillUnitMilliliters: string;
|
||||
RefillUnitPCS: string;
|
||||
ScheduleDrainType: string;
|
||||
SodaChannel: string;
|
||||
StockAdjust: string;
|
||||
SyrupChannel: string;
|
||||
id: string;
|
||||
idAlternate: string;
|
||||
isUse: string;
|
||||
SyrupChannel: boolean;
|
||||
id: number;
|
||||
idAlternate: number;
|
||||
isUse: boolean;
|
||||
pay_rettry_max_count: string;
|
||||
feed_mode: string;
|
||||
MaterialParameter: string;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,20 @@
|
|||
<div class="overflow-auto h-[90vh] w-[85vw]">
|
||||
<div class="flex sticky top-10 p-2 space-x-2 items-center">
|
||||
<p class="m-4 font-bold text-lg">Material Settings</p>
|
||||
<button class="btn m-4">New Category</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-auto h-[80vh] w-[85vw]">
|
||||
<div class="m-4 w-[90%]" *ngFor="let cat of getCategories()">
|
||||
<details class="bg-stone-300 p-4 collapse collapse-arrow">
|
||||
<summary class="font-bold text-lg collapse-title">{{ cat }}</summary>
|
||||
<summary class="font-bold text-lg collapse-title">
|
||||
{{ cat }}
|
||||
</summary>
|
||||
|
||||
<div class="collapse-content">
|
||||
<button class="btn">New {{ cat }} material</button>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="grid grid-flow-row grid-cols-7 gap-4">
|
||||
<div
|
||||
class="m-1"
|
||||
|
|
@ -11,10 +22,151 @@
|
|||
>
|
||||
<button
|
||||
class="bg-slate-100 p-2 rounded-md w-44"
|
||||
(click)="getMaterialSettingsById(material.id)"
|
||||
(click)="openMaterialSettingModal(material.id)"
|
||||
>
|
||||
<p>{{ material.name }} ({{ material.id }})</p>
|
||||
</button>
|
||||
<dialog
|
||||
id="material_settings_modal_{{ material.id }}"
|
||||
class="modal"
|
||||
>
|
||||
<div class="modal-box max-w-5xl">
|
||||
|
||||
<div *ngIf="currentMaterialSettings != null" [formGroup]="materialSettingForm">
|
||||
<p>Material Settings</p>
|
||||
|
||||
<!-- TODO: add form -->
|
||||
|
||||
<div class="divider"></div>
|
||||
<div formArrayName="materialSetting" *ngFor="let material of materialSetting.controls; let i = index">
|
||||
<!-- only show the current form matched by index -->
|
||||
<div formGroupName="{{i}}">
|
||||
<div class="" *ngIf="i == currentFormIndex">
|
||||
|
||||
<!-- <p>Form at {{i}}</p> -->
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Is Use</th>
|
||||
<th>ID</th>
|
||||
<th>ID Alternate</th>
|
||||
<th>Name</th>
|
||||
<th>Other Name</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<input class="toggle" type="checkbox" formControlName="isUse" />
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input class="input input-sm input-bordered" formControlName="id" />
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input class="input input-sm input-bordered" formControlName="idAlternate" />
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
<input class="input input-sm input-bordered" formControlName="materialName" />
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input class="input input-sm input-bordered" formControlName="materialOtherName" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<!-- <input class="input input-sm input-bordered" formControlName="RawMaterialUnit" />
|
||||
<input class="input input-sm input-bordered" formControlName="MaterialParameter" />
|
||||
<input class="input input-sm input-bordered" formControlName="id" />
|
||||
<input class="input input-sm input-bordered" formControlName="idAlternate" />
|
||||
<input class="input input-sm input-bordered" formControlName="pay_rettry_max_count" />
|
||||
<input class="input input-sm input-bordered" formControlName="feed_mode" />
|
||||
<input class="input input-sm input-bordered" formControlName="MaterialStatus" /> -->
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- channels -->
|
||||
|
||||
<div class="bg-stone-500 p-2 rounded-md text-white">
|
||||
|
||||
<div class="flex space-x-2 justify-evenly">
|
||||
<div class="justify-center">
|
||||
<p class="font-bold">Powder</p>
|
||||
<input class="checkbox checkbox-info" type="checkbox" formControlName="PowderChannel" />
|
||||
</div>
|
||||
|
||||
<div class="justify-center">
|
||||
<p class="font-bold">Syrup</p>
|
||||
<input class="checkbox checkbox-info" type="checkbox" formControlName="SyrupChannel" />
|
||||
</div>
|
||||
|
||||
<div class="justify-center">
|
||||
<p class="font-bold">Bean</p>
|
||||
<input class="checkbox checkbox-info" type="checkbox" formControlName="BeanChannel" />
|
||||
</div>
|
||||
|
||||
<div class="justify-center">
|
||||
<p class="font-bold">Soda</p>
|
||||
<input class="checkbox checkbox-info" type="checkbox" formControlName="SodaChannel" />
|
||||
</div>
|
||||
|
||||
<div class="justify-center">
|
||||
<p class="font-bold">Equipment</p>
|
||||
<input class="checkbox checkbox-info" type="checkbox" formControlName="IsEquipment" />
|
||||
</div>
|
||||
|
||||
<div class="justify-center">
|
||||
<p class="font-bold">Leaves</p>
|
||||
<input class="checkbox checkbox-info" type="checkbox" formControlName="LeavesChannel" />
|
||||
</div>
|
||||
|
||||
<div class="justify-center">
|
||||
<p class="font-bold">Ice Cream</p>
|
||||
<input class="checkbox checkbox-info" type="checkbox" formControlName="IceScreamBingsuChannel" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- <input class="input input-sm input-bordered" formControlName="StringParam" /> -->
|
||||
<!-- <input class="input input-sm input-bordered" formControlName="AlarmIDWhenOffline" /> -->
|
||||
<!-- <input class="input input-sm input-bordered" formControlName="CanisterType" /> -->
|
||||
<!-- <input class="input input-sm input-bordered" formControlName="DrainTimer" /> -->
|
||||
<!-- <input class="input input-sm input-bordered" formControlName="LowToOffline" /> -->
|
||||
<!-- <input class="input input-sm input-bordered" formControlName="MaterialStatus" /> -->
|
||||
<!-- <input class="input input-sm input-bordered" formControlName="RefillUnitGram" /> -->
|
||||
<!-- <input class="input input-sm input-bordered" formControlName="RefillUnitMilliliters" /> -->
|
||||
<!-- <input class="input input-sm input-bordered" formControlName="RefillUnitPCS" /> -->
|
||||
<!-- <input class="input input-sm input-bordered" formControlName="ScheduleDrainType" /> -->
|
||||
<!-- <input class="input input-sm input-bordered" formControlName="StockAdjust" /> -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-action sticky bottom-0 right-0">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-warning">Close</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -22,66 +174,4 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 0: "StringParam"1: "AlarmIDWhenOffline"2: "BeanChannel"3: "CanisterType"4: "DrainTimer"5: "IceScreamBingsuChannel"6: "IsEquipment"7: "LeavesChannel"8: "LowToOffline"9: "MaterialStatus"10: "PowderChannel"11: "RefillUnitGram"12: "RefillUnitMilliliters"13: "RefillUnitPCS"14: "ScheduleDrainType"15: "SodaChannel"16: "StockAdjust"17: "SyrupChannel"18: "id"19: "idAlternate"20: "isUse"21: "pay_rettry_max_count"22: "feed_mode"23: "MaterialParameter"24: "materialName"25: "materialOtherName"26: "RawMaterialUnit" -->
|
||||
|
||||
<!-- open material settings modal -->
|
||||
<div *ngIf="currentMaterialSettings != null">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="material_settings_modal"
|
||||
class="modal-toggle"
|
||||
#checkBox="ngModel"
|
||||
[(ngModel)]="showMaterialSettingModal"
|
||||
(close)="currentMaterialSettings = null"
|
||||
/>
|
||||
<label for="material_settings_modal" class="modal">
|
||||
<div class="modal-box max-w-5xl">
|
||||
<p>Material Settings</p>
|
||||
|
||||
<input type="checkbox" value="{{currentMaterialSettings.isUse == 'true' ? true : false}}">
|
||||
|
||||
|
||||
<p>{{ currentMaterialSettings.materialName }}</p>
|
||||
<p>{{ currentMaterialSettings.materialId }}</p>
|
||||
<p>{{ currentMaterialSettings.materialOtherName }}</p>
|
||||
|
||||
<p>Status {{ currentMaterialSettings.MaterialStatus }}</p>
|
||||
<p>CanisterType {{ currentMaterialSettings.CanisterType }}</p>
|
||||
<p>AlarmIDWhenOffline {{ currentMaterialSettings.AlarmIDWhenOffline }}</p>
|
||||
<p>ScheduleDrainType {{ currentMaterialSettings.ScheduleDrainType }}</p>
|
||||
<p>LowToOffline {{ currentMaterialSettings.LowToOffline }}</p>
|
||||
|
||||
<p>StockAdjust {{ currentMaterialSettings.StockAdjust }}</p>
|
||||
|
||||
<p>pay_rettry_max_count {{ currentMaterialSettings.pay_rettry_max_count }}</p>
|
||||
<!-- Material Param -->
|
||||
<div
|
||||
class="bg-green-200 rounded-md p-4"
|
||||
>
|
||||
<p>StringParam {{ currentMaterialSettings.StringParam }}</p>
|
||||
<p>Parameter {{ currentMaterialSettings.MaterialParameter }}</p>
|
||||
<p>RawMaterialUnit {{ currentMaterialSettings.RawMaterialUnit }}</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Types -->
|
||||
<div class="bg-amber-300 rounded-md p-4">
|
||||
<p>IsEquipment? {{ currentMaterialSettings.IsEquipment }}</p>
|
||||
<p>Bean? {{ currentMaterialSettings.BeanChannel }}</p>
|
||||
<p>Powder? {{ currentMaterialSettings.PowderChannel }}</p>
|
||||
<p>Syrup? {{ currentMaterialSettings.SyrupChannel }}</p>
|
||||
<p>Soda? {{ currentMaterialSettings.SodaChannel }}</p>
|
||||
<p>Leaves? {{ currentMaterialSettings.LeavesChannel }}</p>
|
||||
<p>IceScreamBingsu? {{ currentMaterialSettings.IceScreamBingsuChannel }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Refill -->
|
||||
<div class="bg-blue-200 rounded-md p-4">
|
||||
<p>RefillUnitGram? {{ currentMaterialSettings.RefillUnitGram }}</p>
|
||||
<p>RefillUnitMilliliters? {{ currentMaterialSettings.RefillUnitMilliliters }}</p>
|
||||
<p>RefillUnitPCS? {{ currentMaterialSettings.RefillUnitPCS }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { CommonModule, NgIf } from '@angular/common';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { FormArray, FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MaterialSetting } from 'src/app/core/models/recipe.model';
|
||||
import { MaterialService } from 'src/app/core/services/material.service';
|
||||
import { getCategories, getMaterialType } from 'src/app/shared/helpers/recipe';
|
||||
|
||||
|
|
@ -21,23 +22,36 @@ export class MaterialSettingsComponent implements OnInit {
|
|||
}[]
|
||||
| null = [];
|
||||
allMaterialsGroupedByTypes: { [key: string]: any[] } = {};
|
||||
currentMaterialSettings: any = null;
|
||||
currentMaterialSettings: MaterialSetting | undefined = undefined;
|
||||
showMaterialSettingModal: boolean = false;
|
||||
|
||||
constructor(private _materialService: MaterialService) {}
|
||||
// current form index
|
||||
currentFormIndex: number | undefined = undefined;
|
||||
|
||||
// mandatory form
|
||||
materialSettingForm = this.formBuilder.group({
|
||||
materialSetting: this.formBuilder.array([]),
|
||||
}, { updateOn: 'blur' });
|
||||
|
||||
// getter
|
||||
get materialSetting() {
|
||||
return this.materialSettingForm.get('materialSetting') as FormArray;
|
||||
}
|
||||
|
||||
constructor(private _materialService: MaterialService, private formBuilder: FormBuilder) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
// do fetch material settings
|
||||
(await this._materialService.getFullMaterialDetail()).subscribe((data) => {
|
||||
this.allMaterials = data;
|
||||
this.allMaterialsGroupedByTypes = this.ListCategory();
|
||||
this.allMaterialsGroupedByTypes = this.listCategory();
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------ Functions ---------------------
|
||||
|
||||
// filter material by type
|
||||
ListCategory = () => {
|
||||
listCategory = () => {
|
||||
let catMap: { [key: string]: any[] } = {
|
||||
others: [],
|
||||
};
|
||||
|
|
@ -52,7 +66,20 @@ export class MaterialSettingsComponent implements OnInit {
|
|||
this.allMaterials!.forEach((mat) => {
|
||||
let category = getMaterialType(mat.materialId);
|
||||
// try again
|
||||
if (category == 'others') {
|
||||
if(category == 'others' && mat.type.includes("true")){
|
||||
|
||||
// extract type
|
||||
let type = mat.type.split(",");
|
||||
type.forEach((t)=>{
|
||||
let tt = t.split(":");
|
||||
// powder:true -> powder-[0] | true-[1]
|
||||
category = tt[1] == "true" ? tt[0] : category;
|
||||
});
|
||||
|
||||
if(!Object.keys(catMap).includes(category)){
|
||||
catMap[category] = [];
|
||||
}
|
||||
} else if (category == 'others') {
|
||||
// find min
|
||||
// console.log(Math.floor(mat.materialId / 1000) );
|
||||
let interCode = Math.floor(mat.materialId / 10000) * 10000;
|
||||
|
|
@ -82,16 +109,77 @@ export class MaterialSettingsComponent implements OnInit {
|
|||
return catList;
|
||||
};
|
||||
|
||||
// get material settings by id
|
||||
async getMaterialSettingsById(id: number) {
|
||||
// Implementation goes here
|
||||
(await this._materialService.getMaterialSettingById(id)).subscribe(
|
||||
|
||||
createMaterialSettingFormGroup(mat_set: MaterialSetting){
|
||||
return this.formBuilder.group({
|
||||
materialName: mat_set.materialName,
|
||||
materialId: mat_set.materialId,
|
||||
materialOtherName: mat_set.materialOtherName,
|
||||
RawMaterialUnit: mat_set.RawMaterialUnit,
|
||||
IceScreamBingsuChannel: mat_set.IceScreamBingsuChannel,
|
||||
StringParam: mat_set.StringParam,
|
||||
AlarmIDWhenOffline: mat_set.AlarmIDWhenOffline,
|
||||
BeanChannel: mat_set.BeanChannel,
|
||||
CanisterType: mat_set.CanisterType,
|
||||
DrainTimer: mat_set.DrainTimer,
|
||||
IsEquipment: mat_set.IsEquipment,
|
||||
LeavesChannel: mat_set.LeavesChannel,
|
||||
LowToOffline: mat_set.LowToOffline,
|
||||
MaterialStatus: mat_set.MaterialStatus,
|
||||
PowderChannel: mat_set.PowderChannel,
|
||||
RefillUnitGram: mat_set.RefillUnitGram,
|
||||
RefillUnitMilliliters: mat_set.RefillUnitMilliliters,
|
||||
RefillUnitPCS: mat_set.RefillUnitPCS,
|
||||
ScheduleDrainType: mat_set.ScheduleDrainType,
|
||||
SodaChannel: mat_set.SodaChannel,
|
||||
StockAdjust: mat_set.StockAdjust,
|
||||
SyrupChannel: mat_set.SyrupChannel,
|
||||
id: mat_set.id,
|
||||
idAlternate: mat_set.idAlternate,
|
||||
isUse: mat_set.isUse,
|
||||
pay_rettry_max_count: mat_set.pay_rettry_max_count,
|
||||
feed_mode: mat_set.feed_mode,
|
||||
MaterialParameter: mat_set.MaterialParameter
|
||||
});
|
||||
}
|
||||
|
||||
// open material id modal
|
||||
async openMaterialSettingModal(id: string){
|
||||
// set current material
|
||||
(await this._materialService.getMaterialSettingById(parseInt(id))).subscribe(
|
||||
(data) => {
|
||||
this.currentMaterialSettings = data;
|
||||
this.showMaterialSettingModal = true;
|
||||
console.log('material setting', data);
|
||||
|
||||
// do create form, if not exist
|
||||
// - find matching form
|
||||
// - if not exist, create new form
|
||||
let pushableFlag = false;
|
||||
|
||||
// filter find index
|
||||
let foundFormIndex = this.materialSetting.controls.findIndex((control) => {
|
||||
return (control.value as any).id == id
|
||||
});
|
||||
|
||||
console.log("found form index", foundFormIndex);
|
||||
|
||||
if(foundFormIndex < 0){
|
||||
foundFormIndex = this.materialSetting.length - 1 < 0 ? 0 : this.materialSetting.length;
|
||||
|
||||
pushableFlag = true;
|
||||
}
|
||||
|
||||
if(pushableFlag){
|
||||
this.materialSetting.push(this.createMaterialSettingFormGroup(data));
|
||||
console.log('push new form', this.materialSetting);
|
||||
}
|
||||
|
||||
// export index
|
||||
this.currentFormIndex = foundFormIndex;
|
||||
|
||||
console.log('material setting', data, "at index", foundFormIndex, "current form index", this.currentFormIndex);
|
||||
// console.log("keys of material settings", Object.keys(data));
|
||||
console.log('material setting', data.isUse, typeof data.isUse);
|
||||
(document.getElementById("material_settings_modal_"+id) as any)!.showModal();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@
|
|||
>
|
||||
<app-recipe-list
|
||||
[productCode]="productCode"
|
||||
[isSubMenu]="false"
|
||||
[noFetch]="false"
|
||||
(recipeListFormChange)="onRecipeListFormChange($event)"
|
||||
></app-recipe-list>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
<div class="justify-center w-full flex flex-row sticky top-0 z-10">
|
||||
<button class="btn w-1/2" (click)="addRow()">Add</button>
|
||||
<button class="btn w-1/2" (click)="removeRow()">Remove</button>
|
||||
</div>
|
||||
<table class="table" [formGroup]="recipeListForm">
|
||||
<thead>
|
||||
<tr class="bg-gray-200">
|
||||
|
|
@ -13,8 +17,12 @@
|
|||
*ngFor="let mat of recipeListData.controls; let i = index"
|
||||
>
|
||||
<tr
|
||||
class="bg-white border-b hover:bg-secondary max-h-4"
|
||||
class="bg-white border-b max-h-4"
|
||||
[ngClass]="{
|
||||
'bg-red-400': selectedRecipeList.includes(i)
|
||||
}"
|
||||
formGroupName="{{ i }}"
|
||||
(click)="addToSelection(i)"
|
||||
(mousedown)="initHoldEvent()"
|
||||
(mouseup)="openRecipeListEditor(i)"
|
||||
>
|
||||
|
|
@ -190,6 +198,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<!-- show string param modal -->
|
||||
|
||||
<input
|
||||
|
|
@ -396,7 +405,7 @@
|
|||
[(ngModel)]="showMaterialSelector"
|
||||
/>
|
||||
<label for="material_selector" class="modal">
|
||||
<div class="modal-box max-h-[400px] overflow-scroll">
|
||||
<div class="modal-box max-h-[75vh] overflow-scroll">
|
||||
<div class="flex flex-row m-2 modal">
|
||||
<p class="font-bold text-lg m-2">Materials</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { NgFor, NgIf } from '@angular/common';
|
||||
import { CommonModule, NgFor, NgIf } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR } from '@angular/core';
|
||||
import {
|
||||
FormArray,
|
||||
|
|
@ -8,7 +8,7 @@ import {
|
|||
NgModel,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms';
|
||||
import { forEach, isBoolean, isEqual, sortBy } from 'lodash';
|
||||
import { forEach, isBoolean, isEqual, sortBy, xorWith } from 'lodash';
|
||||
import { first } from 'rxjs';
|
||||
import { UserPermissions } from 'src/app/core/auth/userPermissions';
|
||||
import {
|
||||
|
|
@ -40,11 +40,12 @@ import Lang from 'src/app/shared/helpers/lang';
|
|||
selector: 'app-recipe-list',
|
||||
templateUrl: './recipe-list.component.html',
|
||||
standalone: true,
|
||||
imports: [NgIf, NgFor, ReactiveFormsModule, FormsModule, RecipeToppingComponent]
|
||||
imports: [CommonModule,NgIf, NgFor, ReactiveFormsModule, FormsModule, RecipeToppingComponent]
|
||||
})
|
||||
export class RecipeListComponent implements OnInit {
|
||||
@Input({ required: true }) productCode!: string;
|
||||
@Input() isSubMenu: boolean = false;
|
||||
@Input() noFetch: boolean = false;
|
||||
@Input() recipeList: any | undefined = undefined;
|
||||
@Output() recipeListFormChange = new EventEmitter<unknown[]>();
|
||||
|
||||
materialList: MaterialCode[] = [];
|
||||
|
|
@ -85,6 +86,9 @@ export class RecipeListComponent implements OnInit {
|
|||
// topping list
|
||||
public toppingList: any[] = [];
|
||||
|
||||
// selected recipelist
|
||||
public selectedRecipeList: number[] = [];
|
||||
|
||||
constructor(
|
||||
private _recipeService: RecipeService,
|
||||
private _materialService: MaterialService,
|
||||
|
|
@ -102,181 +106,283 @@ export class RecipeListComponent implements OnInit {
|
|||
private _recipeListOriginalArray!: RecipeDetailMat[];
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
(await this._recipeService
|
||||
.getRecipeDetailMat(this.productCode))
|
||||
.pipe(first())
|
||||
.subscribe(({ result }) => {
|
||||
this._recipeListOriginalArray = result;
|
||||
result.forEach((recipeDetailMat: RecipeDetailMat, index: number) => {
|
||||
|
||||
// fetch material
|
||||
|
||||
(await
|
||||
this._materialService.getMaterialCodes()).subscribe((materials) => {
|
||||
this.materialList = materials;
|
||||
// console.log("[MatService] get materials", materials.length);
|
||||
});
|
||||
|
||||
(await this._materialService.getFullMaterialDetail()).subscribe((materials) => {
|
||||
this.fullMaterialList = materials;
|
||||
this.categoriedMaterial = this.ListCategory();
|
||||
console.log(this.categoriedMaterial);
|
||||
|
||||
|
||||
console.log(this.recipeListData);
|
||||
// remapping missing material attr to recipeListData
|
||||
this.recipeListData.controls.forEach((recipeList: any, index: number) => {
|
||||
// console.log("recipeList", recipeList);
|
||||
// do map name
|
||||
let materialName = this.fullMaterialList!.find(
|
||||
(mat) => mat.materialId.toString() == recipeList.get('materialPathId').value.toString()
|
||||
);
|
||||
|
||||
// StringParam
|
||||
if (
|
||||
recipeDetailMat.StringParam != '' ||
|
||||
recipeDetailMat.StringParam != null
|
||||
) {
|
||||
let currStringParam = new StringParam(recipeDetailMat.StringParam);
|
||||
let stringParamList = currStringParam.extract().as_list();
|
||||
if(materialName){
|
||||
recipeList.get('name').setValue(materialName.name);
|
||||
}
|
||||
})
|
||||
|
||||
let stringParamListTransform = [];
|
||||
for (let param of stringParamList) {
|
||||
// boolean transform
|
||||
if (param.pvalue == 'true') {
|
||||
param.pvalue = true;
|
||||
} else if (param.pvalue == 'false') {
|
||||
param.pvalue = false;
|
||||
});
|
||||
|
||||
// this do not fetch recipe list when initialized
|
||||
// use when doing `topping`
|
||||
if(!this.noFetch){
|
||||
(await this._recipeService
|
||||
.getRecipeDetailMat(this.productCode))
|
||||
.pipe(first())
|
||||
.subscribe(({ result }) => {
|
||||
this._recipeListOriginalArray = result;
|
||||
result.forEach((recipeDetailMat: RecipeDetailMat, index: number) => {
|
||||
// console.log(this.recipeListData);
|
||||
|
||||
// StringParam
|
||||
if (
|
||||
recipeDetailMat.StringParam != '' ||
|
||||
recipeDetailMat.StringParam != null
|
||||
) {
|
||||
let currStringParam = new StringParam(recipeDetailMat.StringParam);
|
||||
let stringParamList = currStringParam.extract().as_list();
|
||||
|
||||
let stringParamListTransform = [];
|
||||
for (let param of stringParamList) {
|
||||
// boolean transform
|
||||
if (param.pvalue == 'true') {
|
||||
param.pvalue = true;
|
||||
} else if (param.pvalue == 'false') {
|
||||
param.pvalue = false;
|
||||
}
|
||||
|
||||
stringParamListTransform.push(
|
||||
this._formBuilder.group({
|
||||
pkey: [{ value: param.pkey, disabled: !this.isEditable() }],
|
||||
pvalue: [
|
||||
{ value: param.pvalue, disabled: !this.isEditable() },
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
stringParamListTransform.push(
|
||||
this._formBuilder.group({
|
||||
pkey: [{ value: param.pkey, disabled: !this.isEditable() }],
|
||||
pvalue: [
|
||||
{ value: param.pvalue, disabled: !this.isEditable() },
|
||||
],
|
||||
})
|
||||
this.stringParamData.push(
|
||||
this._formBuilder.array(stringParamListTransform)
|
||||
);
|
||||
|
||||
this.stringParams[index] = stringParamList;
|
||||
|
||||
// console.log("string param at index", index, this.stringParams[index]);
|
||||
}
|
||||
|
||||
this.stringParamData.push(
|
||||
this._formBuilder.array(stringParamListTransform)
|
||||
this.recipeListData.push(
|
||||
this._formBuilder.group({
|
||||
StringParam: [
|
||||
{
|
||||
value: recipeDetailMat.StringParam,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
isUse: [
|
||||
{ value: recipeDetailMat.isUse, disabled: !this.isEditable() },
|
||||
],
|
||||
materialPathId: [
|
||||
{
|
||||
value: recipeDetailMat.materialPathId,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
name: [{ value: recipeDetailMat.name, disabled: true }],
|
||||
mixOrder: [
|
||||
{
|
||||
value: recipeDetailMat.mixOrder,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
stirTime: [
|
||||
{
|
||||
value: recipeDetailMat.stirTime / 10,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
powderGram: [
|
||||
{
|
||||
value: recipeDetailMat.powderGram,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
powderTime: [
|
||||
{
|
||||
value: recipeDetailMat.powderTime,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
syrupGram: [
|
||||
{
|
||||
value: recipeDetailMat.syrupGram,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
syrupTime: [
|
||||
{
|
||||
value: recipeDetailMat.syrupTime,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
waterCold: [
|
||||
{
|
||||
value: recipeDetailMat.waterCold,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
waterYield: [
|
||||
{
|
||||
value: recipeDetailMat.waterYield,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
feedPattern: [
|
||||
{
|
||||
value: recipeDetailMat.feedPattern,
|
||||
disabled: !this.isEditable(),
|
||||
}
|
||||
],
|
||||
feedParameter: [
|
||||
{
|
||||
value: recipeDetailMat.feedParameter,
|
||||
disabled: !this.isEditable(),
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
|
||||
this.stringParams[index] = stringParamList;
|
||||
|
||||
// console.log("string param at index", index, this.stringParams[index]);
|
||||
}
|
||||
|
||||
this.recipeListData.push(
|
||||
this._formBuilder.group({
|
||||
StringParam: [
|
||||
{
|
||||
value: recipeDetailMat.StringParam,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
isUse: [
|
||||
{ value: recipeDetailMat.isUse, disabled: !this.isEditable() },
|
||||
],
|
||||
materialPathId: [
|
||||
{
|
||||
value: recipeDetailMat.materialPathId,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
name: [{ value: recipeDetailMat.name, disabled: true }],
|
||||
mixOrder: [
|
||||
{
|
||||
value: recipeDetailMat.mixOrder,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
stirTime: [
|
||||
{
|
||||
value: recipeDetailMat.stirTime / 10,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
powderGram: [
|
||||
{
|
||||
value: recipeDetailMat.powderGram,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
powderTime: [
|
||||
{
|
||||
value: recipeDetailMat.powderTime,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
syrupGram: [
|
||||
{
|
||||
value: recipeDetailMat.syrupGram,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
syrupTime: [
|
||||
{
|
||||
value: recipeDetailMat.syrupTime,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
waterCold: [
|
||||
{
|
||||
value: recipeDetailMat.waterCold,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
waterYield: [
|
||||
{
|
||||
value: recipeDetailMat.waterYield,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
feedPattern: [
|
||||
{
|
||||
value: recipeDetailMat.feedPattern,
|
||||
disabled: !this.isEditable(),
|
||||
}
|
||||
],
|
||||
feedParameter: [
|
||||
{
|
||||
value: recipeDetailMat.feedParameter,
|
||||
disabled: !this.isEditable(),
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
});
|
||||
this.isMatLoaded = true;
|
||||
});
|
||||
this.isMatLoaded = true;
|
||||
});
|
||||
} else if(this.recipeList != undefined){
|
||||
this.recipeList.forEach( (recipeDetailMat: RecipeDetailMat, index: number) => {
|
||||
|
||||
this.stringParamForm.valueChanges.subscribe((value) => {
|
||||
// value.stringParamData: Array
|
||||
// where this.stringParams: {[key: number] : {pkey: string, pvalue: any}}
|
||||
// StringParam
|
||||
if (
|
||||
recipeDetailMat.StringParam != '' ||
|
||||
recipeDetailMat.StringParam != null
|
||||
) {
|
||||
let currStringParam = new StringParam(recipeDetailMat.StringParam);
|
||||
let stringParamList = currStringParam.extract().as_list();
|
||||
|
||||
// transform value to map
|
||||
let mapValue: { [key: number]: { pkey: string; pvalue: any }[] } = {};
|
||||
forEach(value.stringParamData, (param: any, index: number) => {
|
||||
mapValue[index] = param;
|
||||
});
|
||||
|
||||
let checkLen =
|
||||
Object.keys(mapValue).length ==
|
||||
Object.keys(this.stringParams as any).length;
|
||||
let baseLen =
|
||||
Object.keys(this.stringParams as any).length >=
|
||||
Object.keys(mapValue).length
|
||||
? Object.keys(this.stringParams as any).length
|
||||
: Object.keys(mapValue).length;
|
||||
|
||||
if (checkLen) {
|
||||
for (let i = 0; i < baseLen; i++) {
|
||||
if (!isEqual(this.stringParams[i], mapValue[i])) {
|
||||
console.log('check', (this.stringParams as any)[i], mapValue[i]);
|
||||
|
||||
// transform back to string
|
||||
let initString = ',';
|
||||
for (let key of Object.keys(mapValue[i])) {
|
||||
initString += `${mapValue[i][parseInt(key)].pkey}=${
|
||||
mapValue[i][parseInt(key)].pvalue
|
||||
},`;
|
||||
let stringParamListTransform: any[] = [];
|
||||
for (let param of stringParamList) {
|
||||
// boolean transform
|
||||
if (param.pvalue == 'true') {
|
||||
param.pvalue = true;
|
||||
} else if (param.pvalue == 'false') {
|
||||
param.pvalue = false;
|
||||
}
|
||||
|
||||
if (initString.length > 1) {
|
||||
(this.recipeListData.at(i) as any).controls.StringParam.setValue(
|
||||
initString
|
||||
);
|
||||
console.log('set', initString);
|
||||
}
|
||||
|
||||
// do last
|
||||
this.stringParams[i] = mapValue[i];
|
||||
}
|
||||
|
||||
this.stringParamData.push(
|
||||
this._formBuilder.array(stringParamListTransform)
|
||||
)
|
||||
|
||||
this.stringParams[index] = stringParamList;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// --------------- mapping missing data ---------------
|
||||
|
||||
// map name
|
||||
console.log("use recipeList input; ", recipeDetailMat);
|
||||
|
||||
this.recipeListData.push(
|
||||
this._formBuilder.group({
|
||||
StringParam: [
|
||||
{
|
||||
value: recipeDetailMat.StringParam,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
isUse: [
|
||||
{ value: recipeDetailMat.isUse, disabled: !this.isEditable() },
|
||||
],
|
||||
materialPathId: [
|
||||
{
|
||||
value: recipeDetailMat.materialPathId,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
name: [{ value: recipeDetailMat.name, disabled: true }],
|
||||
mixOrder: [
|
||||
{
|
||||
value: recipeDetailMat.mixOrder,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
stirTime: [
|
||||
{
|
||||
value: recipeDetailMat.stirTime / 10,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
powderGram: [
|
||||
{
|
||||
value: recipeDetailMat.powderGram,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
powderTime: [
|
||||
{
|
||||
value: recipeDetailMat.powderTime,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
syrupGram: [
|
||||
{
|
||||
value: recipeDetailMat.syrupGram,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
syrupTime: [
|
||||
{
|
||||
value: recipeDetailMat.syrupTime,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
waterCold: [
|
||||
{
|
||||
value: recipeDetailMat.waterCold,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
waterYield: [
|
||||
{
|
||||
value: recipeDetailMat.waterYield,
|
||||
disabled: !this.isEditable(),
|
||||
},
|
||||
],
|
||||
feedPattern: [
|
||||
{
|
||||
value: recipeDetailMat.feedPattern,
|
||||
disabled: !this.isEditable(),
|
||||
}
|
||||
],
|
||||
feedParameter: [
|
||||
{
|
||||
value: recipeDetailMat.feedParameter,
|
||||
disabled: !this.isEditable(),
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// event listener
|
||||
|
||||
this.recipeListForm.valueChanges.subscribe((value) => {
|
||||
// console.log(value.recipeListData);
|
||||
|
|
@ -307,21 +413,101 @@ export class RecipeListComponent implements OnInit {
|
|||
}
|
||||
});
|
||||
|
||||
// TODO: embed this to recipelist
|
||||
(await
|
||||
// TODO: embed this to recipelist
|
||||
this._materialService.getMaterialCodes()).subscribe((materials) => {
|
||||
this.materialList = materials;
|
||||
console.log("[MatService] get materials", materials.length);
|
||||
});
|
||||
this.stringParamForm.valueChanges.subscribe((value) => {
|
||||
// value.stringParamData: Array
|
||||
// where this.stringParams: {[key: number] : {pkey: string, pvalue: any}}
|
||||
|
||||
(await this._materialService.getFullMaterialDetail()).subscribe((materials) => {
|
||||
this.fullMaterialList = materials;
|
||||
this.categoriedMaterial = this.ListCategory();
|
||||
console.log(this.categoriedMaterial);
|
||||
// transform value to map
|
||||
let mapValue: { [key: number]: { pkey: string; pvalue: any }[] } = {};
|
||||
forEach(value.stringParamData, (param: any, index: number) => {
|
||||
mapValue[index] = param;
|
||||
});
|
||||
|
||||
let checkLen =
|
||||
Object.keys(mapValue).length ==
|
||||
Object.keys(this.stringParams as any).length;
|
||||
let baseLen =
|
||||
Object.keys(this.stringParams as any).length >=
|
||||
Object.keys(mapValue).length
|
||||
? Object.keys(this.stringParams as any).length
|
||||
: Object.keys(mapValue).length;
|
||||
|
||||
if (checkLen) {
|
||||
for (let i = 0; i < baseLen; i++) {
|
||||
if (!isEqual(this.stringParams[i], mapValue[i])) {
|
||||
// console.log('check', (this.stringParams as any)[i], mapValue[i]);
|
||||
|
||||
// transform back to string
|
||||
let initString = ',';
|
||||
for (let key of Object.keys(mapValue[i])) {
|
||||
initString += `${mapValue[i][parseInt(key)].pkey}=${
|
||||
mapValue[i][parseInt(key)].pvalue
|
||||
},`;
|
||||
}
|
||||
|
||||
if (initString.length > 1) {
|
||||
(this.recipeListData.at(i) as any).controls.StringParam.setValue(
|
||||
initString
|
||||
);
|
||||
// console.log('set', initString);
|
||||
}
|
||||
|
||||
// do last
|
||||
this.stringParams[i] = mapValue[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// add new row
|
||||
addRow() {
|
||||
this.recipeListData.push(
|
||||
this._formBuilder.group({
|
||||
isUse: [{ value: false, disabled: !this.isEditable() }],
|
||||
materialPathId: [{ value: 0, disabled: !this.isEditable() }],
|
||||
name: [{ value: "", disabled: true }],
|
||||
mixOrder: [{ value: "", disabled: !this.isEditable() }],
|
||||
stirTime: [{ value: 1, disabled: !this.isEditable() }],
|
||||
powderGram: [{ value: 1, disabled: !this.isEditable() }],
|
||||
powderTime: [{ value: 0, disabled: !this.isEditable() }],
|
||||
syrupGram: [{ value: 1, disabled: !this.isEditable() }],
|
||||
syrupTime: [{ value: 0, disabled: !this.isEditable() }],
|
||||
waterCold: [{ value: 1, disabled: !this.isEditable() }],
|
||||
waterYield: [{ value: 1, disabled: !this.isEditable() }],
|
||||
feedPattern: [{ value: "", disabled: !this.isEditable() }],
|
||||
feedParameter: [{ value: "", disabled: !this.isEditable() }],
|
||||
StringParam: [{ value: "", disabled: !this.isEditable() }],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// remove from selection
|
||||
removeRow() {
|
||||
|
||||
let i = this.selectedRecipeList;
|
||||
|
||||
i.forEach((idx) => {
|
||||
this.recipeListData.removeAt(idx);
|
||||
});
|
||||
|
||||
this.selectedRecipeList = [];
|
||||
}
|
||||
|
||||
addToSelection(i: number){
|
||||
|
||||
if(this.selectedRecipeList.includes(i)){
|
||||
let index = this.selectedRecipeList.indexOf(i);
|
||||
this.selectedRecipeList.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
this.selectedRecipeList.push(i);
|
||||
console.log("selected recipe list", this.selectedRecipeList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
get recipeListData(): FormArray {
|
||||
return this.recipeListForm.get('recipeListData') as FormArray;
|
||||
}
|
||||
|
|
@ -341,7 +527,7 @@ export class RecipeListComponent implements OnInit {
|
|||
}
|
||||
|
||||
openMaterialList(i: any) {
|
||||
console.log('open material list for ', i);
|
||||
// console.log('open material list for ', i);
|
||||
this.showMaterialSelector = true;
|
||||
this.currentSelectRecipeList = i;
|
||||
}
|
||||
|
|
@ -360,7 +546,7 @@ export class RecipeListComponent implements OnInit {
|
|||
materialName
|
||||
);
|
||||
|
||||
console.log('set mat ', material, materialName,'to slot', i);
|
||||
// console.log('set mat ', material, materialName,'to slot', i);
|
||||
}
|
||||
|
||||
getTypeForRecipeListAtIndex(i: any) {
|
||||
|
|
@ -391,7 +577,7 @@ export class RecipeListComponent implements OnInit {
|
|||
};
|
||||
}
|
||||
|
||||
// TODO: Filter from full detail by type
|
||||
// Filter from full detail by type
|
||||
ListCategory = () => {
|
||||
let catMap: { [key: string]: any[] } = {
|
||||
others: [],
|
||||
|
|
@ -402,7 +588,7 @@ export class RecipeListComponent implements OnInit {
|
|||
catMap[category] = [];
|
||||
});
|
||||
|
||||
console.log('generated category', catMap);
|
||||
// console.log('generated category', catMap);
|
||||
|
||||
this.fullMaterialList!.forEach((mat) => {
|
||||
let category = getMaterialType(mat.materialId);
|
||||
|
|
@ -497,7 +683,7 @@ export class RecipeListComponent implements OnInit {
|
|||
};
|
||||
|
||||
openStringParamEditor(i: any) {
|
||||
console.log('open param list for ', i);
|
||||
// console.log('open param list for ', i);
|
||||
this.showStringParamSelector = true;
|
||||
this.currentSelectStringParam = i;
|
||||
}
|
||||
|
|
@ -517,11 +703,6 @@ export class RecipeListComponent implements OnInit {
|
|||
timeoutHandler: any;
|
||||
timeout: number = 0;
|
||||
initHoldEvent() {
|
||||
// if(this.timeoutHandler){
|
||||
// clearInterval(this.timeoutHandler);
|
||||
// this.timeoutHandler = undefined;
|
||||
// }
|
||||
// let timeout = 0;
|
||||
this.timeoutHandler = setInterval(() => {
|
||||
this.timeout += 1;
|
||||
}, 100);
|
||||
|
|
@ -531,7 +712,7 @@ export class RecipeListComponent implements OnInit {
|
|||
|
||||
await Promise.resolve();
|
||||
if (this.timeoutHandler) {
|
||||
console.log("timeout get", this.timeout);
|
||||
|
||||
if (this.timeout >= 20) {
|
||||
// alert("Opening Recipe List Editor in detail")
|
||||
if (confirm("Are you sure you want to open Recipe List Editor in detail?")) {
|
||||
|
|
@ -592,6 +773,7 @@ export class RecipeListComponent implements OnInit {
|
|||
|
||||
|
||||
|
||||
|
||||
// language handler
|
||||
async displayLang(material: any){
|
||||
let currLang = await Lang.getCurrentLanguage();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,121 @@
|
|||
<main class="relative overflow-auto max-h-[80%] h-[88vh]">
|
||||
<main class="relative overflow-auto max-h-[80%] h-[88vh] w-[84vw] bg-stone-100">
|
||||
<div class="w-full m-4 space-x-4 sticky top-0 bg-stone-100 z-10">
|
||||
<!-- topping builder -->
|
||||
<button class="btn" onclick="topping_builder_modal.showModal()">
|
||||
Create Topping
|
||||
</button>
|
||||
<dialog id="topping_builder_modal" class="modal">
|
||||
<div class="modal-box max-w-screen-2xl m-4">
|
||||
<div class="sticky top-0 z-10 bg-stone-200 rounded-md">
|
||||
<div class="flex items-center justify-start space-x-4 p-4">
|
||||
<h3 class="text-xl font-bold">Topping Builder</h3>
|
||||
|
||||
<div class="">
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
|
||||
<!-- body -->
|
||||
|
||||
<div class="flex space-x-2 items-center justify-evenly">
|
||||
<!-- id -->
|
||||
<div class="flex space-x-3">
|
||||
<p class="text-lg font-bold">ID</p>
|
||||
<input
|
||||
type="text"
|
||||
class="input input-sm input-bordered input-ghost w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- name -->
|
||||
<div class="flex space-x-3">
|
||||
<p class="text-lg font-bold">Name</p>
|
||||
<input
|
||||
type="text"
|
||||
class="input input-sm input-bordered input-ghost w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- productCode -->
|
||||
<div class="flex space-x-3">
|
||||
<p class=" font-bold">Product Code</p>
|
||||
<input
|
||||
type="text"
|
||||
class="input input-sm input-bordered input-ghost w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full h-screen">
|
||||
<app-recipe-list
|
||||
[productCode]="productCode!"
|
||||
[noFetch]="true"
|
||||
(recipeListFormChange)="onRecipeListFormChange($event)"
|
||||
></app-recipe-list>
|
||||
</div>
|
||||
|
||||
<!-- other config -->
|
||||
<!-- [
|
||||
"ExtendID",
|
||||
"OnTOP",
|
||||
"MenuStatus",
|
||||
"cashPrice",
|
||||
"disable",
|
||||
"disable_by_cup",
|
||||
"disable_by_ice",
|
||||
"EncoderCount",
|
||||
"id", ✅
|
||||
"isUse",
|
||||
"isShow",
|
||||
"StringParam",
|
||||
"name", ✅
|
||||
"nonCashPrice",
|
||||
"otherName",
|
||||
"productCode",
|
||||
"recipes",
|
||||
"total_time",
|
||||
"total_weight",
|
||||
"useGram",
|
||||
"weight_float"
|
||||
] -->
|
||||
|
||||
<div class="modal-action right-0 bottom-0 sticky">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-warning">Close</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<!-- group builder -->
|
||||
<button class="btn" onclick="group_editor_modal.showModal()">
|
||||
Group Editor
|
||||
</button>
|
||||
<dialog id="group_editor_modal" class="modal">
|
||||
<div class="modal-box max-w-screen-2xl">
|
||||
<div class="w-full h-screen"></div>
|
||||
<div class="modal-action sticky bottom-0 right-0">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-warning">Close</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<!-- TODO: save all changes and send to server -->
|
||||
<button class="btn">
|
||||
<p>Save Changes</p>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="table w-full" [formGroup]="toppingGroupForm">
|
||||
<thead class="text-xs sticky top-0">
|
||||
<thead class="text-xs sticky top-12">
|
||||
<tr class="bg-primary">
|
||||
<th>Is Use</th>
|
||||
<th>ID</th>
|
||||
|
|
@ -8,32 +123,120 @@
|
|||
<th>Other Name</th>
|
||||
<th>Desciption</th>
|
||||
<!-- <th>Default</th> -->
|
||||
<th>Default</th>
|
||||
<!-- <th>Default</th> -->
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody formArrayName="toppingGroup" *ngFor="let tpg of toppingGroup.controls; let i = index">
|
||||
<tr formGroupName="{{i}}">
|
||||
<td><input class="toggle" type="checkbox" formControlName="inUse"></td>
|
||||
<td><input class="input input-sm" formControlName="groupID"></td>
|
||||
<td><input class="input input-sm" formControlName="name"></td>
|
||||
<td><input class="input input-sm" formControlName="otherName"></td>
|
||||
<td><input class="input input-sm" formControlName="Desc"></td>
|
||||
<tbody
|
||||
formArrayName="toppingGroup"
|
||||
*ngFor="let tpg of toppingGroup.controls; let i = index"
|
||||
>
|
||||
<tr class="hover:bg-gray-300" formGroupName="{{ i }}" (click)="showToppingList(tpg.value.groupID)">
|
||||
<td>
|
||||
<input class="toggle" type="checkbox" formControlName="inUse" />
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
class="input input-sm input-bordered"
|
||||
formControlName="groupID"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input class="input input-sm input-bordered" formControlName="name" />
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
class="input input-sm input-bordered"
|
||||
formControlName="otherName"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input class="input input-sm input-bordered" formControlName="Desc" />
|
||||
</td>
|
||||
<!-- <td>{{tpg.idDefault}}</td> -->
|
||||
<td class=" rounded-md">
|
||||
<div *ngFor="let m of getMemberByGroupId(getAttrFromForm(i, 'groupID'))">
|
||||
<!-- <div class="tooltip tooltip-warning" data-tip="Default select id not in group or does not set" *ngIf="!isDefaultInMember(getAttrFromForm(i, 'groupID'), getAttrFromForm(i, 'idDefault'))">
|
||||
<p class="text-red-500 text-5xl">⚠️</p>
|
||||
</div> -->
|
||||
<!-- <td class="grid grid-flow-row grid-cols-3 rounded-md gap-2">
|
||||
|
||||
|
||||
|
||||
<div
|
||||
*ngFor="let m of getMemberByGroupId(getAttrFromForm(i, 'groupID'))"
|
||||
>
|
||||
<button
|
||||
class="button border-solid border-2 border-black rounded-md p-2 hover:bg-yellow-300"
|
||||
[ngClass]="{ 'button bg-red-200': m == getAttrFromForm(i, 'idDefault') }"
|
||||
class="button border-solid border-2 border-black rounded-md p-2"
|
||||
[ngClass]="{
|
||||
'button bg-red-200': m == getAttrFromForm(i, 'idDefault')
|
||||
}"
|
||||
(click)="setDefaultOfToppingGroup(i, m)"
|
||||
>
|
||||
{{ returnThisOrElse(getMemberData(getAttrFromForm(i, 'groupID'), m).name, "") }} ({{
|
||||
m
|
||||
}})
|
||||
{{
|
||||
returnThisOrElse(
|
||||
getMemberData(getAttrFromForm(i, "groupID"), m).name,
|
||||
""
|
||||
)
|
||||
}}
|
||||
({{ m }})
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</td> -->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<dialog id="topping_list_modal" class="modal">
|
||||
<div class="modal-box max-w-screen-2xl">
|
||||
|
||||
<!-- topping list data here -->
|
||||
<div *ngIf="currentMembersData != undefined">
|
||||
<div [formGroup]="currentMemberDataForm">
|
||||
<div formArrayName="currentMembersOfToppingGroup" *ngFor="let m of currentMembersOfToppingGroup.controls; let i = index">
|
||||
|
||||
<div formGroupName="{{i}}">
|
||||
|
||||
<details class="collapse collapse-arrow space-y-4 p-2">
|
||||
<summary class="collapse-title">{{currentMembersData[i]['name']}}</summary>
|
||||
|
||||
<div class="flex space-x-4">
|
||||
|
||||
<div class="flex items-center space-x-2 p-2">
|
||||
<p>ID</p>
|
||||
<input class="input input-bordered" type="text" formControlName="id">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2 p-2">
|
||||
<p>Name</p>
|
||||
<input class="input input-bordered" type="text" formControlName="name">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<app-recipe-list
|
||||
[productCode]="productCode!"
|
||||
[noFetch]="true"
|
||||
[recipeList]="currentMembersData[i]['recipes']"
|
||||
(recipeListFormChange)="onRecipeListFormChange($event)"
|
||||
>
|
||||
</app-recipe-list>
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-action sticky bottom-0 right-0">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-warning">Close</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</dialog>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -12,22 +12,39 @@ import {
|
|||
ReactiveFormsModule,
|
||||
} from '@angular/forms';
|
||||
import { NgSelectModule } from '@ng-select/ng-select';
|
||||
import { forEach } from 'lodash';
|
||||
import { RecipeListComponent } from '../recipes/recipe-details/recipe-list/recipe-list.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-toppings',
|
||||
standalone: true,
|
||||
imports: [CommonModule, NgSelectModule,FormsModule, ReactiveFormsModule],
|
||||
templateUrl: './toppings.component.html',
|
||||
imports: [
|
||||
CommonModule,
|
||||
NgSelectModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
RecipeListComponent,
|
||||
],
|
||||
})
|
||||
export class ToppingsComponent implements OnInit {
|
||||
onRecipeListFormChange($event: unknown[]) {
|
||||
console.log("recipe list form change",$event);
|
||||
}
|
||||
// topping group
|
||||
toppingGroupList: ToppingGroup[] = [];
|
||||
toppingLists: ToppingList[] = [];
|
||||
|
||||
groupMemebersMap: { [key: string]: { [key: string]: any } } = {
|
||||
groupMembersMap: { [key: string]: { [key: string]: any } } = {
|
||||
'0': { members: [] },
|
||||
};
|
||||
|
||||
// topping list keys
|
||||
toppingListKeys: string[] = [];
|
||||
|
||||
// modals controller
|
||||
showToppingBuilder: boolean = false;
|
||||
|
||||
// forms
|
||||
|
||||
toppingGroupForm = this._formBuilder.group(
|
||||
|
|
@ -36,11 +53,23 @@ export class ToppingsComponent implements OnInit {
|
|||
},
|
||||
{ updateOn: 'blur' }
|
||||
);
|
||||
productCode: string | undefined;
|
||||
addingNewRecipeList: boolean = false;
|
||||
|
||||
// current data variables
|
||||
currentMembersData: { [key: string]: any }[] | undefined = undefined;
|
||||
currentMemberDataForm: FormGroup = this._formBuilder.group({
|
||||
currentMembersOfToppingGroup: this._formBuilder.array([]),
|
||||
});
|
||||
|
||||
get toppingGroup(): FormArray {
|
||||
return this.toppingGroupForm.get('toppingGroup') as FormArray;
|
||||
}
|
||||
|
||||
get currentMembersOfToppingGroup(): FormArray {
|
||||
return this.currentMemberDataForm.get('currentMembersOfToppingGroup') as FormArray;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
constructor(
|
||||
|
|
@ -67,7 +96,7 @@ export class ToppingsComponent implements OnInit {
|
|||
'topping groups: ',
|
||||
this.toppingGroupList,
|
||||
'mapper:',
|
||||
this.groupMemebersMap,
|
||||
this.groupMembersMap,
|
||||
'length',
|
||||
this.toppingGroupList.length
|
||||
);
|
||||
|
|
@ -99,14 +128,25 @@ export class ToppingsComponent implements OnInit {
|
|||
this.toppingLists = data;
|
||||
this.mapNameToMember();
|
||||
|
||||
console.log(
|
||||
'get topping list',
|
||||
this.toppingLists,
|
||||
'mapper:',
|
||||
this.groupMemebersMap
|
||||
);
|
||||
// push keys to list
|
||||
for (let tpl of this.toppingLists) {
|
||||
let currentKeys = Object.keys(tpl);
|
||||
// console.log(tpl);
|
||||
if (this.toppingListKeys.length == 0) {
|
||||
this.toppingListKeys = currentKeys;
|
||||
}
|
||||
|
||||
console.log('undefined name of topping list', this.findUndefinedName());
|
||||
if (currentKeys.length > this.toppingListKeys.length) {
|
||||
this.toppingListKeys = currentKeys;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
'topping lists: ',
|
||||
this.toppingLists,
|
||||
'keys: ',
|
||||
this.toppingListKeys
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -116,26 +156,26 @@ export class ToppingsComponent implements OnInit {
|
|||
mapMembers = () => {
|
||||
this.toppingGroupList.forEach((tpg) => {
|
||||
let spl_mem = tpg.idInGroup.split(',');
|
||||
this.groupMemebersMap[tpg.groupID] = { members: spl_mem };
|
||||
this.groupMembersMap[tpg.groupID] = { members: spl_mem };
|
||||
});
|
||||
};
|
||||
|
||||
// get members of group id
|
||||
getMemberByGroupId = (id: string) =>
|
||||
this.groupMemebersMap[id]['members'] as string[];
|
||||
this.groupMembersMap[id]['members'] as string[];
|
||||
|
||||
// match name to member
|
||||
mapNameToMember = () => {
|
||||
if (this.toppingLists.length > 0) {
|
||||
this.toppingGroupList.forEach((tpg) => {
|
||||
let members = this.groupMemebersMap[tpg.groupID]['members'];
|
||||
let members = this.groupMembersMap[tpg.groupID]['members'];
|
||||
members.forEach((member_id: string) => {
|
||||
// do get member data from topping list
|
||||
let member_data = this.toppingLists.find(
|
||||
(v) => v.id.toString() == member_id.toString()
|
||||
);
|
||||
// set data to group
|
||||
this.groupMemebersMap[tpg.groupID][member_id] = member_data;
|
||||
this.groupMembersMap[tpg.groupID][member_id] = member_data;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -143,10 +183,10 @@ export class ToppingsComponent implements OnInit {
|
|||
|
||||
// get member data from group
|
||||
getMemberData = (group: string, member_id: string) => {
|
||||
if (this.groupMemebersMap[group][member_id] == undefined) {
|
||||
return {};
|
||||
if (this.groupMembersMap[group][member_id] == undefined) {
|
||||
return { name: '' };
|
||||
}
|
||||
return this.groupMemebersMap[group][member_id];
|
||||
return this.groupMembersMap[group][member_id];
|
||||
};
|
||||
|
||||
// check which list does not have name
|
||||
|
|
@ -158,11 +198,100 @@ export class ToppingsComponent implements OnInit {
|
|||
value == undefined || value == '' ? default_value : value;
|
||||
|
||||
// get value from form by given key
|
||||
getAttrFromForm(index: number, key: string){
|
||||
getAttrFromForm(index: number, key: string) {
|
||||
let x = this.toppingGroup.controls.at(index) as any;
|
||||
return x.value[key];
|
||||
}
|
||||
|
||||
// diff and find matched
|
||||
comapareFunction = (a: any, b: any) => ( a != undefined && b != undefined)&& (a.toString() === b.toString());
|
||||
comapareFunction = (a: any, b: any) =>
|
||||
a != undefined && b != undefined && a.toString() === b.toString();
|
||||
|
||||
// onclick: set default of topping in the group
|
||||
setDefaultOfToppingGroup = (index: number, member: string) => {
|
||||
// get default of group in form
|
||||
let targetForm = this.toppingGroup.controls.at(index);
|
||||
console.log('get id default', targetForm);
|
||||
|
||||
let targetDefault = targetForm?.get('idDefault');
|
||||
|
||||
// if click on the same index, set 0
|
||||
if (targetDefault?.value == member) {
|
||||
member = '0';
|
||||
}
|
||||
|
||||
targetDefault?.setValue(member);
|
||||
};
|
||||
|
||||
// boolean checking if default is within the member of set group
|
||||
isDefaultInMember = (group: string, member: string) => {
|
||||
// get member from group
|
||||
// console.log('isDefaultInMember', group, member, this.getMemberByGroupId(group), this.getMemberByGroupId(group).includes(member.toString()));
|
||||
return this.getMemberByGroupId(group).includes(member.toString());
|
||||
};
|
||||
|
||||
// topping list form structure
|
||||
createToppingListForm = (data: ToppingList) => {
|
||||
return this._formBuilder.group({
|
||||
ExtendID: data.ExtendID,
|
||||
OnTOP: data.OnTOP,
|
||||
MenuStatus: data.MenuStatus,
|
||||
cashPrice: data.cashPrice,
|
||||
disable: data.disable,
|
||||
disable_by_cup: data.disable_by_cup,
|
||||
disable_by_ice: data.disable_by_ice,
|
||||
EncoderCount: data.EncoderCount,
|
||||
id: data.id,
|
||||
isUse: data.isUse,
|
||||
isShow: data.isShow,
|
||||
StringParam: data.StringParam,
|
||||
name: data.name,
|
||||
nonCashPrice: data.nonCashPrice,
|
||||
otherName: data.otherName,
|
||||
productCode: data.productCode,
|
||||
recipes: data.recipes,
|
||||
total_time: data.total_time,
|
||||
total_weight: data.total_weight,
|
||||
useGram: data.useGram,
|
||||
weight_float: data.weight_float,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// use when selected a group, show its member data
|
||||
showToppingList = (groupID: string) => {
|
||||
|
||||
// check empty
|
||||
console.log("toppingList.empty", this.toppingLists, "groupMembersMap.empty", this.groupMembersMap);
|
||||
|
||||
// do another mapping, just to make sure data is included
|
||||
this.mapNameToMember();
|
||||
|
||||
if(this.currentMembersData != undefined){
|
||||
this.currentMembersData = undefined;
|
||||
this.currentMembersOfToppingGroup.clear();
|
||||
}
|
||||
|
||||
|
||||
let members = this.groupMembersMap[groupID]['members'];
|
||||
members.forEach((member_id: string) => {
|
||||
// get each member data from group
|
||||
let member_data = this.getMemberData(groupID, member_id);
|
||||
if(this.currentMembersData == undefined){
|
||||
this.currentMembersData = [];
|
||||
}
|
||||
this.currentMembersData!.push(member_data);
|
||||
this.currentMembersOfToppingGroup.push(this.createToppingListForm(member_data));
|
||||
});
|
||||
// this.isShowToppingList = true;
|
||||
|
||||
// query selector
|
||||
let toppingListModal = document.getElementById('topping_list_modal') as any;
|
||||
toppingListModal?.showModal();
|
||||
|
||||
console.log('current members data', this.currentMembersData);
|
||||
console.log("current members of topping group", this.currentMembersOfToppingGroup);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@ export function inRange(min: number, max: number, value: number) {
|
|||
}
|
||||
|
||||
export function getCategories() {
|
||||
return Object.keys(rangeMaterialMapping);
|
||||
|
||||
let keys = Object.keys(rangeMaterialMapping);
|
||||
keys.push('equipment');
|
||||
return keys;
|
||||
}
|
||||
|
||||
export function getMaterialType(materialId: number) {
|
||||
|
|
|
|||
|
|
@ -27,10 +27,11 @@ type Data struct {
|
|||
CurrentCountryID map[string]string
|
||||
DefaultCountryMap []DefaultByCountry
|
||||
AllRecipeFiles map[string][]helpers.RecipePath
|
||||
currentRecipe map[string]*models.Recipe
|
||||
CurrentRecipe map[string]*models.Recipe
|
||||
recipeMap map[string]RecipeWithTimeStamps
|
||||
Countries []helpers.CountryName
|
||||
taoLogger *logger.TaoLogger
|
||||
redisClient *RedisCli
|
||||
}
|
||||
|
||||
type DefaultByCountry struct {
|
||||
|
|
@ -53,7 +54,7 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
func NewData(taoLogger *logger.TaoLogger) *Data {
|
||||
func NewData(taoLogger *logger.TaoLogger, redisClient *RedisCli) *Data {
|
||||
|
||||
allRecipeFiles := helpers.ScanRecipeFiles(countries)
|
||||
|
||||
|
|
@ -131,6 +132,8 @@ func NewData(taoLogger *logger.TaoLogger) *Data {
|
|||
log.Panic("Error when read default recipe file for each country:", v.CountryShortName, err)
|
||||
}
|
||||
|
||||
redisClient.SetToKey(v2.Name, defaultRecipe)
|
||||
|
||||
currentDefaultFileForEachCountry[v.CountryShortName] = defaultRecipe
|
||||
break
|
||||
}
|
||||
|
|
@ -169,7 +172,7 @@ func NewData(taoLogger *logger.TaoLogger) *Data {
|
|||
CurrentFile: currentFileMap,
|
||||
CurrentCountryID: CurrentCountryIDMap,
|
||||
AllRecipeFiles: allRecipeFiles,
|
||||
currentRecipe: currentDefaultFileForEachCountry,
|
||||
CurrentRecipe: currentDefaultFileForEachCountry,
|
||||
recipeMap: map[string]RecipeWithTimeStamps{
|
||||
defaultFile: {
|
||||
Recipe: currentDefaultFileForEachCountry,
|
||||
|
|
@ -179,6 +182,7 @@ func NewData(taoLogger *logger.TaoLogger) *Data {
|
|||
Countries: countries,
|
||||
taoLogger: taoLogger,
|
||||
DefaultCountryMap: defaultForEachCountry,
|
||||
redisClient: redisClient,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,17 +193,47 @@ func (d *Data) GetRecipe(countryID, filename string) *models.Recipe {
|
|||
// TODO: concat submenu into recipe
|
||||
|
||||
if countryID == "" {
|
||||
return d.currentRecipe["tha"]
|
||||
d.taoLogger.Log.Debug("GetRecipe", zap.Any("EmptyCountryId", "return default country = tha"))
|
||||
return d.CurrentRecipe["tha"]
|
||||
}
|
||||
|
||||
if filename == "" || filename == d.CurrentFile[countryID] {
|
||||
return d.currentRecipe[countryID]
|
||||
if filename == "" {
|
||||
d.taoLogger.Log.Debug("GetRecipe", zap.Any("EmptyFilename", filename))
|
||||
return d.CurrentRecipe[countryID]
|
||||
}
|
||||
|
||||
if recipe, ok := d.recipeMap[filename]; ok {
|
||||
// do check if match the current pointer
|
||||
if d.CurrentFile[countryID] == filename {
|
||||
d.taoLogger.Log.Debug("GetRecipe", zap.Any("FileMatchCurrent", "return from stored "+filename), zap.Any("CurrentFile", d.CurrentFile))
|
||||
|
||||
// make sure recipe vesion is equal
|
||||
currentConfig := d.CurrentRecipe[countryID].MachineSetting.ConfigNumber
|
||||
// get requested version
|
||||
requestedConfig, _ := strconv.Atoi(strings.Split(strings.Split(filename, "_")[1], ".")[0])
|
||||
if currentConfig != requestedConfig {
|
||||
d.taoLogger.Log.Debug("GetRecipe", zap.Any("ActualFileNotMatch", "Skip this!"))
|
||||
} else {
|
||||
// if equal, OK
|
||||
return d.CurrentRecipe[countryID]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if recipe, ok := d.recipeMap[filename]; ok && d.redisClient.HealthCheck() != nil {
|
||||
d.taoLogger.Log.Debug("GetRecipe", zap.Any("ValidOnStored", "return from stored "+filename))
|
||||
d.CurrentFile[countryID] = filename
|
||||
// d.CurrentCountryID[countryID] = countryID
|
||||
return recipe.Recipe[countryID]
|
||||
|
||||
// make sure recipe vesion is equal
|
||||
currentConfig := d.CurrentRecipe[countryID].MachineSetting.ConfigNumber
|
||||
// get requested version
|
||||
requestedConfig, _ := strconv.Atoi(strings.Split(strings.Split(filename, "_")[1], ".")[0])
|
||||
if currentConfig != requestedConfig {
|
||||
d.taoLogger.Log.Debug("GetRecipe", zap.Any("InvalidOnStored", "Skip this!"))
|
||||
} else {
|
||||
// if equal, OK
|
||||
return recipe.Recipe[countryID]
|
||||
}
|
||||
}
|
||||
|
||||
// change current version and read new recipe
|
||||
|
|
@ -208,17 +242,49 @@ func (d *Data) GetRecipe(countryID, filename string) *models.Recipe {
|
|||
filename = d.CurrentFile[countryID]
|
||||
}
|
||||
|
||||
// d.CurrentFile[countryID] = filename
|
||||
d.taoLogger.Log.Debug("GetRecipe", zap.String("filename", filename), zap.String("countryID", countryID))
|
||||
// d.CurrentCountryID[countryID] = countryID
|
||||
// var recipe *models.Recipe = &models.Recipe{}
|
||||
|
||||
// do check if redis contains the recipe
|
||||
var cached_recipe *models.Recipe = &models.Recipe{}
|
||||
|
||||
if err := d.redisClient.GetKeyTo(filename, cached_recipe); err != nil {
|
||||
d.taoLogger.Log.Debug("GetRecipe.Cached", zap.Any("GetCacheRecipeError", err))
|
||||
d.taoLogger.Log.Debug("GetRecipe", zap.String("filename", filename), zap.String("countryID", countryID))
|
||||
// d.CurrentCountryID[countryID] = countryID
|
||||
cached_recipe = nil
|
||||
}
|
||||
|
||||
if cached_recipe != nil {
|
||||
d.taoLogger.Log.Debug("GetRecipe", zap.Any("Check on cached recipe invalid", cached_recipe == nil), zap.Any("test config number", cached_recipe.MachineSetting.ConfigNumber))
|
||||
}
|
||||
|
||||
recipe, err := helpers.ReadRecipeFile(countryID, filename)
|
||||
|
||||
if err != nil {
|
||||
d.taoLogger.Log.Error("GetRecipe: Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
return d.currentRecipe[countryID]
|
||||
d.taoLogger.Log.Debug("GetRecipe", zap.Any("ReadRecipeError -> return default", err))
|
||||
return d.CurrentRecipe[countryID]
|
||||
}
|
||||
|
||||
d.currentRecipe[countryID] = recipe
|
||||
// cache to redis
|
||||
d.redisClient.SetToKey(filename, recipe)
|
||||
|
||||
if err != nil {
|
||||
d.taoLogger.Log.Error("GetRecipe: Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
return d.CurrentRecipe[countryID]
|
||||
}
|
||||
|
||||
//. service is connected. Use from cache
|
||||
|
||||
// check healthcheck redis
|
||||
err = d.redisClient.HealthCheck()
|
||||
d.taoLogger.Log.Info("GetRecipe: HealthCheck", zap.Any("result", err))
|
||||
if d.redisClient.HealthCheck() == nil && cached_recipe != nil {
|
||||
d.taoLogger.Log.Debug("GetRecipeCached", zap.Any("cached_recipe", "yes"))
|
||||
d.CurrentRecipe[countryID] = cached_recipe
|
||||
} else {
|
||||
d.taoLogger.Log.Debug("GetRecipeCached", zap.Any("cached_recipe", "no"))
|
||||
d.CurrentRecipe[countryID] = recipe
|
||||
}
|
||||
|
||||
// save to map
|
||||
if len(d.recipeMap) > 5 { // limit keep in memory 5 version
|
||||
|
|
@ -235,11 +301,11 @@ func (d *Data) GetRecipe(countryID, filename string) *models.Recipe {
|
|||
}
|
||||
|
||||
d.recipeMap[filename] = RecipeWithTimeStamps{
|
||||
Recipe: d.currentRecipe,
|
||||
Recipe: d.CurrentRecipe,
|
||||
TimeStamps: time.Now().Unix(),
|
||||
}
|
||||
|
||||
return d.currentRecipe[countryID]
|
||||
return d.CurrentRecipe[countryID]
|
||||
}
|
||||
|
||||
// func (d *Data) GetRecipe01() []models.Recipe01 {
|
||||
|
|
@ -272,7 +338,7 @@ func (d *Data) GetRecipe01ByProductCode(filename, countryID, productCode string)
|
|||
// fmt.Println("GetRecipe01ByProductCode.ReadCurrent::CurrentFile", d.CurrentFile)
|
||||
// fmt.Println("GetRecipe01ByProductCode.ReadCurrent::CurrentCountryID", d.CurrentCountryID)
|
||||
|
||||
for _, v := range d.currentRecipe[countryID].Recipe01 {
|
||||
for _, v := range d.CurrentRecipe[countryID].Recipe01 {
|
||||
if v.ProductCode == productCode {
|
||||
return v, nil
|
||||
} else if len(v.SubMenu) > 0 {
|
||||
|
|
@ -320,26 +386,26 @@ func (d *Data) GetRecipe01ByProductCode(filename, countryID, productCode string)
|
|||
}
|
||||
}
|
||||
|
||||
recipe, err := helpers.ReadRecipeFile(countryID, filename)
|
||||
recipe := d.GetRecipe(countryID, filename)
|
||||
|
||||
if err != nil {
|
||||
d.taoLogger.Log.Error("GetRecipe01ByProductCode: Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
for _, v := range d.currentRecipe[countryID].Recipe01 {
|
||||
if v.ProductCode == productCode {
|
||||
return v, fmt.Errorf("[DEFAULT]-ERR")
|
||||
} else if len(v.SubMenu) > 0 {
|
||||
for _, subMenu := range v.SubMenu {
|
||||
if subMenu.ProductCode == productCode {
|
||||
return subMenu, fmt.Errorf("[DEFAULT]-ERR")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if err != nil {
|
||||
// d.taoLogger.Log.Error("GetRecipe01ByProductCode: Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
// for _, v := range d.CurrentRecipe[countryID].Recipe01 {
|
||||
// if v.ProductCode == productCode {
|
||||
// return v, fmt.Errorf("[DEFAULT]-ERR")
|
||||
// } else if len(v.SubMenu) > 0 {
|
||||
// for _, subMenu := range v.SubMenu {
|
||||
// if subMenu.ProductCode == productCode {
|
||||
// return subMenu, fmt.Errorf("[DEFAULT]-ERR")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
d.taoLogger.Log.Debug("GetRecipe01ByProductCode", zap.Any("productCode", productCode), zap.Any("version", recipe.MachineSetting.ConfigNumber))
|
||||
|
||||
d.currentRecipe[countryID] = recipe
|
||||
d.CurrentRecipe[countryID] = recipe
|
||||
|
||||
// save to map
|
||||
if len(d.recipeMap) > 5 { // limit keep in memory 5 version
|
||||
|
|
@ -356,11 +422,11 @@ func (d *Data) GetRecipe01ByProductCode(filename, countryID, productCode string)
|
|||
}
|
||||
|
||||
d.recipeMap[filename] = RecipeWithTimeStamps{
|
||||
Recipe: d.currentRecipe,
|
||||
Recipe: d.CurrentRecipe,
|
||||
TimeStamps: time.Now().Unix(),
|
||||
}
|
||||
|
||||
for _, v := range d.currentRecipe[countryID].Recipe01 {
|
||||
for _, v := range d.CurrentRecipe[countryID].Recipe01 {
|
||||
if v.ProductCode == productCode {
|
||||
// d.taoLogger.Log.Debug("GetRecipe01ByProductCode", zap.Any("productCode", productCode), zap.Any("result", v))
|
||||
return v, nil
|
||||
|
|
@ -433,26 +499,105 @@ func (d *Data) SetValuesToRecipe(base_recipe []models.Recipe01, recipe models.Re
|
|||
}
|
||||
}
|
||||
|
||||
func (d *Data) SetValuesToMaterialSetting(base_mat_setting []models.MaterialSetting, updated_mat_setting models.MaterialSetting) {
|
||||
not_found := false
|
||||
global_idx := 0
|
||||
|
||||
for index, v := range base_mat_setting {
|
||||
// find matched id
|
||||
if v.ID == updated_mat_setting.ID {
|
||||
// change only changed values
|
||||
for k, v := range updated_mat_setting.ToMap() {
|
||||
if !reflect.DeepEqual(base_mat_setting[index].ToMap()[k], v) {
|
||||
d.taoLogger.Log.Debug("SetValuesToMaterialSetting", zap.Any("key", k), zap.Any("old", base_mat_setting[index].ToMap()[k]), zap.Any("new", v))
|
||||
base_mat_setting[index].ToMap()[k] = v
|
||||
}
|
||||
}
|
||||
} else {
|
||||
not_found = true
|
||||
global_idx = index
|
||||
}
|
||||
}
|
||||
|
||||
// is new value
|
||||
if not_found {
|
||||
base_mat_setting[global_idx+1] = updated_mat_setting
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Data) SetValuesToToppingList(base_topping_list []models.ToppingList, updated_topping_list models.ToppingList) {
|
||||
not_found := false
|
||||
global_idx := 0
|
||||
|
||||
for index, v := range base_topping_list {
|
||||
// find matched id
|
||||
if v.ID == updated_topping_list.ID {
|
||||
// change only changed values
|
||||
for k, v := range updated_topping_list.ToMap() {
|
||||
if !reflect.DeepEqual(base_topping_list[index].ToMap()[k], v) {
|
||||
d.taoLogger.Log.Debug("SetValuesToToppingList", zap.Any("key", k), zap.Any("old", base_topping_list[index].ToMap()[k]), zap.Any("new", v))
|
||||
base_topping_list[index].ToMap()[k] = v
|
||||
}
|
||||
}
|
||||
} else {
|
||||
not_found = true
|
||||
global_idx = index
|
||||
}
|
||||
}
|
||||
|
||||
// is new value
|
||||
if not_found {
|
||||
base_topping_list[global_idx+1] = updated_topping_list
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Data) SetValuesToToppingGroupList(base_topping_group_list []models.ToppingGroup, updated_topping_group_list models.ToppingGroup) {
|
||||
not_found := false
|
||||
global_idx := 0
|
||||
|
||||
for index, v := range base_topping_group_list {
|
||||
|
||||
// find matched id
|
||||
if v.GroupID == updated_topping_group_list.GroupID {
|
||||
// change only changed values
|
||||
for k, v := range updated_topping_group_list.ToMap() {
|
||||
if !reflect.DeepEqual(base_topping_group_list[index].ToMap()[k], v) {
|
||||
d.taoLogger.Log.Debug("SetValuesToToppingGroup", zap.Any("key", k), zap.Any("old", base_topping_group_list[index].ToMap()[k]), zap.Any("new", v))
|
||||
base_topping_group_list[index].ToMap()[k] = v
|
||||
}
|
||||
}
|
||||
} else {
|
||||
not_found = true
|
||||
global_idx = index
|
||||
}
|
||||
}
|
||||
|
||||
// is new value
|
||||
if not_found {
|
||||
base_topping_group_list[global_idx+1] = updated_topping_group_list
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Data) GetMaterialSetting(countryID, filename string) []models.MaterialSetting {
|
||||
result := make([]models.MaterialSetting, 0)
|
||||
|
||||
if countryID == "" {
|
||||
// copy(result, d.currentRecipe[countryID].MaterialSetting)
|
||||
return d.currentRecipe[countryID].MaterialSetting
|
||||
return d.CurrentRecipe[countryID].MaterialSetting
|
||||
}
|
||||
|
||||
if !strings.Contains(filename, "tmp") {
|
||||
if filename == "" || filename == d.CurrentFile[countryID] {
|
||||
// copy(result, d.currentRecipe[countryID].MaterialSetting)
|
||||
// d.taoLogger.Log.Debug("GetMaterialSetting", zap.Any("result", result))
|
||||
return d.currentRecipe[countryID].MaterialSetting
|
||||
return d.CurrentRecipe[countryID].MaterialSetting
|
||||
}
|
||||
|
||||
if recipe, ok := d.recipeMap[filename]; ok {
|
||||
copy(result, recipe.Recipe[countryID].MaterialSetting)
|
||||
d.CurrentFile[countryID] = filename
|
||||
// d.CurrentCountryID[countryID] = countryID
|
||||
return d.currentRecipe[countryID].MaterialSetting
|
||||
return d.CurrentRecipe[countryID].MaterialSetting
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -464,17 +609,17 @@ func (d *Data) GetMaterialSetting(countryID, filename string) []models.MaterialS
|
|||
|
||||
// d.CurrentFile[countryID] = filename
|
||||
// d.CurrentCountryID[countryID] = countryID
|
||||
recipe, err := helpers.ReadRecipeFile(countryID, filename)
|
||||
recipe := d.GetRecipe(countryID, filename)
|
||||
|
||||
if err != nil {
|
||||
d.taoLogger.Log.Error("GetMaterialSetting: Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
copy(result, d.currentRecipe[countryID].MaterialSetting)
|
||||
return d.currentRecipe[countryID].MaterialSetting
|
||||
}
|
||||
// if err != nil {
|
||||
// d.taoLogger.Log.Error("GetMaterialSetting: Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
// copy(result, d.CurrentRecipe[countryID].MaterialSetting)
|
||||
// return d.CurrentRecipe[countryID].MaterialSetting
|
||||
// }
|
||||
|
||||
// d.taoLogger.Log.Debug("GetMaterialSetting", zap.Any("recipe", recipe.MaterialSetting))
|
||||
|
||||
d.currentRecipe[countryID] = recipe
|
||||
d.CurrentRecipe[countryID] = recipe
|
||||
|
||||
// save to map
|
||||
if len(d.recipeMap) > 5 { // limit keep in memory 5 version
|
||||
|
|
@ -491,7 +636,7 @@ func (d *Data) GetMaterialSetting(countryID, filename string) []models.MaterialS
|
|||
}
|
||||
|
||||
d.recipeMap[filename] = RecipeWithTimeStamps{
|
||||
Recipe: d.currentRecipe,
|
||||
Recipe: d.CurrentRecipe,
|
||||
TimeStamps: time.Now().Unix(),
|
||||
}
|
||||
|
||||
|
|
@ -501,29 +646,26 @@ func (d *Data) GetMaterialSetting(countryID, filename string) []models.MaterialS
|
|||
|
||||
func (d *Data) GetAllToppingGroups(countryID, filename string) []models.ToppingGroup {
|
||||
if countryID == "" {
|
||||
return d.currentRecipe[countryID].Topping.ToppingGroup
|
||||
return d.CurrentRecipe[countryID].Topping.ToppingGroup
|
||||
}
|
||||
|
||||
if !strings.Contains(filename, ".tmp") {
|
||||
if filename == "" || filename == d.CurrentFile[countryID] {
|
||||
return d.currentRecipe[countryID].Topping.ToppingGroup
|
||||
return d.CurrentRecipe[countryID].Topping.ToppingGroup
|
||||
}
|
||||
if _, ok := d.recipeMap[countryID]; ok {
|
||||
d.CurrentFile[countryID] = filename
|
||||
|
||||
return d.currentRecipe[countryID].Topping.ToppingGroup
|
||||
return d.CurrentRecipe[countryID].Topping.ToppingGroup
|
||||
}
|
||||
}
|
||||
|
||||
if filename == "default" {
|
||||
filename = d.CurrentFile[countryID]
|
||||
}
|
||||
recipe, err := helpers.ReadRecipeFile(countryID, filename)
|
||||
if err != nil {
|
||||
return d.currentRecipe[countryID].Topping.ToppingGroup
|
||||
}
|
||||
recipe := d.GetRecipe(countryID, filename)
|
||||
|
||||
d.currentRecipe[countryID] = recipe
|
||||
d.CurrentRecipe[countryID] = recipe
|
||||
|
||||
if len(d.recipeMap) > 5 { // limit keep in memory 5 version
|
||||
// remove oldest version
|
||||
|
|
@ -539,7 +681,7 @@ func (d *Data) GetAllToppingGroups(countryID, filename string) []models.ToppingG
|
|||
}
|
||||
|
||||
d.recipeMap[filename] = RecipeWithTimeStamps{
|
||||
Recipe: d.currentRecipe,
|
||||
Recipe: d.CurrentRecipe,
|
||||
TimeStamps: time.Now().Unix(),
|
||||
}
|
||||
|
||||
|
|
@ -549,17 +691,17 @@ func (d *Data) GetAllToppingGroups(countryID, filename string) []models.ToppingG
|
|||
func (d *Data) GetToppingsList(countryID, filename string) []models.ToppingList {
|
||||
// do return default
|
||||
if countryID == "" {
|
||||
return d.currentRecipe[countryID].Topping.ToppingList
|
||||
return d.CurrentRecipe[countryID].Topping.ToppingList
|
||||
}
|
||||
|
||||
// handle temporary file
|
||||
if !strings.Contains(filename, ".tmp") {
|
||||
if filename == "" || filename == d.CurrentFile[countryID] {
|
||||
return d.currentRecipe[countryID].Topping.ToppingList
|
||||
return d.CurrentRecipe[countryID].Topping.ToppingList
|
||||
}
|
||||
if _, ok := d.recipeMap[countryID]; ok {
|
||||
d.CurrentFile[countryID] = filename
|
||||
return d.currentRecipe[countryID].Topping.ToppingList
|
||||
return d.CurrentRecipe[countryID].Topping.ToppingList
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -567,10 +709,7 @@ func (d *Data) GetToppingsList(countryID, filename string) []models.ToppingList
|
|||
filename = d.CurrentFile[countryID]
|
||||
}
|
||||
|
||||
recipe, err := helpers.ReadRecipeFile(countryID, filename)
|
||||
if err != nil {
|
||||
return d.currentRecipe[countryID].Topping.ToppingList
|
||||
}
|
||||
recipe := d.GetRecipe(countryID, filename)
|
||||
|
||||
if len(d.recipeMap) > 5 { // limit keep in memory 5 version
|
||||
// remove oldest version
|
||||
|
|
@ -586,7 +725,7 @@ func (d *Data) GetToppingsList(countryID, filename string) []models.ToppingList
|
|||
}
|
||||
|
||||
d.recipeMap[filename] = RecipeWithTimeStamps{
|
||||
Recipe: d.currentRecipe,
|
||||
Recipe: d.CurrentRecipe,
|
||||
TimeStamps: time.Now().Unix(),
|
||||
}
|
||||
|
||||
|
|
@ -597,7 +736,7 @@ func (d *Data) GetMaterialCode(ids []uint64, countryID, filename string) []model
|
|||
var result []models.MaterialCode
|
||||
|
||||
if filename == "" || filename == d.CurrentFile[countryID] {
|
||||
result = d.currentRecipe[countryID].MaterialCode
|
||||
result = d.CurrentRecipe[countryID].MaterialCode
|
||||
} else if recipe, ok := d.recipeMap[filename]; ok {
|
||||
d.CurrentFile[countryID] = filename
|
||||
return recipe.Recipe[countryID].MaterialCode
|
||||
|
|
@ -609,14 +748,14 @@ func (d *Data) GetMaterialCode(ids []uint64, countryID, filename string) []model
|
|||
|
||||
// d.CurrentFile[countryID] = filename
|
||||
// d.CurrentCountryID[countryID] = countryID
|
||||
recipe, err := helpers.ReadRecipeFile(countryID, filename)
|
||||
recipe := d.GetRecipe(countryID, filename)
|
||||
|
||||
if err != nil {
|
||||
d.taoLogger.Log.Error("GetMaterialCode: Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
return d.currentRecipe[countryID].MaterialCode
|
||||
}
|
||||
// if err != nil {
|
||||
// d.taoLogger.Log.Error("GetMaterialCode: Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
// return d.CurrentRecipe[countryID].MaterialCode
|
||||
// }
|
||||
|
||||
d.currentRecipe[countryID] = recipe
|
||||
d.CurrentRecipe[countryID] = recipe
|
||||
|
||||
// save to map
|
||||
if len(d.recipeMap) > 5 { // limit keep in memory 5 version
|
||||
|
|
@ -633,11 +772,11 @@ func (d *Data) GetMaterialCode(ids []uint64, countryID, filename string) []model
|
|||
}
|
||||
|
||||
d.recipeMap[filename] = RecipeWithTimeStamps{
|
||||
Recipe: d.currentRecipe,
|
||||
Recipe: d.CurrentRecipe,
|
||||
TimeStamps: time.Now().Unix(),
|
||||
}
|
||||
|
||||
result = d.currentRecipe[countryID].MaterialCode
|
||||
result = d.CurrentRecipe[countryID].MaterialCode
|
||||
}
|
||||
|
||||
if len(ids) == 0 {
|
||||
|
|
@ -664,7 +803,7 @@ func (d *Data) GetMaterialCode(ids []uint64, countryID, filename string) []model
|
|||
func (d *Data) GetToppings(countryID, filename string) models.Topping {
|
||||
|
||||
if filename == "" || filename == d.CurrentFile[countryID] {
|
||||
return d.currentRecipe[countryID].Topping
|
||||
return d.CurrentRecipe[countryID].Topping
|
||||
} else if recipe, ok := d.recipeMap[filename]; ok {
|
||||
d.CurrentFile[countryID] = filename
|
||||
return recipe.Recipe[countryID].Topping
|
||||
|
|
@ -676,14 +815,9 @@ func (d *Data) GetToppings(countryID, filename string) models.Topping {
|
|||
|
||||
// d.CurrentFile[countryID] = filename
|
||||
// d.CurrentCountryID[countryID] = countryID
|
||||
recipe, err := helpers.ReadRecipeFile(countryID, filename)
|
||||
recipe := d.GetRecipe(countryID, filename)
|
||||
|
||||
if err != nil {
|
||||
d.taoLogger.Log.Error("GetToppings: Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
return d.currentRecipe[countryID].Topping
|
||||
}
|
||||
|
||||
d.currentRecipe[countryID] = recipe
|
||||
d.CurrentRecipe[countryID] = recipe
|
||||
|
||||
return recipe.Topping
|
||||
}
|
||||
|
|
|
|||
137
server/data/redis.go
Normal file
137
server/data/redis.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type RedisCli struct {
|
||||
Client *redis.Client
|
||||
}
|
||||
|
||||
func NewRedisClient(address, password string) *RedisCli {
|
||||
|
||||
// option, err := redis.ParseURL("redis://" + username + ":" + password + "@localhost:6379/" + db)
|
||||
|
||||
options := redis.Options{
|
||||
Addr: address,
|
||||
Password: password,
|
||||
DB: 0,
|
||||
}
|
||||
|
||||
client := redis.NewClient(&options)
|
||||
|
||||
if err := client.Ping(context.Background()); err != nil {
|
||||
fmt.Println("trying localhost ...", err)
|
||||
client = redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: password,
|
||||
DB: 0,
|
||||
})
|
||||
|
||||
if err_local := client.Ping(context.Background()); err_local != nil {
|
||||
fmt.Println("> result ====> ", err_local)
|
||||
// do as warning
|
||||
} else {
|
||||
fmt.Println("\n> Localhost Redis OK!\n")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("\n> Redis OK! \n")
|
||||
}
|
||||
|
||||
return &RedisCli{
|
||||
Client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RedisCli) HealthCheck() error {
|
||||
return r.Client.Ping(context.Background()).Err()
|
||||
}
|
||||
|
||||
func (r *RedisCli) GetKeyTo(source string, dest interface{}) error {
|
||||
fmt.Println("Redis> GET ", source)
|
||||
|
||||
// if cannot pass healthcheck, return err
|
||||
if err := r.HealthCheck(); err != nil {
|
||||
fmt.Println("HS> GET error ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
saved, err := r.Client.Get(context.Background(), source).Result()
|
||||
|
||||
// chcek EOF
|
||||
// fmt.Println("GET last char ", saved[len(saved)-1:])
|
||||
|
||||
if saved == "" || err != nil {
|
||||
fmt.Println("GET error (empty on error)|", err, "|while saved=", saved)
|
||||
return err
|
||||
}
|
||||
|
||||
// fmt.Println("GET ", saved)
|
||||
|
||||
// if err != nil {
|
||||
// fmt.Println("GET error ", err)
|
||||
// return err
|
||||
// }
|
||||
err = json.NewDecoder(bytes.NewBufferString(saved)).Decode(dest)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("GET error ", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *RedisCli) SetToKey(key string, value interface{}) error {
|
||||
fmt.Println("Redis> SET ", key)
|
||||
|
||||
// if cannot pass healthcheck, return err
|
||||
if err := r.HealthCheck(); err != nil {
|
||||
fmt.Println("HS> SET error ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
saved, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
fmt.Println("SET error ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// late error
|
||||
err = r.Client.Set(context.Background(), key, saved, redis.KeepTTL).Err()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("error on SET ", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *RedisCli) ExpireKey(key string) error {
|
||||
|
||||
// if cannot pass healthcheck, return err
|
||||
if err := r.HealthCheck(); err != nil {
|
||||
fmt.Println("HS> EXPIRE error ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return r.Client.Expire(context.Background(), key, 0).Err()
|
||||
}
|
||||
|
||||
func (r *RedisCli) SetKeyTimeout(key string, value interface{}, timeout int) error {
|
||||
|
||||
// if cannot pass healthcheck, return err
|
||||
if err := r.HealthCheck(); err != nil {
|
||||
fmt.Println("HS> EXPIRE error ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err := r.Client.Expire(context.Background(), key, time.Duration(timeout)*time.Second).Err()
|
||||
fmt.Println("error on EXPIRE ", err)
|
||||
return err
|
||||
}
|
||||
|
|
@ -18,6 +18,12 @@ require (
|
|||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/redis/go-redis/v9 v9.4.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
|||
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40 h1:y4B3+GPxKlrigF1ha5FFErxK+sr6sWxQovRMzwMhejo=
|
||||
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
|
|
@ -58,6 +60,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
|
|
@ -188,6 +192,8 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
|
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
|
||||
github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
|
|
|
|||
|
|
@ -57,6 +57,19 @@ type MaterialSetting struct {
|
|||
RawMaterialUnit string `json:"RawMaterialUnit"`
|
||||
}
|
||||
|
||||
func (r *MaterialSetting) ToMap() map[string]interface{} {
|
||||
var m map[string]interface{}
|
||||
recipeRecord, _ := json.Marshal(r)
|
||||
json.Unmarshal(recipeRecord, &m)
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *MaterialSetting) FromMap(m MaterialSetting) MaterialSetting {
|
||||
recipeRecord, _ := json.Marshal(m)
|
||||
json.Unmarshal(recipeRecord, &r)
|
||||
return *r
|
||||
}
|
||||
|
||||
type Recipe01 struct {
|
||||
Description string `json:"Description"`
|
||||
ExtendID int `json:"ExtendID"`
|
||||
|
|
@ -140,6 +153,19 @@ type ToppingGroup struct {
|
|||
OtherName string `json:"otherName"`
|
||||
}
|
||||
|
||||
func (r *ToppingGroup) ToMap() map[string]interface{} {
|
||||
var m map[string]interface{}
|
||||
recipeRecord, _ := json.Marshal(r)
|
||||
json.Unmarshal(recipeRecord, &m)
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *ToppingGroup) FromMap(m map[string]interface{}) ToppingGroup {
|
||||
recipeRecord, _ := json.Marshal(m)
|
||||
json.Unmarshal(recipeRecord, &r)
|
||||
return *r
|
||||
}
|
||||
|
||||
type ToppingList struct {
|
||||
ExtendID int `json:"ExtendID"`
|
||||
OnTOP bool `json:"OnTOP"`
|
||||
|
|
@ -163,3 +189,16 @@ type ToppingList struct {
|
|||
UseGram bool `json:"useGram"`
|
||||
Weight_float int `json:"weight_float"`
|
||||
}
|
||||
|
||||
func (r *ToppingList) ToMap() map[string]interface{} {
|
||||
var m map[string]interface{}
|
||||
recipeRecord, _ := json.Marshal(r)
|
||||
json.Unmarshal(recipeRecord, &m)
|
||||
return m
|
||||
}
|
||||
|
||||
func (r *ToppingList) FromMap(m map[string]interface{}) ToppingList {
|
||||
recipeRecord, _ := json.Marshal(m)
|
||||
json.Unmarshal(recipeRecord, &r)
|
||||
return *r
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,12 @@ func (mr *MaterialRouter) GetFullMaterialDetail(w http.ResponseWriter, r *http.R
|
|||
matSettings := mr.data.GetMaterialSetting(country, filename)
|
||||
matCodes := mr.data.GetRecipe(country, filename).MaterialCode
|
||||
|
||||
if len(matSettings) == 0 {
|
||||
// mr.taoLogger.Log.Error("MaterialRouter.GetFullMaterialDetail", zap.Error(err))
|
||||
http.Error(w, "Material not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// combine
|
||||
materialDetails := []map[string]interface{}{}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type RecipeRouter struct {
|
|||
sheetService sheet.SheetService
|
||||
recipeService recipe.RecipeService
|
||||
taoLogger *logger.TaoLogger
|
||||
cache_db *data.RedisCli
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -37,12 +38,13 @@ var (
|
|||
updateMutex = sync.Mutex{}
|
||||
)
|
||||
|
||||
func NewRecipeRouter(data *data.Data, recipeService recipe.RecipeService, sheetService sheet.SheetService, taoLogger *logger.TaoLogger) *RecipeRouter {
|
||||
func NewRecipeRouter(data *data.Data, recipeService recipe.RecipeService, sheetService sheet.SheetService, taoLogger *logger.TaoLogger, cache *data.RedisCli) *RecipeRouter {
|
||||
return &RecipeRouter{
|
||||
data,
|
||||
sheetService,
|
||||
recipeService,
|
||||
taoLogger,
|
||||
cache,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -460,6 +462,7 @@ func (rr *RecipeRouter) updateRecipe(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// gen hash
|
||||
commit_hash, err := data.HashCommit(8)
|
||||
rr.cache_db.SetToKey(commit_hash, targetRecipe)
|
||||
|
||||
commit := data.CommitLog{
|
||||
|
||||
|
|
@ -501,6 +504,34 @@ func (rr *RecipeRouter) updateRecipe(w http.ResponseWriter, r *http.Request) {
|
|||
})
|
||||
}
|
||||
|
||||
func (rr *RecipeRouter) updateMaterialSetting(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
country := r.URL.Query().Get("country")
|
||||
filename := r.URL.Query().Get("filename")
|
||||
|
||||
rr.taoLogger.Log.Debug("RecipeRouter.updateMaterialSetting", zap.Any("country", country), zap.Any("filename", filename))
|
||||
|
||||
countryID, err := rr.data.GetCountryIDByName(country)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Country Name: %s not found!!!", country), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
updateMutex.Lock()
|
||||
defer updateMutex.Unlock()
|
||||
// get material setting
|
||||
materialSetting := rr.data.GetMaterialSetting(countryID, filename)
|
||||
|
||||
if len(materialSetting) == 0 {
|
||||
http.Error(w, fmt.Sprintf("Recipe country: %s file: %s found empty settings.", country, filename), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: create commit and set change
|
||||
|
||||
}
|
||||
|
||||
func (rr *RecipeRouter) getSavedRecipes(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
file_version := chi.URLParam(r, "filename_version_only")
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type Server struct {
|
|||
server *http.Server
|
||||
data *data.Data
|
||||
database *sqlx.DB
|
||||
cache_db *data.RedisCli
|
||||
cfg *config.ServerConfig
|
||||
oauth oauth.OAuthService
|
||||
taoLogger *logger.TaoLogger
|
||||
|
|
@ -40,12 +41,15 @@ func NewServer(cfg *config.ServerConfig, oauthService oauth.OAuthService) *Serve
|
|||
taoLogger := logger.NewTaoLogger(cfg)
|
||||
taoLogger.Log = taoLogger.Log.Named("Server")
|
||||
|
||||
redisClient := data.NewRedisClient("redis:6379", "")
|
||||
|
||||
return &Server{
|
||||
server: &http.Server{Addr: fmt.Sprintf(":%d", cfg.ServerPort)},
|
||||
data: data.NewData(taoLogger),
|
||||
data: data.NewData(taoLogger, redisClient),
|
||||
database: data.NewSqliteDatabase(),
|
||||
cache_db: redisClient,
|
||||
cfg: cfg,
|
||||
oauth: oauthService,
|
||||
oauth: oauth.NewOAuthService(cfg),
|
||||
taoLogger: taoLogger,
|
||||
}
|
||||
}
|
||||
|
|
@ -98,6 +102,12 @@ func (s *Server) createHandler() {
|
|||
ar.Route(r)
|
||||
})
|
||||
|
||||
// Initial redis
|
||||
for k, v := range s.data.CurrentRecipe {
|
||||
s.taoLogger.Log.Debug("Caching", zap.Any("Recipe", k))
|
||||
s.cache_db.SetToKey(k, v)
|
||||
}
|
||||
|
||||
// Protected Group
|
||||
r.Group(func(r chi.Router) {
|
||||
|
||||
|
|
@ -113,7 +123,7 @@ func (s *Server) createHandler() {
|
|||
}
|
||||
|
||||
// Recipe Router
|
||||
rr := routers.NewRecipeRouter(s.data, recipeService, sheetService, s.taoLogger)
|
||||
rr := routers.NewRecipeRouter(s.data, recipeService, sheetService, s.taoLogger, s.cache_db)
|
||||
rr.Route(r)
|
||||
|
||||
// Material Router
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue