feat: Working purchase order table and form.

This commit is contained in:
2025-01-09 16:23:42 -05:00
parent da5fec4a94
commit bf71b725f6
23 changed files with 544 additions and 254 deletions

View File

@@ -10,12 +10,12 @@ struct EmployeeViewController: RouteCollection {
let employees = routes.protected.grouped("employees")
employees.get(use: index(req:))
employees.get("form", use: employeeForm(req:))
employees.post(use: postEmployeeForm(req:))
employees.post(use: create(req:))
employees.group(":employeeID") {
$0.get(use: editEmployee(req:))
$0.delete(use: deleteEmployee(req:))
$0.put(use: updateEmployee(req:))
$0.post("toggle-active", use: toggleActiveEmployee(req:))
$0.get(use: edit(req:))
$0.delete(use: delete(req:))
$0.put(use: update(req:))
$0.post("toggle-active", use: toggleActive(req:))
}
}
@@ -25,14 +25,13 @@ struct EmployeeViewController: RouteCollection {
}
@Sendable
func postEmployeeForm(req: Request) async throws -> View {
func create(req: Request) async throws -> View {
_ = try await api.createEmployee(req: req)
let employees = try await api.getSortedEmployees(req: req)
return try await req.view.render("employees/table", ["employees": employees])
return try await req.view.render("employees/index", EmployeesCTX(oob: true, api: api, req: req))
}
@Sendable
func toggleActiveEmployee(req: Request) async throws -> View {
func toggleActive(req: Request) async throws -> View {
guard let employee = try await Employee.find(req.parameters.get("employeeID"), on: req.db) else {
throw Abort(.notFound)
}
@@ -43,14 +42,14 @@ struct EmployeeViewController: RouteCollection {
}
@Sendable
func deleteEmployee(req: Request) async throws -> View {
func delete(req: Request) async throws -> View {
_ = try await api.deleteEmployee(req: req)
let employees = try await api.getSortedEmployees(req: req)
return try await req.view.render("employees/table", ["employees": employees])
}
@Sendable
func editEmployee(req: Request) async throws -> View {
func edit(req: Request) async throws -> View {
guard let employee = try await Employee.find(req.parameters.get("employeeID"), on: req.db) else {
throw Abort(.notFound)
}
@@ -58,7 +57,7 @@ struct EmployeeViewController: RouteCollection {
}
@Sendable
func updateEmployee(req: Request) async throws -> View {
func update(req: Request) async throws -> View {
_ = try await api.updateEmployee(req: req)
return try await req.view.render("employees/index", EmployeesCTX(oob: true, api: api, req: req))
}
@@ -88,12 +87,25 @@ private struct EmployeesCTX: Content {
}
private struct EmployeeFormCTX: Content {
let employee: Employee.DTO?
let oob: Bool
let htmxForm: HtmxFormCTX<Context>
init(employee: Employee.DTO? = nil) {
self.employee = employee
self.oob = employee != nil
self.htmxForm = .init(
formClass: "employee-form",
formId: "employee-form",
htmxTargetUrl: employee?.id == nil ? .post("/employees") : .put("/employees/\(employee!.id!)"),
htmxTarget: "#employee-table",
htmxPushUrl: false,
htmxResetAfterRequest: true,
htmxSwapOob: nil,
htmxSwap: employee == nil ? .outerHTML : nil,
context: .init(employee: employee)
)
}
struct Context: Content {
let employee: Employee.DTO?
}
}

View File

@@ -5,6 +5,136 @@ struct PurchaseOrderViewController: RouteCollection {
private let api = ApiController()
func boot(routes: any RoutesBuilder) throws {
// Do something.
let pos = routes.protected.grouped("purchase-orders")
pos.get(use: index(req:))
pos.post(use: create(req:))
}
@Sendable
func index(req: Request) async throws -> View {
let purchaseOrders = try await api.purchaseOrdersIndex(req: req)
let branches = try await api.getBranches(req: req)
let employees = try await api.employeesIndex(req: req)
req.logger.info("Branches: \(branches)")
return try await req.view.render(
"purchaseOrders/index",
PurchaseOrderCTX(
purchaseOrders: purchaseOrders,
form: .create(branches: branches, employees: employees)
)
)
}
@Sendable
func create(req: Request) async throws -> View {
try PurchaseOrder.FormCreate.validate(content: req)
let createdById = try req.auth.require(User.self).requireID()
let create = try req.content.decode(PurchaseOrder.FormCreate.self)
guard let employee = try await Employee.find(create.createdForID, on: req.db) else {
throw Abort(.notFound, reason: "Employee not found.")
}
guard employee.active else {
throw Abort(.badRequest, reason: "Employee is not active, unable to generate a PO for in-active employees")
}
let purchaseOrder = create.toModel(createdByID: createdById)
try await purchaseOrder.save(on: req.db)
let purchaseOrders = try await api.purchaseOrdersIndex(req: req)
return try await req.view.render("purchaseOrders/table", ["purchaseOrders": purchaseOrders])
}
}
private struct PurchaseOrderCTX: Content {
let purchaseOrders: [PurchaseOrder.DTO]
let form: PurchaseOrderFormCTX?
}
private struct PurchaseOrderFormCTX: Content {
let htmxForm: HtmxFormCTX<Context>
struct Context: Content {
let branches: [VendorBranch.FormDTO]
let employees: [Employee.DTO]
}
static func create(branches: [VendorBranch.FormDTO], employees: [Employee.DTO]) -> Self {
.init(htmxForm: .init(
formClass: "po-form",
formId: "po-form",
htmxTargetUrl: .post("/purchase-orders"),
htmxTarget: "#po-table",
htmxPushUrl: false,
htmxResetAfterRequest: true,
htmxSwapOob: nil,
htmxSwap: .outerHTML,
context: .init(branches: branches, employees: employees)
))
}
}
extension VendorBranch {
struct FormDTO: Content {
let id: UUID
let name: String
let vendor: Vendor.DTO
}
func toFormDTO() throws -> FormDTO {
try .init(
id: requireID(),
name: name,
vendor: vendor.toDTO()
)
}
}
private extension PurchaseOrder {
struct FormCreate: Content {
let id: Int?
let workOrder: String?
let materials: String
let customer: String
let truckStock: Bool?
let createdForID: Employee.IDValue
let vendorBranchID: VendorBranch.IDValue
func toModel(createdByID: User.IDValue) -> PurchaseOrder {
.init(
id: id,
workOrder: workOrder != nil ? (workOrder == "" ? nil : Int(workOrder!)) : nil,
materials: materials,
customer: customer,
truckStock: truckStock ?? false,
createdByID: createdByID,
createdForID: createdForID,
vendorBranchID: vendorBranchID,
createdAt: nil,
updatedAt: nil
)
}
}
}
private extension ApiController {
func getBranches(req: Request) async throws -> [VendorBranch.FormDTO] {
try await VendorBranch.query(on: req.db)
.with(\.$vendor)
// .sort(Vendor.self, \.$name)
.all()
.map { try $0.toFormDTO() }
}
}
extension PurchaseOrder.FormCreate: Validatable {
static func validations(_ validations: inout Validations) {
validations.add("materials", as: String.self, is: !.empty)
validations.add("customer", as: String.self, is: !.empty)
}
}

View File

@@ -6,6 +6,7 @@ struct ViewController: RouteCollection {
private let api = ApiController()
private let employees = EmployeeViewController()
private let purchaseOrders = PurchaseOrderViewController()
private let users = UserViewController()
private let vendors = VendorViewController()
@@ -25,6 +26,7 @@ struct ViewController: RouteCollection {
protected.post("logout", use: logout(req:))
// protected.get("users", use: users(req:))
try routes.register(collection: employees)
try routes.register(collection: purchaseOrders)
try routes.register(collection: users)
try routes.register(collection: vendors)
}