f6cd91f1b5
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1192 lines
39 KiB
Markdown
1192 lines
39 KiB
Markdown
# FabWorks.Api Implementation Plan
|
|
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
|
|
**Goal:** Create a standalone ASP.NET Web API (`FabWorks.Api`) that owns all fabrication data — export records, BOM items, cut templates, form programs — so ExportDXF and other tools communicate via HTTP instead of direct database access.
|
|
|
|
**Architecture:** FabWorks.Api is a minimal ASP.NET Web API targeting net8.0. It uses EF Core with SQL Server (same database ExportDXF already uses). A shared class library (`FabWorks.Core`) holds the entity models and DbContext so both the API and ExportDXF can share the schema during the migration period. CincyLib's press brake models are copied into FabWorks.Core as a net8.0 port (just the PressBrake folder — pure XML parsing, no dependencies). The API exposes RESTful endpoints for export records, BOM items, cut templates, and form programs.
|
|
|
|
**Tech Stack:** ASP.NET Web API (net8.0), EF Core 8 + SQL Server, CincyLib PressBrake parser (ported to net8.0), xUnit for tests
|
|
|
|
---
|
|
|
|
## Project Layout
|
|
|
|
```
|
|
ExportDXF/ (solution root)
|
|
├── ExportDXF.sln (add new projects here)
|
|
├── ExportDXF/ (existing WinForms app)
|
|
├── FabWorks.Core/ ← NEW shared library
|
|
│ ├── FabWorks.Core.csproj
|
|
│ ├── Data/
|
|
│ │ └── FabWorksDbContext.cs
|
|
│ ├── Models/
|
|
│ │ ├── ExportRecord.cs
|
|
│ │ ├── BomItem.cs
|
|
│ │ ├── CutTemplate.cs
|
|
│ │ └── FormProgram.cs ← NEW entity
|
|
│ └── PressBrake/ ← Ported from CincyLib
|
|
│ ├── Program.cs
|
|
│ ├── ProgramReader.cs
|
|
│ ├── Step.cs
|
|
│ ├── ToolSetup.cs
|
|
│ ├── SegEntry.cs
|
|
│ ├── MatType.cs
|
|
│ └── Extensions.cs
|
|
├── FabWorks.Api/ ← NEW web API
|
|
│ ├── FabWorks.Api.csproj
|
|
│ ├── Program.cs
|
|
│ ├── Controllers/
|
|
│ │ ├── ExportsController.cs
|
|
│ │ ├── BomItemsController.cs
|
|
│ │ └── FormProgramsController.cs
|
|
│ ├── DTOs/
|
|
│ │ ├── CreateExportRequest.cs
|
|
│ │ ├── ExportDetailDto.cs
|
|
│ │ ├── BomItemDto.cs
|
|
│ │ └── FormProgramDto.cs
|
|
│ └── Services/
|
|
│ └── FormProgramService.cs
|
|
└── FabWorks.Tests/ ← NEW test project
|
|
├── FabWorks.Tests.csproj
|
|
├── FormProgramServiceTests.cs
|
|
├── ExportsControllerTests.cs
|
|
└── PressBrake/
|
|
└── ProgramReaderTests.cs
|
|
```
|
|
|
|
---
|
|
|
|
## Task 1: Create FabWorks.Core with Shared Models
|
|
|
|
**Files:**
|
|
- Create: `FabWorks.Core/FabWorks.Core.csproj`
|
|
- Create: `FabWorks.Core/Models/ExportRecord.cs`
|
|
- Create: `FabWorks.Core/Models/BomItem.cs`
|
|
- Create: `FabWorks.Core/Models/CutTemplate.cs`
|
|
- Create: `FabWorks.Core/Models/FormProgram.cs`
|
|
- Create: `FabWorks.Core/Data/FabWorksDbContext.cs`
|
|
|
|
**Step 1: Create the class library project**
|
|
|
|
```bash
|
|
cd C:\Users\aisaacs\Desktop\Projects\ExportDXF
|
|
dotnet new classlib -n FabWorks.Core --framework net8.0
|
|
dotnet sln ExportDXF.sln add FabWorks.Core/FabWorks.Core.csproj
|
|
```
|
|
|
|
**Step 2: Add EF Core package to FabWorks.Core**
|
|
|
|
```bash
|
|
cd C:\Users\aisaacs\Desktop\Projects\ExportDXF\FabWorks.Core
|
|
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 8.0.11
|
|
dotnet add package Microsoft.EntityFrameworkCore.Tools --version 8.0.11
|
|
```
|
|
|
|
**Step 3: Create entity models**
|
|
|
|
Move/recreate the existing entity models from `ExportDXF/Models/` into `FabWorks.Core/Models/` under namespace `FabWorks.Core.Models`. Keep the exact same schema so the existing database is compatible.
|
|
|
|
`FabWorks.Core/Models/ExportRecord.cs`:
|
|
```csharp
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace FabWorks.Core.Models
|
|
{
|
|
public class ExportRecord
|
|
{
|
|
public int Id { get; set; }
|
|
public string DrawingNumber { get; set; }
|
|
public string SourceFilePath { get; set; }
|
|
public string OutputFolder { get; set; }
|
|
public DateTime ExportedAt { get; set; }
|
|
public string ExportedBy { get; set; }
|
|
public string PdfContentHash { get; set; }
|
|
|
|
public virtual ICollection<BomItem> BomItems { get; set; } = new List<BomItem>();
|
|
}
|
|
}
|
|
```
|
|
|
|
`FabWorks.Core/Models/BomItem.cs`:
|
|
```csharp
|
|
namespace FabWorks.Core.Models
|
|
{
|
|
public class BomItem
|
|
{
|
|
public int ID { get; set; }
|
|
public string ItemNo { get; set; } = "";
|
|
public string PartNo { get; set; } = "";
|
|
public int SortOrder { get; set; }
|
|
public int? Qty { get; set; }
|
|
public int? TotalQty { get; set; }
|
|
public string Description { get; set; } = "";
|
|
public string PartName { get; set; } = "";
|
|
public string ConfigurationName { get; set; } = "";
|
|
public string Material { get; set; } = "";
|
|
|
|
public int ExportRecordId { get; set; }
|
|
public virtual ExportRecord ExportRecord { get; set; }
|
|
|
|
public virtual CutTemplate CutTemplate { get; set; }
|
|
public virtual FormProgram FormProgram { get; set; }
|
|
}
|
|
}
|
|
```
|
|
|
|
`FabWorks.Core/Models/CutTemplate.cs`:
|
|
```csharp
|
|
using System;
|
|
|
|
namespace FabWorks.Core.Models
|
|
{
|
|
public class CutTemplate
|
|
{
|
|
public int Id { get; set; }
|
|
public string DxfFilePath { get; set; } = "";
|
|
public string ContentHash { get; set; }
|
|
public string CutTemplateName { get; set; } = "";
|
|
|
|
private double? _thickness;
|
|
public double? Thickness
|
|
{
|
|
get => _thickness;
|
|
set => _thickness = value.HasValue ? Math.Round(value.Value, 8) : null;
|
|
}
|
|
|
|
public double? KFactor { get; set; }
|
|
|
|
private double? _defaultBendRadius;
|
|
public double? DefaultBendRadius
|
|
{
|
|
get => _defaultBendRadius;
|
|
set => _defaultBendRadius = value.HasValue ? Math.Round(value.Value, 8) : null;
|
|
}
|
|
|
|
public int BomItemId { get; set; }
|
|
public virtual BomItem BomItem { get; set; }
|
|
}
|
|
}
|
|
```
|
|
|
|
`FabWorks.Core/Models/FormProgram.cs` (NEW):
|
|
```csharp
|
|
namespace FabWorks.Core.Models
|
|
{
|
|
public class FormProgram
|
|
{
|
|
public int Id { get; set; }
|
|
public string ProgramFilePath { get; set; } = "";
|
|
public string ContentHash { get; set; }
|
|
public string ProgramName { get; set; } = "";
|
|
public double? Thickness { get; set; }
|
|
public string MaterialType { get; set; } = "";
|
|
public double? KFactor { get; set; }
|
|
public int BendCount { get; set; }
|
|
public string UpperToolNames { get; set; } = "";
|
|
public string LowerToolNames { get; set; } = "";
|
|
public string SetupNotes { get; set; } = "";
|
|
|
|
public int BomItemId { get; set; }
|
|
public virtual BomItem BomItem { get; set; }
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 4: Create FabWorksDbContext**
|
|
|
|
`FabWorks.Core/Data/FabWorksDbContext.cs`:
|
|
```csharp
|
|
using FabWorks.Core.Models;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace FabWorks.Core.Data
|
|
{
|
|
public class FabWorksDbContext : DbContext
|
|
{
|
|
public DbSet<ExportRecord> ExportRecords { get; set; }
|
|
public DbSet<BomItem> BomItems { get; set; }
|
|
public DbSet<CutTemplate> CutTemplates { get; set; }
|
|
public DbSet<FormProgram> FormPrograms { get; set; }
|
|
|
|
public FabWorksDbContext(DbContextOptions<FabWorksDbContext> options) : base(options) { }
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.Entity<ExportRecord>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.DrawingNumber).HasMaxLength(100);
|
|
entity.Property(e => e.SourceFilePath).HasMaxLength(500);
|
|
entity.Property(e => e.OutputFolder).HasMaxLength(500);
|
|
entity.Property(e => e.ExportedBy).HasMaxLength(100);
|
|
entity.Property(e => e.PdfContentHash).HasMaxLength(64);
|
|
|
|
entity.HasMany(e => e.BomItems)
|
|
.WithOne(b => b.ExportRecord)
|
|
.HasForeignKey(b => b.ExportRecordId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
|
|
modelBuilder.Entity<BomItem>(entity =>
|
|
{
|
|
entity.HasKey(e => e.ID);
|
|
entity.Property(e => e.ItemNo).HasMaxLength(50);
|
|
entity.Property(e => e.PartNo).HasMaxLength(100);
|
|
entity.Property(e => e.Description).HasMaxLength(500);
|
|
entity.Property(e => e.PartName).HasMaxLength(200);
|
|
entity.Property(e => e.ConfigurationName).HasMaxLength(100);
|
|
entity.Property(e => e.Material).HasMaxLength(100);
|
|
|
|
entity.HasOne(e => e.CutTemplate)
|
|
.WithOne(ct => ct.BomItem)
|
|
.HasForeignKey<CutTemplate>(ct => ct.BomItemId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
entity.HasOne(e => e.FormProgram)
|
|
.WithOne(fp => fp.BomItem)
|
|
.HasForeignKey<FormProgram>(fp => fp.BomItemId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
|
|
modelBuilder.Entity<CutTemplate>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.DxfFilePath).HasMaxLength(500);
|
|
entity.Property(e => e.CutTemplateName).HasMaxLength(100);
|
|
entity.Property(e => e.ContentHash).HasMaxLength(64);
|
|
});
|
|
|
|
modelBuilder.Entity<FormProgram>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.ProgramFilePath).HasMaxLength(500);
|
|
entity.Property(e => e.ContentHash).HasMaxLength(64);
|
|
entity.Property(e => e.ProgramName).HasMaxLength(200);
|
|
entity.Property(e => e.MaterialType).HasMaxLength(50);
|
|
entity.Property(e => e.UpperToolNames).HasMaxLength(500);
|
|
entity.Property(e => e.LowerToolNames).HasMaxLength(500);
|
|
entity.Property(e => e.SetupNotes).HasMaxLength(2000);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 5: Delete auto-generated Class1.cs and build**
|
|
|
|
```bash
|
|
del FabWorks.Core\Class1.cs
|
|
dotnet build FabWorks.Core/FabWorks.Core.csproj
|
|
```
|
|
|
|
**Step 6: Commit**
|
|
|
|
```bash
|
|
git add FabWorks.Core/
|
|
git commit -m "feat: add FabWorks.Core shared library with entity models and FormProgram"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 2: Port CincyLib PressBrake Parser into FabWorks.Core
|
|
|
|
**Files:**
|
|
- Create: `FabWorks.Core/PressBrake/Program.cs`
|
|
- Create: `FabWorks.Core/PressBrake/ProgramReader.cs`
|
|
- Create: `FabWorks.Core/PressBrake/Step.cs`
|
|
- Create: `FabWorks.Core/PressBrake/ToolSetup.cs`
|
|
- Create: `FabWorks.Core/PressBrake/SegEntry.cs`
|
|
- Create: `FabWorks.Core/PressBrake/MatType.cs`
|
|
- Create: `FabWorks.Core/PressBrake/Extensions.cs`
|
|
|
|
**Step 1: Copy CincyLib PressBrake files**
|
|
|
|
Copy the following files from `C:\Users\aisaacs\Desktop\Projects\CincyLib\CincyLib\PressBrake\` into `FabWorks.Core\PressBrake\`:
|
|
- `Program.cs`
|
|
- `ProgramReader.cs`
|
|
- `Step.cs`
|
|
- `ToolSetup.cs`
|
|
- `SegEntry.cs`
|
|
- `MatType.cs`
|
|
|
|
Also copy `C:\Users\aisaacs\Desktop\Projects\CincyLib\CincyLib\Extensions.cs` into `FabWorks.Core\PressBrake\Extensions.cs`.
|
|
|
|
**Step 2: Update namespaces**
|
|
|
|
Change all `namespace CincyLib.PressBrake` to `namespace FabWorks.Core.PressBrake`.
|
|
Change `namespace CincyLib` (in Extensions.cs) to `namespace FabWorks.Core.PressBrake`.
|
|
|
|
No other code changes needed — these files only use `System`, `System.IO`, `System.Linq`, `System.Xml.Linq`, `System.Collections.Generic`, all available in net8.0.
|
|
|
|
**Step 3: Build to verify port**
|
|
|
|
```bash
|
|
dotnet build FabWorks.Core/FabWorks.Core.csproj
|
|
```
|
|
|
|
**Step 4: Commit**
|
|
|
|
```bash
|
|
git add FabWorks.Core/PressBrake/
|
|
git commit -m "feat: port CincyLib PressBrake parser to FabWorks.Core (net8.0)"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 3: Create FabWorks.Tests and Validate PressBrake Parser
|
|
|
|
**Files:**
|
|
- Create: `FabWorks.Tests/FabWorks.Tests.csproj`
|
|
- Create: `FabWorks.Tests/PressBrake/ProgramReaderTests.cs`
|
|
- Create: `FabWorks.Tests/TestData/` (copy a sample .pgm file)
|
|
|
|
**Step 1: Create test project**
|
|
|
|
```bash
|
|
cd C:\Users\aisaacs\Desktop\Projects\ExportDXF
|
|
dotnet new xunit -n FabWorks.Tests --framework net8.0
|
|
dotnet sln ExportDXF.sln add FabWorks.Tests/FabWorks.Tests.csproj
|
|
cd FabWorks.Tests
|
|
dotnet add reference ../FabWorks.Core/FabWorks.Core.csproj
|
|
```
|
|
|
|
**Step 2: Copy test data**
|
|
|
|
Copy a sample `.pgm` file into `FabWorks.Tests/TestData/sample.pgm`. Use the file from `S:\4980 GMCH Lockport\4980 A05 Degreaser Casing\4980 A05-1 Access door\Forms\4980 A05-1 PT02.pgm`. Add it to the csproj as `<Content Include="TestData\**" CopyToOutputDirectory="PreserveNewest" />`.
|
|
|
|
**Step 3: Write ProgramReader test**
|
|
|
|
`FabWorks.Tests/PressBrake/ProgramReaderTests.cs`:
|
|
```csharp
|
|
using FabWorks.Core.PressBrake;
|
|
using Xunit;
|
|
|
|
namespace FabWorks.Tests.PressBrake
|
|
{
|
|
public class ProgramReaderTests
|
|
{
|
|
[Fact]
|
|
public void Load_SamplePgm_ParsesProgramName()
|
|
{
|
|
var pgm = Program.Load("TestData/sample.pgm");
|
|
Assert.False(string.IsNullOrEmpty(pgm.ProgName));
|
|
}
|
|
|
|
[Fact]
|
|
public void Load_SamplePgm_ParsesThickness()
|
|
{
|
|
var pgm = Program.Load("TestData/sample.pgm");
|
|
Assert.True(pgm.MatThick > 0);
|
|
}
|
|
|
|
[Fact]
|
|
public void Load_SamplePgm_ParsesSteps()
|
|
{
|
|
var pgm = Program.Load("TestData/sample.pgm");
|
|
Assert.NotEmpty(pgm.Steps);
|
|
}
|
|
|
|
[Fact]
|
|
public void Load_SamplePgm_ParsesToolSetups()
|
|
{
|
|
var pgm = Program.Load("TestData/sample.pgm");
|
|
Assert.NotEmpty(pgm.UpperToolSets);
|
|
Assert.NotEmpty(pgm.LowerToolSets);
|
|
}
|
|
|
|
[Fact]
|
|
public void Load_SamplePgm_ResolvesStepToolReferences()
|
|
{
|
|
var pgm = Program.Load("TestData/sample.pgm");
|
|
var step = pgm.Steps[0];
|
|
Assert.NotNull(step.UpperTool);
|
|
Assert.NotNull(step.LowerTool);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 4: Run tests**
|
|
|
|
```bash
|
|
dotnet test FabWorks.Tests/FabWorks.Tests.csproj -v normal
|
|
```
|
|
|
|
Expected: All 5 tests PASS.
|
|
|
|
**Step 5: Commit**
|
|
|
|
```bash
|
|
git add FabWorks.Tests/
|
|
git commit -m "test: add ProgramReader tests validating CincyLib port"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 4: Create FabWorks.Api Project with Exports Endpoint
|
|
|
|
**Files:**
|
|
- Create: `FabWorks.Api/FabWorks.Api.csproj`
|
|
- Create: `FabWorks.Api/Program.cs`
|
|
- Create: `FabWorks.Api/Controllers/ExportsController.cs`
|
|
- Create: `FabWorks.Api/DTOs/CreateExportRequest.cs`
|
|
- Create: `FabWorks.Api/DTOs/ExportDetailDto.cs`
|
|
|
|
**Step 1: Create web API project**
|
|
|
|
```bash
|
|
cd C:\Users\aisaacs\Desktop\Projects\ExportDXF
|
|
dotnet new webapi -n FabWorks.Api --framework net8.0 --no-openapi
|
|
dotnet sln ExportDXF.sln add FabWorks.Api/FabWorks.Api.csproj
|
|
cd FabWorks.Api
|
|
dotnet add reference ../FabWorks.Core/FabWorks.Core.csproj
|
|
```
|
|
|
|
**Step 2: Configure Program.cs with EF Core**
|
|
|
|
`FabWorks.Api/Program.cs`:
|
|
```csharp
|
|
using FabWorks.Core.Data;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Services.AddControllers();
|
|
builder.Services.AddDbContext<FabWorksDbContext>(options =>
|
|
options.UseSqlServer(builder.Configuration.GetConnectionString("FabWorksDb")));
|
|
|
|
var app = builder.Build();
|
|
|
|
app.MapControllers();
|
|
app.Run();
|
|
```
|
|
|
|
Add connection string to `appsettings.json`:
|
|
```json
|
|
{
|
|
"ConnectionStrings": {
|
|
"FabWorksDb": "Server=localhost;Database=ExportDxfDb;Trusted_Connection=True;TrustServerCertificate=True;"
|
|
}
|
|
}
|
|
```
|
|
|
|
Note: Points at the same `ExportDxfDb` database. Same schema, just adding the FormPrograms table.
|
|
|
|
**Step 3: Create DTOs**
|
|
|
|
`FabWorks.Api/DTOs/CreateExportRequest.cs`:
|
|
```csharp
|
|
namespace FabWorks.Api.DTOs
|
|
{
|
|
public class CreateExportRequest
|
|
{
|
|
public string DrawingNumber { get; set; }
|
|
public string SourceFilePath { get; set; }
|
|
public string OutputFolder { get; set; }
|
|
}
|
|
}
|
|
```
|
|
|
|
`FabWorks.Api/DTOs/ExportDetailDto.cs`:
|
|
```csharp
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace FabWorks.Api.DTOs
|
|
{
|
|
public class ExportDetailDto
|
|
{
|
|
public int Id { get; set; }
|
|
public string DrawingNumber { get; set; }
|
|
public string SourceFilePath { get; set; }
|
|
public string OutputFolder { get; set; }
|
|
public DateTime ExportedAt { get; set; }
|
|
public string ExportedBy { get; set; }
|
|
public string PdfContentHash { get; set; }
|
|
public List<BomItemDto> BomItems { get; set; } = new();
|
|
}
|
|
|
|
public class BomItemDto
|
|
{
|
|
public int ID { get; set; }
|
|
public string ItemNo { get; set; }
|
|
public string PartNo { get; set; }
|
|
public int SortOrder { get; set; }
|
|
public int? Qty { get; set; }
|
|
public int? TotalQty { get; set; }
|
|
public string Description { get; set; }
|
|
public string PartName { get; set; }
|
|
public string ConfigurationName { get; set; }
|
|
public string Material { get; set; }
|
|
public CutTemplateDto CutTemplate { get; set; }
|
|
public FormProgramDto FormProgram { get; set; }
|
|
}
|
|
|
|
public class CutTemplateDto
|
|
{
|
|
public int Id { get; set; }
|
|
public string DxfFilePath { get; set; }
|
|
public string ContentHash { get; set; }
|
|
public double? Thickness { get; set; }
|
|
public double? KFactor { get; set; }
|
|
public double? DefaultBendRadius { get; set; }
|
|
}
|
|
|
|
public class FormProgramDto
|
|
{
|
|
public int Id { get; set; }
|
|
public string ProgramFilePath { get; set; }
|
|
public string ContentHash { get; set; }
|
|
public string ProgramName { get; set; }
|
|
public double? Thickness { get; set; }
|
|
public string MaterialType { get; set; }
|
|
public double? KFactor { get; set; }
|
|
public int BendCount { get; set; }
|
|
public string UpperToolNames { get; set; }
|
|
public string LowerToolNames { get; set; }
|
|
public string SetupNotes { get; set; }
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 4: Create ExportsController**
|
|
|
|
`FabWorks.Api/Controllers/ExportsController.cs`:
|
|
```csharp
|
|
using FabWorks.Api.DTOs;
|
|
using FabWorks.Core.Data;
|
|
using FabWorks.Core.Models;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace FabWorks.Api.Controllers
|
|
{
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
public class ExportsController : ControllerBase
|
|
{
|
|
private readonly FabWorksDbContext _db;
|
|
|
|
public ExportsController(FabWorksDbContext db)
|
|
{
|
|
_db = db;
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult<ExportDetailDto>> Create(CreateExportRequest request)
|
|
{
|
|
var record = new ExportRecord
|
|
{
|
|
DrawingNumber = request.DrawingNumber,
|
|
SourceFilePath = request.SourceFilePath,
|
|
OutputFolder = request.OutputFolder,
|
|
ExportedAt = DateTime.Now,
|
|
ExportedBy = Environment.UserName
|
|
};
|
|
|
|
_db.ExportRecords.Add(record);
|
|
await _db.SaveChangesAsync();
|
|
|
|
return CreatedAtAction(nameof(GetById), new { id = record.Id }, MapToDto(record));
|
|
}
|
|
|
|
[HttpGet("{id}")]
|
|
public async Task<ActionResult<ExportDetailDto>> GetById(int id)
|
|
{
|
|
var record = await _db.ExportRecords
|
|
.Include(r => r.BomItems).ThenInclude(b => b.CutTemplate)
|
|
.Include(r => r.BomItems).ThenInclude(b => b.FormProgram)
|
|
.FirstOrDefaultAsync(r => r.Id == id);
|
|
|
|
if (record == null) return NotFound();
|
|
return MapToDto(record);
|
|
}
|
|
|
|
[HttpGet("by-source")]
|
|
public async Task<ActionResult<ExportDetailDto>> GetBySourceFile([FromQuery] string path)
|
|
{
|
|
var record = await _db.ExportRecords
|
|
.Where(r => r.SourceFilePath.ToLower() == path.ToLower()
|
|
&& !string.IsNullOrEmpty(r.DrawingNumber))
|
|
.OrderByDescending(r => r.Id)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (record == null) return NotFound();
|
|
return MapToDto(record);
|
|
}
|
|
|
|
[HttpGet("by-drawing")]
|
|
public async Task<ActionResult<List<ExportDetailDto>>> GetByDrawing([FromQuery] string drawingNumber)
|
|
{
|
|
var records = await _db.ExportRecords
|
|
.Include(r => r.BomItems).ThenInclude(b => b.CutTemplate)
|
|
.Include(r => r.BomItems).ThenInclude(b => b.FormProgram)
|
|
.Where(r => r.DrawingNumber == drawingNumber)
|
|
.OrderByDescending(r => r.ExportedAt)
|
|
.ToListAsync();
|
|
|
|
return records.Select(MapToDto).ToList();
|
|
}
|
|
|
|
[HttpGet("next-item-number")]
|
|
public async Task<ActionResult<string>> GetNextItemNumber([FromQuery] string drawingNumber)
|
|
{
|
|
if (string.IsNullOrEmpty(drawingNumber)) return "1";
|
|
|
|
var existingItems = await _db.ExportRecords
|
|
.Where(r => r.DrawingNumber == drawingNumber)
|
|
.SelectMany(r => r.BomItems)
|
|
.Select(b => b.ItemNo)
|
|
.ToListAsync();
|
|
|
|
int maxNum = 0;
|
|
foreach (var itemNo in existingItems)
|
|
{
|
|
if (int.TryParse(itemNo, out var num) && num > maxNum)
|
|
maxNum = num;
|
|
}
|
|
return (maxNum + 1).ToString();
|
|
}
|
|
|
|
private static ExportDetailDto MapToDto(ExportRecord r) => new()
|
|
{
|
|
Id = r.Id,
|
|
DrawingNumber = r.DrawingNumber,
|
|
SourceFilePath = r.SourceFilePath,
|
|
OutputFolder = r.OutputFolder,
|
|
ExportedAt = r.ExportedAt,
|
|
ExportedBy = r.ExportedBy,
|
|
PdfContentHash = r.PdfContentHash,
|
|
BomItems = r.BomItems?.Select(b => new BomItemDto
|
|
{
|
|
ID = b.ID,
|
|
ItemNo = b.ItemNo,
|
|
PartNo = b.PartNo,
|
|
SortOrder = b.SortOrder,
|
|
Qty = b.Qty,
|
|
TotalQty = b.TotalQty,
|
|
Description = b.Description,
|
|
PartName = b.PartName,
|
|
ConfigurationName = b.ConfigurationName,
|
|
Material = b.Material,
|
|
CutTemplate = b.CutTemplate == null ? null : new CutTemplateDto
|
|
{
|
|
Id = b.CutTemplate.Id,
|
|
DxfFilePath = b.CutTemplate.DxfFilePath,
|
|
ContentHash = b.CutTemplate.ContentHash,
|
|
Thickness = b.CutTemplate.Thickness,
|
|
KFactor = b.CutTemplate.KFactor,
|
|
DefaultBendRadius = b.CutTemplate.DefaultBendRadius
|
|
},
|
|
FormProgram = b.FormProgram == null ? null : new FormProgramDto
|
|
{
|
|
Id = b.FormProgram.Id,
|
|
ProgramFilePath = b.FormProgram.ProgramFilePath,
|
|
ContentHash = b.FormProgram.ContentHash,
|
|
ProgramName = b.FormProgram.ProgramName,
|
|
Thickness = b.FormProgram.Thickness,
|
|
MaterialType = b.FormProgram.MaterialType,
|
|
KFactor = b.FormProgram.KFactor,
|
|
BendCount = b.FormProgram.BendCount,
|
|
UpperToolNames = b.FormProgram.UpperToolNames,
|
|
LowerToolNames = b.FormProgram.LowerToolNames,
|
|
SetupNotes = b.FormProgram.SetupNotes
|
|
}
|
|
}).ToList() ?? new()
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 5: Clean up auto-generated files, build**
|
|
|
|
Delete the auto-generated `WeatherForecast.cs` and `Controllers/WeatherForecastController.cs` if present.
|
|
|
|
```bash
|
|
dotnet build FabWorks.Api/FabWorks.Api.csproj
|
|
```
|
|
|
|
**Step 6: Commit**
|
|
|
|
```bash
|
|
git add FabWorks.Api/
|
|
git commit -m "feat: add FabWorks.Api with ExportsController and DTOs"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 5: Add BomItems and FormPrograms Controllers
|
|
|
|
**Files:**
|
|
- Create: `FabWorks.Api/Controllers/BomItemsController.cs`
|
|
- Create: `FabWorks.Api/Controllers/FormProgramsController.cs`
|
|
- Create: `FabWorks.Api/Services/FormProgramService.cs`
|
|
|
|
**Step 1: Create FormProgramService (wraps CincyLib parser)**
|
|
|
|
`FabWorks.Api/Services/FormProgramService.cs`:
|
|
```csharp
|
|
using FabWorks.Core.Models;
|
|
using FabWorks.Core.PressBrake;
|
|
using System.Security.Cryptography;
|
|
|
|
namespace FabWorks.Api.Services
|
|
{
|
|
public class FormProgramService
|
|
{
|
|
/// <summary>
|
|
/// Parse a .pgm file and return a FormProgram entity with metadata populated.
|
|
/// </summary>
|
|
public FormProgram ParseFromFile(string filePath)
|
|
{
|
|
var pgm = Program.Load(filePath);
|
|
var hash = ComputeFileHash(filePath);
|
|
|
|
return new FormProgram
|
|
{
|
|
ProgramFilePath = filePath,
|
|
ContentHash = hash,
|
|
ProgramName = pgm.ProgName ?? "",
|
|
Thickness = pgm.MatThick > 0 ? pgm.MatThick : null,
|
|
MaterialType = pgm.MatType.ToString(),
|
|
KFactor = pgm.KFactor > 0 ? pgm.KFactor : null,
|
|
BendCount = pgm.Steps.Count,
|
|
UpperToolNames = string.Join(", ", pgm.UpperToolSets
|
|
.Select(t => t.Name).Where(n => !string.IsNullOrEmpty(n)).Distinct()),
|
|
LowerToolNames = string.Join(", ", pgm.LowerToolSets
|
|
.Select(t => t.Name).Where(n => !string.IsNullOrEmpty(n)).Distinct()),
|
|
SetupNotes = pgm.SetupNotes ?? ""
|
|
};
|
|
}
|
|
|
|
private static string ComputeFileHash(string filePath)
|
|
{
|
|
using var sha = SHA256.Create();
|
|
using var stream = File.OpenRead(filePath);
|
|
var bytes = sha.ComputeHash(stream);
|
|
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 2: Register FormProgramService in DI**
|
|
|
|
In `FabWorks.Api/Program.cs`, add:
|
|
```csharp
|
|
builder.Services.AddSingleton<FormProgramService>();
|
|
```
|
|
|
|
**Step 3: Create BomItemsController**
|
|
|
|
`FabWorks.Api/Controllers/BomItemsController.cs`:
|
|
```csharp
|
|
using FabWorks.Api.DTOs;
|
|
using FabWorks.Core.Data;
|
|
using FabWorks.Core.Models;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace FabWorks.Api.Controllers
|
|
{
|
|
[ApiController]
|
|
[Route("api/exports/{exportId}/bom-items")]
|
|
public class BomItemsController : ControllerBase
|
|
{
|
|
private readonly FabWorksDbContext _db;
|
|
|
|
public BomItemsController(FabWorksDbContext db) => _db = db;
|
|
|
|
[HttpGet]
|
|
public async Task<ActionResult<List<BomItemDto>>> GetByExport(int exportId)
|
|
{
|
|
var items = await _db.BomItems
|
|
.Include(b => b.CutTemplate)
|
|
.Include(b => b.FormProgram)
|
|
.Where(b => b.ExportRecordId == exportId)
|
|
.OrderBy(b => b.SortOrder)
|
|
.ToListAsync();
|
|
|
|
return items.Select(MapToDto).ToList();
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult<BomItemDto>> Create(int exportId, BomItemDto dto)
|
|
{
|
|
var export = await _db.ExportRecords.FindAsync(exportId);
|
|
if (export == null) return NotFound("Export record not found");
|
|
|
|
var item = new BomItem
|
|
{
|
|
ExportRecordId = exportId,
|
|
ItemNo = dto.ItemNo ?? "",
|
|
PartNo = dto.PartNo ?? "",
|
|
SortOrder = dto.SortOrder,
|
|
Qty = dto.Qty,
|
|
TotalQty = dto.TotalQty,
|
|
Description = dto.Description ?? "",
|
|
PartName = dto.PartName ?? "",
|
|
ConfigurationName = dto.ConfigurationName ?? "",
|
|
Material = dto.Material ?? ""
|
|
};
|
|
|
|
if (dto.CutTemplate != null)
|
|
{
|
|
item.CutTemplate = new CutTemplate
|
|
{
|
|
DxfFilePath = dto.CutTemplate.DxfFilePath ?? "",
|
|
ContentHash = dto.CutTemplate.ContentHash,
|
|
Thickness = dto.CutTemplate.Thickness,
|
|
KFactor = dto.CutTemplate.KFactor,
|
|
DefaultBendRadius = dto.CutTemplate.DefaultBendRadius
|
|
};
|
|
}
|
|
|
|
if (dto.FormProgram != null)
|
|
{
|
|
item.FormProgram = new FormProgram
|
|
{
|
|
ProgramFilePath = dto.FormProgram.ProgramFilePath ?? "",
|
|
ContentHash = dto.FormProgram.ContentHash,
|
|
ProgramName = dto.FormProgram.ProgramName ?? "",
|
|
Thickness = dto.FormProgram.Thickness,
|
|
MaterialType = dto.FormProgram.MaterialType ?? "",
|
|
KFactor = dto.FormProgram.KFactor,
|
|
BendCount = dto.FormProgram.BendCount,
|
|
UpperToolNames = dto.FormProgram.UpperToolNames ?? "",
|
|
LowerToolNames = dto.FormProgram.LowerToolNames ?? "",
|
|
SetupNotes = dto.FormProgram.SetupNotes ?? ""
|
|
};
|
|
}
|
|
|
|
_db.BomItems.Add(item);
|
|
await _db.SaveChangesAsync();
|
|
|
|
return CreatedAtAction(nameof(GetByExport), new { exportId }, MapToDto(item));
|
|
}
|
|
|
|
private static BomItemDto MapToDto(BomItem b) => new()
|
|
{
|
|
ID = b.ID,
|
|
ItemNo = b.ItemNo,
|
|
PartNo = b.PartNo,
|
|
SortOrder = b.SortOrder,
|
|
Qty = b.Qty,
|
|
TotalQty = b.TotalQty,
|
|
Description = b.Description,
|
|
PartName = b.PartName,
|
|
ConfigurationName = b.ConfigurationName,
|
|
Material = b.Material,
|
|
CutTemplate = b.CutTemplate == null ? null : new CutTemplateDto
|
|
{
|
|
Id = b.CutTemplate.Id,
|
|
DxfFilePath = b.CutTemplate.DxfFilePath,
|
|
ContentHash = b.CutTemplate.ContentHash,
|
|
Thickness = b.CutTemplate.Thickness,
|
|
KFactor = b.CutTemplate.KFactor,
|
|
DefaultBendRadius = b.CutTemplate.DefaultBendRadius
|
|
},
|
|
FormProgram = b.FormProgram == null ? null : new FormProgramDto
|
|
{
|
|
Id = b.FormProgram.Id,
|
|
ProgramFilePath = b.FormProgram.ProgramFilePath,
|
|
ContentHash = b.FormProgram.ContentHash,
|
|
ProgramName = b.FormProgram.ProgramName,
|
|
Thickness = b.FormProgram.Thickness,
|
|
MaterialType = b.FormProgram.MaterialType,
|
|
KFactor = b.FormProgram.KFactor,
|
|
BendCount = b.FormProgram.BendCount,
|
|
UpperToolNames = b.FormProgram.UpperToolNames,
|
|
LowerToolNames = b.FormProgram.LowerToolNames,
|
|
SetupNotes = b.FormProgram.SetupNotes
|
|
}
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 4: Create FormProgramsController**
|
|
|
|
`FabWorks.Api/Controllers/FormProgramsController.cs`:
|
|
```csharp
|
|
using FabWorks.Api.DTOs;
|
|
using FabWorks.Api.Services;
|
|
using FabWorks.Core.Data;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace FabWorks.Api.Controllers
|
|
{
|
|
[ApiController]
|
|
[Route("api/form-programs")]
|
|
public class FormProgramsController : ControllerBase
|
|
{
|
|
private readonly FabWorksDbContext _db;
|
|
private readonly FormProgramService _formService;
|
|
|
|
public FormProgramsController(FabWorksDbContext db, FormProgramService formService)
|
|
{
|
|
_db = db;
|
|
_formService = formService;
|
|
}
|
|
|
|
[HttpGet("by-drawing")]
|
|
public async Task<ActionResult<List<FormProgramDto>>> GetByDrawing([FromQuery] string drawingNumber)
|
|
{
|
|
var programs = await _db.FormPrograms
|
|
.Include(fp => fp.BomItem)
|
|
.ThenInclude(b => b.ExportRecord)
|
|
.Where(fp => fp.BomItem.ExportRecord.DrawingNumber == drawingNumber)
|
|
.ToListAsync();
|
|
|
|
return programs.Select(fp => new FormProgramDto
|
|
{
|
|
Id = fp.Id,
|
|
ProgramFilePath = fp.ProgramFilePath,
|
|
ContentHash = fp.ContentHash,
|
|
ProgramName = fp.ProgramName,
|
|
Thickness = fp.Thickness,
|
|
MaterialType = fp.MaterialType,
|
|
KFactor = fp.KFactor,
|
|
BendCount = fp.BendCount,
|
|
UpperToolNames = fp.UpperToolNames,
|
|
LowerToolNames = fp.LowerToolNames,
|
|
SetupNotes = fp.SetupNotes
|
|
}).ToList();
|
|
}
|
|
|
|
[HttpPost("parse")]
|
|
public ActionResult<FormProgramDto> Parse([FromQuery] string filePath)
|
|
{
|
|
if (!System.IO.File.Exists(filePath))
|
|
return NotFound($"File not found: {filePath}");
|
|
|
|
var fp = _formService.ParseFromFile(filePath);
|
|
return new FormProgramDto
|
|
{
|
|
ProgramFilePath = fp.ProgramFilePath,
|
|
ContentHash = fp.ContentHash,
|
|
ProgramName = fp.ProgramName,
|
|
Thickness = fp.Thickness,
|
|
MaterialType = fp.MaterialType,
|
|
KFactor = fp.KFactor,
|
|
BendCount = fp.BendCount,
|
|
UpperToolNames = fp.UpperToolNames,
|
|
LowerToolNames = fp.LowerToolNames,
|
|
SetupNotes = fp.SetupNotes
|
|
};
|
|
}
|
|
|
|
[HttpPost("{bomItemId}")]
|
|
public async Task<ActionResult<FormProgramDto>> AttachToItem(int bomItemId, [FromQuery] string filePath)
|
|
{
|
|
var bomItem = await _db.BomItems
|
|
.Include(b => b.FormProgram)
|
|
.FirstOrDefaultAsync(b => b.ID == bomItemId);
|
|
|
|
if (bomItem == null) return NotFound("BOM item not found");
|
|
|
|
if (!System.IO.File.Exists(filePath))
|
|
return NotFound($"File not found: {filePath}");
|
|
|
|
var fp = _formService.ParseFromFile(filePath);
|
|
fp.BomItemId = bomItemId;
|
|
|
|
// Replace existing if present
|
|
if (bomItem.FormProgram != null)
|
|
_db.FormPrograms.Remove(bomItem.FormProgram);
|
|
|
|
bomItem.FormProgram = fp;
|
|
await _db.SaveChangesAsync();
|
|
|
|
return new FormProgramDto
|
|
{
|
|
Id = fp.Id,
|
|
ProgramFilePath = fp.ProgramFilePath,
|
|
ContentHash = fp.ContentHash,
|
|
ProgramName = fp.ProgramName,
|
|
Thickness = fp.Thickness,
|
|
MaterialType = fp.MaterialType,
|
|
KFactor = fp.KFactor,
|
|
BendCount = fp.BendCount,
|
|
UpperToolNames = fp.UpperToolNames,
|
|
LowerToolNames = fp.LowerToolNames,
|
|
SetupNotes = fp.SetupNotes
|
|
};
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 5: Build**
|
|
|
|
```bash
|
|
dotnet build FabWorks.Api/FabWorks.Api.csproj
|
|
```
|
|
|
|
**Step 6: Commit**
|
|
|
|
```bash
|
|
git add FabWorks.Api/
|
|
git commit -m "feat: add BomItems and FormPrograms controllers with parse service"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 6: Add EF Core Migration for FormPrograms Table
|
|
|
|
**Step 1: Add migration from FabWorks.Api**
|
|
|
|
```bash
|
|
cd C:\Users\aisaacs\Desktop\Projects\ExportDXF\FabWorks.Api
|
|
dotnet ef migrations add AddFormPrograms --project ../FabWorks.Core/FabWorks.Core.csproj --startup-project .
|
|
```
|
|
|
|
Note: If the migration tooling complains about the existing tables, we may need a baseline migration. The existing ExportDxfDb already has ExportRecords, BomItems, CutTemplates tables. The migration should only add the FormPrograms table and the FK on BomItems.
|
|
|
|
**Step 2: Apply migration**
|
|
|
|
```bash
|
|
dotnet ef database update --project ../FabWorks.Core/FabWorks.Core.csproj --startup-project .
|
|
```
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add FabWorks.Core/Migrations/
|
|
git commit -m "feat: add EF migration for FormPrograms table"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 7: Add FormProgramService Tests
|
|
|
|
**Files:**
|
|
- Create: `FabWorks.Tests/FormProgramServiceTests.cs`
|
|
|
|
**Step 1: Write tests**
|
|
|
|
`FabWorks.Tests/FormProgramServiceTests.cs`:
|
|
```csharp
|
|
using FabWorks.Api.Services;
|
|
using Xunit;
|
|
|
|
namespace FabWorks.Tests
|
|
{
|
|
public class FormProgramServiceTests
|
|
{
|
|
[Fact]
|
|
public void ParseFromFile_SamplePgm_PopulatesProgramName()
|
|
{
|
|
var service = new FormProgramService();
|
|
var fp = service.ParseFromFile("TestData/sample.pgm");
|
|
Assert.False(string.IsNullOrEmpty(fp.ProgramName));
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseFromFile_SamplePgm_PopulatesThickness()
|
|
{
|
|
var service = new FormProgramService();
|
|
var fp = service.ParseFromFile("TestData/sample.pgm");
|
|
Assert.NotNull(fp.Thickness);
|
|
Assert.True(fp.Thickness > 0);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseFromFile_SamplePgm_PopulatesBendCount()
|
|
{
|
|
var service = new FormProgramService();
|
|
var fp = service.ParseFromFile("TestData/sample.pgm");
|
|
Assert.True(fp.BendCount > 0);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseFromFile_SamplePgm_PopulatesToolNames()
|
|
{
|
|
var service = new FormProgramService();
|
|
var fp = service.ParseFromFile("TestData/sample.pgm");
|
|
Assert.False(string.IsNullOrEmpty(fp.UpperToolNames));
|
|
Assert.False(string.IsNullOrEmpty(fp.LowerToolNames));
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseFromFile_SamplePgm_ComputesContentHash()
|
|
{
|
|
var service = new FormProgramService();
|
|
var fp = service.ParseFromFile("TestData/sample.pgm");
|
|
Assert.NotNull(fp.ContentHash);
|
|
Assert.Equal(64, fp.ContentHash.Length); // SHA256 hex = 64 chars
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 2: Add project reference to FabWorks.Api**
|
|
|
|
```bash
|
|
cd C:\Users\aisaacs\Desktop\Projects\ExportDXF\FabWorks.Tests
|
|
dotnet add reference ../FabWorks.Api/FabWorks.Api.csproj
|
|
```
|
|
|
|
**Step 3: Run all tests**
|
|
|
|
```bash
|
|
dotnet test FabWorks.Tests/FabWorks.Tests.csproj -v normal
|
|
```
|
|
|
|
Expected: All 10 tests PASS.
|
|
|
|
**Step 4: Commit**
|
|
|
|
```bash
|
|
git add FabWorks.Tests/
|
|
git commit -m "test: add FormProgramService tests"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 8: Smoke Test — Run API and Hit Endpoints
|
|
|
|
**Step 1: Run the API**
|
|
|
|
```bash
|
|
cd C:\Users\aisaacs\Desktop\Projects\ExportDXF\FabWorks.Api
|
|
dotnet run
|
|
```
|
|
|
|
**Step 2: Test endpoints with curl/Invoke-WebRequest**
|
|
|
|
```powershell
|
|
# Create export record
|
|
Invoke-RestMethod -Uri "http://localhost:5000/api/exports" -Method Post -ContentType "application/json" -Body '{"drawingNumber":"4980 A05-1","sourceFilePath":"C:\\test.slddrw","outputFolder":"C:\\output"}'
|
|
|
|
# Get by ID
|
|
Invoke-RestMethod -Uri "http://localhost:5000/api/exports/1"
|
|
|
|
# Lookup by source file
|
|
Invoke-RestMethod -Uri "http://localhost:5000/api/exports/by-source?path=C:\test.slddrw"
|
|
|
|
# Parse a .pgm file
|
|
Invoke-RestMethod -Uri "http://localhost:5000/api/form-programs/parse?filePath=S:\4980 GMCH Lockport\4980 A05 Degreaser Casing\4980 A05-1 Access door\Forms\4980 A05-1 PT02.pgm"
|
|
```
|
|
|
|
**Step 3: Verify JSON responses look correct**
|
|
|
|
**Step 4: Commit any fixes needed**
|
|
|
|
---
|
|
|
|
## Future Tasks (Not In This Plan)
|
|
|
|
These are follow-up items after the API is proven out:
|
|
|
|
1. **Wire ExportDXF to call FabWorks.Api** — Add `IFabWorksClient` interface, replace `_dbContextFactory` usage in `DxfExportService` with HTTP calls
|
|
2. **Deploy as Windows Service** — Add `Microsoft.Extensions.Hosting.WindowsServices`, create `deploy.ps1` per deploy script guide
|
|
3. **Add Swagger/OpenAPI** — For discoverability
|
|
4. **LaserQuote integration** — Point LaserQuote at FabWorks.Api for BOM/material lookups
|
|
5. **Batch form program scanner** — Endpoint to scan a directory tree and auto-link .pgm files to BOM items by naming convention
|