import DatabaseClient import FluentKit import Foundation import SharedModels public extension DatabaseClient.PurchaseOrders { static func live(database: any Database) -> Self { .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() } fetchAll: { try await PurchaseOrderModel.allQuery(on: database) .all() .map { try $0.toDTO() } } fetchPage: { request in try await PurchaseOrderModel.allQuery(on: database) .paginate(request) .map { try $0.toDTO() } } get: { id in try await PurchaseOrderModel.allQuery(on: database) .filter(\.$id == id) .first() .map { try $0.toDTO() } } delete: { id in guard let model = try await PurchaseOrderModel.find(id, on: database) else { throw NotFoundError() } try await model.delete(on: database) } search: { search, page in let query = PurchaseOrderModel.allQuery(on: database) switch search { case let .employee(id): return try await query.filter(\.$createdFor.$id == id) .paginate(page) .map { try $0.toDTO() } case let .customer(search): return try await query.filter(\.$customer ~~ search) .paginate(page) .map { try $0.toDTO() } case let .vendor(id): return try await query.filter(\.$vendorBranch.$id == id) .paginate(page) .map { try $0.toDTO() } } } } } private extension Page where T == PurchaseOrder { static var empty: Self { .init(items: [], metadata: .init(page: 1, per: 1, total: 0)) } } extension PurchaseOrder { struct Migrate: AsyncMigration { let name = "CreatePurchaseOrder" func prepare(on database: any Database) async throws { try await database.schema(PurchaseOrderModel.schema) .field("id", .int, .identifier(auto: true)) .field("work_order", .int) .field("customer", .string, .required) .field("materials", .string, .required) .field("truck_stock", .bool, .required) .field("created_by_id", .uuid, .required, .references(UserModel.schema, "id")) .field("created_for_id", .uuid, .required, .references(EmployeeModel.schema, "id")) .field("vendor_branch_id", .uuid, .required, .references(VendorBranchModel.schema, "id")) .field("created_at", .datetime) .field("updated_at", .datetime) .create() } func revert(on database: any Database) async throws { try await database.schema(PurchaseOrderModel.schema).delete() } } } extension PurchaseOrder.Create { func toModel() throws -> PurchaseOrderModel { try validate() return .init( materials: materials, customer: customer, truckStock: truckStock ?? false, createdByID: createdByID, createdForID: createdForID, vendorBranchID: vendorBranchID ) } func validate() throws { guard !materials.isEmpty else { throw ValidationError(message: "Materials should not be empty.") } guard !customer.isEmpty else { throw ValidationError(message: "Customer should not be empty.") } } } /// The purchase order database model. /// /// # NOTE: An initial purchase order should be created with an `id` higher than our current PO /// so that subsequent PO's are generated with higher values than our current system produces. /// once the first one is set, the rest will auto-increment from there. final class PurchaseOrderModel: Model, Codable, @unchecked Sendable { static let schema = "purchase_order" @ID(custom: "id", generatedBy: .database) var id: Int? @Field(key: "work_order") var workOrder: Int? @Field(key: "materials") var materials: String @Field(key: "customer") var customer: String @Field(key: "truck_stock") var truckStock: Bool @Parent(key: "created_by_id") var createdBy: UserModel @Parent(key: "created_for_id") var createdFor: EmployeeModel @Parent(key: "vendor_branch_id") var vendorBranch: VendorBranchModel @Timestamp(key: "created_at", on: .create) var createdAt: Date? @Timestamp(key: "updated_at", on: .update) var updatedAt: Date? init() {} init( id: Int? = nil, workOrder: Int? = nil, materials: String, customer: String, truckStock: Bool, createdByID: UserModel.IDValue, createdForID: EmployeeModel.IDValue, vendorBranchID: VendorBranchModel.IDValue, createdAt: Date? = nil, updatedAt: Date? = nil ) { self.id = id self.workOrder = workOrder self.materials = materials self.customer = customer self.truckStock = truckStock $createdBy.id = createdByID $createdFor.id = createdForID $vendorBranch.id = vendorBranchID self.createdAt = createdAt self.updatedAt = updatedAt } func toDTO() throws -> PurchaseOrder { try .init( id: requireID(), workOrder: workOrder, materials: materials, customer: customer, truckStock: truckStock, createdBy: createdBy.toDTO(), createdFor: createdFor.toDTO(), vendorBranch: vendorBranch.toDetail(), createdAt: createdAt, updatedAt: updatedAt ) } static func allQuery(on db: any Database) -> QueryBuilder { PurchaseOrderModel.query(on: db) .sort(\.$id, .descending) .with(\.$createdBy) .with(\.$createdFor) .with(\.$vendorBranch) { branch in branch.with(\.$vendor) } } }