PrintLife Studio — 12-Domain System Coupling & Blast-Radius Map
> Why This Document Exists: PrintLife Studio is a tightly coupled, end-to-end shop management platform. Almost every feature reads from and writes to shared Firestore document s...
PrintLife Studio — 12-Domain System Coupling & Blast-Radius Map
Why This Document Exists: PrintLife Studio is a tightly coupled, end-to-end shop management platform. Almost every feature reads from and writes to shared Firestore document structures (
Quote,Project,LineItem,Decoration,Customer,StoreOrder,PurchaseOrder,PriceMatrix) and triggers downstream workflows across pricing, mockups, automations, production scheduling, inventory, shipping, and accounting.No agent may modify any file in
src/without first consulting this Coupling Map, reading the matchingdocs/*.mddomain specification(s), and tracing all upstream/downstream callers.
1. End-to-End Cross-System Data Flow
flowchart TD
D1["1. Suppliers & Product Catalog\n(PSRestful, SanMar, S&S, Custom)"] --> D2["2. Pricing & Markup Matrices\n(Screen Print, Embroidery, DTF, Garment Markup)"]
D1 --> D3["3. Art Library & Mockup Generator\n(Color Separations, Vector Renderer, Multi-Proofs)"]
D4["4. Customers, Contacts & Locations\n(Tax Exemption, Credit Terms, Addresses)"] --> D5["5. Central Quote & Invoice Engine\n(Projects, LineItems, Decorations, Fees, Totals)"]
D2 --> D5
D3 --> D5
D5 <-->|"Dual-Write Mirror\n(.set with merge:true)"| D6["6. Public Quote (/q/[id]), Portal & Design Lab\n(Mockup/Roster Approval, Self-Serve Changes)"]
D7["7. Online Stores, Value Offers & Gang Sheets\n(/s/[slug], Fundraising, DTF Builder)"] --> D8["8. Payments, Surcharges, Refunds & QuickBooks\n(Stripe, Square, Cash/Check, Credit Terms, QBO)"]
D5 --> D8
D6 --> D8
D6 --> D9["9. Automation Engine & Two-Way Inbox\n(Triggers, Shortcodes, Gmail/Outlook/SMTP)"]
D8 --> D9
D5 --> D9
D8 --> D10["10. Production Dashboard, Kanban & Auto-Scheduler\n(Capacity Minutes, Dept Queues, Rush Dates)"]
D9 --> D10
D5 --> D11["11. Inventory, Ready-to-Order, POs & Receiving\n(Stock Allocation, Vendor POs, Supacolor)"]
D7 --> D11
D5 --> D12["12. Work Orders, Contractor Portal, Printables & Shipping\n(Job Cards, Packing Slips, EasyPost/ShipStation)"]
D10 --> D12
2. Shared Firestore Document Contracts & Dual-Write Invariants
2.1 Primary Quote Document (tenants/{tenantId}/quotes/{quoteId}) & Public Mirror (publicQuotes/{quoteId})
- Dual-Write Mirror Rule:
tenants/{tenantId}/quotes/{quoteId}is the canonical shop document.publicQuotes/{quoteId}is the top-level lookup/mirror document used by/q/[quoteId], public payment routes, and public actions (findQuoteTenantId).- CRITICAL INVARIANT: Whenever any API route or server helper mutates
publicQuotes/{quoteId}, it MUST usetransaction.set(publicQuoteRef, { ...payload, tenantId }, { merge: true })orbatch.set(publicQuoteRef, { ...payload, tenantId }, { merge: true })— NEVER raw.update(publicQuoteRef, ...). Legacy quotes, imported quotes, or quotes created before public mirror initialization may not have an existingpublicQuotes/{quoteId}document, causing raw.update()to throw5 NOT_FOUND: No document to updateand roll back the entire transaction (breaking cash payments, card payments, refunds, status changes, and manual actions).
- Firestore
nullvs.undefinedRule:- The Firebase Admin SDK throws a fatal runtime error if
undefinedappears anywhere inside.update(),.set(), orFieldValue.arrayUnion(...)(including optional fields likedetails: payment.notes || ''inactivityLogentries ormockup/mockupsfields on decorations). Always usenullor'', neverundefined.
- The Firebase Admin SDK throws a fatal runtime error if
2.2 Quote -> projects[] -> lineItems[] & decorations[] Schema Contract
Every Project inside quote.projects[] contains two tightly coupled arrays that 10 different domains read and mutate:
lineItems[]:- Key fields:
id,productId,productName,brandName,styleNumber,color,sizes(map ofsizeName -> quantity),unitCosts(map ofsizeName -> wholesaleCost),priceOverrides(map ofsizeName -> manualRetailOverride),taxable,personalizations[](custom names & numbers roster),fulfillmentStatus/poId(inventory & PO tracking). - Who reads/writes
lineItems[]:- Pricing Engine (
src/lib/utils/quote-pricing-calculations.ts) - Customer Public Quote & Roster Approval (
src/lib/data/public-actions.ts,src/app/q/[id]/page.tsx) - Inventory & Ready-to-Order Aggregation (
src/lib/data/inventory.ts,src/lib/data/purchase-orders.ts) - Shipping Rate & Weight Calculation (
src/lib/server/shipping/shipping-service.ts) - QuickBooks Invoice Line Sync (
src/lib/server/quickbooks-sync.ts) - Printables: Job Cards, Work Orders, Packing Slips (
src/app/(app)/quotes-invoices/[id]/job-card/,work-order/,packing-slip/)
- Pricing Engine (
- Key fields:
decorations[]:- Key fields:
id,type/method(Screen Printing, Embroidery, DTF, etc.),location(Front, Back, Left Chest, etc.),colors/stitchCount/dimensions,matrixId,artwork(linked Art Library item),mockup(legacy single{ id, url, path } | null),mockups[](multi-proof array of{ id, url, path, label, isApproved, approvedAt, approvedBy, isRejected, rejectionReason, rejectedAt }),isApproved,approvedAt,approvedBy,isRejected,rejectionReason,rejectedAt. - CRITICAL INVARIANT:
- Always read proofs via
getDecorationMockups(decoration)fromsrc/lib/utils/mockup.tsso both legacyd.mockupand modernd.mockups[]shapes work identically. - Always evaluate quote-wide proof & roster sign-off via
checkAllMockupsApproved(projects, quoteData)fromsrc/lib/utils/mockup.ts. - When updating
d.mockups[], only writeupdatedDecoration.mockupswhenArray.isArray(d.mockups)is true (or initialize it explicitly as an array), and syncupdatedDecoration.mockuptofirst ? { id: first.id, url: first.url, path: first.path } : null.
- Always read proofs via
- Key fields:
3. The 12 Core Domains — Specifications, Key Files & Blast-Radius Coupling
Domain 1: Suppliers, Product Catalog & Custom Products
- Authoritative Docs:
- Key Files:
src/services/psrestful.ts,src/services/psrestful-Inventory.tssrc/app/api/psrestful/**src/lib/data/products.ts,src/lib/data/suppliers.ts
- Coupled Downstream Domains (Blast Radius):
- Domain 2 (Pricing): Supplier wholesale tier costs (
unitCostsper size) feed garment markup matrices. - Domain 3 (Mockup Generator): Supplier product colorways and high-res blank images feed the mockup canvas and image proxy (
/api/image-proxy). - Domain 6 & 7 (Design Lab & Online Stores): Storefront products and Design Lab blank selectors depend on normalized style/color/size structures.
- Domain 11 (Inventory & POs): Ready-to-Order and Purchase Orders group items by supplier, style number, color, and size.
- Domain 2 (Pricing): Supplier wholesale tier costs (
Domain 2: Pricing & Markup Matrices
- Authoritative Docs:
- Key Files:
src/lib/data/matrix-calculations.ts(pure, directive-free)src/lib/utils/quote-pricing-calculations.ts(pure, directive-free)src/lib/data/matrices.ts,src/lib/data/Fleece-Upcharge.ts
- Coupled Downstream Domains (Blast Radius):
- Domain 5 (Quote/Invoice Builder):
computeProjectsWithCalculations()andcomputeQuoteGrandTotal()compute every line item's unit price, size upcharges, fleece upcharges, decoration run charges, setup fees, personalization fees, discounts, and sales tax. - Domain 6 (Public Quote & Design Lab): Self-serve quantity edits (
updatePublicQuoteQuantities) and Design Lab estimates run the exact samecomputeProjectsWithCalculations()andcomputeQuoteGrandTotal()functions on the server. - Domain 8 (Payments & QuickBooks):
quote.subtotal,quote.taxAmount,quote.shippingCost, andquote.totaldrivecomputeBalanceDue()and QuickBooks invoice line sync.
- Domain 5 (Quote/Invoice Builder):
Domain 3: Art Library, Vector Renderer & Mockup Generator
- Authoritative Docs:
- Key Files:
src/lib/utils/mockup.ts(getDecorationMockups,checkAllMockupsApproved,isDecorationApproved,getDecorationRejectedMockups)src/lib/data/art-library.tssrc/app/api/image-proxy/route.ts(usesfetchWithSsrfGuard)
- Coupled Downstream Domains (Blast Radius):
- Domain 5 & 6 (Quote Builder & Public Quote
/q/[id]): Mockup proofs saved onproject.decorations[]render on/q/[id]for customer approval/rejection. - Domain 8 (Payments): When a customer pays (or staff records a payment),
checkAllMockupsApproved()determines whether theall_mockups_approved_paidautomation fires orfirst_paymentfires. - Domain 9 (Automations): Sending mockups fires
mockups_sent; approving all mockups firesall_mockups_approvedorall_mockups_approved_paid(with{ fallbackTrigger: 'all_mockups_approved' }); rejecting firesmockup_change_request. - Domain 12 (Job Cards, Work Orders & Contractor Portal): Print dimensions, Pantone/ink colors, stitch counts, and proof thumbnails render directly on shop floor Job Cards and Vendor Work Orders.
- Domain 5 & 6 (Quote Builder & Public Quote
Domain 4: Customers, Contacts, Locations & Tax Exemptions
- Authoritative Docs:
- Key Files:
src/lib/data/customers.tssrc/app/api/tenants/[tenantId]/customers/**src/lib/data/public-actions.ts(addPublicQuoteCustomerLocation,updatePublicQuoteCustomerAddress,updatePublicQuoteContact)
- Coupled Downstream Domains (Blast Radius):
- Domain 5 & 6 (Quotes & Public Quote): Updating a customer's contacts, billing/shipping locations,
isTaxExemptstatus, orcreditTermsDayssyncs into embeddedquote.customerandquote.contactsnapshots across open quotes (tenants/{tenantId}/quotesandpublicQuotes). - Domain 8 (Payments & AR):
customer.creditTermsDaysunlocks the "Credit Terms / Net Terms" payment option on/q/[id]and calculates invoicedueDate;isTaxExemptzeroes out sales tax incomputeQuoteGrandTotal(). - Domain 9 (Emails):
quote.contact.email(orquote.customer.contacts[0].email) is the recipient for automated quote/invoice emails and payment receipts.
- Domain 5 & 6 (Quotes & Public Quote): Updating a customer's contacts, billing/shipping locations,
Domain 5: Central Quote & Invoice Document Engine
- Authoritative Docs:
- Key Files:
src/lib/data/quotes.tssrc/app/api/tenants/[tenantId]/quotes/route.tssrc/app/api/tenants/[tenantId]/quotes/[id]/route.tssrc/app/(app)/quotes-invoices/**
- Coupled Downstream Domains (Blast Radius):
- Every single domain in PrintLife Studio reads or mutates
tenants/{tenantId}/quotes/{quoteId}. - Any mutation to a quote MUST keep
publicQuotes/{quoteId}in sync via.set(publicQuoteRef, { ...data, tenantId }, { merge: true }). - Converting
documentTypeto'Invoice'with adueDateautomatically triggersautoScheduleTenantInvoices(tenantId)(Domain 10). - Moving
statusIdcancels pending scheduled automations configured to cancel on status change, triggers/api/tenants/[tenantId]/quotes/[id]/status-log-email, and (whenstatusId === 'system_complete') triggersorder_completedautomations.
- Every single domain in PrintLife Studio reads or mutates
Domain 6: Public Quote (/q/[id]), Customer Portal & Design Lab
- Authoritative Docs:
- Key Files:
src/app/q/[id]/page.tsxsrc/lib/data/public-actions.ts(getPublicQuoteData,approveMockup,rejectMockup,approveRoster,rejectRoster,updatePublicQuoteQuantities,selectTurnaroundOption,acceptPaymentTerms,selectPayLater)src/lib/utils/tenant-subdomain.ts,src/proxy.ts
- Coupled Downstream Domains (Blast Radius):
- Domain 2 & 5 (Pricing & Quotes): Customer quantity edits and turnaround option selections recalculate quote totals on the server via
@/lib/utils/quote-pricing-calculationsand update bothtenants/{tenantId}/quotes/{quoteId}andpublicQuotes/{quoteId}. - Domain 8 (Payments): Public Stripe, Square, Credit Terms (
acceptPaymentTerms), and Pay Later (selectPayLater) convert quotes to invoices, record payments, and trigger automations. - Domain 9 (Automations): Public mockup/roster approvals (
approveMockup,approveRoster) evaluatealreadyPaid = (quoteData.paidAmount || 0) > 0 || quoteData.isTermsPayment === trueand callexecutePublicAutomationwith'all_mockups_approved_paid'and{ fallbackTrigger: 'all_mockups_approved' }.
- Domain 2 & 5 (Pricing & Quotes): Customer quantity edits and turnaround option selections recalculate quote totals on the server via
Domain 7: Online Stores, Value Offers & Gang Sheet Builder
- Authoritative Docs:
- Key Files:
src/app/s/[slug]/**,src/lib/data/stores.tssrc/lib/data/public-actions.ts(getPublicStoreData,processStoreCheckout)src/app/api/gang-sheet/**,src/lib/data/gang-sheets.tssrc/lib/data/value-offers.ts
- Coupled Downstream Domains (Blast Radius):
- Domain 8 (Payments & Refunds): Storefront checkout (
/s/[slug]), Gang Sheet checkout, and Value Offer checkout process live Stripe/Square payments and support processor-first refunds (/api/tenants/[tenantId]/stores/[storeId]/orders/[orderId]/refund). - Domain 11 (Inventory & POs): Store orders aggregate garment demand into Ready-to-Order / Purchase Orders.
- Domain 12 (Shipping): Storefront and Gang Sheet checkouts calculate live carrier rates via
/api/public/shipping/store-ratesand/api/public/shipping/gang-sheet-rates.
- Domain 8 (Payments & Refunds): Storefront checkout (
Domain 8: Payments, Surcharges, Credit Terms, Refunds, Financials & QuickBooks
- Authoritative Docs:
- Key Files:
src/lib/utils/payment-math.ts(computeBalanceDue,getRealizedSurcharge,isBalancePaidInFull)src/lib/server/quote-card-payment.ts(recordQuoteCardPayment)src/lib/data/public-actions.ts(recordPublicCardPayment,acceptPaymentTerms,selectPayLater)src/app/api/tenants/[tenantId]/quotes/[id]/payments/route.ts(manual Cash, Check, Wire, Credit Terms, Pay Later)src/app/api/tenants/[tenantId]/quotes/[id]/refunds/route.ts&src/lib/server/payment-refund.tssrc/lib/server/quickbooks-sync.ts(syncQuoteToQuickbooks,deleteQuickbooksPayment)
- Coupled Downstream Domains (Blast Radius):
- 5 Payment Surfaces: Staff Quote Card, Public Quote/Portal Card, Staff Manual Payment (Cash/Check/Credit Terms), Online Store Checkout, Gang Sheet Checkout, Value Offer Checkout.
- Surcharge Invariant:
paidAmountincludesamount + surcharge, whilecardSurchargeAmounttracks cumulative card surcharges socomputeBalanceDuesubtracts(paidAmount - cardSurchargeAmount)fromtotal. - Downstream Triggers: Every quote payment updates
documentType = 'Invoice'on first payment, syncspublicQuotes/{quoteId}via.set(..., { merge: true }), writes topaymentsandtransactionscollections, evaluates mockup approvals to run automations, triggersautoScheduleTenantInvoices(tenantId), and runs non-blockingsyncQuoteToQuickbooks(tenantId, quoteId).catch(...).
Domain 9: Automation Engine & Two-Way Communication Hub
- Authoritative Docs:
- Key Files:
src/lib/server/automation-scheduler.ts(executeOrScheduleAutomationActions,cancelPendingScheduledAutomations)src/lib/server/quote-email-dispatch.ts(dispatchQuoteEmail)src/lib/server/email-utils.ts(sendTenantEmail,replaceEmailShortcodes)src/lib/server/inbox-providers.ts(sanitizeEmailHtml,invalidateInboxCaches)src/app/api/tenants/[tenantId]/quotes/[id]/manual-action/route.ts
- Coupled Downstream Domains (Blast Radius):
- Domain 5 & 10 (Quote Status & Kanban): Automation
moveactions transitionquote.statusIdon the Kanban board (unlessquote.statusId === 'system_complete'). - Domain 6 (White-Label URLs): Email shortcodes (
{{quoteLink}},{{portalLink}}) resolve tenant custom subdomain URLs viagetTenantQuoteUrlandgetTenantPortalLoginUrl.
- Domain 5 & 10 (Quote Status & Kanban): Automation
Domain 10: Production Dashboard, Kanban & Capacity Auto-Scheduler
- Authoritative Docs:
- Key Files:
src/lib/server/auto-schedule-invoice.ts(autoScheduleTenantInvoices)src/lib/data/production-capacity.ts(calculateProductionMinutes,calculateQuoteProductionMinutes)src/app/api/tenants/[tenantId]/quotes/[id]/schedule/route.ts
- Coupled Downstream Domains (Blast Radius):
- Domain 5 & 8 (Invoices & Payments): Whenever a quote converts to an
'Invoice'with adueDate(via payment, Credit Terms, or manual edit),autoScheduleTenantInvoices(tenantId)calculates production minutes fromprojects[].lineItemsandprojects[].decorationsand assignsproductionDate/assignedDaysbased on shop department capacity and holidays.
- Domain 5 & 8 (Invoices & Payments): Whenever a quote converts to an
Domain 11: Inventory, Ready-to-Order, Purchase Orders & Receiving
- Authoritative Docs:
- Key Files:
src/lib/data/inventory.ts,src/lib/data/purchase-orders.tssrc/app/api/tenants/[tenantId]/purchase-orders/**src/lib/server/supacolor/client.ts
- Coupled Downstream Domains (Blast Radius):
- Domain 5 & 7 (Invoices & Store Orders): Ready-to-Order aggregates unfulfilled
lineItems[]across active invoices and store orders. Creating or receiving a PO updates item fulfillment status and stock levels across linked orders.
- Domain 5 & 7 (Invoices & Store Orders): Ready-to-Order aggregates unfulfilled
Domain 12: Work Orders, Contractor Portal, Printables & Shipping
- Authoritative Docs:
- Key Files:
src/lib/server/shipping/shipping-service.ts(getActiveShippingProvider, EasyPost -> ShipStation)src/app/api/shipping/**,src/app/api/public/shipping/**src/lib/data/work-orders.ts,src/app/portal/vendor/**src/app/(app)/quotes-invoices/[id]/job-card/**,work-order/**,packing-slip/**
- Coupled Downstream Domains (Blast Radius):
- Domain 5 & 9 (Quotes & Emails): Purchasing a shipping label (
POST /api/shipping/create-label) writes tracking numbers and label URLs onto the quote/order and populates{{trackingNumber}},{{trackingLink}}, and{{carrier}}shortcodes for shipping confirmation emails.
- Domain 5 & 9 (Quotes & Emails): Purchasing a shipping label (
4. Complete Documentation Index (docs/*.md)
Have a question about this feature?
Log into your Print Life dashboard anytime and click the Shop Co-Pilot button. You can ask anything from margin math to work center capacity, and the AI wizard will guide you through step-by-step.