Compare commits

...

6 Commits

Author SHA1 Message Date
fd9fcd47ab Merge remote-tracking branch 'origin/dev' into fix-pr-14
Some checks failed
Dev Build / build-test (pull_request) Failing after 58s
Dev Build / deploy-dev (pull_request) Has been skipped
Dev Build / notify-success (pull_request) Has been skipped
Dev Build / notify-failure (pull_request) Successful in 3s
# Conflicts:
#	frontend/.dockerignore
#	frontend/Dockerfile
#	frontend/nginx.conf
2026-04-27 14:30:03 -04:00
8a2f97d2cd Merge pull request 'CUB-40: Add cost summary API endpoint' (#15) from agent/dex/CUB-40-cost-summary-api into dev
Some checks failed
Dev Build / build-test (push) Failing after 52s
Dev Build / deploy-dev (push) Has been skipped
Dev Build / notify-success (push) Has been skipped
Dev Build / notify-failure (push) Successful in 3s
Reviewed-on: #15
2026-04-27 14:17:30 -04:00
b43edad5f0 Merge branch 'dev' into agent/dex/CUB-40-cost-summary-api
Some checks failed
Dev Build / build-test (pull_request) Failing after 52s
Dev Build / deploy-dev (pull_request) Has been skipped
Dev Build / notify-success (pull_request) Has been skipped
Dev Build / notify-failure (pull_request) Successful in 3s
2026-04-27 14:14:13 -04:00
12888c4f3f Merge pull request 'CUB-66: Frontend Dockerfile (Angular Static Build)' (#12) from agent/rex/CUB-64-frontend-dockerfile into dev
Some checks failed
Dev Build / build-test (push) Failing after 51s
Dev Build / deploy-dev (push) Has been skipped
Dev Build / notify-success (push) Has been skipped
Dev Build / notify-failure (push) Successful in 3s
Reviewed-on: #12
Reviewed-by: Otto the Minion <otto@code.cubecraftcreations.com>
2026-04-27 14:11:55 -04:00
c1a115c938 feat(CUB-40): [Extrudex] Add cost summary API endpoint
Some checks failed
Dev Build / build-test (pull_request) Failing after 47s
Dev Build / deploy-dev (pull_request) Has been skipped
Dev Build / notify-success (pull_request) Has been skipped
Dev Build / notify-failure (pull_request) Successful in 3s
2026-04-27 17:09:08 +00:00
61178ebb7b feat(CUB-64): Docker runtime setup for development & deployment
Some checks failed
Dev Build / build-test (pull_request) Failing after 47s
Dev Build / deploy-dev (pull_request) Has been skipped
Dev Build / notify-success (pull_request) Has been skipped
Dev Build / notify-failure (pull_request) Successful in 3s
- Backend Dockerfile: added curl install for health check (not in aspnet base image)
- Frontend Dockerfile: multi-stage Angular build with nginx serving
- Frontend nginx.conf: SPA routing, API proxy, SignalR WebSocket support, health endpoint
- Frontend .dockerignore: excludes node_modules, dist, .angular, etc.
- docker-compose.dev.yml: added PostgreSQL service, fixed frontend context path,
  renamed web service from control-center-web to extrudex-web, added DB env vars,
  proper service dependencies with health checks
- deploy.sh: updated service list to include PostgreSQL port
2026-04-27 08:33:18 +00:00
5 changed files with 182 additions and 7 deletions

View File

@@ -413,6 +413,92 @@ public class PrintJobsController : ControllerBase
return NoContent(); return NoContent();
} }
// ── GET /api/printjobs/{id}/cost-summary ──────────────────────────
/// <summary>
/// Gets the material cost summary for a specific print job.
/// Calculates total material cost from filament usage (grams derived)
/// and the spool's purchase price. Returns warnings instead of errors
/// when cost data is unavailable.
/// </summary>
/// <param name="id">The unique identifier of the print job.</param>
/// <returns>A cost summary with breakdown and any warnings about missing data.</returns>
/// <response code="200">Returns the cost summary. Warnings field lists any missing data.</response>
/// <response code="404">If the print job with the given ID is not found.</response>
[HttpGet("{id:guid}/cost-summary")]
[ProducesResponseType(typeof(CostSummaryResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<CostSummaryResponse>> GetCostSummary(Guid id)
{
_logger.LogDebug("Getting cost summary for print job {Id}", id);
var job = await _dbContext.PrintJobs
.Include(j => j.Spool)
.ThenInclude(s => s!.MaterialBase)
.FirstOrDefaultAsync(j => j.Id == id);
if (job is null)
{
_logger.LogWarning("Print job {Id} not found for cost summary", id);
return NotFound(new { error = $"Print job with ID '{id}' not found." });
}
var warnings = new List<string>();
var spool = job.Spool;
// Build response with what we have
var response = new CostSummaryResponse
{
PrintJobId = job.Id,
PrintName = job.PrintName,
SpoolId = job.SpoolId,
SpoolSerial = spool?.SpoolSerial ?? string.Empty,
SpoolBrand = spool?.Brand ?? string.Empty,
SpoolColorName = spool?.ColorName ?? string.Empty,
MmExtruded = job.MmExtruded,
GramsDerived = job.GramsDerived,
SpoolPurchasePrice = spool?.PurchasePrice,
SpoolWeightTotalGrams = spool?.WeightTotalGrams,
StoredCostPerPrint = job.CostPerPrint
};
// Validate spool data availability
if (spool is null)
{
warnings.Add("Spool data is not available for this print job. Cost cannot be calculated.");
response.Warnings = warnings;
return Ok(response);
}
// Check if we can calculate cost
if (!spool.PurchasePrice.HasValue)
{
warnings.Add("Spool purchase price is not set. Cost per gram and total material cost cannot be calculated.");
}
if (spool.WeightTotalGrams <= 0)
{
warnings.Add("Spool total weight is zero or invalid. Cost per gram and total material cost cannot be calculated.");
}
// If we have enough data, calculate the cost
if (spool.PurchasePrice.HasValue && spool.WeightTotalGrams > 0)
{
var pricePerGram = spool.PurchasePrice.Value / spool.WeightTotalGrams;
response.PricePerGram = Math.Round(pricePerGram, 4);
response.TotalMaterialCost = Math.Round(job.GramsDerived * pricePerGram, 4);
}
// Warn if grams derived is zero but mm extruded is non-zero
if (job.GramsDerived == 0 && job.MmExtruded > 0)
{
warnings.Add("GramsDerived is zero despite MmExtruded being non-zero. Cost may be inaccurate. Consider re-deriving grams from filament parameters.");
}
response.Warnings = warnings;
return Ok(response);
}
// ── Gram Derivation Formula ──────────────────────────────────── // ── Gram Derivation Formula ────────────────────────────────────
/// <summary> /// <summary>

View File

@@ -0,0 +1,55 @@
namespace Extrudex.API.DTOs.PrintJobs;
/// <summary>
/// Response DTO for the cost summary of a print job.
/// Provides a breakdown of material cost based on filament usage
/// and spool pricing data. If cost data is incomplete, warnings
/// are returned instead of throwing an error.
/// </summary>
public class CostSummaryResponse
{
/// <summary>Unique identifier of the print job.</summary>
public Guid PrintJobId { get; set; }
/// <summary>Human-readable name of the print job.</summary>
public string PrintName { get; set; } = string.Empty;
/// <summary>Foreign key to the spool used for this print job.</summary>
public Guid SpoolId { get; set; }
/// <summary>Serial number of the spool.</summary>
public string SpoolSerial { get; set; } = string.Empty;
/// <summary>Brand of the spool.</summary>
public string SpoolBrand { get; set; } = string.Empty;
/// <summary>Color name of the spool.</summary>
public string SpoolColorName { get; set; } = string.Empty;
/// <summary>Total millimeters of filament extruded during this print.</summary>
public decimal MmExtruded { get; set; }
/// <summary>Derived grams consumed for this print job.</summary>
public decimal GramsDerived { get; set; }
/// <summary>Purchase price of the full spool, if available.</summary>
public decimal? SpoolPurchasePrice { get; set; }
/// <summary>Total weight of the spool in grams when full.</summary>
public decimal? SpoolWeightTotalGrams { get; set; }
/// <summary>Calculated price per gram (purchase price / total weight), if available.</summary>
public decimal? PricePerGram { get; set; }
/// <summary>Calculated total material cost for this print job, if available.</summary>
public decimal? TotalMaterialCost { get; set; }
/// <summary>The CostPerPrint stored on the print job entity, if set.</summary>
public decimal? StoredCostPerPrint { get; set; }
/// <summary>
/// Warnings about missing data that prevent cost calculation.
/// Empty if all data is available and cost was calculated successfully.
/// </summary>
public List<string> Warnings { get; set; } = new();
}

View File

@@ -17,6 +17,9 @@ RUN dotnet publish Extrudex.csproj \
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime
WORKDIR /app WORKDIR /app
# Install curl for health check (not included in aspnet base image)
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
# Non-root user for security # Non-root user for security
RUN adduser --disabled-password --gecos "" appuser RUN adduser --disabled-password --gecos "" appuser
USER appuser USER appuser

View File

@@ -18,13 +18,14 @@ echo "📦 Building and starting services..."
$COMPOSE_CMD -f docker-compose.dev.yml up -d --build $COMPOSE_CMD -f docker-compose.dev.yml up -d --build
echo "⏳ Waiting for services to become healthy..." echo "⏳ Waiting for services to become healthy..."
sleep 10 sleep 15
echo "✅ Deployment complete!" echo "✅ Deployment complete!"
echo "" echo ""
echo "Services running:" echo "Services running:"
echo " • PostgreSQL: localhost:5433"
echo " • Extrudex API: http://localhost:5080" echo " • Extrudex API: http://localhost:5080"
echo " • Control Center Web: http://localhost:5081" echo " • Extrudex Web: http://localhost:5081"
echo "" echo ""
echo "To view logs:" echo "To view logs:"
echo " $COMPOSE_CMD -f docker-compose.dev.yml logs -f" echo " $COMPOSE_CMD -f docker-compose.dev.yml logs -f"

View File

@@ -1,6 +1,25 @@
version: '3.8'
services: services:
extrudex-db:
image: postgres:16-alpine
container_name: extrudex-db
environment:
POSTGRES_USER: extrudex
POSTGRES_PASSWORD: changeme
POSTGRES_DB: extrudex
ports:
- "5433:5432"
volumes:
- extrudex-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U extrudex"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
networks:
- extrudex-network
extrudex-api: extrudex-api:
build: build:
context: ./backend context: ./backend
@@ -11,6 +30,14 @@ services:
environment: environment:
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://+:8080 - ASPNETCORE_URLS=http://+:8080
- EXTRUDEX_DB_HOST=extrudex-db
- EXTRUDEX_DB_PORT=5432
- EXTRUDEX_DB_NAME=extrudex
- EXTRUDEX_DB_USER=extrudex
- EXTRUDEX_DB_PASSWORD=changeme
depends_on:
extrudex-db:
condition: service_healthy
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"] test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
@@ -21,11 +48,11 @@ services:
networks: networks:
- extrudex-network - extrudex-network
control-center-web: extrudex-web:
build: build:
context: ../Control-Center/frontend context: ./frontend
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: control-center-web container_name: extrudex-web
ports: ports:
- "5081:80" - "5081:80"
depends_on: depends_on:
@@ -35,6 +62,9 @@ services:
networks: networks:
- extrudex-network - extrudex-network
volumes:
extrudex-db-data:
networks: networks:
extrudex-network: extrudex-network:
driver: bridge driver: bridge