Files
tilbudgivern/docs/features/MATERIALE_PRIS_FIX.md
alexpolo1 bdb9e524db Implement manual and automated tests for auto-save countdown feature
- Added a comprehensive manual testing guide for the countdown functionality in `test_countdown_manual.md`.
- Developed a detailed Selenium test suite in `test_countdown_selenium.py` to verify countdown behavior, including server accessibility, login, countdown progression, and UI elements.
- Created a minimal Selenium test version in `test_countdown_selenium_minimal.py` for quick checks without complex setup.
- Introduced a verification script in `test_countdown_verification.py` to analyze code correctness, including server checks, build file existence, and countdown logic.
2025-11-14 11:28:58 +00:00

5.6 KiB

Materiale Pris Integration Fix

Dato: 2024-11-07

Status: Færdig

Problem

Når man tilføjer et materiale i SmartPackageForm via "Tilføj materiale" knappen, viste prisen altid 0.00 kr selvom materialer har priser i databasen.

Symptomer

  • MaterialDialog viste "Standardpris: 0.00 kr/stk"
  • Effektiv pris viste også 0.00 kr/stk
  • Brugerdefineret pris skulle indtastes manuelt

Root Cause

/api/materials endpoint hentede kun data fra materials tabellen uden at JOIN'e med material_prices tabellen hvor priserne faktisk er gemt.

Løsning

Backend Ændringer

Fil: /backend/unified-server.js

Ændring 1: Tilføjet JOIN med material_prices

const materials = await databaseService.query(
  `SELECT 
    m.*,
    mp.price,
    mp.unit as price_unit,
    mp.valid_from,
    mp.supplier_name as price_supplier
  FROM materials m
  LEFT JOIN material_prices mp ON m.id = mp.material_id 
    AND mp.is_active = 1
    AND mp.id = (
      SELECT id 
      FROM material_prices 
      WHERE material_id = m.id 
        AND is_active = 1 
      ORDER BY valid_from DESC, id DESC 
      LIMIT 1
    )
  ${whereClause} 
  ORDER BY m.name ASC 
  LIMIT ? OFFSET ?`,
  [...params, parseInt(limit), parseInt(offset)]
);

Ændring 2: Rettede ambiguous column names

// FØR:
if (search) {
  whereClause += ' AND (name LIKE ? OR description LIKE ? OR sku LIKE ?)';
}

// EFTER:
if (search) {
  whereClause += ' AND (m.name LIKE ? OR m.description LIKE ? OR m.sku LIKE ?)';
}

Hvordan det virker

  1. LEFT JOIN: Alle materialer vises, også dem uden priser
  2. Subquery: Finder den nyeste aktive pris for hvert materiale
  3. ORDER BY valid_from DESC: Bruger den senest gyldige pris
  4. is_active = 1: Kun aktive priser inkluderes

Database Schema

Tabeller involveret:

  • materials - Materiale grunddata (id, name, unit, description, etc.)
  • material_prices - Priser for materialer (material_id, price, valid_from, etc.)

Relationer:

materials.id ← material_prices.material_id (1:N)

Test Resultater

Før Fix

curl http://localhost:4031/api/materials?limit=5
{
  "id": 16,
  "name": "B7 tagplader",
  "unit": "m2",
  "price": null  // ❌ Ingen pris
}

Efter Fix

curl http://localhost:4031/api/materials?search=B7%20tag
{
  "id": 16,
  "name": "B7 tagplader",
  "unit": "m2",
  "price": "125.00"  // ✅ Pris hentet fra material_prices
}

Test Case: Eternit tagsten

{
  "id": 37,
  "name": "Eternit tagsten B6 rød",
  "unit": "m2",
  "price": "145.00"
}

Frontend Integration

MaterialDialog modtager nu automatisk pris fra backend:

Før (linje 105-107 i MaterialDialog.js):

const materialPrice = value.price ? parseFloat(value.price) : 0;
// value.price var altid undefined → price = 0

Efter:

const materialPrice = value.price ? parseFloat(value.price) : 0;
// value.price = "125.00" fra database → price = 125.00 ✅

Autocomplete Visning

MaterialDialog's Autocomplete viser nu priser:

getOptionLabel={(option) => {
  const priceInfo = option.price 
    ? ` - ${parseFloat(option.price).toFixed(2)} kr/${option.unit}` 
    : '';
  return `${option.name}${priceInfo}`;
}}

Resultat:

  • "B7 tagplader - 125.00 kr/m2"
  • "Eternit tagsten B6 rød - 145.00 kr/m2"

Materialer med Priser

Efter fix har følgende materialer priser:

ID Materiale Pris Enhed
16 B7 tagplader 125.00 m2
35 Test materiale 150.00 stk
36 Test materiale efter fix 200.00 m2
37 Eternit tagsten B6 rød 145.00 m2
38 Tagpap underlag T400 35.50 m2
39 Tagplade stål 35/1035 sort 185.00 m2
40 Tagrender halvrund zink 150mm 245.00 m
41 Nedløbsrør zink 100mm 195.00 m
42 Stålskotskinne galvaniseret 45.00 m
43 Tagisolering mineraluld 200mm 85.00 m2
44 Eternit vindskeder B6 75.00 stk
45 Eternit rygningsten B6 195.00 m
46 Lægter 38x63mm trykimprægneret 18.50 m

Deployment

# Genstart backend
pm2 restart tilbudgivern-unified

# Frontend kræver ikke rebuild - ændringen er kun i backend

Server Status

  • Backend: Online (port 4031) - 11 restarts
  • Frontend: Online (port 3001) - 5 restarts

Known Limitations

  1. Materialer uden priser: Vises stadig i listen men med price: null
  2. Historiske priser: Kun den nyeste pris vises (baseret på valid_from)
  3. Multiple leverandører: Hvis samme materiale har flere priser, vises kun den nyeste

Fremtidige Forbedringer

1. Pris Import System

Automatisk import af priser fra leverandører:

  • Bygma prisliste
  • CSV upload
  • API integration

2. Pris Historik

Vis prisudvikling over tid:

SELECT 
  material_id,
  price,
  valid_from,
  supplier_name
FROM material_prices
WHERE material_id = ?
ORDER BY valid_from DESC

3. Leverandør Valg

Lad brugeren vælge mellem forskellige leverandører:

const [selectedSupplier, setSelectedSupplier] = useState('Bygma');
// Filtrer priser baseret på supplier_name

4. Bulk Pris Opdatering

Interface til at opdatere mange priser på én gang:

  • Upload CSV fil med nye priser
  • Automatisk validering
  • Preview før import

Konklusion

Materialer trækker nu automatisk priser fra databasen
MaterialDialog viser korrekt standardpris
Autocomplete viser priser i dropdown
Brugerdefineret pris kan stadig anvendes til at overskrive
Ingen frontend ændringer nødvendige

Systemet er nu fuldt funktionelt med prisintegration!