fix: renomeia timestamp da migration de CRM (estava antes de resellers já aplicada)
node-pg-migrate recusa rodar uma migration cujo timestamp precede uma já aplicada (check-order). O agente gerou 1788439012582 (CRM) antes de 1788439142825 (resellers) já ter sido mesclada e aplicada. Renomeado para 1788444193993, aplicado com sucesso.
This commit is contained in:
93
packages/database/migrations/1788444193993_crm.cjs
Normal file
93
packages/database/migrations/1788444193993_crm.cjs
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* CRM — Leads & Opportunities (Master Prompt §6.2 "CRM e Comercial").
|
||||
*
|
||||
* Greenfield for EDEN: the legacy OrçaFácil system (eden.md) has no CRM
|
||||
* module — it starts directly at Quotes. This migration is driven entirely
|
||||
* by the Master Prompt's requirements, not a port of legacy behavior.
|
||||
*
|
||||
* MVP scope: `leads` and `opportunities` with free-text `status`/`stage`
|
||||
* columns (documented via COMMENT ON COLUMN, no CHECK constraint yet — a
|
||||
* fully configurable pipeline is a future refinement, not this MVP).
|
||||
*
|
||||
* `leads.company_name` is a free-text prospect company name — it is
|
||||
* deliberately NOT a foreign key to the `companies` table, which models
|
||||
* issuing legal entities, not sales prospects.
|
||||
*
|
||||
* The `set_updated_at()` trigger function is created by the baseline
|
||||
* schema migration (1788430575858_baseline-schema.cjs) — reused here, not
|
||||
* redefined.
|
||||
*/
|
||||
|
||||
exports.shorthands = undefined;
|
||||
|
||||
exports.up = (pgm) => {
|
||||
pgm.sql(`
|
||||
-- =====================================================================
|
||||
-- leads
|
||||
-- =====================================================================
|
||||
CREATE TABLE leads (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_by UUID REFERENCES users(id),
|
||||
source TEXT,
|
||||
contact_name TEXT NOT NULL,
|
||||
contact_email TEXT,
|
||||
contact_phone TEXT,
|
||||
company_name TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'new',
|
||||
owner_id UUID REFERENCES users(id),
|
||||
notes TEXT
|
||||
);
|
||||
COMMENT ON COLUMN leads.source IS
|
||||
'Free-text origin/campaign (e.g. "indicação", "site", "feira X") — no fixed taxonomy in this MVP.';
|
||||
COMMENT ON COLUMN leads.company_name IS
|
||||
'Free-text prospect company name. Deliberately NOT a FK to companies — that table models issuing legal entities, not sales prospects.';
|
||||
COMMENT ON COLUMN leads.status IS
|
||||
'Plain TEXT, no CHECK constraint yet. Intended values: new, contacted, qualified, converted, discarded. converted is set only via POST /v1/leads/:id/convert.';
|
||||
COMMENT ON COLUMN leads.owner_id IS 'Responsible salesperson.';
|
||||
|
||||
CREATE INDEX idx_leads_status ON leads(status);
|
||||
CREATE INDEX idx_leads_owner ON leads(owner_id);
|
||||
|
||||
CREATE TRIGGER trg_leads_updated_at BEFORE UPDATE ON leads
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- =====================================================================
|
||||
-- opportunities
|
||||
-- =====================================================================
|
||||
CREATE TABLE opportunities (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_by UUID REFERENCES users(id),
|
||||
lead_id UUID REFERENCES leads(id),
|
||||
title TEXT NOT NULL,
|
||||
stage TEXT NOT NULL DEFAULT 'prospecting',
|
||||
probability INTEGER CHECK (probability >= 0 AND probability <= 100),
|
||||
owner_id UUID REFERENCES users(id),
|
||||
loss_reason TEXT,
|
||||
notes TEXT
|
||||
);
|
||||
COMMENT ON COLUMN opportunities.lead_id IS
|
||||
'Nullable — an opportunity can be created directly, not only converted from a lead.';
|
||||
COMMENT ON COLUMN opportunities.stage IS
|
||||
'Plain TEXT, no CHECK constraint yet. Intended values: prospecting, qualification, proposal, negotiation, won, lost. A fully configurable pipeline is a future refinement, not this MVP.';
|
||||
COMMENT ON COLUMN opportunities.loss_reason IS
|
||||
'Nullable — filled only when stage = ''lost''. Not enforced by a DB constraint in this MVP.';
|
||||
COMMENT ON COLUMN opportunities.owner_id IS 'Responsible salesperson.';
|
||||
|
||||
CREATE INDEX idx_opportunities_stage ON opportunities(stage);
|
||||
CREATE INDEX idx_opportunities_owner ON opportunities(owner_id);
|
||||
|
||||
CREATE TRIGGER trg_opportunities_updated_at BEFORE UPDATE ON opportunities
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
`);
|
||||
};
|
||||
|
||||
exports.down = (pgm) => {
|
||||
pgm.sql(`
|
||||
DROP TABLE IF EXISTS opportunities;
|
||||
DROP TABLE IF EXISTS leads;
|
||||
`);
|
||||
};
|
||||
Reference in New Issue
Block a user