feat(api,ui): MFA/TOTP, Inventory, Support, HR-Timeclock, Fiscal, Billing e Design System
Leva grande, pedida como "pelo menos mais 20 etapas, use subagentes":
fecha a pendência de MFA/TOTP da Fase 1 (feito diretamente, autenticação é
sensível demais para delegar) e lança 5 subagentes em paralelo em 2 ondas
(eden-inventory, eden-support, eden-hr-timeclock; depois eden-finance,
eden-fiscal, eden-frontend), cada um em worktree isolado, com integração
seletiva manual (barrel, app.module.ts, migrations renomeadas/aplicadas,
build/typecheck/teste ao vivo antes de cada merge).
## Backend
- MFA/TOTP (`apps/api/src/auth/mfa.service.ts`, `crypto/field-encryption.service.ts`):
login em duas etapas quando `mfa_enabled=true`, `FieldEncryptionService`
genérico (AES-256-GCM versionado, ADR-0005) reaproveitado por outros
segredos reversíveis (ex. credencial de equipamento Control iD).
- Inventory: warehouses, stock_movements (ledger append-only), serialized_assets,
asset_assignments, stock_reservations — invariante "serial/patrimônio/MAC
nunca duplicado entre ativos ativos" reforçada por índices únicos parciais
no banco (Master Prompt §15.3).
- Support/Service Desk: tickets com máquina de estados, SLA com pausa/retomada
em `waiting_customer`/`waiting_third_party` (funções puras testáveis).
- HR-Timeclock (fundação): dispositivos Control iD (senha cifrada), import de
AFD com parser + CRC-16/KERMIT, `afd_records` imutável sem exceção nem
para super_admin (sem válvula de escape, diferente de audit_log).
- Fiscal: NCM/CFOP/municípios/naturezas de operação (eden.md módulo 5, MVP de
5 das ~19 tabelas), product_fiscal_profiles com invariante de único
profile ativo por billing_component.
- Billing: subscription→billing_run→invoice idempotente (regenerar em draft
substitui, nunca duplica) e imutável após consolidar (ADR-0008: nunca
confunde faturamento/documento fiscal/recebimento).
- FKs cruzadas fechadas: quotes/client_registrations/contracts.
## Frontend — primeira leva
- `packages/ui`: Design System real extraído do tema DreamsERP comprado —
achado que mudou o escopo: o tema já vem em React+Tailwind (não só
Angular), tokens visuais reais portados (cores, Geist como fonte real,
divergindo da doc antiga que citava Nunito/Poppins).
- `apps/core-web`: login real (com suporte a MFA), sessão, shell autenticado
via `AppShell` — substitui a tela estática "Bootstrap da Fase 1".
## Bugs reais encontrados e corrigidos (todos via teste ao vivo, não leitura)
1. **Segurança**: o token de desafio MFA era aceito como access token normal
pelo `JwtAuthGuard` (mesma assinatura, nenhuma checagem da claim
`purpose`) — bypass total do segundo fator. Explorado e fechado ao vivo
contra o container real; teste de regressão em `test/mfa.spec.ts`.
2. Crase solta dentro de comentário SQL na migration de hr-timeclock quebrava
o template literal JS (`SyntaxError` ao rodar a migration).
3. CORS ausente por completo na API.
4. `turbo.json` não declarava `VITE_API_BASE_URL` como input de build — o
Turborepo cacheava o bundle do frontend ignorando mudança na env var.
5. `compose.yaml` usava `${VITE_API_BASE_URL:-/api/v1}`, que colidia com a
variável de mesmo nome do `.env` local — o default nunca era aplicado.
6. Não existia `.dockerignore` — todo build copiava node_modules/.git/
worktrees de subagentes (665MB) para dentro do contexto Docker.
Decisão do operador incorporada: a API nunca tem domínio público próprio
(só IP interno) — os 3 frontends chamam por caminho relativo `/api/v1`,
proxy externo roteia same-origin; CORS só é relevante para dev local.
Verificado: `pnpm turbo run typecheck build` (28/28), `pnpm --filter
@eden/api test` (30/30, cresceu de 15), todo o lote testado ao vivo via
curl contra os containers reais reconstruídos, incluindo os 6 bugs acima
já fechados e confirmados, e a trilha de auditoria conferida no Postgres.
Claude-Session: https://claude.ai/code/session_01BcWWuKfykGtUweXmHD6cKx
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
264
packages/database/migrations/1788451164762_inventory.cjs
Normal file
264
packages/database/migrations/1788451164762_inventory.cjs
Normal file
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* Inventory/Warehouse/Assets bounded context — Master Prompt §6.8, ADR-0007.
|
||||
* Greenfield: the legacy OrçaFácil has no real stock module, only the
|
||||
* requisições cited in the Master Prompt, so there is no eden.md section
|
||||
* to port faithfully here — this schema is designed from the Master
|
||||
* Prompt §6.8 requirements directly.
|
||||
*
|
||||
* Master Prompt §6.8 lists 15 possible entities. This migration builds
|
||||
* the 5 that form one coherent, end-to-end-testable slice (reserve →
|
||||
* select serial → install/comodato → return), mirroring the
|
||||
* 4-of-11-tables MVP approach taken by the Contracts module
|
||||
* (docs/adr/0006, see 1788447802216_contracts.cjs's header comment):
|
||||
*
|
||||
* - warehouses (this migration)
|
||||
* - stock_movements (this migration)
|
||||
* - serialized_assets (this migration)
|
||||
* - asset_assignments (this migration)
|
||||
* - stock_reservations (this migration)
|
||||
*
|
||||
* Explicitly DEFERRED, not forgotten — each needs a module/workflow that
|
||||
* hasn't landed yet:
|
||||
* - warehouse_locations — bin/shelf-level location inside a warehouse;
|
||||
* needs a UX for put-away/picking before a bare table is useful.
|
||||
* - stock_items — a per-(warehouse,product) balance cache row;
|
||||
* deliberately NOT built — this MVP's invariant is that on-hand
|
||||
* balance is ALWAYS derived from stock_movements (SUM by sign), never
|
||||
* a stored/editable column (see stock_movements' comment). A
|
||||
* materialized cache can be added later purely as a performance
|
||||
* optimization, refreshed FROM the ledger, never the other way round.
|
||||
* - stock_lots — batch/lot tracking (expiry, supplier batch);
|
||||
* no product in the current catalog needs it yet.
|
||||
* - inventory_counts — physical count / cycle-count workflow with
|
||||
* its own reconciliation UX; a count's variance would itself need to
|
||||
* post `adjustment_in`/`adjustment_out` movements once built.
|
||||
* - transfers — a formal multi-step inter-warehouse transfer
|
||||
* (request → in-transit → receive) with its own approval workflow;
|
||||
* `transfer_out`/`transfer_in` movement types are already modeled in
|
||||
* the ledger vocabulary below so this can be layered on top later
|
||||
* without a ledger migration.
|
||||
* - receipts / issues — formal goods-receipt-note / issue-note
|
||||
* documents (e.g. tied to a purchase order); `purchase_in` already
|
||||
* covers the ledger side for this MVP without the PO workflow.
|
||||
* - returns — a formal customer-return workflow distinct
|
||||
* from the simple `/assets/:id/return` endpoint this MVP ships
|
||||
* (which only closes the assignment and posts `return_in`); needed
|
||||
* later for e.g. partial/quantity-based non-serialized returns.
|
||||
* - rma — formal vendor RMA case tracking (tracking
|
||||
* number, vendor, resolution); `rma_out` is already modeled in the
|
||||
* ledger vocabulary below so the case-tracking table can be added
|
||||
* later without a ledger migration.
|
||||
* - asset_maintenance — service/repair history for a serialized
|
||||
* asset; needs its own workflow design, not just a bare log table.
|
||||
*
|
||||
* No FK to `contracts` yet: `serialized_assets.current_contract_id` and
|
||||
* `asset_assignments.contract_id` are deliberately nullable/plain UUID
|
||||
* with NO REFERENCES clause — same adopted pattern as Quotes/Contracts/
|
||||
* ClientRegistrations' cross-module FKs while modules land in parallel
|
||||
* worktrees. Wiring those FKs is integration work for the merge step.
|
||||
*
|
||||
* ON DELETE: no table in this migration ever CASCADEs a delete from
|
||||
* warehouses/products/serialized_assets — an asset or warehouse with
|
||||
* movement/assignment history is never hard-deleted, only inactivated
|
||||
* (warehouses.is_active) or status-transitioned (serialized_assets.status
|
||||
* = 'written_off'). This is a deliberate invariant per the Inventory
|
||||
* agent's mandate, not an oversight.
|
||||
*/
|
||||
|
||||
exports.shorthands = undefined;
|
||||
|
||||
exports.up = (pgm) => {
|
||||
pgm.sql(`
|
||||
-- =====================================================================
|
||||
-- warehouses — armazém/depósito físico, pertence a uma legal entity
|
||||
-- =====================================================================
|
||||
CREATE TABLE warehouses (
|
||||
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),
|
||||
company_id UUID NOT NULL REFERENCES companies(id),
|
||||
name TEXT NOT NULL,
|
||||
code TEXT NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
UNIQUE (code)
|
||||
);
|
||||
COMMENT ON TABLE warehouses IS
|
||||
'Armazém/depósito físico — Master Prompt §6.8. Pertence a uma company (legal entity, companies.id). code é único globalmente (não só por company) para servir como identificador humano curto e inequívoco em toda a UI/relatórios. Nunca deletado fisicamente — um armazém desativado é is_active=false, preservando o histórico de stock_movements/serialized_assets que apontam para ele (ver comentário de topo da migration).';
|
||||
|
||||
CREATE TRIGGER trg_warehouses_updated_at BEFORE UPDATE ON warehouses
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE INDEX idx_warehouses_company ON warehouses(company_id);
|
||||
|
||||
-- =====================================================================
|
||||
-- stock_movements — ledger append-only. Saldo de estoque é SEMPRE
|
||||
-- derivado daqui (SUM com sinal implícito pelo tipo), nunca uma coluna
|
||||
-- de saldo editável em nenhuma outra tabela.
|
||||
-- =====================================================================
|
||||
CREATE TABLE stock_movements (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_by UUID REFERENCES users(id),
|
||||
warehouse_id UUID NOT NULL REFERENCES warehouses(id),
|
||||
product_id UUID NOT NULL REFERENCES products(id),
|
||||
movement_type TEXT NOT NULL CHECK (movement_type IN (
|
||||
'purchase_in', 'adjustment_in', 'adjustment_out',
|
||||
'transfer_out', 'transfer_in',
|
||||
'reservation', 'reservation_release',
|
||||
'sale_out', 'install_out', 'comodato_out',
|
||||
'return_in', 'rma_out', 'write_off'
|
||||
)),
|
||||
quantity NUMERIC(14,3) NOT NULL CHECK (quantity > 0),
|
||||
reference_type TEXT,
|
||||
reference_id UUID,
|
||||
notes TEXT
|
||||
);
|
||||
COMMENT ON TABLE stock_movements IS
|
||||
'Ledger append-only de estoque — Master Prompt §6.8. quantity é SEMPRE positivo; o sinal/direção é implícito pelo movement_type (ver COMMENT de movement_type). Uma correção de um movimento passado é sempre um NOVO movimento de estorno (ex.: um purchase_in lançado errado é corrigido com um adjustment_out equivalente), nunca um UPDATE/DELETE da linha original — daí os triggers trg_stock_movements_no_update/no_delete abaixo, mesmo padrão de audit_log (ver baseline-schema). reference_type/reference_id são polimórficos e opcionais (sem FK, por natureza) — ex.: reference_type=''contract''/reference_id=<contracts.id> num install_out, ou reference_type=''serialized_asset''/reference_id=<serialized_assets.id> numa reservation de um ativo específico.';
|
||||
COMMENT ON COLUMN stock_movements.movement_type IS
|
||||
'Direção implícita por tipo (usada para calcular o saldo on-hand de um warehouse+product via SUM):
|
||||
ENTRADA (+ saldo on-hand): purchase_in, adjustment_in, transfer_in, return_in
|
||||
SAÍDA (- saldo on-hand): adjustment_out, transfer_out, sale_out, install_out, comodato_out, rma_out, write_off
|
||||
NEUTRO para saldo on-hand, afeta apenas "disponível para prometer"
|
||||
(on_hand - reservas ativas), não o saldo físico: reservation, reservation_release
|
||||
Saldo on-hand de um (warehouse_id, product_id) = SUM(quantity) das entradas − SUM(quantity) das saídas, sempre calculado em tempo de consulta a partir desta tabela — nunca cacheado numa coluna editável (ver header da migration, item stock_items deferido).';
|
||||
|
||||
CREATE OR REPLACE FUNCTION prevent_stock_movement_mutation() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF current_setting('eden.allow_stock_movement_mutation', true) IS DISTINCT FROM 'true' THEN
|
||||
RAISE EXCEPTION 'stock_movements é append-only — correções são novos movimentos de estorno, nunca UPDATE/DELETE (ver Master Prompt §6.8 / .claude/agents/eden-inventory.md)';
|
||||
END IF;
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trg_stock_movements_no_update BEFORE UPDATE ON stock_movements
|
||||
FOR EACH ROW EXECUTE FUNCTION prevent_stock_movement_mutation();
|
||||
CREATE TRIGGER trg_stock_movements_no_delete BEFORE DELETE ON stock_movements
|
||||
FOR EACH ROW EXECUTE FUNCTION prevent_stock_movement_mutation();
|
||||
|
||||
CREATE INDEX idx_stock_movements_warehouse_product ON stock_movements(warehouse_id, product_id);
|
||||
CREATE INDEX idx_stock_movements_reference ON stock_movements(reference_type, reference_id);
|
||||
CREATE INDEX idx_stock_movements_created_at ON stock_movements(created_at);
|
||||
|
||||
-- =====================================================================
|
||||
-- serialized_assets — um ativo físico rastreado individualmente
|
||||
-- =====================================================================
|
||||
CREATE TABLE serialized_assets (
|
||||
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),
|
||||
updated_by UUID REFERENCES users(id),
|
||||
product_id UUID NOT NULL REFERENCES products(id),
|
||||
serial TEXT,
|
||||
patrimony_tag TEXT,
|
||||
mac_address TEXT,
|
||||
warehouse_id UUID REFERENCES warehouses(id),
|
||||
status TEXT NOT NULL DEFAULT 'in_stock' CHECK (status IN (
|
||||
'in_stock', 'reserved', 'installed', 'comodato', 'returned', 'written_off'
|
||||
)),
|
||||
current_contract_id UUID,
|
||||
warranty_expires_at TIMESTAMPTZ,
|
||||
CONSTRAINT chk_serialized_assets_has_identifier CHECK (serial IS NOT NULL OR patrimony_tag IS NOT NULL)
|
||||
);
|
||||
COMMENT ON TABLE serialized_assets IS
|
||||
'Ativo físico rastreado individualmente (serial/patrimônio/MAC) — Master Prompt §6.8. Ao menos um identificador (serial OU patrimony_tag) é obrigatório; produtos que só rastreiam patrimônio (ex.: mobiliário) podem não ter serial de fabricante. warehouse_id é a localização atual e é NULL quando o ativo está instalado/comodato no cliente (status IN (''installed'',''comodato'')) — current_contract_id aponta pra onde ele está nesse caso. current_contract_id NÃO tem FK para contracts ainda (contracts está sendo construído em paralelo por outro agente neste worktree) — mesmo padrão de FK cruzada adiada de Quotes/Contracts/ClientRegistrations; a FK real é trabalho de integração no merge. Nunca deletado fisicamente: um ativo dado de baixa vira status=''written_off'' (ver header da migration).';
|
||||
COMMENT ON COLUMN serialized_assets.mac_address IS
|
||||
'MVP: uma única coluna de MAC primário (nullable), não um array nem uma tabela filha. Decisão documentada: o índice único parcial que garante "MAC nunca duplicado entre ativos simultaneamente ativos" (ver abaixo) precisa colocalizar mac_address com status na MESMA tabela — um índice parcial não pode referenciar uma condição de uma tabela filha via JOIN. Se um produto real precisar rastrear múltiplos MACs (ex.: CPE dual-band com MAC de LAN e de WLAN), promover para uma tabela filha então, com a invariante reforçada por trigger em vez de índice parcial — deferido conscientemente, não esquecido.';
|
||||
COMMENT ON COLUMN serialized_assets.status IS
|
||||
'in_stock/reserved/installed/comodato contam como "ativo" para a invariante de não-duplicação de identificador (ver índices únicos parciais abaixo). returned/written_off NÃO contam — liberam o serial/patrimônio/MAC para reuso por uma linha nova, ex.: uma correção de cadastro. Nota: o endpoint POST /assets/:id/return desta MVP leva o ativo direto de volta a ''in_stock'' (mesma linha) — o status ''returned'' fica reservado para um fluxo futuro de RMA/inspeção pós-devolução (ainda não implementado, ver header da migration) em que o ativo chega mas ainda não foi conferido/restocado.';
|
||||
COMMENT ON COLUMN serialized_assets.current_contract_id IS
|
||||
'Sem FK para contracts.id ainda — deliberado, ver COMMENT da tabela. Preenchido em /assets/:id/install, limpo em /assets/:id/return.';
|
||||
|
||||
CREATE TRIGGER trg_serialized_assets_updated_at BEFORE UPDATE ON serialized_assets
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- Invariante obrigatória (Master Prompt §15.3 / §6.8: "nunca reutilizar
|
||||
-- o mesmo serial/MAC/patrimônio simultaneamente em dois ativos
|
||||
-- ativos") — reforçada em DB, não só em validação de aplicação.
|
||||
CREATE UNIQUE INDEX idx_serialized_assets_serial_active ON serialized_assets(serial)
|
||||
WHERE serial IS NOT NULL AND status IN ('in_stock', 'reserved', 'installed', 'comodato');
|
||||
CREATE UNIQUE INDEX idx_serialized_assets_patrimony_active ON serialized_assets(patrimony_tag)
|
||||
WHERE patrimony_tag IS NOT NULL AND status IN ('in_stock', 'reserved', 'installed', 'comodato');
|
||||
CREATE UNIQUE INDEX idx_serialized_assets_mac_active ON serialized_assets(mac_address)
|
||||
WHERE mac_address IS NOT NULL AND status IN ('in_stock', 'reserved', 'installed', 'comodato');
|
||||
|
||||
CREATE INDEX idx_serialized_assets_product ON serialized_assets(product_id);
|
||||
CREATE INDEX idx_serialized_assets_warehouse ON serialized_assets(warehouse_id);
|
||||
CREATE INDEX idx_serialized_assets_status ON serialized_assets(status);
|
||||
CREATE INDEX idx_serialized_assets_contract ON serialized_assets(current_contract_id);
|
||||
|
||||
-- =====================================================================
|
||||
-- asset_assignments — histórico de vínculo de um serialized_asset a um
|
||||
-- contrato/cliente ao longo do tempo
|
||||
-- =====================================================================
|
||||
CREATE TABLE asset_assignments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_by UUID REFERENCES users(id),
|
||||
asset_id UUID NOT NULL REFERENCES serialized_assets(id),
|
||||
contract_id UUID NOT NULL,
|
||||
assignment_type TEXT NOT NULL CHECK (assignment_type IN ('installed', 'comodato')),
|
||||
assigned_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
unassigned_at TIMESTAMPTZ
|
||||
);
|
||||
COMMENT ON TABLE asset_assignments IS
|
||||
'Histórico de vínculo de um ativo serializado a um contrato/cliente — Master Prompt §6.8. contract_id sem FK ainda (mesmo motivo/padrão de serialized_assets.current_contract_id). Nunca UPDATE de um assignment fechado (unassigned_at já preenchido): encerrar um vínculo é preencher unassigned_at UMA vez (camada de repositório só permite esse UPDATE específico, guardado pelo índice único parcial abaixo que impede reabrir/duplicar um vínculo aberto); um novo vínculo é sempre uma linha nova.';
|
||||
|
||||
-- Reforça em DB que um ativo nunca tem mais de um vínculo ABERTO
|
||||
-- simultâneo (unassigned_at IS NULL) — mesma invariante de
|
||||
-- "não alocado duas vezes" do Master Prompt §15.3, vista pelo lado do
|
||||
-- histórico de vínculos em vez do status do ativo.
|
||||
CREATE UNIQUE INDEX idx_asset_assignments_open ON asset_assignments(asset_id)
|
||||
WHERE unassigned_at IS NULL;
|
||||
|
||||
CREATE INDEX idx_asset_assignments_asset ON asset_assignments(asset_id);
|
||||
CREATE INDEX idx_asset_assignments_contract ON asset_assignments(contract_id);
|
||||
|
||||
-- =====================================================================
|
||||
-- stock_reservations — reserva de quantidade de um produto num
|
||||
-- warehouse, com ou sem um serial específico já selecionado
|
||||
-- =====================================================================
|
||||
CREATE TABLE stock_reservations (
|
||||
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),
|
||||
warehouse_id UUID NOT NULL REFERENCES warehouses(id),
|
||||
product_id UUID NOT NULL REFERENCES products(id),
|
||||
quantity NUMERIC(14,3) NOT NULL CHECK (quantity > 0),
|
||||
reference_type TEXT,
|
||||
reference_id UUID,
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'released', 'fulfilled')),
|
||||
notes TEXT
|
||||
);
|
||||
COMMENT ON TABLE stock_reservations IS
|
||||
'Reserva de quantidade de um produto num warehouse, ANTES (ou no lugar) da seleção de um serial específico — Master Prompt §6.8, primeiro passo do fluxo reservar→selecionar serial→instalar. Duas formas de uso, ambas suportadas por esta mesma tabela (sem coluna asset_id — decisão documentada):
|
||||
1. Reserva GENÉRICA (produto+quantidade, ainda sem serial escolhido): reference_type/reference_id apontam, por exemplo, para a quote_id ou contract_id que originou a necessidade. Caso descrito no Master Prompt como o primeiro passo do fluxo.
|
||||
2. Reserva de um ATIVO ESPECÍFICO (POST /assets/:id/reserve): quantity=1, reference_type=''serialized_asset'', reference_id=<serialized_assets.id> — rastreável ao ativo exato sem precisar de uma coluna dedicada.
|
||||
status=''fulfilled'' quando a reserva efetivamente vira uma saída (ex.: o ativo reservado é instalado); ''released'' quando cancelada sem uso. Sem FK em reference_id (polimórfico).';
|
||||
|
||||
CREATE TRIGGER trg_stock_reservations_updated_at BEFORE UPDATE ON stock_reservations
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE INDEX idx_stock_reservations_warehouse_product ON stock_reservations(warehouse_id, product_id);
|
||||
CREATE INDEX idx_stock_reservations_status ON stock_reservations(status);
|
||||
CREATE INDEX idx_stock_reservations_reference ON stock_reservations(reference_type, reference_id);
|
||||
`);
|
||||
};
|
||||
|
||||
exports.down = (pgm) => {
|
||||
pgm.sql(`
|
||||
DROP TABLE IF EXISTS stock_reservations;
|
||||
DROP TABLE IF EXISTS asset_assignments;
|
||||
DROP TABLE IF EXISTS serialized_assets;
|
||||
DROP TABLE IF EXISTS stock_movements;
|
||||
DROP FUNCTION IF EXISTS prevent_stock_movement_mutation();
|
||||
DROP TABLE IF EXISTS warehouses;
|
||||
`);
|
||||
};
|
||||
Reference in New Issue
Block a user