Kanban board, Project Management, Proposals/Quotes, Time Tracking, Custom Fields
Build & Auto-Repair / build (push) Has been cancelled
Build & Auto-Repair / build (push) Has been cancelled
- Kanban board with drag-and-drop per stage, colour-coded cards, score bars - Project Management with milestones, Gantt timeline, inline add milestone/task - Proposal/Quote builder with line items, VAT, terms, e-signature capture - Time Tracking with stopwatch, manual entry, hours-by-project chart - Custom Fields system: admin-defined fields per entity (leads/customers/ projects/opportunities) with drag-and-drop reordering in settings - Reusable CustomFieldsSection component for entity detail pages - Customers API endpoint - All builds pass with zero errors
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
-- ============================================================================
|
||||
-- Proposal/Quote Builder + Time Tracking
|
||||
-- ============================================================================
|
||||
|
||||
-- ── Proposals / Quotes ──────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS proposal_statuses (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name VARCHAR(50) NOT NULL UNIQUE,
|
||||
color VARCHAR(7),
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
INSERT INTO proposal_statuses (name, color, sort_order) VALUES
|
||||
('Draft', '#6366f1', 1),
|
||||
('Sent', '#eab308', 2),
|
||||
('Viewed', '#22d3ee', 3),
|
||||
('Accepted', '#22c55e', 4),
|
||||
('Declined', '#ef4444', 5),
|
||||
('Expired', '#a1a1aa', 6)
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS proposals (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
lead_id UUID REFERENCES leads(id),
|
||||
customer_id UUID REFERENCES customers(id),
|
||||
opportunity_id UUID REFERENCES opportunities(id),
|
||||
title VARCHAR(255) NOT NULL,
|
||||
proposal_number VARCHAR(50) NOT NULL,
|
||||
status_id UUID NOT NULL REFERENCES proposal_statuses(id),
|
||||
content JSONB,
|
||||
subtotal DECIMAL(15,2) NOT NULL DEFAULT 0,
|
||||
tax_percent DECIMAL(5,2) DEFAULT 15,
|
||||
tax_amount DECIMAL(15,2) DEFAULT 0,
|
||||
total_amount DECIMAL(15,2) NOT NULL DEFAULT 0,
|
||||
valid_until DATE,
|
||||
notes TEXT,
|
||||
terms TEXT DEFAULT 'Payment due within 30 days. 50% deposit required to commence work.',
|
||||
created_by UUID NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_proposal_number ON proposals(proposal_number) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_proposals_customer ON proposals(customer_id) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_proposals_lead ON proposals(lead_id) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_proposals_status ON proposals(status_id) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_proposals_created ON proposals(created_at) WHERE deleted_at IS NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS proposal_items (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
proposal_id UUID NOT NULL REFERENCES proposals(id) ON DELETE CASCADE,
|
||||
product_id UUID REFERENCES products(id),
|
||||
description TEXT NOT NULL,
|
||||
quantity INT NOT NULL DEFAULT 1 CHECK (quantity > 0),
|
||||
unit_price DECIMAL(15,2) NOT NULL CHECK (unit_price >= 0),
|
||||
discount_percent DECIMAL(5,2) NOT NULL DEFAULT 0 CHECK (discount_percent >= 0 AND discount_percent <= 100),
|
||||
total_price DECIMAL(15,2) NOT NULL CHECK (total_price >= 0),
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_proposal_items_proposal ON proposal_items(proposal_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS proposal_signatures (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
proposal_id UUID NOT NULL REFERENCES proposals(id) ON DELETE CASCADE,
|
||||
signatory_name VARCHAR(255) NOT NULL,
|
||||
signatory_email VARCHAR(255) NOT NULL,
|
||||
signature_data TEXT,
|
||||
ip_address INET,
|
||||
signed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
is_valid BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_proposal_signatures_proposal ON proposal_signatures(proposal_id);
|
||||
|
||||
-- ── Time Tracking ──────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS time_entries (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id),
|
||||
project_id UUID REFERENCES projects(id),
|
||||
milestone_id UUID REFERENCES project_milestones(id),
|
||||
task_id UUID REFERENCES tasks(id),
|
||||
description TEXT,
|
||||
date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
start_time TIMESTAMPTZ,
|
||||
end_time TIMESTAMPTZ,
|
||||
duration_minutes INT NOT NULL CHECK (duration_minutes > 0),
|
||||
billable BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
hourly_rate DECIMAL(15,2),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_time_entries_user ON time_entries(user_id);
|
||||
CREATE INDEX idx_time_entries_project ON time_entries(project_id);
|
||||
CREATE INDEX idx_time_entries_date ON time_entries(date);
|
||||
CREATE INDEX idx_time_entries_billable ON time_entries(billable) WHERE billable = TRUE;
|
||||
|
||||
-- Seed sample time entry
|
||||
DO $$
|
||||
DECLARE
|
||||
v_user_id UUID;
|
||||
v_project_id UUID;
|
||||
BEGIN
|
||||
SELECT id INTO v_user_id FROM users LIMIT 1;
|
||||
SELECT id INTO v_project_id FROM projects LIMIT 1;
|
||||
IF v_user_id IS NOT NULL AND v_project_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM time_entries) THEN
|
||||
INSERT INTO time_entries (user_id, project_id, description, date, duration_minutes, billable, hourly_rate)
|
||||
VALUES
|
||||
(v_user_id, v_project_id, 'Initial planning and requirements gathering', CURRENT_DATE - 2, 240, TRUE, 1500),
|
||||
(v_user_id, v_project_id, 'Wireframe design', CURRENT_DATE - 1, 180, TRUE, 1500),
|
||||
(v_user_id, v_project_id, 'Team standup and sprint planning', CURRENT_DATE, 60, FALSE, NULL);
|
||||
END IF;
|
||||
END $$;
|
||||
Reference in New Issue
Block a user