POS Micro - Ultra-minimal point of sale (100 MXN/month): - Database DDL: 10 tables (tenants, users, categories, products, payment_methods, sales, sale_items, cash_movements, daily_summaries, whatsapp_sessions) - NestJS Backend: Complete API with modules for auth, products, categories, sales, and payments - Docker Compose: Development environment with PostgreSQL - Dev scripts: Easy setup for local development SaaS Billing Schema: - 10 tables for subscription management (plans, subscriptions, invoices, payments, usage_tracking, support_tickets) - Default plans: POS Micro (100 MXN), ERP Básico (300 MXN), ERP Pro (500 MXN), Vertical (1000+ MXN) - Support for Stripe and Conekta payment providers - CFDI integration ready for Mexico Target: Mexican informal market (street vendors, small shops, food stands) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
677 lines
22 KiB
PL/PgSQL
677 lines
22 KiB
PL/PgSQL
-- ============================================================================
|
|
-- SAAS LAYER - BILLING SCHEMA
|
|
-- ============================================================================
|
|
-- Version: 1.0.0
|
|
-- Description: Billing, subscriptions, and payments management for SaaS
|
|
-- Target: All ERP Suite products (POS Micro, ERP Básico, Verticales)
|
|
-- ============================================================================
|
|
|
|
-- Enable required extensions
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
|
|
-- ============================================================================
|
|
-- SCHEMA CREATION
|
|
-- ============================================================================
|
|
|
|
CREATE SCHEMA IF NOT EXISTS saas;
|
|
|
|
SET search_path TO saas, public;
|
|
|
|
-- ============================================================================
|
|
-- ENUMS
|
|
-- ============================================================================
|
|
|
|
CREATE TYPE saas.plan_type AS ENUM (
|
|
'pos_micro', -- 100 MXN/mes
|
|
'erp_basic', -- 300 MXN/mes
|
|
'erp_pro', -- 500 MXN/mes
|
|
'vertical' -- 1000+ MXN/mes
|
|
);
|
|
|
|
CREATE TYPE saas.billing_cycle AS ENUM (
|
|
'monthly',
|
|
'yearly'
|
|
);
|
|
|
|
CREATE TYPE saas.subscription_status AS ENUM (
|
|
'trial',
|
|
'active',
|
|
'past_due',
|
|
'suspended',
|
|
'cancelled',
|
|
'expired'
|
|
);
|
|
|
|
CREATE TYPE saas.payment_status AS ENUM (
|
|
'pending',
|
|
'processing',
|
|
'completed',
|
|
'failed',
|
|
'refunded',
|
|
'cancelled'
|
|
);
|
|
|
|
CREATE TYPE saas.invoice_status AS ENUM (
|
|
'draft',
|
|
'pending',
|
|
'paid',
|
|
'overdue',
|
|
'cancelled',
|
|
'refunded'
|
|
);
|
|
|
|
CREATE TYPE saas.payment_provider AS ENUM (
|
|
'stripe',
|
|
'conekta',
|
|
'oxxo',
|
|
'transfer',
|
|
'manual'
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- TABLE 1: plans (Subscription plans)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE saas.plans (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Identification
|
|
code VARCHAR(50) UNIQUE NOT NULL,
|
|
name VARCHAR(100) NOT NULL,
|
|
description TEXT,
|
|
|
|
-- Type
|
|
plan_type saas.plan_type NOT NULL,
|
|
|
|
-- Pricing (MXN)
|
|
price_monthly DECIMAL(10,2) NOT NULL,
|
|
price_yearly DECIMAL(10,2), -- Usually with discount
|
|
|
|
-- Limits
|
|
max_users INTEGER NOT NULL DEFAULT 1,
|
|
max_products INTEGER DEFAULT 500,
|
|
max_sales_per_month INTEGER DEFAULT 1000,
|
|
max_storage_mb INTEGER DEFAULT 100,
|
|
|
|
-- Features (JSON for flexibility)
|
|
features JSONB NOT NULL DEFAULT '{}',
|
|
|
|
-- Status
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
is_public BOOLEAN NOT NULL DEFAULT TRUE, -- Show in pricing page
|
|
sort_order INTEGER DEFAULT 0,
|
|
|
|
-- Stripe/Conekta IDs
|
|
stripe_price_id_monthly VARCHAR(100),
|
|
stripe_price_id_yearly VARCHAR(100),
|
|
conekta_plan_id VARCHAR(100),
|
|
|
|
-- Audit
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- TABLE 2: plan_features (Feature definitions)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE saas.plan_features (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
plan_id UUID NOT NULL REFERENCES saas.plans(id) ON DELETE CASCADE,
|
|
|
|
-- Feature
|
|
feature_code VARCHAR(50) NOT NULL, -- e.g., 'accounting', 'hr', 'cfdi'
|
|
feature_name VARCHAR(100) NOT NULL,
|
|
|
|
-- Pricing (for add-ons)
|
|
is_included BOOLEAN NOT NULL DEFAULT FALSE,
|
|
addon_price_monthly DECIMAL(10,2), -- Price if add-on
|
|
|
|
-- Status
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
|
|
CONSTRAINT uq_plan_features UNIQUE (plan_id, feature_code)
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- TABLE 3: tenants (All SaaS customers)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE saas.tenants (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Business info
|
|
business_name VARCHAR(200) NOT NULL,
|
|
legal_name VARCHAR(200),
|
|
tax_id VARCHAR(20), -- RFC México
|
|
|
|
-- Contact
|
|
owner_name VARCHAR(200) NOT NULL,
|
|
email VARCHAR(255) NOT NULL,
|
|
phone VARCHAR(20) NOT NULL,
|
|
whatsapp VARCHAR(20),
|
|
|
|
-- Address
|
|
address TEXT,
|
|
city VARCHAR(100),
|
|
state VARCHAR(50),
|
|
zip_code VARCHAR(10),
|
|
country VARCHAR(2) DEFAULT 'MX',
|
|
|
|
-- Product reference (which product schema to use)
|
|
product_type saas.plan_type NOT NULL,
|
|
product_schema VARCHAR(50), -- e.g., 'pos_micro', 'erp_basic'
|
|
|
|
-- Settings
|
|
timezone VARCHAR(50) DEFAULT 'America/Mexico_City',
|
|
currency VARCHAR(3) DEFAULT 'MXN',
|
|
language VARCHAR(10) DEFAULT 'es',
|
|
settings JSONB DEFAULT '{}',
|
|
|
|
-- Audit
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP,
|
|
|
|
CONSTRAINT uq_tenants_email UNIQUE (email),
|
|
CONSTRAINT uq_tenants_phone UNIQUE (phone)
|
|
);
|
|
|
|
CREATE INDEX idx_tenants_email ON saas.tenants(email);
|
|
CREATE INDEX idx_tenants_phone ON saas.tenants(phone);
|
|
CREATE INDEX idx_tenants_product_type ON saas.tenants(product_type);
|
|
|
|
-- ============================================================================
|
|
-- TABLE 4: subscriptions (Active subscriptions)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE saas.subscriptions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES saas.tenants(id) ON DELETE CASCADE,
|
|
plan_id UUID NOT NULL REFERENCES saas.plans(id),
|
|
|
|
-- Billing
|
|
billing_cycle saas.billing_cycle NOT NULL DEFAULT 'monthly',
|
|
current_price DECIMAL(10,2) NOT NULL,
|
|
|
|
-- Dates
|
|
trial_starts_at TIMESTAMP,
|
|
trial_ends_at TIMESTAMP,
|
|
current_period_start TIMESTAMP NOT NULL,
|
|
current_period_end TIMESTAMP NOT NULL,
|
|
cancelled_at TIMESTAMP,
|
|
|
|
-- Status
|
|
status saas.subscription_status NOT NULL DEFAULT 'trial',
|
|
cancel_reason TEXT,
|
|
|
|
-- External IDs
|
|
stripe_subscription_id VARCHAR(100),
|
|
conekta_subscription_id VARCHAR(100),
|
|
|
|
-- Features (active add-ons)
|
|
active_features JSONB DEFAULT '[]',
|
|
|
|
-- Audit
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP,
|
|
|
|
CONSTRAINT uq_subscriptions_tenant UNIQUE (tenant_id) -- One active subscription per tenant
|
|
);
|
|
|
|
CREATE INDEX idx_subscriptions_tenant_id ON saas.subscriptions(tenant_id);
|
|
CREATE INDEX idx_subscriptions_status ON saas.subscriptions(status);
|
|
CREATE INDEX idx_subscriptions_period_end ON saas.subscriptions(current_period_end);
|
|
|
|
-- ============================================================================
|
|
-- TABLE 5: invoices (Billing invoices)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE saas.invoices (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES saas.tenants(id) ON DELETE CASCADE,
|
|
subscription_id UUID REFERENCES saas.subscriptions(id),
|
|
|
|
-- Invoice number
|
|
invoice_number VARCHAR(50) NOT NULL,
|
|
|
|
-- Dates
|
|
invoice_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
|
due_date DATE NOT NULL,
|
|
paid_at TIMESTAMP,
|
|
|
|
-- Amounts
|
|
subtotal DECIMAL(10,2) NOT NULL,
|
|
tax_amount DECIMAL(10,2) NOT NULL DEFAULT 0,
|
|
discount_amount DECIMAL(10,2) NOT NULL DEFAULT 0,
|
|
total DECIMAL(10,2) NOT NULL,
|
|
|
|
-- Currency
|
|
currency VARCHAR(3) NOT NULL DEFAULT 'MXN',
|
|
|
|
-- Status
|
|
status saas.invoice_status NOT NULL DEFAULT 'draft',
|
|
|
|
-- CFDI (Mexico)
|
|
cfdi_uuid VARCHAR(50),
|
|
cfdi_xml TEXT,
|
|
cfdi_pdf_url VARCHAR(500),
|
|
|
|
-- External IDs
|
|
stripe_invoice_id VARCHAR(100),
|
|
conekta_order_id VARCHAR(100),
|
|
|
|
-- Notes
|
|
notes TEXT,
|
|
|
|
-- Audit
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP,
|
|
|
|
CONSTRAINT uq_invoices_number UNIQUE (invoice_number)
|
|
);
|
|
|
|
CREATE INDEX idx_invoices_tenant_id ON saas.invoices(tenant_id);
|
|
CREATE INDEX idx_invoices_status ON saas.invoices(status);
|
|
CREATE INDEX idx_invoices_due_date ON saas.invoices(due_date);
|
|
|
|
-- ============================================================================
|
|
-- TABLE 6: invoice_items (Invoice line items)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE saas.invoice_items (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
invoice_id UUID NOT NULL REFERENCES saas.invoices(id) ON DELETE CASCADE,
|
|
|
|
-- Description
|
|
description VARCHAR(255) NOT NULL,
|
|
|
|
-- Amounts
|
|
quantity INTEGER NOT NULL DEFAULT 1,
|
|
unit_price DECIMAL(10,2) NOT NULL,
|
|
subtotal DECIMAL(10,2) NOT NULL,
|
|
|
|
-- Reference
|
|
plan_id UUID REFERENCES saas.plans(id),
|
|
feature_code VARCHAR(50),
|
|
|
|
-- Audit
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_invoice_items_invoice_id ON saas.invoice_items(invoice_id);
|
|
|
|
-- ============================================================================
|
|
-- TABLE 7: payments (Payment transactions)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE saas.payments (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES saas.tenants(id) ON DELETE CASCADE,
|
|
invoice_id UUID REFERENCES saas.invoices(id),
|
|
|
|
-- Amount
|
|
amount DECIMAL(10,2) NOT NULL,
|
|
currency VARCHAR(3) NOT NULL DEFAULT 'MXN',
|
|
|
|
-- Provider
|
|
provider saas.payment_provider NOT NULL,
|
|
provider_payment_id VARCHAR(100),
|
|
provider_customer_id VARCHAR(100),
|
|
|
|
-- Status
|
|
status saas.payment_status NOT NULL DEFAULT 'pending',
|
|
|
|
-- Details
|
|
payment_method_type VARCHAR(50), -- card, oxxo, spei, etc.
|
|
last_four VARCHAR(4),
|
|
brand VARCHAR(20), -- visa, mastercard, etc.
|
|
|
|
-- Error handling
|
|
failure_code VARCHAR(50),
|
|
failure_message TEXT,
|
|
|
|
-- Dates
|
|
paid_at TIMESTAMP,
|
|
refunded_at TIMESTAMP,
|
|
|
|
-- Metadata
|
|
metadata JSONB DEFAULT '{}',
|
|
|
|
-- Audit
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_payments_tenant_id ON saas.payments(tenant_id);
|
|
CREATE INDEX idx_payments_invoice_id ON saas.payments(invoice_id);
|
|
CREATE INDEX idx_payments_status ON saas.payments(status);
|
|
CREATE INDEX idx_payments_provider ON saas.payments(provider);
|
|
|
|
-- ============================================================================
|
|
-- TABLE 8: payment_methods (Saved payment methods)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE saas.payment_methods (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES saas.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Provider
|
|
provider saas.payment_provider NOT NULL,
|
|
provider_payment_method_id VARCHAR(100) NOT NULL,
|
|
|
|
-- Card details (if applicable)
|
|
card_type VARCHAR(20),
|
|
card_brand VARCHAR(20),
|
|
card_last_four VARCHAR(4),
|
|
card_exp_month INTEGER,
|
|
card_exp_year INTEGER,
|
|
|
|
-- Status
|
|
is_default BOOLEAN NOT NULL DEFAULT FALSE,
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
|
|
-- Audit
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_payment_methods_tenant_id ON saas.payment_methods(tenant_id);
|
|
|
|
-- ============================================================================
|
|
-- TABLE 9: usage_tracking (Usage metrics for billing)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE saas.usage_tracking (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES saas.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Period
|
|
period_start DATE NOT NULL,
|
|
period_end DATE NOT NULL,
|
|
|
|
-- Metrics
|
|
users_count INTEGER DEFAULT 0,
|
|
products_count INTEGER DEFAULT 0,
|
|
sales_count INTEGER DEFAULT 0,
|
|
storage_used_mb INTEGER DEFAULT 0,
|
|
api_calls INTEGER DEFAULT 0,
|
|
|
|
-- WhatsApp/AI usage (billable)
|
|
whatsapp_messages INTEGER DEFAULT 0,
|
|
ai_tokens_used INTEGER DEFAULT 0,
|
|
|
|
-- Audit
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP,
|
|
|
|
CONSTRAINT uq_usage_tracking_tenant_period UNIQUE (tenant_id, period_start, period_end)
|
|
);
|
|
|
|
CREATE INDEX idx_usage_tracking_tenant_id ON saas.usage_tracking(tenant_id);
|
|
CREATE INDEX idx_usage_tracking_period ON saas.usage_tracking(period_start, period_end);
|
|
|
|
-- ============================================================================
|
|
-- TABLE 10: support_tickets (Basic support)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE saas.support_tickets (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES saas.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Ticket
|
|
ticket_number VARCHAR(20) NOT NULL,
|
|
subject VARCHAR(255) NOT NULL,
|
|
description TEXT NOT NULL,
|
|
|
|
-- Category
|
|
category VARCHAR(50) NOT NULL DEFAULT 'general', -- billing, technical, feature, general
|
|
priority VARCHAR(20) NOT NULL DEFAULT 'normal', -- low, normal, high, urgent
|
|
|
|
-- Status
|
|
status VARCHAR(20) NOT NULL DEFAULT 'open', -- open, in_progress, waiting, resolved, closed
|
|
|
|
-- Assignment
|
|
assigned_to VARCHAR(100),
|
|
|
|
-- Resolution
|
|
resolution TEXT,
|
|
resolved_at TIMESTAMP,
|
|
|
|
-- Audit
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP,
|
|
|
|
CONSTRAINT uq_support_tickets_number UNIQUE (ticket_number)
|
|
);
|
|
|
|
CREATE INDEX idx_support_tickets_tenant_id ON saas.support_tickets(tenant_id);
|
|
CREATE INDEX idx_support_tickets_status ON saas.support_tickets(status);
|
|
|
|
-- ============================================================================
|
|
-- UTILITY FUNCTIONS
|
|
-- ============================================================================
|
|
|
|
-- Function: Update updated_at timestamp
|
|
CREATE OR REPLACE FUNCTION saas.update_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Function: Generate invoice number
|
|
CREATE OR REPLACE FUNCTION saas.generate_invoice_number()
|
|
RETURNS VARCHAR AS $$
|
|
DECLARE
|
|
v_year VARCHAR;
|
|
v_seq INTEGER;
|
|
BEGIN
|
|
v_year := TO_CHAR(CURRENT_DATE, 'YYYY');
|
|
|
|
SELECT COALESCE(MAX(
|
|
CAST(SUBSTRING(invoice_number FROM 5) AS INTEGER)
|
|
), 0) + 1
|
|
INTO v_seq
|
|
FROM saas.invoices
|
|
WHERE invoice_number LIKE v_year || '%';
|
|
|
|
RETURN v_year || LPAD(v_seq::TEXT, 6, '0');
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Function: Generate ticket number
|
|
CREATE OR REPLACE FUNCTION saas.generate_ticket_number()
|
|
RETURNS VARCHAR AS $$
|
|
DECLARE
|
|
v_date VARCHAR;
|
|
v_seq INTEGER;
|
|
BEGIN
|
|
v_date := TO_CHAR(CURRENT_DATE, 'YYMMDD');
|
|
|
|
SELECT COALESCE(MAX(
|
|
CAST(SUBSTRING(ticket_number FROM 8) AS INTEGER)
|
|
), 0) + 1
|
|
INTO v_seq
|
|
FROM saas.support_tickets
|
|
WHERE DATE(created_at) = CURRENT_DATE;
|
|
|
|
RETURN 'TK' || v_date || LPAD(v_seq::TEXT, 4, '0');
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- ============================================================================
|
|
-- TRIGGERS
|
|
-- ============================================================================
|
|
|
|
CREATE TRIGGER trg_plans_updated_at
|
|
BEFORE UPDATE ON saas.plans
|
|
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
|
|
|
|
CREATE TRIGGER trg_tenants_updated_at
|
|
BEFORE UPDATE ON saas.tenants
|
|
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
|
|
|
|
CREATE TRIGGER trg_subscriptions_updated_at
|
|
BEFORE UPDATE ON saas.subscriptions
|
|
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
|
|
|
|
CREATE TRIGGER trg_invoices_updated_at
|
|
BEFORE UPDATE ON saas.invoices
|
|
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
|
|
|
|
CREATE TRIGGER trg_payments_updated_at
|
|
BEFORE UPDATE ON saas.payments
|
|
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
|
|
|
|
CREATE TRIGGER trg_payment_methods_updated_at
|
|
BEFORE UPDATE ON saas.payment_methods
|
|
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
|
|
|
|
CREATE TRIGGER trg_usage_tracking_updated_at
|
|
BEFORE UPDATE ON saas.usage_tracking
|
|
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
|
|
|
|
CREATE TRIGGER trg_support_tickets_updated_at
|
|
BEFORE UPDATE ON saas.support_tickets
|
|
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
|
|
|
|
-- Auto-generate invoice number
|
|
CREATE OR REPLACE FUNCTION saas.auto_invoice_number()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.invoice_number IS NULL THEN
|
|
NEW.invoice_number := saas.generate_invoice_number();
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trg_invoices_auto_number
|
|
BEFORE INSERT ON saas.invoices
|
|
FOR EACH ROW EXECUTE FUNCTION saas.auto_invoice_number();
|
|
|
|
-- Auto-generate ticket number
|
|
CREATE OR REPLACE FUNCTION saas.auto_ticket_number()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.ticket_number IS NULL THEN
|
|
NEW.ticket_number := saas.generate_ticket_number();
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trg_tickets_auto_number
|
|
BEFORE INSERT ON saas.support_tickets
|
|
FOR EACH ROW EXECUTE FUNCTION saas.auto_ticket_number();
|
|
|
|
-- ============================================================================
|
|
-- SEED DATA: Default plans
|
|
-- ============================================================================
|
|
|
|
INSERT INTO saas.plans (code, name, description, plan_type, price_monthly, price_yearly, max_users, max_products, max_sales_per_month, features) VALUES
|
|
-- POS Micro
|
|
('pos_micro', 'POS Micro', 'Punto de venta ultra-básico para pequeños negocios', 'pos_micro', 100.00, 1000.00, 1, 500, 1000,
|
|
'{"offline_mode": true, "whatsapp_basic": true, "reports_basic": true}'),
|
|
|
|
-- ERP Básico
|
|
('erp_basic', 'ERP Básico', 'ERP completo pero austero para PyMEs', 'erp_basic', 300.00, 3000.00, 5, 10000, 5000,
|
|
'{"inventory": true, "sales": true, "purchases": true, "reports": true, "multi_warehouse": false}'),
|
|
|
|
-- ERP Pro
|
|
('erp_pro', 'ERP Pro', 'ERP completo con módulos avanzados', 'erp_pro', 500.00, 5000.00, 10, 50000, 20000,
|
|
'{"inventory": true, "sales": true, "purchases": true, "reports": true, "multi_warehouse": true, "accounting_basic": true}'),
|
|
|
|
-- Vertical (template)
|
|
('vertical_base', 'Vertical Industry', 'ERP especializado por industria', 'vertical', 1000.00, 10000.00, 20, 100000, 50000,
|
|
'{"inventory": true, "sales": true, "purchases": true, "reports": true, "multi_warehouse": true, "accounting": true, "industry_specific": true}');
|
|
|
|
-- Feature definitions
|
|
INSERT INTO saas.plan_features (plan_id, feature_code, feature_name, is_included, addon_price_monthly) VALUES
|
|
-- POS Micro add-ons
|
|
((SELECT id FROM saas.plans WHERE code = 'pos_micro'), 'cfdi', 'Facturación CFDI', FALSE, 50.00),
|
|
((SELECT id FROM saas.plans WHERE code = 'pos_micro'), 'whatsapp_pro', 'WhatsApp Avanzado', FALSE, 100.00),
|
|
|
|
-- ERP Básico add-ons
|
|
((SELECT id FROM saas.plans WHERE code = 'erp_basic'), 'accounting', 'Contabilidad', FALSE, 150.00),
|
|
((SELECT id FROM saas.plans WHERE code = 'erp_basic'), 'hr', 'Recursos Humanos', FALSE, 100.00),
|
|
((SELECT id FROM saas.plans WHERE code = 'erp_basic'), 'cfdi', 'Facturación CFDI', FALSE, 100.00),
|
|
((SELECT id FROM saas.plans WHERE code = 'erp_basic'), 'extra_user', 'Usuario Extra', FALSE, 50.00),
|
|
((SELECT id FROM saas.plans WHERE code = 'erp_basic'), 'multi_warehouse', 'Multi-Almacén', FALSE, 100.00),
|
|
|
|
-- ERP Pro included features
|
|
((SELECT id FROM saas.plans WHERE code = 'erp_pro'), 'accounting', 'Contabilidad Básica', TRUE, NULL),
|
|
((SELECT id FROM saas.plans WHERE code = 'erp_pro'), 'multi_warehouse', 'Multi-Almacén', TRUE, NULL),
|
|
((SELECT id FROM saas.plans WHERE code = 'erp_pro'), 'cfdi', 'Facturación CFDI', FALSE, 100.00),
|
|
((SELECT id FROM saas.plans WHERE code = 'erp_pro'), 'hr', 'Recursos Humanos', FALSE, 100.00),
|
|
((SELECT id FROM saas.plans WHERE code = 'erp_pro'), 'extra_user', 'Usuario Extra', FALSE, 50.00);
|
|
|
|
-- ============================================================================
|
|
-- VIEWS
|
|
-- ============================================================================
|
|
|
|
-- View: Active subscriptions with plan details
|
|
CREATE OR REPLACE VIEW saas.active_subscriptions_view AS
|
|
SELECT
|
|
s.id,
|
|
s.tenant_id,
|
|
t.business_name,
|
|
t.email,
|
|
p.code as plan_code,
|
|
p.name as plan_name,
|
|
s.billing_cycle,
|
|
s.current_price,
|
|
s.status,
|
|
s.current_period_start,
|
|
s.current_period_end,
|
|
s.trial_ends_at,
|
|
CASE
|
|
WHEN s.status = 'trial' THEN s.trial_ends_at - CURRENT_TIMESTAMP
|
|
ELSE s.current_period_end - CURRENT_TIMESTAMP
|
|
END as time_remaining
|
|
FROM saas.subscriptions s
|
|
JOIN saas.tenants t ON s.tenant_id = t.id
|
|
JOIN saas.plans p ON s.plan_id = p.id
|
|
WHERE s.status IN ('trial', 'active', 'past_due');
|
|
|
|
-- View: Revenue by plan
|
|
CREATE OR REPLACE VIEW saas.revenue_by_plan AS
|
|
SELECT
|
|
p.code as plan_code,
|
|
p.name as plan_name,
|
|
COUNT(s.id) as active_subscriptions,
|
|
SUM(s.current_price) as monthly_revenue
|
|
FROM saas.plans p
|
|
LEFT JOIN saas.subscriptions s ON p.id = s.plan_id
|
|
AND s.status IN ('active', 'trial')
|
|
GROUP BY p.id, p.code, p.name
|
|
ORDER BY monthly_revenue DESC NULLS LAST;
|
|
|
|
-- ============================================================================
|
|
-- COMMENTS
|
|
-- ============================================================================
|
|
|
|
COMMENT ON SCHEMA saas IS 'SaaS Layer - Billing, subscriptions, and multi-tenancy management';
|
|
COMMENT ON TABLE saas.plans IS 'Available subscription plans with pricing';
|
|
COMMENT ON TABLE saas.plan_features IS 'Features included or available as add-ons per plan';
|
|
COMMENT ON TABLE saas.tenants IS 'All SaaS customers/businesses';
|
|
COMMENT ON TABLE saas.subscriptions IS 'Active subscriptions per tenant';
|
|
COMMENT ON TABLE saas.invoices IS 'Generated invoices for billing';
|
|
COMMENT ON TABLE saas.payments IS 'Payment transactions';
|
|
COMMENT ON TABLE saas.payment_methods IS 'Saved payment methods per tenant';
|
|
COMMENT ON TABLE saas.usage_tracking IS 'Usage metrics for billing and limits';
|
|
COMMENT ON TABLE saas.support_tickets IS 'Customer support tickets';
|
|
|
|
-- ============================================================================
|
|
-- SCHEMA COMPLETE
|
|
-- ============================================================================
|
|
|
|
DO $$
|
|
BEGIN
|
|
RAISE NOTICE 'SaaS Billing schema created successfully!';
|
|
RAISE NOTICE 'Tables: 10 (plans, plan_features, tenants, subscriptions, invoices, invoice_items, payments, payment_methods, usage_tracking, support_tickets)';
|
|
RAISE NOTICE 'Default plans: POS Micro (100 MXN), ERP Básico (300 MXN), ERP Pro (500 MXN), Vertical (1000+ MXN)';
|
|
END $$;
|