Accounts Payable (AP) System Proposal

Document Version: 1.0 Date: October 3, 2025 Status: Proposal / Design Document


Executive Summary

This document proposes an Accounts Payable (AP) system to track vendor billing and payments within the Quarry Rentals application. Currently, the system tracks customer billing (Accounts Receivable) but has no mechanism to track vendor costs (Accounts Payable), creating a blind spot in financial management.

Key Problems Being Solved:

  1. No visibility into vendor bills - We don't track what vendors charge us
  2. No payment tracking - We don't record when/how we pay vendors
  3. No cost reconciliation - Can't match revenue vs costs per order
  4. Different billing cycles - Vendors may bill us on different schedules than we bill customers
  5. Variable charges - Overage fees (weight, time) aren't tracked on vendor side

Current State Analysis

What We Have (AR - Accounts Receivable)

Customer Side - Fully Automated:

Order
  ├─ OrderBillingPeriod (recurring charges)
  │   ├─ Invoices (auto-generated)
  │   └─ Payments (tracked via PaymentTransaction)
  └─ One-time charges
      ├─ Base rental fee
      ├─ Delivery/removal fees
      └─ Overage fees (weight, time)

What We DON'T Have (AP - Accounts Payable)

Vendor Side - Not Tracked:


Business Requirements

1. Recurring Charges (Storage Containers, Mobile Offices)

Scenario: Monthly storage container rental

Customer Side (AR) Vendor Side (AP)
Customer rented: $300/mo Vendor cost: $200/mo
Billing: 1st of month Vendor bills: 15th of month
Cycle: Monthly (calendar) Vendor cycle: 28-day
Auto-invoice generated Need to track vendor bill
Customer payment tracked Need to track our payment

Key Issues:

2. One-Time + Variable Charges (Dumpsters)

Scenario: 7-day dumpster rental with overages

Charge Type Customer Pays Vendor Charges Us Current Tracking
Base rental $350 (7 days) $250 (7 days) ✅ Order.base_price / VendorProduct.cost
Delivery $75 $50 ✅ Order.delivery_fee / DeliveryZone
Removal $75 $50 ✅ Order.removal_fee / DeliveryZone
Weight overage 3 tons @ $65/ton = $195 3 tons @ $45/ton = $135 ❌ Not tracked on vendor side
Time overage 3 days @ $25/day = $75 3 days @ $15/day = $45 ❌ Not tracked on vendor side
Total $770 $530 Partial
Margin $240 (31%) Can't calculate!

Key Issues:


Proposed Solution Architecture

1. Database Schema

VendorBill (Main table for vendor invoices)

VendorBill
  - vendor_id (FK)
  - order_id (FK, optional - may cover multiple orders)
  - bill_number (vendor's invoice number)
  - bill_date
  - due_date
  - bill_type (one_time, recurring, overage_adjustment)
  - status (pending, approved, paid, disputed)
  - subtotal
  - tax_amount (if vendor charges tax)
  - total_amount
  - paid_amount
  - balance_due
  - notes
  - pdf_attachment (ActiveStorage)
  - created_at, updated_at

VendorBillLineItem (Individual charges on vendor bill)

VendorBillLineItem
  - vendor_bill_id (FK)
  - order_id (FK, optional - ties charge to specific order)
  - description (e.g., "Monthly rental - Oct 2025", "Weight overage - 3 tons")
  - quantity
  - unit_price
  - amount
  - charge_type (base_rental, delivery, removal, weight_overage, time_overage, fuel_surcharge, etc.)
  - taxable (boolean)

VendorBillingPeriod (For recurring vendor costs)

VendorBillingPeriod
  - vendor_id (FK)
  - order_id (FK)
  - period_start
  - period_end
  - amount
  - status (pending, billed, paid)
  - vendor_bill_id (FK, null until billed)
  - billed_at
  - notes

VendorPayment (Track payments we make to vendors)

VendorPayment
  - vendor_id (FK)
  - vendor_bill_id (FK, optional - may pay multiple bills)
  - payment_date
  - payment_method (check, ach, credit_card, wire_transfer)
  - payment_reference (check number, transaction ID, etc.)
  - amount
  - notes
  - created_by_user_id (FK)
  - created_at, updated_at

VendorBillPayment (Join table for many-to-many)

VendorBillPayment
  - vendor_bill_id (FK)
  - vendor_payment_id (FK)
  - amount_applied

2. Business Logic & Workflows

Workflow A: One-Time Order (Dumpster)

1. Order Created
   ↓
2. Order Delivered
   ├─ Customer billed (base + delivery)
   └─ **Create VendorBill (pending)**
      ├─ Base rental cost
      └─ Delivery cost
   ↓
3. Order Picked Up
   ├─ Calculate overages (weight, time)
   ├─ Customer invoiced for overages
   └─ **Update VendorBill with overage line items**
   ↓
4. Vendor sends actual invoice
   ├─ Admin reviews/approves VendorBill
   └─ Mark status: approved
   ↓
5. Payment to vendor
   ├─ Create VendorPayment
   └─ Mark VendorBill: paid

Auto-generation Rules:


Workflow B: Recurring Order (Storage Container)

1. Recurring Order Created
   ↓
2. Order Delivered (Start billing)
   ├─ Customer: OrderBillingPeriod created
   └─ **Vendor: VendorBillingPeriod created**
   ↓
3. Daily Job Runs (2am)
   ├─ Generate customer invoices (existing)
   └─ **Generate vendor bills (new)**
      ├─ Check VendorBillingPeriod.due_for_billing
      ├─ Create VendorBill
      └─ Mark VendorBillingPeriod: billed
   ↓
4. Vendor sends actual invoice
   ├─ Admin matches to auto-generated VendorBill
   └─ Approve or adjust
   ↓
5. Payment to vendor
   ├─ Create VendorPayment
   └─ Mark VendorBill: paid

Key Considerations:


3. Model Relationships

class Vendor
  has_many :vendor_bills
  has_many :vendor_payments
  has_many :vendor_billing_periods
end

class Order
  has_many :vendor_bills
  has_one :vendor_billing_period  # For recurring orders
end

class VendorBill
  belongs_to :vendor
  belongs_to :order, optional: true
  has_many :line_items, class_name: "VendorBillLineItem"
  has_many :vendor_bill_payments
  has_many :vendor_payments, through: :vendor_bill_payments

  # Statuses: pending, approved, paid, disputed
  # Types: one_time, recurring, overage_adjustment
end

class VendorBillingPeriod
  belongs_to :vendor
  belongs_to :order
  belongs_to :vendor_bill, optional: true

  # Similar to OrderBillingPeriod but for vendor costs
end

class VendorPayment
  belongs_to :vendor
  has_many :vendor_bill_payments
  has_many :vendor_bills, through: :vendor_bill_payments
end

4. User Interface Components

A. Vendor Bill Management

Location: /admin/vendor_bills

Features:

Screens Needed:

  1. Index page (list view with filters)
  2. Show page (bill detail with line items)
  3. New/Edit form
  4. Payment modal

B. Accounts Payable Dashboard

Location: /admin/accounts_payable

Key Metrics:

Reports:


C. Order Show Page Enhancement

Add Section: "Vendor Costs"

Display:


D. Vendor Portal Enhancement (Future)

Location: vendors.quarryrents.com/bills

Allow vendors to:


5. Implementation Phases

Phase 1: Core AP System (Week 1-2)

Phase 2: Automation (Week 3)

Phase 3: Reporting (Week 4)

Phase 4: Advanced Features (Future)


Technical Considerations

1. Billing Cycle Mismatch

Problem: Customer billed monthly on 1st, vendor bills us on 28-day cycle

Solution:

# VendorProduct defines vendor's billing terms
VendorProduct
  - billing_cycle (weekly, 28_day, 30_day, monthly, anniversary)
  - billing_day (for monthly)

# Order tracks BOTH cycles
Order
  - billing_frequency (customer's cycle)
  - Customer: OrderBillingPeriod
  - Vendor: VendorBillingPeriod (separate tracking)

# Result: Independent billing periods
Customer: Oct 1 - Oct 31 ($300)
Vendor: Sep 28 - Oct 25 ($200)

2. Overage Handling

Customer Overages (Existing):

Order
  - weight_included: 2 tons
  - weight_used: 5 tons
  - weight_overage_fee: $195 (3 tons × $65)

  # Calculated from:
  - Product.price_per_ton or
  - VendorProduct.our_price_per_ton

Vendor Overages (Proposed):

VendorBillLineItem
  - charge_type: "weight_overage"
  - description: "Weight overage - 3 tons"
  - quantity: 3
  - unit_price: $45 (from VendorProduct.vendor_price_per_ton)
  - amount: $135

# Auto-generated when order completed
# Can be adjusted before approval

3. Cost Reconciliation

Per-Order Profit Analysis:

class Order
  def customer_revenue
    # All customer charges
    base_price + delivery_fee + removal_fee +
    weight_overage_fee + time_overage_fee
  end

  def vendor_cost
    # All vendor charges
    vendor_bills.sum(:total_amount)
  end

  def gross_profit
    customer_revenue - vendor_cost
  end

  def profit_margin
    return 0 if customer_revenue == 0
    (gross_profit / customer_revenue) * 100
  end
end

4. Payment Application Logic

Scenario: Pay vendor $500, they have 3 outstanding bills

Options:

  1. Specific bills: Apply to Bill #123 ($500)
  2. Multiple bills: Apply to Bills #123 ($300) + #124 ($200)
  3. Oldest first: Auto-apply to oldest bills (FIFO)
  4. Partial payment: Apply $500 to $1,000 bill (tracks balance)

Implementation:

# VendorBillPayment join table tracks application
VendorPayment (total: $500)
  ├─ VendorBillPayment (bill_id: 123, amount_applied: $300)
  └─ VendorBillPayment (bill_id: 124, amount_applied: $200)

# Each VendorBill calculates balance:
def balance_due
  total_amount - vendor_bill_payments.sum(:amount_applied)
end

Data Migration Strategy

Historical Orders

Question: How to handle existing orders without vendor bills?

Options:

  1. Ignore history - Only track going forward

  2. Estimate from VendorProduct - Create estimated bills

  3. Manual entry - Admin enters past vendor bills

Recommendation: Option 1 (track going forward) with optional manual entry for recent high-value orders.


Security & Permissions

Role-Based Access

Role Vendor Bills Payments Reports
Viewer View only View only View only
Employee View No access View
Manager View, Create, Edit View View all
Admin Full access Record payments Full access
Super Admin Full access Full access Full access

Audit Trail


Integration Points

Existing Systems

  1. Orders - Bills tied to orders
  2. Products - Cost data from VendorProduct
  3. Vendors - Bill and payment tracking
  4. Invoices - Compare AR vs AP
  5. Reports - Profit margin analysis

External Systems (Future)

  1. QuickBooks - Sync AP data
  2. Banking - ACH payment automation
  3. OCR - Auto-extract data from vendor invoice PDFs
  4. Email - Vendor bills received via email
  5. Ramp Virtual Cards - Automated card generation per order/vendor (See Ramp Integration below)

Ramp Virtual Card Integration

Overview

Integrate Ramp's virtual card API to automate vendor payments with per-order spending controls.

Key Benefits

  1. Automated Payment Setup - Generate virtual card when order placed with vendor
  2. Spending Controls - Set exact limit per order (prevents overcharges)
  3. Automatic Lifecycle - Disable card when order complete
  4. Fraud Prevention - One card per vendor/order (limit blast radius)
  5. Transaction Tracking - Auto-reconcile charges to orders
  6. No Manual Checks - Vendors paid via card automatically

Architecture

RampVirtualCard Model

RampVirtualCard
  - vendor_id (FK)
  - order_id (FK, optional - might be per-location instead)
  - ramp_card_id (Ramp's card ID)
  - card_last_four
  - card_name (e.g., "ABC Containers - Order #12345")
  - spending_limit (total amount vendor can charge)
  - spent_amount (tracked from Ramp transactions)
  - status (active, suspended, cancelled)
  - activated_at
  - expires_at (for recurring, null; for one-time, order end date)
  - auto_disable (boolean - disable when order completes)
  - notes
  - created_at, updated_at

RampTransaction Model (Sync from Ramp)

RampTransaction
  - ramp_virtual_card_id (FK)
  - vendor_bill_id (FK, linked when reconciled)
  - ramp_transaction_id (Ramp's transaction ID)
  - transaction_date
  - merchant_name
  - amount
  - description
  - status (pending, cleared, declined)
  - reconciled (boolean - matched to VendorBill)
  - reconciled_at
  - created_at, updated_at

Card Generation Strategies

Option A: One Card Per Order (Recommended for One-Time Orders)

Use Case: Dumpster rental

Workflow:

1. Order Created & Confirmed
   ↓
2. Generate Ramp Virtual Card
   - Name: "Vendor ABC - Order #O20251003001"
   - Limit: $530 (estimated vendor cost + 10% buffer)
   - Memo: "Dumpster rental - 123 Main St"
   ↓
3. Send Card to Vendor
   - Email card details to vendor
   - Include order reference
   ↓
4. Vendor Charges Card
   - $250 (delivery)
   - $45 (overages - auto-charged by vendor)
   ↓
5. Transaction Synced
   - RampTransaction created
   - Auto-match to VendorBill
   ↓
6. Order Completed
   - Auto-disable card
   - Verify spending vs. limit
   - Close out order

Benefits:

Settings:

spending_limit: order.estimated_vendor_cost * 1.10  # 10% buffer for overages
expires_at: order.pickup_date + 30.days  # Allow time for final charges
auto_disable: true

Option B: One Card Per Location/Vendor (Recommended for Recurring Orders)

Use Case: Monthly storage container at same location

Workflow:

1. First Order at Location
   ↓
2. Generate Ramp Virtual Card
   - Name: "Vendor ABC - 123 Main St"
   - Limit: $250/month (recurring limit)
   - Memo: "Storage container - Monthly"
   ↓
3. Send Card to Vendor (One Time)
   - Vendor keeps on file
   - Charges monthly automatically
   ↓
4. Monthly Vendor Charge
   - Ramp transaction created
   - Auto-match to VendorBillingPeriod
   ↓
5. Spending Limit Reset
   - Monthly: limit resets on billing date
   - Or: limit replenished per billing period
   ↓
6. Order Ends
   - Update card limit to $0 or
   - Disable card

Benefits:

Settings:

spending_limit: monthly_cost * 1.05  # 5% buffer
limit_reset_frequency: "monthly"  # Ramp feature
auto_disable: false  # Keep active for recurring

Ramp API Integration

Card Creation

class RampCardService
  def create_virtual_card(order:, vendor:, limit:, name:)
    response = ramp_client.post('/cards/virtual', {
      cardholder_id: vendor.ramp_cardholder_id,
      display_name: name,
      spending_restrictions: {
        amount: limit,
        interval: 'TOTAL',  # or 'MONTHLY' for recurring
        categories: ['5093'], # MCC code for dumpsters/containers
      },
      fulfillment: {
        shipping: nil,  # Virtual only
      }
    })

    RampVirtualCard.create!(
      vendor: vendor,
      order: order,
      ramp_card_id: response['id'],
      card_last_four: response['last_four'],
      card_name: name,
      spending_limit: limit,
      status: 'active',
      activated_at: Time.current
    )
  end
end

Transaction Sync (Webhook)

# POST /webhooks/ramp/transactions
class RampWebhooksController < ApplicationController
  def transaction_created
    transaction_data = params[:data]

    card = RampVirtualCard.find_by(ramp_card_id: transaction_data[:card_id])
    return head :ok unless card

    RampTransaction.create!(
      ramp_virtual_card: card,
      ramp_transaction_id: transaction_data[:id],
      transaction_date: transaction_data[:transaction_date],
      merchant_name: transaction_data[:merchant_name],
      amount: transaction_data[:amount],
      description: transaction_data[:description],
      status: transaction_data[:status]
    )

    # Auto-reconcile if vendor bill exists
    AutoReconcileRampTransactionJob.perform_later(card.id)
  end
end

Card Lifecycle Management

# When order completes
class Order
  after_update :disable_ramp_card, if: :completed?

  def disable_ramp_card
    return unless ramp_virtual_card.present?
    return unless ramp_virtual_card.auto_disable?

    RampCardService.new.disable_card(ramp_virtual_card)
  end
end

class RampCardService
  def disable_card(ramp_card)
    ramp_client.patch("/cards/#{ramp_card.ramp_card_id}", {
      status: 'SUSPENDED'
    })

    ramp_card.update!(
      status: 'suspended',
      expires_at: Time.current
    )
  end
end

Spending Limit Calculation

One-Time Orders (Dumpsters)

def calculate_card_limit(order)
  base_cost = order.vendor_product.cost || 0
  delivery_cost = order.vendor.delivery_fee_for_zipcode(order.delivery_zipcode) || 0
  removal_cost = delivery_cost  # Usually same

  # Estimate potential overages
  max_overage = calculate_max_overage(order)

  total = base_cost + delivery_cost + removal_cost + max_overage

  # Add 10% buffer for unexpected charges
  total * 1.10
end

def calculate_max_overage(order)
  weight_overage = 0
  time_overage = 0

  # Assume worst case: 2x standard weight/days
  if order.product.dumpster?
    weight_overage = order.vendor_product.vendor_price_per_ton * 2
    time_overage = order.vendor_product.vendor_daily_rate * 7
  end

  weight_overage + time_overage
end

Recurring Orders (Containers)

def calculate_monthly_limit(order)
  monthly_cost = order.vendor_product.cost || 0

  # Add 5% buffer (less buffer needed for predictable recurring)
  monthly_cost * 1.05
end

Auto-Reconciliation

Match Ramp Transactions to Vendor Bills:

class AutoReconcileRampTransactionJob < ApplicationJob
  def perform(ramp_card_id)
    card = RampVirtualCard.find(ramp_card_id)
    transactions = card.ramp_transactions.where(reconciled: false)

    transactions.each do |transaction|
      # Find matching vendor bill
      vendor_bill = find_matching_bill(card, transaction)

      if vendor_bill
        # Link transaction to bill
        transaction.update!(
          vendor_bill: vendor_bill,
          reconciled: true,
          reconciled_at: Time.current
        )

        # Auto-create VendorPayment
        create_payment_from_transaction(vendor_bill, transaction)
      end
    end
  end

  private

  def find_matching_bill(card, transaction)
    # Try to match by order
    if card.order.present?
      vendor_bill = card.order.vendor_bills
        .where(status: ['pending', 'approved'])
        .where('total_amount <= ?', transaction.amount * 1.05)  # 5% tolerance
        .first
    end

    # Try to match by amount and date
    vendor_bill ||= VendorBill
      .where(vendor: card.vendor)
      .where(status: ['pending', 'approved'])
      .where('bill_date BETWEEN ? AND ?', transaction.transaction_date - 7.days, transaction.transaction_date + 7.days)
      .where('total_amount BETWEEN ? AND ?', transaction.amount * 0.95, transaction.amount * 1.05)
      .first

    vendor_bill
  end

  def create_payment_from_transaction(vendor_bill, transaction)
    payment = VendorPayment.create!(
      vendor: vendor_bill.vendor,
      payment_date: transaction.transaction_date,
      payment_method: 'ramp_virtual_card',
      payment_reference: transaction.ramp_transaction_id,
      amount: transaction.amount,
      notes: "Auto-payment via Ramp card #{transaction.ramp_virtual_card.card_last_four}"
    )

    VendorBillPayment.create!(
      vendor_bill: vendor_bill,
      vendor_payment: payment,
      amount_applied: transaction.amount
    )

    vendor_bill.update!(status: 'paid') if vendor_bill.balance_due <= 0
  end
end

UI Components

Order Show Page - Ramp Card Section

┌─────────────────────────────────────────────┐
│ Vendor Payment Card                         │
├─────────────────────────────────────────────┤
│ Card: **** **** **** 1234                   │
│ Limit: $530.00                              │
│ Spent: $495.00 (93%)                        │
│ Status: Active ●                            │
│                                             │
│ Transactions:                               │
│   Oct 1  Delivery       $250.00             │
│   Oct 8  Weight Overage $135.00             │
│   Oct 8  Time Overage   $110.00             │
│                                             │
│ [Disable Card]  [Adjust Limit]             │
└─────────────────────────────────────────────┘

Vendor Show Page - Cards Section

┌─────────────────────────────────────────────┐
│ Virtual Cards                               │
├─────────────────────────────────────────────┤
│ Card          Order      Limit    Spent     │
│ **** 1234     #O001      $530    $495  [●]  │
│ **** 5678     #O002      $200    $200  [○]  │
│ **** 9012     Location   $250/mo $250  [●]  │
│                                             │
│ [Generate New Card]                         │
└─────────────────────────────────────────────┘

Ramp Cards Dashboard

Location: /admin/ramp_cards

Stats:
- Active Cards: 45
- Total Spend This Month: $12,450
- Cards Needing Review: 3
- Average Utilization: 87%

Filters: Active | Suspended | All | Near Limit (>90%)

Table:
Card | Vendor | Order/Location | Limit | Spent | Status | Actions

Security & Compliance

Card Data Handling

Access Control

Fraud Prevention


Implementation Plan

Phase 1: Core Integration (Week 1)

Phase 2: Order Integration (Week 2)

Phase 3: Transaction Sync (Week 3)

Phase 4: UI & Reporting (Week 4)


Configuration

Environment Variables

RAMP_API_KEY=your_api_key
RAMP_API_SECRET=your_api_secret
RAMP_WEBHOOK_SECRET=your_webhook_secret
RAMP_ENVIRONMENT=sandbox  # or production

Ramp Settings

# config/initializers/ramp.rb
Ramp.configure do |config|
  config.api_key = ENV['RAMP_API_KEY']
  config.api_secret = ENV['RAMP_API_SECRET']
  config.webhook_secret = ENV['RAMP_WEBHOOK_SECRET']
  config.environment = ENV.fetch('RAMP_ENVIRONMENT', 'sandbox')
end

Vendor Onboarding

First-Time Setup

  1. Admin creates vendor in system
  2. Set vendor's Ramp cardholder profile
  3. Generate first card for test
  4. Send card details to vendor
  5. Vendor tests card with small charge
  6. Confirm setup working

Recurring Vendors


Edge Cases & Handling

Overspending

Scenario: Vendor tries to charge $600 on $530 limit

Ramp Behavior: Transaction declined

Our Response:

  1. Webhook: transaction.declined
  2. Alert admin
  3. Review overage reason
  4. Options:

Card Expiry

Scenario: Order extends beyond card expiry

Handling:

# Check expiring cards daily
class CheckExpiringCardsJob < ApplicationJob
  def perform
    expiring_soon = RampVirtualCard
      .active
      .where('expires_at BETWEEN ? AND ?', Date.current, 30.days.from_now)

    expiring_soon.each do |card|
      if card.order.still_active?
        # Extend expiry
        extend_card_expiry(card, 30.days)
      else
        # Warn admin
        AdminMailer.card_expiring_soon(card).deliver_later
      end
    end
  end
end

Vendor Doesn't Accept Cards

Scenario: Vendor only accepts ACH/Check

Handling:


Cost-Benefit Analysis

Costs

Benefits

ROI: Positive within 2 months


Alternatives Considered

Solution Pros Cons Verdict
Ramp Virtual Cards Auto-generation, per-order limits, API Monthly fee ✅ Recommended
Manual Credit Cards Simple, no fees No spending limits, fraud risk ❌ Not scalable
ACH/Checks Low cost Manual, slow, no auto-tracking ❌ Too manual
Divvy/Brex Similar to Ramp Slightly less flexible API ✅ Alternative

Ramp + AP System Integration

Combined Workflow

Order Created
   ↓
Generate Ramp Card (spending limit = estimated cost)
   ↓
Send Card to Vendor
   ↓
Auto-Create VendorBill (status: pending)
   ↓
Vendor Charges Card
   ↓
Ramp Transaction Synced
   ↓
Auto-Match Transaction → VendorBill
   ↓
Auto-Create VendorPayment (payment_method: ramp_virtual_card)
   ↓
Mark VendorBill: paid
   ↓
Order Completed → Disable Card

Result: Fully automated vendor payment with zero manual intervention! 🎉


Success Metrics

KPIs to Track

  1. Cost Visibility

  2. Payment Performance

  3. Profitability

  4. Operational Efficiency


Risk Analysis

Potential Issues

  1. Data Entry Burden

  2. Billing Cycle Complexity

  3. Cost Variance

  4. Payment Errors


Open Questions

  1. Vendor invoice format: Do vendors provide structured data or just PDFs?
  2. Payment timing: Net 30? Net 60? Varies by vendor?
  3. Dispute process: How to handle disputed charges?
  4. Recurring bill adjustments: How to handle mid-cycle price changes?
  5. Tax handling: Do vendors charge sales tax to us?
  6. International vendors: Different currencies?

Recommendations

Immediate Actions (Must Have)

  1. ✅ Build core VendorBill and VendorPayment models
  2. ✅ Create admin UI for manual bill entry
  3. ✅ Add vendor cost tracking to Order show page
  4. ✅ Basic payment recording

Short-Term (Should Have)

  1. ⏳ Auto-generate bills for new orders
  2. ⏳ VendorBillingPeriod for recurring costs
  3. ⏳ AP Dashboard with key metrics
  4. ⏳ Profit margin reporting

Long-Term (Nice to Have)

  1. 🔮 Vendor portal for invoice submission
  2. 🔮 ACH payment automation
  3. 🔮 QuickBooks integration
  4. 🔮 OCR for PDF invoice processing
  5. 🔮 Predictive cash flow analysis

Conclusion

The proposed AP system will:

Close the financial visibility gap - Track all vendor costs ✅ Enable profit analysis - Know actual margins per order ✅ Automate recurring costs - Match customer recurring billing ✅ Improve cash management - Know exactly what we owe ✅ Support growth - Scale as business grows

Estimated Development Time:

Next Steps:

  1. Review and approve proposal
  2. Prioritize features for MVP
  3. Begin Phase 1 implementation
  4. Test with sample data
  5. Train admin users
  6. Roll out to production

Implementation Progress

Last Updated: October 3, 2025 Status: 85% Complete - Core functionality + UI integration complete Next Steps: Order lifecycle integration, recurring billing UI, reporting

✅ Completed (Steps 1-32, 35)

Database Layer (100% Complete)

Models (100% Complete)

Services & API (100% Complete)

Automation (100% Complete)

Webhooks (100% Complete)

Controllers & Views (90% Complete)

⏳ Remaining Work

High Priority (For Full Functionality)

Medium Priority (Enhanced Features)

Lower Priority (Polish)

📊 Feature Completion

Feature Area Completion Status
Database Schema 100% ✅ All 7 migrations created and run
Models 100% ✅ All 8 models with validations
API Integration 100% ✅ Full Ramp API client
Services 100% ✅ 3 core services
Background Jobs 100% ✅ 4 jobs scheduled
Webhooks 100% ✅ Ramp webhook handler
Controllers 100% ✅ 3 admin controllers (VendorBills, RampCards, RampTransactions)
Views 90% ✅ 7 of 8 views complete
Order Integration 50% ⏳ Form/show updated, lifecycle pending
Reporting 0% ⏳ Pending
Overall 85% ✅ Core + UI integration complete

🚀 What's Working Now

Fully Functional:

Requires Manual Setup:

Not Yet Implemented:


Detailed Implementation Steps

Phase 1: Database Schema (Days 1-2) ✅ COMPLETE

Step 1: Create VendorBills Migration

bin/rails generate migration CreateVendorBills

Step 2: Create VendorBillLineItems Migration

bin/rails generate migration CreateVendorBillLineItems

Step 3: Create VendorBillingPeriods Migration

bin/rails generate migration CreateVendorBillingPeriods

Step 4: Create VendorPayments Migration

bin/rails generate migration CreateVendorPayments

Step 5: Create RampVirtualCards Migration

bin/rails generate migration CreateRampVirtualCards

Step 6: Create RampTransactions Migration

bin/rails generate migration CreateRampTransactions

Step 7: Add Vendor Cost Tracking to Orders

bin/rails generate migration AddVendorCostFieldsToOrders

Phase 2: Models & Validations (Day 3) ✅ COMPLETE

Step 8: Create VendorBill Model

Step 9: Create VendorBillLineItem Model

Step 10: Create VendorBillingPeriod Model

Step 11: Create VendorPayment Model

Step 12: Create RampVirtualCard Model

Step 13: Create RampTransaction Model

Step 14: Update Order Model

Phase 3: Ramp API Integration (Days 4-5) ✅ COMPLETE

Step 15: Create Ramp API Client

Step 16: Create RampCardService

Step 17: Add Ramp Credentials ⏳ PENDING (Manual Setup)

bin/rails credentials:edit

Phase 4: Recurring Vendor Billing Automation (Days 6-7) ✅ COMPLETE

Step 18: Create VendorRecurringBillingService

Step 19: Create GenerateVendorBillsJob

Step 20: Create Rake Tasks

Phase 5: Webhooks (Day 8) ✅ COMPLETE

Step 21: Create Ramp Webhook Endpoint

bin/rails generate controller webhooks/ramp create

Step 22: Add Webhook Routes

Step 23: Create SyncRampTransactionsJob

Phase 6: Controllers & Views (Days 9-11) - Partially Complete

Step 24: Create VendorBillsController

Step 25: Create VendorBills Index View

Step 26: Create VendorBills Show View

Step 27: Create VendorBills Form Partial

Step 28: Create RampCardsController

Step 29: Create RampCards Dashboard

Step 30: Create RampCards Show View

Step 31: Update Orders Form

Step 32: Update Orders Show Page

Step 33: Create VendorRecurringBillingController ⏳ PENDING

Step 34: Create VendorRecurringBilling Index View ⏳ PENDING

Step 35: Add Navigation Links

Phase 7: Order Lifecycle Integration (Days 12-13) ⏳ PENDING

Step 36: Update OrdersController ⏳ PENDING

Step 37: Add Order Callbacks ⏳ PENDING

Step 38: Create VendorBillGenerationService ⏳ PENDING

Phase 8: Reporting & Analytics (Day 14) ⏳ PENDING

Step 39: Add Vendor Cost Reports ⏳ PENDING

Step 40: Create Vendor Cost Report View ⏳ PENDING

Step 41: Add Ramp Reconciliation Report ⏳ PENDING

Phase 9: Testing & Validation (Day 15) ⏳ PENDING

Step 42: Test Ramp Sandbox Integration ⏳ PENDING

Step 43: Test Recurring Billing ⏳ PENDING

Step 44: Edge Case Testing ⏳ PENDING

Phase 10: Documentation & Deployment (Day 16) ⏳ PENDING

Step 45: Add Seed Data ⏳ PENDING

Step 46: Update README ⏳ PENDING

Step 47: Deploy to Staging ⏳ PENDING

Step 48: Production Rollout ⏳ PENDING


Estimated Timeline: 16 days (3-4 weeks)

Key Files to Reference:


Document Owner: Development Team Stakeholders: Finance, Operations, Management Review Date: October 10, 2025