fix(back-end) calculator improvements
This commit is contained in:
@@ -208,9 +208,31 @@ export class CalculatorPageComponent implements OnInit {
|
||||
|
||||
// 2. Update backend session if ID exists
|
||||
if (event.id) {
|
||||
const currentSessionId = this.result()?.sessionId;
|
||||
if (!currentSessionId) return;
|
||||
|
||||
this.loading.set(true);
|
||||
this.estimator.updateLineItem(event.id, { quantity: event.quantity }).subscribe({
|
||||
next: (res) => console.log('Line item updated', res),
|
||||
error: (err) => console.error('Failed to update line item', err)
|
||||
next: () => {
|
||||
// 3. Fetch the updated session totals from the backend
|
||||
this.estimator.getQuoteSession(currentSessionId).subscribe({
|
||||
next: (sessionData) => {
|
||||
const newResult = this.estimator.mapSessionToQuoteResult(sessionData);
|
||||
// Preserve notes
|
||||
newResult.notes = this.result()?.notes;
|
||||
this.result.set(newResult);
|
||||
this.loading.set(false);
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Failed to refresh session totals', err);
|
||||
this.loading.set(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Failed to update line item', err);
|
||||
this.loading.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
</div>
|
||||
|
||||
<div class="setup-note">
|
||||
<small>{{ 'CALC.SETUP_NOTE' | translate:{cost: (result().setupCost | currency:result().currency)} }}</small>
|
||||
<small>{{ 'CALC.SETUP_NOTE' | translate:{cost: (result().setupCost | currency:result().currency)} }}</small><br>
|
||||
<small class="shipping-note" style="color: #666;">{{ 'CALC.SHIPPING_NOTE' | translate }}</small>
|
||||
</div>
|
||||
|
||||
@if (result().notes) {
|
||||
|
||||
@@ -10,11 +10,15 @@ import { StlViewerComponent } from '../../../../shared/components/stl-viewer/stl
|
||||
import { ColorSelectorComponent } from '../../../../shared/components/color-selector/color-selector.component';
|
||||
import { QuoteRequest, QuoteEstimatorService, OptionsResponse, SimpleOption, MaterialOption, VariantOption } from '../../services/quote-estimator.service';
|
||||
import { getColorHex } from '../../../../core/constants/colors.const';
|
||||
import * as THREE from 'three';
|
||||
// @ts-ignore
|
||||
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
|
||||
|
||||
interface FormItem {
|
||||
file: File;
|
||||
quantity: number;
|
||||
color: string;
|
||||
dimensions?: {x: number, y: number, z: number};
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -69,6 +73,35 @@ export class UploadFormComponent implements OnInit {
|
||||
return name.endsWith('.stl');
|
||||
}
|
||||
|
||||
private async getStlDimensions(file: File): Promise<{x: number, y: number, z: number} | null> {
|
||||
return new Promise((resolve) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const loader = new STLLoader();
|
||||
const geometry = loader.parse(e.target?.result as ArrayBuffer);
|
||||
geometry.computeBoundingBox();
|
||||
if (geometry.boundingBox) {
|
||||
const size = new THREE.Vector3();
|
||||
geometry.boundingBox.getSize(size);
|
||||
resolve({
|
||||
x: Math.round(size.x * 10) / 10,
|
||||
y: Math.round(size.y * 10) / 10,
|
||||
z: Math.round(size.z * 10) / 10
|
||||
});
|
||||
return;
|
||||
}
|
||||
resolve(null);
|
||||
} catch (err) {
|
||||
console.error("Error parsing STL for dimensions:", err);
|
||||
resolve(null);
|
||||
}
|
||||
};
|
||||
reader.onerror = () => resolve(null);
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.form = this.fb.group({
|
||||
itemsTouched: [false], // Hack to track touched state for custom items list
|
||||
@@ -77,7 +110,7 @@ export class UploadFormComponent implements OnInit {
|
||||
items: [[]], // Track items in form for validation if needed
|
||||
notes: [''],
|
||||
// Advanced fields
|
||||
infillDensity: [20, [Validators.min(0), Validators.max(100)]],
|
||||
infillDensity: [15, [Validators.min(0), Validators.max(100)]],
|
||||
layerHeight: [0.2, [Validators.min(0.05), Validators.max(1.0)]],
|
||||
nozzleDiameter: [0.4, Validators.required],
|
||||
infillPattern: ['grid'],
|
||||
@@ -136,7 +169,7 @@ export class UploadFormComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
onFilesDropped(newFiles: File[]) {
|
||||
async onFilesDropped(newFiles: File[]) {
|
||||
const MAX_SIZE = 200 * 1024 * 1024; // 200MB
|
||||
const validItems: FormItem[] = [];
|
||||
let hasError = false;
|
||||
@@ -145,8 +178,13 @@ export class UploadFormComponent implements OnInit {
|
||||
if (file.size > MAX_SIZE) {
|
||||
hasError = true;
|
||||
} else {
|
||||
let dimensions = undefined;
|
||||
if (file.name.toLowerCase().endsWith('.stl')) {
|
||||
const dims = await this.getStlDimensions(file);
|
||||
if (dims) dimensions = dims;
|
||||
}
|
||||
// Default color is Black
|
||||
validItems.push({ file, quantity: 1, color: 'Black' });
|
||||
validItems.push({ file, quantity: 1, color: 'Black', dimensions });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,11 +276,16 @@ export class UploadFormComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
setFiles(files: File[]) {
|
||||
async setFiles(files: File[]) {
|
||||
const validItems: FormItem[] = [];
|
||||
for (const file of files) {
|
||||
let dimensions = undefined;
|
||||
if (file.name.toLowerCase().endsWith('.stl')) {
|
||||
const dims = await this.getStlDimensions(file);
|
||||
if (dims) dimensions = dims;
|
||||
}
|
||||
// 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: 'Black', dimensions });
|
||||
}
|
||||
|
||||
if (validItems.length > 0) {
|
||||
|
||||
@@ -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, dimensions?: {x: number, y: number, z: number} }[];
|
||||
material: string;
|
||||
quality: string;
|
||||
notes?: string;
|
||||
@@ -32,6 +32,7 @@ export interface QuoteResult {
|
||||
sessionId?: string;
|
||||
items: QuoteItem[];
|
||||
setupCost: number;
|
||||
globalMachineCost: number;
|
||||
currency: string;
|
||||
totalPrice: number;
|
||||
totalTimeHours: number;
|
||||
@@ -219,6 +220,9 @@ export class QuoteEstimatorService {
|
||||
quality: request.quality,
|
||||
supportsEnabled: request.supportEnabled,
|
||||
color: item.color || '#FFFFFF',
|
||||
boundingBoxX: item.dimensions?.x,
|
||||
boundingBoxY: item.dimensions?.y,
|
||||
boundingBoxZ: item.dimensions?.z,
|
||||
layerHeight: request.mode === 'advanced' ? request.layerHeight : null,
|
||||
infillDensity: request.mode === 'advanced' ? request.infillDensity : null,
|
||||
infillPattern: request.mode === 'advanced' ? request.infillPattern : null,
|
||||
@@ -260,59 +264,19 @@ export class QuoteEstimatorService {
|
||||
});
|
||||
|
||||
const finalize = (responses: any[], setupCost: number, sessionId: string) => {
|
||||
observer.next(100);
|
||||
const items: QuoteItem[] = [];
|
||||
let grandTotal = 0;
|
||||
let totalTime = 0;
|
||||
let totalWeight = 0;
|
||||
let validCount = 0;
|
||||
|
||||
responses.forEach((res, idx) => {
|
||||
if (!res || !res.success) return;
|
||||
validCount++;
|
||||
|
||||
const unitPrice = res.unitPriceChf || 0;
|
||||
const quantity = res.originalQty || 1;
|
||||
|
||||
items.push({
|
||||
id: res.id,
|
||||
fileName: res.fileName,
|
||||
unitPrice: unitPrice,
|
||||
unitTime: res.printTimeSeconds || 0,
|
||||
unitWeight: res.materialGrams || 0,
|
||||
quantity: quantity,
|
||||
material: request.material,
|
||||
color: res.originalItem.color || 'Default'
|
||||
// Store ID if needed for updates? QuoteItem interface might need update
|
||||
// or we map it in component
|
||||
});
|
||||
|
||||
grandTotal += unitPrice * quantity;
|
||||
totalTime += (res.printTimeSeconds || 0) * quantity;
|
||||
totalWeight += (res.materialGrams || 0) * quantity;
|
||||
this.http.get<any>(`${environment.apiUrl}/api/quote-sessions/${sessionId}`, { headers }).subscribe({
|
||||
next: (sessionData) => {
|
||||
observer.next(100);
|
||||
const result = this.mapSessionToQuoteResult(sessionData);
|
||||
result.notes = request.notes;
|
||||
observer.next(result);
|
||||
observer.complete();
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Failed to fetch final session calculation', err);
|
||||
observer.error('Failed to calculate final quote');
|
||||
}
|
||||
});
|
||||
|
||||
if (validCount === 0) {
|
||||
observer.error('All calculations failed.');
|
||||
return;
|
||||
}
|
||||
|
||||
grandTotal += setupCost;
|
||||
|
||||
const result: QuoteResult = {
|
||||
sessionId: sessionId,
|
||||
items,
|
||||
setupCost: setupCost,
|
||||
currency: 'CHF',
|
||||
totalPrice: Math.round(grandTotal * 100) / 100,
|
||||
totalTimeHours: Math.floor(totalTime / 3600),
|
||||
totalTimeMinutes: Math.ceil((totalTime % 3600) / 60),
|
||||
totalWeight: Math.ceil(totalWeight),
|
||||
notes: request.notes
|
||||
};
|
||||
|
||||
observer.next(result);
|
||||
observer.complete();
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -361,10 +325,11 @@ export class QuoteEstimatorService {
|
||||
// But line items might have different colors.
|
||||
color: item.colorCode
|
||||
})),
|
||||
setupCost: session.setupCostChf,
|
||||
currency: 'CHF', // Fixed for now
|
||||
totalPrice: sessionData.grandTotalChf,
|
||||
totalTimeHours: Math.floor(totalTime / 3600),
|
||||
setupCost: session.setupCostChf || 0,
|
||||
globalMachineCost: sessionData.globalMachineCostChf || 0,
|
||||
currency: 'CHF', // Fixed for now
|
||||
totalPrice: (sessionData.itemsTotalChf || 0) + (session.setupCostChf || 0) + (sessionData.shippingCostChf || 0),
|
||||
totalTimeHours: Math.floor(totalTime / 3600),
|
||||
totalTimeMinutes: Math.ceil((totalTime % 3600) / 60),
|
||||
totalWeight: Math.ceil(totalWeight),
|
||||
notes: session.notes
|
||||
|
||||
Reference in New Issue
Block a user