Add lead workflows, PDF signing, AR projects and photo gallery integration

This commit is contained in:
Dennis Nemec
2026-11-08 17:49:39 +01:00
parent a343bde741
commit 8d18873412
82 changed files with 6401 additions and 34 deletions

View File

@ -0,0 +1,92 @@
import Foundation
import CoreGraphics
enum LeadDetailSampleData {
static let tanks = [
LeadTankOption(id: "2700", title: "2.700 l", tank: Tank(size: .t1_2)),
LeadTankOption(id: "4850", title: "4.850 l", tank: Tank(size: .t2_1)),
LeadTankOption(id: "underground", title: "Unterirdisch",
tank: Tank(size: .t1_2, color: .green, placement: .belowGround))
]
static func make(for lead: Lead) -> LeadDetail {
let isHoffmann = lead.id == "L-240318"
let offers = isHoffmann ? hoffmannOffers(leadID: lead.id) : genericOffers(for: lead)
let photos = isHoffmann ? (1...10).map { index in
LeadPhoto(id: "\(lead.id)-photo-\(index)", title: "Standortaufnahme \(index)", isAR: index < 3,
thumbnail: LeadFileReference(id: "\(lead.id)-thumb-\(index)", version: "1", kind: .image),
original: LeadFileReference(id: "\(lead.id)-image-\(index)", version: "1", kind: .image))
} : []
let name = lead.contact.map { [$0.title, $0.firstName, $0.lastName].compactMap { $0 }.joined(separator: " ") }
return LeadDetail(lead: lead, customerType: lead.companyName == nil ? "Privatkunde" : "Gewerbekunde",
contactName: isHoffmann ? "Klaus Hoffmann" : name,
contactRole: isHoffmann ? "Geschäftsführer" : nil,
mobile: isHoffmann ? "0171 338 4206" : nil,
email: isHoffmann ? "info@baeckerei-hoffmann.de" : nil,
tankOptions: tanks, offers: offers, photos: photos)
}
private static func document(_ id: String, _ title: String, pages: Int, signed: Bool = false) -> LeadDocument {
LeadDocument(id: id, title: title, pageCount: pages, status: signed ? .signed : .pending,
file: LeadFileReference(id: id, version: "signature-preview-1", kind: .pdf),
signaturePlacement: PDFSignaturePlacement(
pageIndex: 0, bounds: CGRect(x: 310, y: 76, width: 237, height: 60)))
}
private static func date(_ month: Int, _ day: Int) -> Date? {
Calendar(identifier: .gregorian).date(from: DateComponents(year: 2026, month: month, day: day, hour: 12))
}
private static func hoffmannOffers(leadID: String) -> [LeadOffer] {
let prefix = "\(leadID)-"
let contracts = LeadDocumentGroup(id: prefix + "contracts", title: "Vertragsunterlagen", documents: [
document(prefix + "purchase", "Kaufvertrag Flüssiggastank.pdf", pages: 4),
document(prefix + "gas", "Gasliefervertrag.pdf", pages: 6),
document(prefix + "sepa", "SEPA-Lastschriftmandat.pdf", pages: 1)
])
let technical = LeadDocumentGroup(id: prefix + "technical", title: "Technik & Sicherheit", documents: [
document(prefix + "plan", "Aufstellungsplan Tank.pdf", pages: 2),
document(prefix + "safety", "Sicherheitsdatenblatt Propan.pdf", pages: 3, signed: true)
])
let privacy = LeadDocumentGroup(id: prefix + "privacy", title: "Datenschutz", documents: [
document(prefix + "consent", "Datenschutzeinwilligung.pdf", pages: 1, signed: true)
])
let rental = LeadDocumentGroup(id: prefix + "rental", title: "Vertragsunterlagen", documents: [
document(prefix + "rental-contract", "Mietvertrag Flüssiggastank.pdf", pages: 5),
document(prefix + "rental-gas", "Gasliefervertrag.pdf", pages: 6),
document(prefix + "rental-sepa", "SEPA-Lastschriftmandat.pdf", pages: 1),
document(prefix + "rental-plan", "Aufstellungsplan.pdf", pages: 2),
document(prefix + "rental-privacy", "Datenschutzeinwilligung.pdf", pages: 1)
])
return [
LeadOffer(id: "A-2026-1182", title: "Flüssiggastank 2.700 l oberirdisch", kind: .purchase,
amount: 4980, billing: .once, validUntil: date(11, 30), acceptedAt: nil,
groups: [contracts, technical, privacy], tank: Tank(size: .t1_2)),
LeadOffer(id: "A-2026-1197", title: "Flüssiggastank 4.850 l unterirdisch", kind: .rental,
amount: 39, billing: .monthly, validUntil: date(12, 15), acceptedAt: nil, groups: [rental],
tank: Tank(size: .t2_1, color: .olive, placement: .belowGround)),
LeadOffer(id: "A-2026-1043", title: "Wartungsvertrag Gasanlage", kind: .service,
amount: 129, billing: .annually, validUntil: nil, acceptedAt: date(10, 30),
groups: [LeadDocumentGroup(id: prefix + "service", title: "Vertragsunterlagen", documents: [
document(prefix + "maintenance", "Wartungsvertrag Gasanlage.pdf", pages: 3, signed: true)
])])
]
}
private static func genericOffers(for lead: Lead) -> [LeadOffer] {
guard lead.offerCount > 0 else { return [] }
return (0..<lead.offerCount).map { index in
let count = lead.unsignedContractCount / lead.offerCount
+ (index < lead.unsignedContractCount % lead.offerCount ? 1 : 0)
let documents = (0..<max(1, count)).map { number in
document("\(lead.id)-offer-\(index)-doc-\(number)", "Vertragsunterlage \(number + 1).pdf",
pages: 2, signed: count == 0)
}
return LeadOffer(id: "\(lead.id)-A-\(index + 1)", title: "Flüssiggastank 2.700 l oberirdisch",
kind: .purchase, amount: 4980, billing: .once,
validUntil: date(11, 30), acceptedAt: nil,
groups: [LeadDocumentGroup(id: "\(lead.id)-group-\(index)",
title: "Vertragsunterlagen", documents: documents)], tank: Tank(size: .t1_2))
}
}
}