BrainGrid

FilaOps ERP - Functional Specification

3D Print Farm ERP - Production-grade manufacturing resource planning for additive manufacturing

Used in: 1 reposβ€’Updated: recently

FilaOps ERP - Functional Specification

#Purpose-Built for 3D Print Farm Operations

Version: 0.2 (Based on operator interview) Last Updated: 2025-12-29 Status: πŸ”„ Core workflows mapped


#Document Purpose

This is THE source of truth for what FilaOps does. Every feature, button, and calculation should trace back to this document.

Rules:

  1. If it's not documented here, it doesn't exist
  2. Code implements this spec, not the other way around
  3. Tests validate this spec

#Design Philosophy

The operator works 20 hrs/day, 7 days/week. This system exists to reduce that.

AUTOMATE: Web orders, MRP calculations, low stock alerts
SIMPLIFY: Morning dashboard, one-click actions, fast decisions
REMEMBER: So the operator doesn't have to hold it all in their head

What FilaOps IS:

  • Operations management (quotes, orders, production, inventory, shipping)
  • Decision support (what to make, what to buy, what's blocking)

What FilaOps IS NOT:

  • Accounting system (QuickBooks does that)
  • Full MES/shop floor control (overkill for small print farm)
  • Printer firmware (Bambu handles that)

#Operator Profile

Solo print farm owner
β”œβ”€β”€ Multiple sales channels (web, wholesale, POS, custom quotes)
β”œβ”€β”€ 6+ printers running daily
β”œβ”€β”€ MTO (make-to-order) + MTS (make-to-stock) hybrid
β”œβ”€β”€ One-person: sales, production, QC, shipping, IT
└── Needs: Less mental load, fewer surprises, faster decisions

PART 1: CORE ENTITIES (Master Data)

#1.1 Items (Products & Materials)

#Item Types (Simple)

TypeDescriptionExampleHas BOMPurchasedProducedSold
raw_materialStuff you buy to make thingsFilament, screws, LEDsNoβœ…--
finished_goodStuff you sellWidget Pro, Custom LampYes-βœ…βœ…
packagingBoxes, labels, bagsBox-Small, Poly MailerNoβœ…--

#Standard Fields

FieldTypeRequiredDescription
skustringyesYour unique code (PLA-BLK-1KG, WIDGET-PRO)
namestringyesDisplay name
item_typeenumyesraw_material, finished_good, packaging
standard_costdecimalyesYour cost basis for pricing
activeboolyesStill in use?

#Replenishment Fields

FieldTypeDescription
replenishment_typeenumreorder_point, order_driven
reorder_pointdecimalAlert when on_hand drops below this
reorder_qtydecimalSuggested order quantity

Staples (reorder_point): PLA-WHITE, PLA-BLACK, common hardware Specialty (order_driven): TPU, silk filaments, one-off components

#UOM Configuration (The Special Sauce)

FieldTypeDescription
base_uomstringInternal tracking unit (grams for filament, ea for hardware)
purchase_uomstringHow vendors sell it (spool, box, bag)
purchase_conversiondecimal1 purchase_uom = X base_uom

Example:

Item: PLA-BLACK
β”œβ”€β”€ base_uom: grams (what slicer reports, what BOM uses)
β”œβ”€β”€ purchase_uom: spool
β”œβ”€β”€ purchase_conversion: 1000 (1 spool = 1000g)
β”œβ”€β”€ standard_cost: $0.02/gram
β”œβ”€β”€ replenishment_type: reorder_point
β”œβ”€β”€ reorder_point: 2000 (grams = 2 spools)
└── reorder_qty: 5000 (grams = 5 spools)

When you buy: "5 spools" β†’ system adds 5000g to inventory
When BOM says: "47g" β†’ system knows what that means
When low: Alert at 2000g, suggest order 5000g

#Filament-Specific Fields (Optional)

FieldTypeDescription
material_typeenumPLA, PETG, ABS, TPU, ASA, etc.
colorstringBlack, White, Silk Gold
manufacturerstringBambu, PolyMaker, Sunlu

Nice for filtering/reporting, not required for core function.


#1.2 Bill of Materials (BOM)

A recipe. "To make 1 Widget, you need these things."

#BOM Structure

BOM: LAMP-DESK-01
β”œβ”€β”€ Parent: LAMP-DESK-01 (what we're making)
β”œβ”€β”€ Makes: 1 ea
β”‚
└── Components:
    β”œβ”€β”€ Line 1: PLA-BLACK, 235g, $0.02/g = $4.70
    β”œβ”€β”€ Line 2: LED-PUCK, 1 ea, $4.50 = $4.50
    β”œβ”€β”€ Line 3: POWER-CORD, 1 ea, $2.00 = $2.00
    β”œβ”€β”€ Line 4: HARDWARE-KIT, 1 ea, $0.50 = $0.50
    β”œβ”€β”€ Line 5: BOX-LAMP, 1 ea, $1.20 = $1.20
    β”‚
    β”œβ”€β”€ Material Cost: $12.90
    β”œβ”€β”€ Runtime: 6 hours Γ— $1.50/hr = $9.00
    β”‚
    β”œβ”€β”€ TOTAL COST: $21.90
    β”œβ”€β”€ Retail (Γ—3.5): $76.65 β†’ $75.99
    └── B2B 10+ (Γ—2.5): $54.75 β†’ $52.99

#BOM Fields

FieldTypeDescription
product_idFK→ItemThe finished good
component_idFK→ItemWhat goes into it
quantity_perdecimalHow many/much per unit (in component's base_uom)
scrap_pctdecimalExpected waste (default 0%)
FieldTypeDescription
print_time_minutesintFrom slicer
gcode_filestringPath to sliced file (optional)

#Pricing Formula (Brandan's Method)

1def calculate_price(bom, markup_multiplier=3.5):
2    """
3    markup_multiplier: 3.5 for retail, 2.5 for B2B 10+
4    """
5    material_cost = sum(line.qty * line.component.standard_cost for line in bom.lines)
6    runtime_cost = bom.print_time_minutes / 60 * 1.50  # $1.50/hr
7    total_cost = material_cost + runtime_cost
8    price = total_cost * markup_multiplier
9    return round_to_99(price)  # $76.65 β†’ $75.99

#1.3 Customers

Keep it simple.

FieldTypeRequiredNotes
codestringyesShort identifier
namestringyesDisplay name
emailstringyesFor quotes/invoices
phonestringno
addresstextnoShipping address
price_levelenumyesretail (Γ—3.5), wholesale (Γ—2.5)
notestextno"Prefers USPS", "Net 30 approved"

Sources:

  • Manual entry (custom quotes, phone orders)
  • Auto-created from web orders (Shopify, Etsy, etc.)

#1.4 Vendors (Suppliers)

Who you buy from.

FieldTypeNotes
codestringBAMBU, AMAZON, ULINE, MCMASTER
namestringFull name
lead_time_daysintDefault for planning (Bambu=5, Amazon=2)
notestextAccount numbers, contacts

#1.5 Units of Measure (UOM)

Keep the list small.

CodeNameClassUsed For
eaEachquantityHardware, components, finished goods
gGramsweightFilament (base unit)
kgKilogramsweightBulk filament
spoolSpoolquantityFilament purchasing
mMeterslengthSome specialty materials

#Conversions (System-Defined)

FromToFactor
kgg1000
spoolg(per item, typically 1000)

#1.6 Inventory Locations

Simple for a home/small print farm.

CodeNamePurpose
MAINMain StorageRaw materials, finished stock
WIPWork in ProgressStaging area (optional tracking)
SHIPReady to ShipPacked and labeled (optional)

Many small operations just use MAIN for everything. That's fine.


#1.7 Printers (Future Enhancement)

Not required for core function, but useful for:

  • Knowing which printer ran which job
  • Status dashboard
  • Capacity planning
FieldTypeNotes
codestringP1S-01, A1-MINI-02
modelenumP1S, P1P, X1C, A1, A1-Mini
statusenumidle, printing, error, offline
current_jobFK→WOWhat's running

Bambu Handy already does this. FilaOps integration = nice to have, not essential.


PART 2: CORE WORKFLOWS

#2.1 The Big Picture

HOW WORK FLOWS THROUGH THE SYSTEM
─────────────────────────────────

DEMAND COMES IN                    SUPPLY GOES OUT
(why are we making stuff?)         (how do we fulfill it?)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                    
β”‚ Custom      │──→ Quote ──→┐      
β”‚ Request     β”‚             β”‚      
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β”‚      
                            β–Ό      
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Web Order   │──→──→│   SALES    │──→──→│ PRODUCTION │──→──→│  SHIP   β”‚
β”‚ (auto)      β”‚      β”‚   ORDER    β”‚      β”‚   ORDER    β”‚      β”‚         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”‚                   β”‚
β”‚ Phone/POS   β”‚β”€β”€β†’β”€β”€β†’β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚
β”‚ (manual)    β”‚                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                 β”‚
                                                β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Low Stock   │──→──→──→──→──→──→──→──→──│ PRODUCTION β”‚ (restock run)
β”‚ Alert       β”‚                          β”‚   ORDER    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                β”‚
                                                β–Ό
                                         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                         β”‚    MRP     β”‚
                                         β”‚ "What do   β”‚
                                         β”‚  I need?"  β”‚
                                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                β”‚
                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                    β–Ό                       β–Ό
                             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                             β”‚  PURCHASE  β”‚          β”‚  HAVE IT   β”‚
                             β”‚   ORDER    β”‚          β”‚  (proceed) β”‚
                             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚
                                    β–Ό
                             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                             β”‚  RECEIVE   │──→ Inventory
                             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

#2.2 Quote (Custom Work)

When: Customer asks "can you make this?"

States:

StatusMeaningActions Available
draftYou're working on itEdit, Send, Delete
sentCustomer has itConvert to SO, Expire, Cancel
convertedBecame an order(done)
expiredToo old(done)
cancelledThey said no(done)

Quote Document:

Quote Q-2025-0042
β”œβ”€β”€ Customer: Jane's Craft Shop
β”œβ”€β”€ Created: 2025-01-10
β”œβ”€β”€ Valid Until: 2025-01-24
β”œβ”€β”€ Status: sent
β”‚
β”œβ”€β”€ Lines:
β”‚   β”œβ”€β”€ CUSTOM-BRACKET-001 Γ— 50 @ $4.99 = $249.50
β”‚   └── (includes: 23g PLA, 45min print, Γ—3.5 markup)
β”‚
β”œβ”€β”€ Notes: "Customer provided STL, wants matte black"
└── Total: $249.50

Key Action: Convert to Sales Order

WHEN: Customer says "yes, let's do it"

1. Create Sales Order from quote
   - Copy customer
   - Copy lines
   - Link SO back to quote
2. Update quote.status = 'converted'
3. Navigate to new SO

#2.3 Sales Order (Commitment to Deliver)

When: Customer wants something and you've agreed to make/ship it

Sources:

  • Converted quote (custom)
  • Web order import (Shopify, Etsy, etc.)
  • Manual entry (phone, wholesale, POS)

States:

StatusMeaningActions Available
draftEntering orderEdit, Confirm, Cancel
confirmedReady to produceCreate WO, Cancel (if no WO)
in_productionBeing madeView progress
readyComplete, ready to shipShip
shippedOut the door(done - goes to QB as invoice)
cancelledCancelled(done)

Sales Order Document:

Sales Order SO-2025-0089
β”œβ”€β”€ Customer: Acme Corp
β”œβ”€β”€ Order Date: 2025-01-15
β”œβ”€β”€ Ship By: 2025-01-22
β”œβ”€β”€ Source: Q-2025-0042 (or "Shopify #1234", or "Manual")
β”œβ”€β”€ Status: in_production
β”‚
β”œβ”€β”€ Lines:
β”‚   β”œβ”€β”€ Line 1: WIDGET-PRO Γ— 10 @ $12.99 = $129.90
β”‚   β”‚   β”œβ”€β”€ Qty Ready: 6
β”‚   β”‚   └── Production: WO-0112 (60% complete)
β”‚   β”‚
β”‚   └── Line 2: GADGET-MINI Γ— 5 @ $6.99 = $34.95
β”‚       β”œβ”€β”€ Qty Ready: 5 βœ“
β”‚       └── Production: WO-0113 (complete)
β”‚
β”œβ”€β”€ Subtotal: $164.85
β”œβ”€β”€ Shipping: $8.50
└── Total: $173.35

BLOCKING ISSUES:
β”œβ”€β”€ WO-0112 needs 47g PLA-RED (out of stock)
└── [Order Missing Material] button

Key Actions:

Confirm Order:

WHEN: Order is complete and you're committing to it

1. Validate has lines
2. Set status = 'confirmed'
3. Trigger MRP check (do we have material?)

Create Production Order:

WHEN: Confirmed order has items that need to be made

For each line:
1. Check if product has BOM (if not, skip or error)
2. Create Production Order:
   - Product = line.product
   - Quantity = line.qty
   - Due date = SO.ship_by
   - Link back to SO line
3. Set SO status = 'in_production'

Order Missing Material:

WHEN: Blocking issues show material shortages

1. Gather all material shortages across linked WOs
2. Open Purchase Order dialog pre-filled:
   - Suggested vendor (from item.preferred_vendor)
   - Items and quantities needed
3. On submit: Create PO, link to demand
4. Blocking issues now show "Incoming: PO-xxxx, ETA: date"

Ship Order:

WHEN: All lines ready (or partial ship)

1. Create Shipment record
2. Deduct inventory
3. Set SO status = 'shipped'
4. (Future: create QB invoice, notify customer)

#2.4 Production Order (Work Order)

When: You need to make something

Sources:

  • Created from Sales Order (MTO)
  • Created manually for restocking (MTS)
  • Suggested by MRP/low stock alert

States:

StatusMeaningActions Available
draftPlanningEdit, Release, Cancel
releasedReady to printStart, Cancel
in_progressOn the printerRecord completion, Scrap
completeDone printing(done)
cancelledCancelled(done)

Production Order Document:

Production Order WO-2025-0112
β”œβ”€β”€ Product: WIDGET-PRO
β”œβ”€β”€ Quantity: 10
β”œβ”€β”€ Due Date: 2025-01-22
β”œβ”€β”€ Status: in_progress
β”‚
β”œβ”€β”€ Demand Source:
β”‚   β”œβ”€β”€ Sales Order: SO-2025-0089, Line 1
β”‚   └── Customer: Acme Corp
β”‚
β”œβ”€β”€ Progress:
β”‚   β”œβ”€β”€ Completed: 6
β”‚   β”œβ”€β”€ Scrapped: 1
β”‚   └── Remaining: 3
β”‚
β”œβ”€β”€ Material Requirements:
β”‚   β”œβ”€β”€ PLA-BLACK: 470g needed (47g Γ— 10)
β”‚   β”‚   β”œβ”€β”€ Available: 2,500g
β”‚   β”‚   └── Status: βœ“ OK
β”‚   β”œβ”€β”€ SPRING-CLIP: 20 ea needed (2 Γ— 10)
β”‚   β”‚   β”œβ”€β”€ Available: 45 ea
β”‚   β”‚   └── Status: βœ“ OK
β”‚
└── Print Time: 4.5 hrs total (27 min Γ— 10)

Key Actions:

Release:

WHEN: WO is planned and materials are checked

1. Set status = 'released'
2. Materials are now "allocated" (reserved for this job)

Record Completion:

WHEN: Prints come off the printer

INPUT: quantity_good, quantity_scrapped

1. Update WO.qty_completed += quantity_good
2. Update WO.qty_scrapped += quantity_scrapped
3. Add finished goods to inventory
4. If qty_completed >= qty_ordered: status = 'complete'
5. Update linked SO line qty_ready

Scrap:

WHEN: Print fails, bad quality, etc.

INPUT: quantity, reason

1. Update WO.qty_scrapped += quantity
2. (Material was consumed but no output)
3. Log reason for review

#2.5 Purchase Order

When: You need to buy materials

Sources:

  • Manual ("I need to order filament")
  • From "Order Missing Material" button on SO
  • From MRP suggestion
  • From low stock alert

States:

StatusMeaningActions Available
draftEntering orderEdit, Send, Cancel
sentOrdered from vendorReceive, Cancel
partialSome receivedReceive more, Close
receivedAll received(done)
cancelledCancelled(done)

Purchase Order Document:

Purchase Order PO-2025-0034
β”œβ”€β”€ Vendor: Bambu Lab
β”œβ”€β”€ Order Date: 2025-01-15
β”œβ”€β”€ Expected: 2025-01-20
β”œβ”€β”€ Status: sent
β”‚
β”œβ”€β”€ Lines:
β”‚   β”œβ”€β”€ PLA-BLACK Γ— 5 spools @ $19.99 = $99.95
β”‚   β”‚   └── Qty Received: 0
β”‚   └── PLA-WHITE Γ— 3 spools @ $19.99 = $59.97
β”‚       └── Qty Received: 0
β”‚
└── Total: $159.92

LINKED DEMAND:
β”œβ”€β”€ WO-0112 (needs PLA-BLACK for SO-0089)
└── Low stock replenishment

Key Action: Receive

WHEN: Package arrives

For each line:
1. Enter qty received
2. Convert to base UOM: 5 spools Γ— 1000g = 5000g
3. Add to inventory (location: MAIN)
4. Update PO line qty_received

If all lines fully received:
5. Set PO status = 'received'
6. (Future: create QB bill)

#2.6 Inventory Transactions

Every time inventory changes, record it.

#Transaction Types

TypeDirectionTriggerExample
receive+PO received+5000g PLA-BLACK
issue-WO material used-470g PLA-BLACK
complete+WO finished+10 ea WIDGET-PRO
ship-Order shipped-10 ea WIDGET-PRO
adjust+/-Physical count+50g (found extra)
scrap-Waste/damage-47g (failed print)

#Running Balance Example

Item: PLA-BLACK
Location: MAIN

Date     | Type     | Reference  | Qty     | Balance
---------|----------|------------|---------|--------
01/01    | -        | Opening    | -       | 3,000g
01/10    | issue    | WO-0098    | -235g   | 2,765g
01/12    | issue    | WO-0101    | -470g   | 2,295g
01/15    | receive  | PO-0034    | +5,000g | 7,295g
01/16    | issue    | WO-0112    | -282g   | 7,013g

#2.7 Shipment

When: Order is ready to go out the door

Shipment Document:

Shipment SH-2025-0067
β”œβ”€β”€ Sales Order: SO-2025-0089
β”œβ”€β”€ Customer: Acme Corp
β”œβ”€β”€ Ship Date: 2025-01-22
β”œβ”€β”€ Carrier: USPS
β”œβ”€β”€ Tracking: 9400111899223456789012
β”‚
β”œβ”€β”€ Items Shipped:
β”‚   β”œβ”€β”€ WIDGET-PRO Γ— 10
β”‚   └── GADGET-MINI Γ— 5
β”‚
└── Status: shipped

On Ship:

  1. Deduct inventory for each line
  2. Update SO status
  3. Record tracking number
  4. (Future: notify customer, sync to Pirateship)

#2.8 Returns (Edge Case)

Happens rarely, but need to handle.

Process:

  1. Create Return record linked to original SO/Shipment
  2. Receive items back
  3. Inspect: restock or scrap?
  4. Adjust inventory accordingly
  5. (Refund handled in QuickBooks)

PART 3: MRP (The Brain)

MRP answers: "What do I need, and when?"

#3.1 How It Works

DEMAND                              SUPPLY
(what I need to make/ship)          (what I have or is coming)
───────────────────────────         ───────────────────────────
Sales Order lines (confirmed)       On-hand inventory
+ Restock needs (below reorder)     + Open PO lines (not received)
+ Safety stock                      + Open WO completions
= GROSS REQUIREMENT                 = AVAILABLE SUPPLY

GROSS - AVAILABLE = NET REQUIREMENT

If NET > 0 β†’ You need to buy or make something

#3.2 Simple MRP Check (Per Item)

1def check_item_availability(item_id):
2    """
3    The core calculation used everywhere
4    """
5    # What do I have?
6    on_hand = get_inventory_qty(item_id)
7    
8    # What's coming in?
9    incoming_po = sum(po_line.qty_open for po_line in open_po_lines(item_id))
10    incoming_wo = sum(wo.qty_remaining for wo in open_wos_producing(item_id))
11    
12    # What's promised out?
13    allocated_to_wos = sum(
14        wo.qty_ordered * bom_line.qty_per
15        for wo in open_wos
16        for bom_line in wo.product.bom
17        if bom_line.component_id == item_id
18    )
19    
20    # What's the situation?
21    available = on_hand - allocated_to_wos
22    net_position = available + incoming_po + incoming_wo
23    
24    return {
25        'on_hand': on_hand,
26        'allocated': allocated_to_wos,
27        'available': available,
28        'incoming': incoming_po + incoming_wo,
29        'net_position': net_position,
30        'is_short': available < 0
31    }

#3.3 MRP Suggestions

When you confirm a Sales Order:

For each line:
  1. Can we ship from stock? 
     - If yes: reserve inventory, done
     - If no: need to produce
  
  2. To produce, check materials:
     - For each BOM component:
       - Is there enough available?
       - If no: suggest PO
       
  3. Show user:
     - "Ready to go" (have everything)
     - "Need to order: X, Y, Z" (shortages)
     - "Waiting for: PO-123" (already ordered)

#3.4 Reorder Point Alerts

Nightly (or on-demand):

For each item where replenishment_type = 'reorder_point':
  if on_hand < reorder_point:
    create_alert(
      item=item,
      on_hand=on_hand,
      reorder_point=reorder_point,
      suggested_qty=reorder_qty,
      severity='critical' if on_hand < (reorder_point / 2) else 'warning'
    )

PART 4: DASHBOARDS

#4.1 Morning Dashboard

Purpose: What do I need to DO today?

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ“¦ SHIP TODAY                                  3 orders β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ SO-0089 β”‚ Acme Corp   β”‚ 2 items β”‚ βœ… READY     β”‚ [Ship] β”‚
β”‚ SO-0091 β”‚ Jane Doe    β”‚ 1 item  β”‚ βœ… READY     β”‚ [Ship] β”‚
β”‚ SO-0092 β”‚ Bob's Shop  β”‚ 3 items β”‚ ⚠️ 2 of 3   β”‚ [View] β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ–¨οΈ PRINT TODAY                                  5 jobs β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ WO-0112 β”‚ Widget Pro Γ—10 β”‚ Due Today β”‚ βœ… Mat Ready    β”‚
β”‚ WO-0113 β”‚ Gadget Γ—5      β”‚ Due Today β”‚ βœ… Mat Ready    β”‚
β”‚ WO-0114 β”‚ Custom Γ—1      β”‚ Due Today β”‚ ⚠️ Need PLA-RED β”‚
β”‚ WO-0115 β”‚ Lamp Γ—2        β”‚ Due Tmrw  β”‚ βœ… Mat Ready    β”‚
β”‚ WO-0116 β”‚ Stand Γ—20      β”‚ Due Tmrw  β”‚ βœ… Mat Ready    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ›’ NEED TO ORDER                               2 items β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PLA-RED      β”‚ Have: 200g β”‚ Need: 450g β”‚ Short: 250g  β”‚
β”‚ SPRING-CLIP  β”‚ Have: 12   β”‚ Need: 40   β”‚ Short: 28    β”‚
β”‚                                                         β”‚
β”‚ [Create PO for All Shortages]                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ–¨οΈ PRINTERS                                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ P1S-01: 🟒 Idle        β”‚ P1S-02: πŸ”΅ Printing 67%       β”‚
β”‚ A1-01:  🟒 Idle        β”‚ A1-02:  🟒 Idle               β”‚
β”‚ A1-03:  πŸ”΄ Error       β”‚ A1-04:  🟒 Idle               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ’¬ QUOTES PENDING RESPONSE                    2 quotes β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Q-0156 β”‚ Jane's Crafts β”‚ Custom bracket β”‚ Sent 3d ago β”‚
β”‚ Q-0158 β”‚ Wholesale Co  β”‚ 50Γ— widgets    β”‚ Sent 1d ago β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

#4.2 Evening Dashboard

Purpose: What do I need to PLAN?

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ›’ NEED TO ORDER                               4 items β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PLA-WHITE  β”‚ 800g   β”‚ Min: 2kg  β”‚ πŸ”΄ CRITICAL         β”‚
β”‚ PLA-BLACK  β”‚ 1.5kg  β”‚ Min: 2kg  β”‚ 🟑 LOW              β”‚
β”‚ LED-PUCK   β”‚ 4 ea   β”‚ Min: 10   β”‚ 🟑 LOW              β”‚
β”‚ BOX-SMALL  β”‚ 18 ea  β”‚ Min: 25   β”‚ 🟑 LOW              β”‚
β”‚                                                         β”‚
β”‚ [Create PO - Bambu Lab]  [Create PO - Amazon]          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ“¬ ARRIVING SOON                               2 POs   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PO-0034 β”‚ Bambu Lab β”‚ 5Γ— PLA spools   β”‚ ETA: Tomorrow β”‚
β”‚ PO-0036 β”‚ Amazon    β”‚ LED pucks, wire β”‚ ETA: Friday   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸŒ™ OVERNIGHT SUGGESTIONS                      3 jobs   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ These items are below reorder point and material ready β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ ☐ WIDGET-PRO Γ—10    β”‚ 8hr print β”‚ PLA-BLK βœ…          β”‚
β”‚ ☐ PHONE-STAND Γ—15   β”‚ 6hr print β”‚ PLA-BLK βœ…          β”‚
β”‚ ☐ GADGET-MINI Γ—20   β”‚ 4hr print β”‚ PLA-WHT ⚠️ low     β”‚
β”‚                                                         β”‚
β”‚ [Create WO for Selected]                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ“‰ STOCK LEVELS                                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Finished Goods Below Reorder:                          β”‚
β”‚ β€’ WIDGET-PRO: 3 ea (min: 10)                          β”‚
β”‚ β€’ PHONE-STAND: 5 ea (min: 15)                         β”‚
β”‚ β€’ GADGET-MINI: 8 ea (min: 20)                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

PART 5: INTEGRATIONS (Future)

#5.1 Priority Order

IntegrationValueEffortWhen
Web order import (Shopify/Etsy)HIGHMediumAfter core stable
QuickBooks exportHIGHMediumAfter core stable
Pirateship syncMEDIUMLowNice to have
Bambu printer statusLOWHighMuch later

#5.2 QuickBooks Integration

What to sync:

  • Shipped orders β†’ QB Invoices
  • Received POs β†’ QB Bills
  • Customers β†’ QB Customers
  • Vendors β†’ QB Vendors

What NOT to sync:

  • Inventory (let QB do its own thing or sync periodically)
  • Anything in draft status

#5.3 Web Order Import

Shopify/Etsy/TikTok flow:

1. Order placed on platform (payment collected there)
2. FilaOps polls or receives webhook
3. Create Customer (if new)
4. Create Sales Order (status: confirmed)
5. Trigger normal fulfillment flow
6. On ship: send tracking back to platform

PART 6: DATA INTEGRITY RULES

#6.1 What Must Always Be True

These are assertions. If any is false, something is broken.

1# Every SO line has a valid product
2assert all(line.product_id is not None for so in sales_orders for line in so.lines)
3
4# Every WO has a valid BOM
5assert all(wo.product.bom is not None for wo in production_orders)
6
7# Inventory can never be negative
8assert all(inv.qty >= 0 for inv in inventory_records)
9
10# Completed WO qty_completed <= qty_ordered (can't make more than asked)
11assert all(wo.qty_completed <= wo.qty_ordered for wo in production_orders)
12
13# Shipped qty can't exceed ordered qty
14assert all(line.qty_shipped <= line.qty_ordered for so in sales_orders for line in so.lines)
15
16# PO received can't exceed ordered
17assert all(line.qty_received <= line.qty_ordered for po in purchase_orders for line in po.lines)

#6.2 State Machine Rules

Quote: draft β†’ sent β†’ [converted | expired | cancelled]
             ↓
       (no going back from converted/expired/cancelled)

Sales Order: draft β†’ confirmed β†’ in_production β†’ ready β†’ shipped
                  ↓
             cancelled (only from draft/confirmed)

Production Order: draft β†’ released β†’ in_progress β†’ complete
                       ↓
                  cancelled (only from draft/released)

Purchase Order: draft β†’ sent β†’ [partial β†’] received
                     ↓
                cancelled (only before receiving)

#6.3 Referential Integrity

Delete Rules:
- Can't delete Customer with open SOs
- Can't delete Item with inventory > 0
- Can't delete Item used in open orders
- Can't delete Vendor with open POs

Cascade Rules:
- Archive Item β†’ archives BOMs using it (soft delete)
- Cancel SO β†’ releases any material allocations

PART 7: TESTING REQUIREMENTS

#7.1 Critical Path Tests

Before ANY release, these must pass:

1def test_quote_to_shipment_flow():
2    """The happy path works end-to-end"""
3    # Create quote
4    quote = create_quote(customer, lines=[{product: widget, qty: 10}])
5    # Convert to SO
6    so = convert_quote(quote.id)
7    assert so.status == 'confirmed'
8    # Create production
9    wo = create_production_order(so.lines[0])
10    # Complete production
11    complete_wo(wo.id, qty=10)
12    # Ship
13    shipment = ship_order(so.id)
14    # Verify inventory changed
15    assert get_inventory(widget.id) >= 0  # didn't go negative
16
17def test_material_shortage_detected():
18    """System warns when we can't make something"""
19    product = create_product_with_bom(needs=[{item: pla, qty: 100}])
20    set_inventory(pla, qty=50)  # not enough
21    so = create_so(product, qty=1)
22    blocking = get_blocking_issues(so.id)
23    assert blocking['can_fulfill'] == False
24    assert 'pla' in str(blocking['issues']).lower()
25
26def test_po_receipt_adds_inventory_correctly():
27    """UOM conversion works on receipt"""
28    item = create_item(base_uom='g', purchase_uom='spool', conversion=1000)
29    set_inventory(item, qty=0)
30    po = create_po(item, qty=5)  # 5 spools
31    receive_po(po.id, qty=5)
32    assert get_inventory(item.id) == 5000  # grams
33
34def test_reorder_alerts_fire():
35    """Low stock creates alerts"""
36    item = create_item(reorder_point=100, reorder_qty=500)
37    set_inventory(item, qty=50)  # below reorder point
38    alerts = run_reorder_check()
39    assert any(a.item_id == item.id for a in alerts)

#7.2 Integration Tests by Workflow

WorkflowKey Tests
QuoteCreate, send, convert, expire
Sales OrderCreate, confirm, produce, ship, cancel
ProductionCreate, release, record completion, scrap
PurchasingCreate, send, receive (partial, full)
InventoryTransactions, running balance, negative prevention
MRPShortage detection, suggestions, reorder alerts

APPENDIX: OPEN QUESTIONS

Decisions to make as we build:

  1. Partial shipments? Can we ship 8 of 10 and ship 2 later?
  2. Lot/serial tracking? Needed for traceability?
  3. Multi-location? Or just MAIN for now?
  4. Approval workflows? Or trust the operator?
  5. Audit log? How detailed?
  6. Seasonal forecasting? Or just reorder points?

APPENDIX: GLOSSARY

TermMeaning
BOMBill of Materials - recipe for making a product
MTOMake-to-Order - produce after customer orders
MTSMake-to-Stock - produce to replenish inventory
MRPMaterial Requirements Planning - "what do I need"
SOSales Order
POPurchase Order
WOWork Order / Production Order
UOMUnit of Measure
FGFinished Goods
RMRaw Materials

END OF SPECIFICATION v0.2