Просмотр исходного кода

Add currency symbol, hide projected cost for no costs configured, add cost rounding to decimal point, fixed isExternal logic

Matteo Parenti 6 месяцев назад
Родитель
Сommit
9f19e8a2ff

+ 12 - 3
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)

+ 18 - 6
frontend/src/components/PrintModal/FilamentMapping.tsx

@@ -46,9 +46,7 @@ export function FilamentMapping({
     const map = new Map<number, number | null>();
     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({
               )}
             </div>
           ))}
-          <div className="text-xs text-bambu-gray">
-            {t('printModal.totalCost')} <span className="text-white">{currencySymbol}{totalCost.toFixed(2)}</span>
-          </div>
+          {(() => {
+            // 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 (
+                <div className="text-xs text-bambu-gray">
+                  {t('printModal.totalCost')} <span className="text-white">{currencySymbol}{totalCost.toFixed(2)}</span>
+                </div>
+              );
+            } else {
+              return (
+                <div className="text-xs text-bambu-gray">
+                  {t('printModal.totalCost')} <span className="text-white">N/A</span>
+                </div>
+              );
+            }
+          })()}
           {hasTypeMismatch && (
             <p className="text-xs text-orange-400 mt-2">Required filament type not found in printer.</p>
           )}

+ 3 - 1
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}
                 />
               </div>
 

+ 4 - 1
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({
         <label className="block text-sm font-medium text-bambu-gray mb-1">{t('inventory.costPerKg', 'Cost per kg')}</label>
         <div className="flex items-center gap-2">
           <div className="relative flex-1">
+            <span className="absolute left-3 top-1/2 -translate-y-1/2 text-bambu-gray text-sm pointer-events-none">{currencySymbol}</span>
             <input
               type="number"
               value={formData.cost_per_kg ?? ''}
@@ -274,7 +276,8 @@ export function AdditionalSection({
                 const value = e.target.value === '' ? null : parseFloat(e.target.value);
                 updateField('cost_per_kg', value);
               }}
-              className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm focus:outline-none focus:border-bambu-green"
+              style={{ paddingLeft: `${Math.max(2, currencySymbol.length * 0.6 + 1)}rem` }}
+              className="w-full py-2 pr-3 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm focus:outline-none focus:border-bambu-green"
             />
           </div>
         </div>

+ 1 - 0
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

+ 1 - 1
frontend/src/pages/ArchivesPage.tsx

@@ -923,7 +923,7 @@ function ArchiveCard({
               {archive.filament_used_grams.toFixed(1)}g
             </div>
           )}
-          {archive.cost != null && archive.cost > 0 && (
+          {archive.cost != null && (
             <div className="flex items-center gap-1.5 text-bambu-gray">
               <Coins className="w-3 h-3" />
               {currency}{archive.cost.toFixed(2)}

+ 1 - 0
frontend/src/pages/InventoryPage.tsx

@@ -1068,6 +1068,7 @@ export default function InventoryPage() {
           isOpen={true}
           onClose={() => setFormModal(null)}
           spool={formModal.spool}
+          currencySymbol={currencySymbol}
         />
       )}