feat: Initial purchase order views, login seems to be broken though.

This commit is contained in:
2025-01-17 12:58:32 -05:00
parent e1d07008a1
commit be0b5a6033
18 changed files with 534 additions and 288 deletions

View File

@@ -26,9 +26,11 @@ h1 { font-size: 2.5em; }
overflow: hidden;
}
header {
.header {
position: sticky;
background-color: var(--dark-bg);
color: var(--primary);
top: 0;
padding: 10px 0;
height: 60px;
border-bottom: 1px solid grey;
@@ -97,6 +99,10 @@ form {
text-align: center;
}
form .label {
text-align: right;
}
#user-form input {
width: 100%;
margin: 20px;
@@ -142,6 +148,7 @@ select {
padding: 10px 20px;
width: 100%;
margin-bottom: 10px;
font-size: 1.2em;
}
option {
@@ -291,11 +298,12 @@ a.toggle, a img.toggle {
.float {
z-index: 1;
position: absolute;
top: 60px;
top: 0;
left: 0;
width: 100%;
background-color: #14141f;
padding: 20px;
display: flex;
}
.float .closebtn {

View File

@@ -28,8 +28,8 @@ struct PurchaseOrderApiController: RouteCollection {
@Sendable
func create(req: Request) async throws -> PurchaseOrder {
try await purchaseOrders.create(
req.content.decode(PurchaseOrder.Create.self),
req.auth.require(User.self).id
req.content.decode(PurchaseOrder.CreateIntermediate.self)
.toCreate(createdByID: req.auth.require(User.self).id)
)
}

View File

@@ -1,196 +1,103 @@
// import Dependencies
// import Fluent
// import Vapor
//
// struct PurchaseOrderViewController: RouteCollection {
// @Dependency(\.employees) var employees
// @Dependency(\.purchaseOrders) var purchaseOrders
// @Dependency(\.vendorBranches) var vendorBranches
//
// func boot(routes: any RoutesBuilder) throws {
// let pos = routes.protected.grouped("purchase-orders")
//
// pos.get(use: index(req:))
// pos.group("details", "close") {
// $0.get(use: detailClose(req:))
// }
// pos.post(use: create(req:))
// pos.group(":id") {
// $0.get(use: detail(req:))
// }
// }
//
// @Sendable
// func index(req: Request) async throws -> View {
// let params = try? req.query.decode(PurchaseOrderIndex.self)
// let purchaseOrdersPage = try await purchaseOrders.fetchPage(
// .init(page: params?.page ?? 1, per: params?.limit ?? 50)
// )
// let branches = try await vendorBranches.getBranches(req: req)
// let employees = try await employees.fetchAll()
// req.logger.debug("Branches: \(branches)")
// return try await req.view.render(
// "purchaseOrders/index",
// PurchaseOrderCTX(
// page: purchaseOrdersPage,
// form: .create(branches: branches, employees: employees)
// )
// )
// }
//
// @Sendable
// func detail(req: Request) async throws -> View {
// guard let id = req.parameters.get("id", as: PurchaseOrder.IDValue.self) else {
// throw Abort(.badRequest, reason: "Id not supplied.")
// }
// let purchaseOrder = try await purchaseOrders.get(id)
// return try await req.view.render("purchaseOrders/detail", ["purchaseOrderDetail": purchaseOrder])
// }
//
// @Sendable
// func detailClose(req: Request) async throws -> View {
// return try await req.view.render("purchaseOrders/detail")
// }
//
// @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).toCreate()
// let purchaseOrder = try await purchaseOrders.create(create, createdById)
// return try await req.view.render("purchaseOrders/table-row", purchaseOrder)
// }
// }
//
// private struct PurchaseOrderIndex: Content {
// let page: Int?
// let limit: Int?
// }
//
// private struct PurchaseOrderCTX: Content {
// let purchaseOrderDetail: PurchaseOrder.DTO?
// let purchaseOrders: [PurchaseOrder.DTO]
// let page: Int
// let limit: Int
// let hasNext: Bool
// let hasPrevious: Bool
// let form: PurchaseOrderFormCTX?
//
// init(
// detail: PurchaseOrder.DTO? = nil,
// page: Page<PurchaseOrder.DTO>,
// form: PurchaseOrderFormCTX?
// ) {
// self.purchaseOrderDetail = detail
// self.purchaseOrders = page.items
// self.page = page.metadata.page
// self.limit = page.metadata.per
// self.hasNext = page.metadata.hasNext
// self.hasPrevious = page.metadata.page > 1
// self.form = form
// }
// }
//
// private extension PageMetadata {
// var hasNext: Bool {
// total > (page * per)
// }
// }
//
// 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-body",
// htmxPushUrl: false,
// htmxResetAfterRequest: true,
// htmxSwapOob: nil,
// htmxSwap: .afterbegin,
// context: .init(branches: branches, employees: employees)
// ))
// }
// }
//
// extension VendorBranch {
// struct FormDTO: Content {
// let id: UUID
// let name: String
// let vendor: Vendor.DTO
// }
//
// func toFormDTO() throws -> VendorBranch.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
//
// // TODO: Remove.
// 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
// )
// }
//
// func toCreate() -> PurchaseOrder.Create {
// .init(
// id: id,
// workOrder: workOrder != nil ? (workOrder == "" ? nil : Int(workOrder!)) : nil,
// materials: materials,
// customer: customer,
// truckStock: truckStock,
// createdForID: createdForID,
// vendorBranchID: vendorBranchID
// )
// }
// }
// }
//
// private extension VendorBranchDB {
//
// func getBranches(req: Request) async throws -> [VendorBranch.FormDTO] {
// try await VendorBranch.query(on: req.db)
// .with(\.$vendor)
// .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)
import Dependencies
import Elementary
import SharedModels
import Vapor
import VaporElementary
struct PurchaseOrderViewController: RouteCollection {
@Dependency(\.database.employees) var employees
@Dependency(\.database.vendorBranches) var vendorBranches
@Dependency(\.database.purchaseOrders) var purchaseOrders
func boot(routes: any RoutesBuilder) throws {
let route = routes.protected.grouped("purchase-orders")
route.get(use: index)
route.get("next", use: nextPage)
route.post(use: create(req:))
route.group("create") {
$0.get(use: form)
$0.get("vendor-branch-select", use: vendorBranchSelect(req:))
$0.get("employee-select", use: employeeSelect(req:))
}
route.group(":id") {
$0.get(use: get)
}
}
@Sendable
func index(req: Request) async throws -> HTMLResponse {
let page = try? req.query.decode(IndexQuery.self)
return try await req.render { try await mainPage(PurchaseOrderForm(), page: page ?? .default) }
}
@Sendable
func nextPage(req: Request) async throws -> HTMLResponse {
let query = try req.query.decode(IndexQuery.self)
let page = try await purchaseOrders.fetchPage(.init(page: query.page, per: query.limit))
return await req.render { PurchaseOrderTable.Rows(page: page) }
}
@Sendable
func form(req: Request) async throws -> HTMLResponse {
// guard req.isHtmxRequest else {
// return try await req.render {
// try await mainPage(PurchaseOrderForm(shouldShow: true), page: .default)
// }
// }
return await req.render { PurchaseOrderForm(shouldShow: true) }
}
@Sendable
func vendorBranchSelect(req: Request) async throws -> HTMLResponse {
let branches = try await vendorBranches.fetchAllWithDetail()
return await req.render { PurchaseOrderForm.VendorSelect(vendorBranches: branches) }
}
@Sendable
func employeeSelect(req: Request) async throws -> HTMLResponse {
let employees = try await self.employees.fetchAll()
return await req.render { PurchaseOrderForm.EmployeeSelect(employees: employees) }
}
@Sendable
func get(req: Request) async throws -> HTMLResponse {
let purchaseOrder = try await purchaseOrders.get(req.ensureIDPathComponent(as: Int.self))
let form = PurchaseOrderForm(purchaseOrder: purchaseOrder, shouldShow: true)
guard req.isHtmxRequest else {
return try await req.render {
try await mainPage(form, page: .default)
}
}
return await req.render { form }
}
@Sendable
func create(req: Request) async throws -> HTMLResponse {
let create = try req.content.decode(PurchaseOrder.CreateIntermediate.self)
let user = try req.auth.require(User.self)
let purchaseOrder = try await purchaseOrders.create(create.toCreate(createdByID: user.id))
return await req.render { PurchaseOrderTable.Row(purchaseOrder: purchaseOrder) }
}
private func mainPage<C: HTML>(
_ html: C,
page: IndexQuery
) async throws -> some SendableHTMLDocument where C: Sendable {
let page = try await purchaseOrders.fetchPage(.init(page: page.page, per: page.limit))
return MainPage(displayNav: true, route: .purchaseOrders) {
div(.class("container")) {
html
PurchaseOrderTable(page: page)
}
}
}
}
struct IndexQuery: Content {
let page: Int
let limit: Int
static var `default`: Self {
.init(page: 1, limit: 25)
}
}

View File

@@ -9,15 +9,14 @@ extension RoutesBuilder {
// Used to ensure views are protected, redirects users to the login page if they're
// not authenticated.
var protected: any RoutesBuilder {
return self
// return grouped(
// UserPasswordAuthenticator(),
// UserTokenAuthenticator(),
// UserSessionAuthenticator(),
// User.redirectMiddleware { req in
// "login?next=\(req.url)"
// }
// )
// return self
return grouped(
UserPasswordAuthenticator(),
UserSessionAuthenticator(),
User.redirectMiddleware { req in
"login?next=\(req.url)"
}
)
}
func apiUnprotected(route: PathComponent) -> any RoutesBuilder {

View File

@@ -21,6 +21,15 @@
var vendors: [Vendor] = []
var vendorBranches: [VendorBranch] = []
let adminUser = User.Create(
username: Environment.get("ADMIN_USERNAME") ?? "admin",
email: Environment.get("ADMIN_EMAIL") ?? "admin@development.com",
password: Environment.get("ADMIN_PASSWORD") ?? "super-secret",
confirmPassword: Environment.get("ADMIN_PASSWORD") ?? "super-secret"
)
_ = try await database.users.create(adminUser)
for user in User.Create.generateMocks() {
let created = try await database.users.create(user)
users.append(created)
@@ -41,8 +50,13 @@
vendorBranches.append(created)
}
for purchaseOrder in PurchaseOrder.Create.generateMocks(employees: employees, vendorBranches: vendorBranches) {
_ = try await database.purchaseOrders.create(purchaseOrder, users.randomElement()!.id)
for purchaseOrder in PurchaseOrder.CreateIntermediate.generateMocks(
employees: employees,
vendorBranches: vendorBranches
) {
_ = try await database.purchaseOrders.create(
purchaseOrder.toCreate(createdByID: users.randomElement()!.id)
)
}
}
}

View File

@@ -28,7 +28,7 @@ struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
}
var body: some HTML {
header {
header(.class("header")) {
Logo()
if displayNav {
Navbar()

View File

@@ -0,0 +1,161 @@
import Elementary
import ElementaryHTMX
import SharedModels
struct PurchaseOrderForm: HTML {
let purchaseOrder: PurchaseOrder?
let shouldShow: Bool
init(purchaseOrder: PurchaseOrder? = nil, shouldShow: Bool = false) {
self.purchaseOrder = purchaseOrder
self.shouldShow = shouldShow
}
var content: some HTML {
Float(shouldDisplay: shouldShow, resetURL: "/purchase-orders") {
if shouldShow {
if purchaseOrder != nil {
p {
span(.class("label"), .style("margin-right: 15px;")) { "Note:" }
span { i(.style("font-size: 1em;")) {
"Vendor and Employee can not be changed once a purchase order has been created."
} }
}
}
form(
.hx.post("/purchase-orders"),
.hx.target("purchase-order-table"),
.hx.swap(.afterBegin.transition(true).swap("1s")),
.custom(
name: "hx-on::after-request",
value: "if (event.detail.successful) toggleContent('float'); window.location.href='/purchase-orders';"
)
) {
div(.class("row")) {
label(
.for("customer"), .class("label col-2"), .style("margin-right: 15px; margin-bottom: 5px;")
) { "Customer:" }
input(
.type(.text), .class("col-3"),
.name("customer"), .placeholder("Customer"),
.value(purchaseOrder?.customer ?? ""),
.required, .autofocus
)
label(
.for("workOrder"), .class("label col-2"), .style("margin-right: 15px; margin-bottom: 5px;")
) { "Work Order:" }
input(
.type(.text), .class("col-4"),
.name("workOrder"), .placeholder("Work Order: (12345)"),
.value("\(purchaseOrder?.workOrder != nil ? String(purchaseOrder!.workOrder!) : "")")
)
}
div(.class("row")) {
label(
.for("materials"), .class("label col-2"), .style("margin-right: 15px; margin-bottom: 5px;")
) { "Materials:" }
input(
.type(.text), .class("col-3"),
.name("materials"), .placeholder("Materials"),
.value(purchaseOrder?.materials ?? ""),
.required
)
label(
.for("vendorBranchID"), .class("label col-2"), .style("margin-right: 15px; margin-bottom: 5px;")
) { "Vendor:" }
if purchaseOrder == nil {
div(
.class("col-4"),
.hx.get("/purchase-orders/create/vendor-branch-select"),
.hx.target("this"),
.hx.swap(.outerHTML.transition(true).swap("0.5s")),
.hx.trigger(.event(.revealed)),
.hx.indicator(".hx-indicator")
) {
Img.spinner().attributes(.class("hx-indicator"), .style("float: left;"))
}
} else {
input(
.type(.text), .class("col-4"),
.name("vendorBranchID"),
.value("\(purchaseOrder!.vendorBranch.vendor.name) - \(purchaseOrder!.vendorBranch.name)"),
.disabled
)
}
}
div(.class("row")) {
label(
.for("createdForID"), .class("label col-2"), .style("margin-right: 15px; margin-bottom: 5px;")
) { "Employee:" }
if purchaseOrder == nil {
div(
.class("col-3"),
.hx.get("/purchase-orders/create/employee-select"),
.hx.target("this"),
.hx.swap(.outerHTML.transition(true).swap("0.5s")),
.hx.trigger(.event(.revealed)),
.hx.indicator(".hx-indicator")
) {
Img.spinner().attributes(.class("hx-indicator"), .style("float: left;"))
}
} else {
input(
.type(.text), .class("col-3"),
.value(purchaseOrder!.createdFor.fullName),
.disabled
)
}
label(
.for("truckStock"), .class("label col-2"), .style("margin-right: 15px; margin-bottom: 5px;")
) { "Truck Stock:" }
if purchaseOrder?.truckStock == true {
input(
.type(.checkbox), .class("col-2"), .name("truckStock"), .style("margin-top: 20px;"), .checked
)
} else {
input(
.type(.checkbox), .class("col-2"), .name("truckStock"), .style("margin-top: 20px;")
)
}
}
div(.class("btn-row")) {
button(.class("btn-primary"), .type(.submit)) { buttonLabel }
if purchaseOrder != nil {
Button.danger { "Delete" }
}
}
}
}
}
}
private var buttonLabel: String {
guard purchaseOrder != nil else { return "Create" }
return "Update"
}
struct VendorSelect: HTML {
let vendorBranches: [VendorBranch.Detail]
var content: some HTML<HTMLTag.select> {
select(.name("vendorBranchID"), .class("col-3")) {
for branch in vendorBranches {
option(.value(branch.id.uuidString)) { "\(branch.vendor.name) - \(branch.name)" }
}
}
}
}
struct EmployeeSelect: HTML {
let employees: [Employee]
var content: some HTML<HTMLTag.select> {
select(.name("createdForID"), .class("col-3")) {
for employee in employees {
option(.value(employee.id.uuidString)) { employee.fullName }
}
}
}
}
}

View File

@@ -0,0 +1,92 @@
import Elementary
import ElementaryHTMX
import Fluent
import SharedModels
import Vapor
struct PurchaseOrderTable: HTML {
let page: Page<PurchaseOrder>
var content: some HTML {
table {
thead {
tr {
th { "PO" }
th { "Work Order" }
th { "Customer" }
th { "Vendor" }
th { "Materials" }
th { "Created For" }
th {
Button.add()
.attributes(
.hx.get("/purchase-orders/create"),
.hx.target("#float"),
.hx.swap(.outerHTML.transition(true).swap("1s")),
.hx.pushURL(true)
)
}
}
}
tbody(.id("purchase-order-table")) {
Rows(page: page)
}
}
}
// Produces only the rows for the given page
struct Rows: HTML {
let page: Page<PurchaseOrder>
var content: some HTML {
for purchaseOrder in page.items {
Row(purchaseOrder: purchaseOrder)
}
if page.metadata.pageCount > page.metadata.page {
tr(
.hx.get("/purchase-orders/next?page=\(page.metadata.page + 1)&limit=\(page.metadata.per)"),
.hx.trigger(.event(.revealed)),
.hx.swap(.outerHTML.transition(true).swap("1s")),
.hx.target("this"),
.hx.indicator(".htmx-indicator")
) {
img(.src("/images/spinner.svg"), .class("htmx-indicator"), .width(60), .height(60))
}
}
}
}
// A single row.
struct Row: HTML {
let purchaseOrder: PurchaseOrder
var content: some HTML<HTMLTag.tr> {
tr(
.id("purchase_order_\(purchaseOrder.id)")
) {
td { "\(purchaseOrder.id)" }
td { purchaseOrder.workOrder != nil ? String(purchaseOrder.workOrder!) : "" }
td { purchaseOrder.customer }
td { purchaseOrder.vendorBranch.displayName }
td { purchaseOrder.materials }
td { purchaseOrder.createdFor.fullName }
td {
Button.detail()
.attributes(
.hx.get("/purchase-orders/\(purchaseOrder.id)"),
.hx.target("#float"),
.hx.swap(.outerHTML.transition(true).swap("0.5s")),
.hx.pushURL(true)
)
}
}
}
}
}
private extension VendorBranch.Detail {
var displayName: String {
"\(vendor.name.capitalized) - \(name.capitalized)"
}
}

View File

@@ -0,0 +1,7 @@
import Elementary
enum Img {
static func spinner(width: Int = 30, height: Int = 30) -> some HTML<HTMLTag.img> {
img(.src("/images/spinner.svg"), .width(width), .height(height))
}
}

View File

@@ -2,6 +2,7 @@ import DatabaseClientLive
import Dependencies
import Elementary
import Fluent
import SharedModels
import Vapor
import VaporElementary
@@ -10,7 +11,7 @@ func routes(_ app: Application) throws {
try app.register(collection: UserViewController())
try app.register(collection: VendorViewController())
try app.register(collection: EmployeeViewController())
// try app.register(collection: ViewController())
try app.register(collection: PurchaseOrderViewController())
app.get { _ in
HTMLResponse {
@@ -22,20 +23,37 @@ func routes(_ app: Application) throws {
}
}
app.get("login") { _ in
HTMLResponse {
app.get("login") { req in
let context = try req.query.decode(LoginContext.self)
return await req.render {
MainPage(displayNav: false, route: .login) {
UserForm(context: .login(next: nil))
UserForm(context: .login(next: context.next))
}
}
}
// app.get("users") { _ in
// HTMLResponse {
// // UserIndex()
// MainPage(displayNav: false, route: .users) {
// UserTable()
// }
// }
// }
app.post("login") { req in
@Dependency(\.database.users) var users
let loginForm = try req.content.decode(User.Login.self)
let token = try await users.login(loginForm)
let user = try await users.get(token.userID)!
req.session.authenticate(user)
return try await PurchaseOrderViewController().index(req: req)
}
let protected = app.grouped(UserPasswordAuthenticator(), UserSessionAuthenticator())
protected.get("me") { req in
let user = try req.auth.require(User.self)
return HTMLResponse {
MainPage(displayNav: false, route: .purchaseOrders) {
h1 { "You are logged in as: \(user.username)" }
}
}
}
}
private struct LoginContext: Content {
let next: String?
}

View File

@@ -7,7 +7,7 @@ import Vapor
public extension DatabaseClient {
@DependencyClient
struct PurchaseOrders: Sendable {
public var create: @Sendable (PurchaseOrder.Create, User.ID) async throws -> PurchaseOrder
public var create: @Sendable (PurchaseOrder.Create) async throws -> PurchaseOrder
public var fetchAll: @Sendable () async throws -> [PurchaseOrder]
public var fetchPage: @Sendable (PageRequest) async throws -> Page<PurchaseOrder>
public var get: @Sendable (PurchaseOrder.ID) async throws -> PurchaseOrder?

View File

@@ -9,6 +9,7 @@ public extension DatabaseClient {
public var create: @Sendable (VendorBranch.Create) async throws -> VendorBranch
public var delete: @Sendable (VendorBranch.ID) async throws -> Void
public var fetchAll: @Sendable (FetchRequest) async throws -> [VendorBranch]
public var fetchAllWithDetail: @Sendable () async throws -> [VendorBranch.Detail]
public var get: @Sendable (VendorBranch.ID) async throws -> VendorBranch?
public var update: @Sendable (VendorBranch.ID, VendorBranch.Update) async throws -> VendorBranch

View File

@@ -6,8 +6,8 @@ import SharedModels
public extension DatabaseClient.PurchaseOrders {
static func live(database: any Database) -> Self {
.init { create, createdById in
let model = try create.toModel(createdByID: createdById)
.init { create in
let model = try create.toModel()
try await model.save(on: database)
let fetched = try await PurchaseOrderModel.allQuery(on: database).filter(\.$id == model.id!).first()!
return try fetched.toDTO()
@@ -62,7 +62,7 @@ extension PurchaseOrder {
extension PurchaseOrder.Create {
func toModel(createdByID: User.ID) throws -> PurchaseOrderModel {
func toModel() throws -> PurchaseOrderModel {
try validate()
return .init(
materials: materials,
@@ -157,7 +157,7 @@ final class PurchaseOrderModel: Model, Codable, @unchecked Sendable {
truckStock: truckStock,
createdBy: createdBy.toDTO(),
createdFor: createdFor.toDTO(),
vendorBranch: vendorBranch.toDTO(),
vendorBranch: vendorBranch.toDetail(),
createdAt: createdAt,
updatedAt: updatedAt
)

View File

@@ -34,6 +34,11 @@ public extension DatabaseClient.VendorBranches {
}
return try await query.all().map { try $0.toDTO() }
} fetchAllWithDetail: {
try await VendorBranchModel.query(on: db)
.with(\.$vendor)
.all()
.map { try $0.toDetail() }
} get: { id in
try await VendorBranchModel.find(id, on: db).map { try $0.toDTO() }
} update: { id, updates in
@@ -147,6 +152,16 @@ final class VendorBranchModel: Model, @unchecked Sendable {
)
}
func toDetail() throws -> VendorBranch.Detail {
try .init(
id: requireID(),
name: name,
vendor: vendor.toDTO(),
createdAt: createdAt,
updatedAt: updatedAt
)
}
func applyUpdates(_ updates: VendorBranch.Update) throws {
try updates.validate()
if let name = updates.name {

View File

@@ -24,6 +24,10 @@ public struct Employee: Codable, Equatable, Identifiable, Sendable {
self.lastName = lastName
self.updatedAt = updatedAt
}
public var fullName: String {
"\(firstName.capitalized) \(lastName.capitalized)"
}
}
public extension Employee {

View File

@@ -10,7 +10,7 @@ public struct PurchaseOrder: Codable, Equatable, Identifiable, Sendable {
public var truckStock: Bool
public var createdBy: User
public var createdFor: Employee
public var vendorBranch: VendorBranch
public var vendorBranch: VendorBranch.Detail
public var createdAt: Date?
public var updatedAt: Date?
@@ -22,7 +22,7 @@ public struct PurchaseOrder: Codable, Equatable, Identifiable, Sendable {
truckStock: Bool,
createdBy: User,
createdFor: Employee,
vendorBranch: VendorBranch,
vendorBranch: VendorBranch.Detail,
createdAt: Date?,
updatedAt: Date?
) {
@@ -41,9 +41,40 @@ public struct PurchaseOrder: Codable, Equatable, Identifiable, Sendable {
public extension PurchaseOrder {
// TODO: Add created by id.
struct Create: Codable, Sendable {
public let id: Int?
public let workOrder: Int?
public let materials: String
public let customer: String
public let truckStock: Bool?
public let createdByID: User.ID
public let createdForID: Employee.ID
public let vendorBranchID: VendorBranch.ID
public init(
id: Int? = nil,
workOrder: Int? = nil,
materials: String,
customer: String,
truckStock: Bool? = nil,
createdByID: User.ID,
createdForID: Employee.ID,
vendorBranchID: VendorBranch.ID
) {
self.id = id
self.workOrder = workOrder
self.materials = materials
self.customer = customer
self.truckStock = truckStock
self.createdByID = createdByID
self.createdForID = createdForID
self.vendorBranchID = vendorBranchID
}
}
struct CreateIntermediate: Codable, Sendable {
public let id: Int?
public let workOrder: Int?
public let materials: String
@@ -69,44 +100,26 @@ public extension PurchaseOrder {
self.createdForID = createdForID
self.vendorBranchID = vendorBranchID
}
public func toCreate(createdByID userID: User.ID) -> PurchaseOrder.Create {
.init(
id: id,
workOrder: workOrder,
materials: materials,
customer: customer,
truckStock: truckStock,
createdByID: userID,
createdForID: createdForID,
vendorBranchID: vendorBranchID
)
}
}
}
#if DEBUG
// public extension PurchaseOrder {
// static func generateMocks(
// count: Int = 50,
// employees: [Employee],
// users: [User],
// vendorBranches: [VendorBranch]
// ) -> [Self] {
// @Dependency(\.date.now) var now
// @Dependency(\.uuid) var uuid
//
// var output = [Self]()
//
// for id in 0 ... count {
// output.append(.init(
// id: id,
// workOrder: Int.random(in: 0 ... 100),
// materials: "Some thing",
// customer: "\(RandomNames.firstNames.randomElement()!) \(RandomNames.lastNames.randomElement()!)",
// truckStock: Bool.random(),
// createdBy: users.randomElement()!,
// createdFor: employees.randomElement()!,
// vendorBranch: vendorBranches.randomElement()!,
// createdAt: now,
// updatedAt: now
// ))
// }
//
// return output
// }
// }
public extension PurchaseOrder.Create {
public extension PurchaseOrder.CreateIntermediate {
static func generateMocks(
count: Int = 50,
employees: [Employee],

View File

@@ -34,6 +34,28 @@ public extension VendorBranch {
}
}
struct Detail: Codable, Equatable, Identifiable, Sendable {
public var id: UUID
public var name: String
public var vendor: Vendor
public var createdAt: Date?
public var updatedAt: Date?
public init(
id: UUID,
name: String,
vendor: Vendor,
createdAt: Date? = nil,
updatedAt: Date? = nil
) {
self.id = id
self.name = name
self.vendor = vendor
self.createdAt = createdAt
self.updatedAt = updatedAt
}
}
struct Update: Codable, Sendable {
public let name: String?
@@ -44,30 +66,6 @@ public extension VendorBranch {
}
#if DEBUG
// public extension VendorBranch {
//
// static func generateMocks(countPerVendor: Int = 3, vendors: [Vendor]) -> [Self] {
// @Dependency(\.date.now) var now
// @Dependency(\.uuid) var uuid
//
// var output = [Self]()
//
// for vendor in vendors {
// for _ in 0 ... countPerVendor {
// output.append(.init(
// id: uuid(),
// name: RandomNames.cityNames.randomElement()!,
// vendorID: vendor.id,
// createdAt: now,
// updatedAt: now
// ))
// }
// }
//
// return output
// }
// }
public extension VendorBranch.Create {
static func generateMocks(countPerVendor: Int = 3, vendors: [Vendor]) -> [Self] {

9
justfile Normal file
View File

@@ -0,0 +1,9 @@
seed:
swift run App seed
rm-seed:
rm -rf seed.sqlite
run:
./swift-dev