diff --git a/backend/app/api/routes/archives.py b/backend/app/api/routes/archives.py index 6a43696f5..51475e0ee 100644 --- a/backend/app/api/routes/archives.py +++ b/backend/app/api/routes/archives.py @@ -2,6 +2,7 @@ import io import json import logging import zipfile +from decimal import ROUND_HALF_UP, Decimal from pathlib import Path from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, Request, UploadFile @@ -865,18 +866,26 @@ async def rescan_archive( ) usage_cost = usage_result.scalar() if usage_cost is not None and usage_cost > 0: - archive.cost = round(usage_cost, 2) + archive.cost = float(Decimal(str(usage_cost)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)) else: primary_type = archive.filament_type.split(",")[0].strip() filament_result = await db.execute(select(Filament).where(Filament.type == primary_type).limit(1)) filament = filament_result.scalar_one_or_none() if filament: - archive.cost = round((archive.filament_used_grams / 1000) * filament.cost_per_kg, 2) + archive.cost = float( + Decimal(str((archive.filament_used_grams / 1000) * filament.cost_per_kg)).quantize( + Decimal("0.01"), rounding=ROUND_HALF_UP + ) + ) else: # Use default filament cost from settings default_cost_setting = await get_setting(db, "default_filament_cost") default_cost_per_kg = float(default_cost_setting) if default_cost_setting else 25.0 - archive.cost = round((archive.filament_used_grams / 1000) * default_cost_per_kg, 2) + archive.cost = float( + Decimal(str((archive.filament_used_grams / 1000) * default_cost_per_kg)).quantize( + Decimal("0.01"), rounding=ROUND_HALF_UP + ) + ) await db.commit() await db.refresh(archive) diff --git a/frontend/src/components/PrintModal/FilamentMapping.tsx b/frontend/src/components/PrintModal/FilamentMapping.tsx index e9a31227e..452a37ce5 100644 --- a/frontend/src/components/PrintModal/FilamentMapping.tsx +++ b/frontend/src/components/PrintModal/FilamentMapping.tsx @@ -46,9 +46,7 @@ export function FilamentMapping({ const map = new Map(); for (const assignment of assignments || []) { const isExternal = assignment.ams_id === 255; - const globalTrayId = isExternal - ? 254 + assignment.tray_id - : getGlobalTrayId(assignment.ams_id, assignment.tray_id, false); + const globalTrayId = getGlobalTrayId(assignment.ams_id, assignment.tray_id, isExternal); map.set(globalTrayId, assignment.spool?.cost_per_kg ?? null); } return map; @@ -216,9 +214,23 @@ export function FilamentMapping({ )} ))} -
- {t('printModal.totalCost')} {currencySymbol}{totalCost.toFixed(2)} -
+ {(() => { + // Check if any tray has a configured cost_per_kg + const hasAnyCost = Array.from(trayCostMap.values()).some((v) => v != null && v > 0); + if (totalCost > 0 || hasAnyCost) { + return ( +
+ {t('printModal.totalCost')} {currencySymbol}{totalCost.toFixed(2)} +
+ ); + } else { + return ( +
+ {t('printModal.totalCost')} N/A +
+ ); + } + })()} {hasTypeMismatch && (

Required filament type not found in printer.

)} diff --git a/frontend/src/components/SpoolFormModal.tsx b/frontend/src/components/SpoolFormModal.tsx index 9d9df963f..8407aebe3 100644 --- a/frontend/src/components/SpoolFormModal.tsx +++ b/frontend/src/components/SpoolFormModal.tsx @@ -22,9 +22,10 @@ interface SpoolFormModalProps { onClose: () => void; spool?: InventorySpool | null; printersWithCalibrations?: PrinterWithCalibrations[]; + currencySymbol: string; } -export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibrations = [] }: SpoolFormModalProps) { +export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibrations = [], currencySymbol }: SpoolFormModalProps) { const { t } = useTranslation(); const queryClient = useQueryClient(); const { showToast } = useToast(); @@ -471,6 +472,7 @@ export function SpoolFormModal({ isOpen, onClose, spool, printersWithCalibration formData={formData} updateField={updateField} spoolCatalog={spoolCatalog} + currencySymbol={currencySymbol} /> diff --git a/frontend/src/components/spool-form/AdditionalSection.tsx b/frontend/src/components/spool-form/AdditionalSection.tsx index aa64efa29..c6277c6f1 100644 --- a/frontend/src/components/spool-form/AdditionalSection.tsx +++ b/frontend/src/components/spool-form/AdditionalSection.tsx @@ -172,6 +172,7 @@ export function AdditionalSection({ formData, updateField, spoolCatalog, + currencySymbol, }: AdditionalSectionProps) { const { t } = useTranslation(); const { showToast } = useToast(); @@ -264,6 +265,7 @@ export function AdditionalSection({
+ {currencySymbol}
diff --git a/frontend/src/components/spool-form/types.ts b/frontend/src/components/spool-form/types.ts index 174634a47..069f8e197 100644 --- a/frontend/src/components/spool-form/types.ts +++ b/frontend/src/components/spool-form/types.ts @@ -91,6 +91,7 @@ export interface ColorSectionProps extends SectionProps { // Additional section props export interface AdditionalSectionProps extends SectionProps { spoolCatalog: { id: number; name: string; weight: number }[]; + currencySymbol: string; } // PA Profile section props diff --git a/frontend/src/pages/ArchivesPage.tsx b/frontend/src/pages/ArchivesPage.tsx index 226f94c27..9d28c2d52 100644 --- a/frontend/src/pages/ArchivesPage.tsx +++ b/frontend/src/pages/ArchivesPage.tsx @@ -923,7 +923,7 @@ function ArchiveCard({ {archive.filament_used_grams.toFixed(1)}g )} - {archive.cost != null && archive.cost > 0 && ( + {archive.cost != null && (
{currency}{archive.cost.toFixed(2)} diff --git a/frontend/src/pages/InventoryPage.tsx b/frontend/src/pages/InventoryPage.tsx index 2eff3474d..c778f7c81 100644 --- a/frontend/src/pages/InventoryPage.tsx +++ b/frontend/src/pages/InventoryPage.tsx @@ -1068,6 +1068,7 @@ export default function InventoryPage() { isOpen={true} onClose={() => setFormModal(null)} spool={formModal.spool} + currencySymbol={currencySymbol} /> )}