feat(back-end): new stock db and back-office improvements

This commit is contained in:
2026-03-02 20:19:19 +01:00
parent 02e58ea00f
commit b7c399e3cb
39 changed files with 1605 additions and 257 deletions

View File

@@ -135,7 +135,10 @@ export class CalculatorPageComponent implements OnInit {
// Assuming index matches.
// Need to be careful if items order changed, but usually ID sort or insert order.
if (item.colorCode) {
this.uploadForm.updateItemColor(index, item.colorCode);
this.uploadForm.updateItemColor(index, {
colorName: item.colorCode,
filamentVariantId: item.filamentVariantId
});
}
});
}

View File

@@ -54,6 +54,7 @@
<label>{{ 'CALC.COLOR_LABEL' | translate }}</label>
<app-color-selector
[selectedColor]="item.color"
[selectedVariantId]="item.filamentVariantId ?? null"
[variants]="currentMaterialVariants()"
(colorSelected)="updateItemColor(i, $event)">
</app-color-selector>

View File

@@ -15,6 +15,7 @@ interface FormItem {
file: File;
quantity: number;
color: string;
filamentVariantId?: number;
}
@Component({
@@ -58,6 +59,7 @@ export class UploadFormComponent implements OnInit {
if (matCode && this.fullMaterialOptions.length > 0) {
const found = this.fullMaterialOptions.find(m => m.code === matCode);
this.currentMaterialVariants.set(found ? found.variants : []);
this.syncItemVariantSelections();
} else {
this.currentMaterialVariants.set([]);
}
@@ -166,8 +168,13 @@ export class UploadFormComponent implements OnInit {
if (file.size > MAX_SIZE) {
hasError = true;
} else {
// Default color is Black
validItems.push({ file, quantity: 1, color: 'Black' });
const defaultSelection = this.getDefaultVariantSelection();
validItems.push({
file,
quantity: 1,
color: defaultSelection.colorName,
filamentVariantId: defaultSelection.filamentVariantId
});
}
}
@@ -220,7 +227,9 @@ export class UploadFormComponent implements OnInit {
if (item) {
const vars = this.currentMaterialVariants();
if (vars && vars.length > 0) {
const found = vars.find(v => v.colorName === item.color);
const found = item.filamentVariantId
? vars.find(v => v.id === item.filamentVariantId)
: vars.find(v => v.colorName === item.color);
if (found) return found.hexColor;
}
return getColorHex(item.color);
@@ -240,10 +249,12 @@ export class UploadFormComponent implements OnInit {
});
}
updateItemColor(index: number, newColor: string) {
updateItemColor(index: number, newSelection: string | { colorName: string; filamentVariantId?: number }) {
const colorName = typeof newSelection === 'string' ? newSelection : newSelection.colorName;
const filamentVariantId = typeof newSelection === 'string' ? undefined : newSelection.filamentVariantId;
this.items.update(current => {
const updated = [...current];
updated[index] = { ...updated[index], color: newColor };
updated[index] = { ...updated[index], color: colorName, filamentVariantId };
return updated;
});
}
@@ -261,9 +272,14 @@ export class UploadFormComponent implements OnInit {
setFiles(files: File[]) {
const validItems: FormItem[] = [];
const defaultSelection = this.getDefaultVariantSelection();
for (const file of files) {
// Default color is Black or derive from somewhere if possible, but here we just init
validItems.push({ file, quantity: 1, color: 'Black' });
validItems.push({
file,
quantity: 1,
color: defaultSelection.colorName,
filamentVariantId: defaultSelection.filamentVariantId
});
}
if (validItems.length > 0) {
@@ -274,6 +290,39 @@ export class UploadFormComponent implements OnInit {
}
}
private getDefaultVariantSelection(): { colorName: string; filamentVariantId?: number } {
const vars = this.currentMaterialVariants();
if (vars && vars.length > 0) {
const preferred = vars.find(v => !v.isOutOfStock) || vars[0];
return {
colorName: preferred.colorName,
filamentVariantId: preferred.id
};
}
return { colorName: 'Black' };
}
private syncItemVariantSelections(): void {
const vars = this.currentMaterialVariants();
if (!vars || vars.length === 0) {
return;
}
const fallback = vars.find(v => !v.isOutOfStock) || vars[0];
this.items.update(current => current.map(item => {
const byId = item.filamentVariantId != null
? vars.find(v => v.id === item.filamentVariantId)
: null;
const byColor = vars.find(v => v.colorName === item.color);
const selected = byId || byColor || fallback;
return {
...item,
color: selected.colorName,
filamentVariantId: selected.id
};
}));
}
patchSettings(settings: any) {
if (!settings) return;
// settings object matches keys in our form?

View File

@@ -5,7 +5,7 @@ import { map, catchError, tap } from 'rxjs/operators';
import { environment } from '../../../../environments/environment';
export interface QuoteRequest {
items: { file: File, quantity: number, color?: string }[];
items: { file: File, quantity: number, color?: string, filamentVariantId?: number }[];
material: string;
quality: string;
notes?: string;
@@ -26,6 +26,7 @@ export interface QuoteItem {
quantity: number;
material?: string;
color?: string;
filamentVariantId?: number;
}
export interface QuoteResult {
@@ -72,9 +73,13 @@ export interface MaterialOption {
variants: VariantOption[];
}
export interface VariantOption {
id: number;
name: string;
colorName: string;
hexColor: string;
finishType: string;
stockSpools: number;
stockFilamentGrams: number;
isOutOfStock: boolean;
}
export interface QualityOption {
@@ -250,6 +255,7 @@ export class QuoteEstimatorService {
const settings = {
complexityMode: request.mode === 'easy' ? 'ADVANCED' : request.mode.toUpperCase(),
material: request.material,
filamentVariantId: item.filamentVariantId,
quality: easyPreset ? easyPreset.quality : request.quality,
supportsEnabled: request.supportEnabled,
color: item.color || '#FFFFFF',
@@ -351,7 +357,8 @@ export class QuoteEstimatorService {
material: session.materialCode, // Assumption: session has one material for all? or items have it?
// Backend model QuoteSession has materialCode.
// But line items might have different colors.
color: item.colorCode
color: item.colorCode,
filamentVariantId: item.filamentVariantId
})),
setupCost: session.setupCostChf || 0,
globalMachineCost: sessionData.globalMachineCostChf || 0,