init
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_language WHERE lanname = 'plpgsql') THEN
|
||||
CREATE LANGUAGE plpgsql;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION trg_block_delivered_sales_orders_head()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF OLD.status = 'delivered' THEN
|
||||
RAISE EXCEPTION '已交付的销售订单禁止修改或删除';
|
||||
END IF;
|
||||
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP TRIGGER IF EXISTS block_delivered_sales_orders_head ON sales_orders;
|
||||
CREATE TRIGGER block_delivered_sales_orders_head
|
||||
BEFORE UPDATE OR DELETE ON sales_orders
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trg_block_delivered_sales_orders_head();
|
||||
|
||||
CREATE OR REPLACE FUNCTION trg_block_delivered_sales_orders_items()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_status text;
|
||||
BEGIN
|
||||
SELECT status INTO v_status
|
||||
FROM sales_orders
|
||||
WHERE id = COALESCE(NEW.order_id, OLD.order_id);
|
||||
|
||||
IF v_status = 'delivered' THEN
|
||||
RAISE EXCEPTION '已交付的销售订单明细禁止变更';
|
||||
END IF;
|
||||
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP TRIGGER IF EXISTS block_delivered_sales_orders_items ON sales_order_items;
|
||||
CREATE TRIGGER block_delivered_sales_orders_items
|
||||
BEFORE INSERT OR UPDATE OR DELETE ON sales_order_items
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trg_block_delivered_sales_orders_items();
|
||||
@@ -0,0 +1,65 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'uq_inventory_product_warehouse'
|
||||
) THEN
|
||||
ALTER TABLE inventory
|
||||
ADD CONSTRAINT uq_inventory_product_warehouse UNIQUE (product_id, warehouse_id);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'ck_inventory_qty_nonnegative'
|
||||
) THEN
|
||||
ALTER TABLE inventory
|
||||
ADD CONSTRAINT ck_inventory_qty_nonnegative CHECK (quantity >= 0 AND locked_quantity >= 0 AND locked_quantity <= quantity);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'ck_purchase_order_items_qty'
|
||||
) THEN
|
||||
ALTER TABLE purchase_order_items
|
||||
ADD CONSTRAINT ck_purchase_order_items_qty CHECK (quantity > 0 AND received_quantity >= 0 AND received_quantity <= quantity);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'ck_sales_order_items_qty'
|
||||
) THEN
|
||||
ALTER TABLE sales_order_items
|
||||
ADD CONSTRAINT ck_sales_order_items_qty CHECK (quantity > 0 AND delivered_quantity >= 0 AND delivered_quantity <= quantity);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_purchase_orders_status ON purchase_orders (status);
|
||||
CREATE INDEX IF NOT EXISTS idx_purchase_orders_supplier_status ON purchase_orders (supplier_id, status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_purchase_order_items_order_id ON purchase_order_items (order_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_purchase_order_items_product_id ON purchase_order_items (product_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sales_orders_status ON sales_orders (status);
|
||||
CREATE INDEX IF NOT EXISTS idx_sales_orders_customer_status ON sales_orders (customer_id, status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sales_order_items_order_id ON sales_order_items (order_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sales_order_items_product_id ON sales_order_items (product_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_inventory_warehouse_product ON inventory (warehouse_id, product_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_stock_movements_warehouse_created ON stock_movements (warehouse_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_stock_movements_reference ON stock_movements (reference_type, reference_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_stock_movements_reference_no ON stock_movements (reference_no);
|
||||
@@ -0,0 +1,3 @@
|
||||
UPDATE sales_orders
|
||||
SET status = 'manufacturing'
|
||||
WHERE status = 'pending';
|
||||
@@ -0,0 +1,37 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('sales_orders_p') IS NULL THEN
|
||||
EXECUTE $q$
|
||||
CREATE TABLE sales_orders_p (
|
||||
LIKE sales_orders INCLUDING ALL
|
||||
) PARTITION BY RANGE (order_date)
|
||||
$q$;
|
||||
END IF;
|
||||
|
||||
IF to_regclass('purchase_orders_p') IS NULL THEN
|
||||
EXECUTE $q$
|
||||
CREATE TABLE purchase_orders_p (
|
||||
LIKE purchase_orders INCLUDING ALL
|
||||
) PARTITION BY RANGE (order_date)
|
||||
$q$;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
d date := date_trunc('month', now())::date;
|
||||
next_d date := (date_trunc('month', now()) + interval '1 month')::date;
|
||||
part_name text;
|
||||
BEGIN
|
||||
part_name := format('sales_orders_p_%s', to_char(d, 'YYYYMM'));
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS %I PARTITION OF sales_orders_p FOR VALUES FROM (%L) TO (%L)',
|
||||
part_name, d, next_d
|
||||
);
|
||||
|
||||
part_name := format('purchase_orders_p_%s', to_char(d, 'YYYYMM'));
|
||||
EXECUTE format(
|
||||
'CREATE TABLE IF NOT EXISTS %I PARTITION OF purchase_orders_p FOR VALUES FROM (%L) TO (%L)',
|
||||
part_name, d, next_d
|
||||
);
|
||||
END $$;
|
||||
@@ -0,0 +1,28 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'sales_orders' AND column_name = 'org_id'
|
||||
) THEN
|
||||
ALTER TABLE sales_orders ADD COLUMN org_id bigint NOT NULL DEFAULT 0;
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'purchase_orders' AND column_name = 'org_id'
|
||||
) THEN
|
||||
ALTER TABLE purchase_orders ADD COLUMN org_id bigint NOT NULL DEFAULT 0;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE sales_orders ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE purchase_orders ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
DROP POLICY IF EXISTS sales_orders_org_isolation ON sales_orders;
|
||||
CREATE POLICY sales_orders_org_isolation
|
||||
ON sales_orders
|
||||
USING (org_id::text = current_setting('app.current_org_id', true));
|
||||
|
||||
DROP POLICY IF EXISTS purchase_orders_org_isolation ON purchase_orders;
|
||||
CREATE POLICY purchase_orders_org_isolation
|
||||
ON purchase_orders
|
||||
USING (org_id::text = current_setting('app.current_org_id', true));
|
||||
@@ -0,0 +1,65 @@
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id bigserial PRIMARY KEY,
|
||||
table_name text NOT NULL,
|
||||
op text NOT NULL,
|
||||
pk jsonb,
|
||||
old_data jsonb,
|
||||
new_data jsonb,
|
||||
actor_id bigint,
|
||||
ip text,
|
||||
ua text,
|
||||
changed_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_table_time ON audit_log (table_name, changed_at DESC);
|
||||
|
||||
CREATE OR REPLACE FUNCTION audit_row_change()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
v_actor_id bigint;
|
||||
v_ip text;
|
||||
v_ua text;
|
||||
v_pk jsonb;
|
||||
BEGIN
|
||||
v_actor_id := NULLIF(current_setting('app.current_user_id', true), '')::bigint;
|
||||
v_ip := NULLIF(current_setting('app.current_ip', true), '');
|
||||
v_ua := NULLIF(current_setting('app.current_ua', true), '');
|
||||
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
v_pk := jsonb_build_object('id', NEW.id);
|
||||
INSERT INTO audit_log(table_name, op, pk, old_data, new_data, actor_id, ip, ua)
|
||||
VALUES (TG_TABLE_NAME, TG_OP, v_pk, NULL, to_jsonb(NEW), v_actor_id, v_ip, v_ua);
|
||||
RETURN NEW;
|
||||
ELSIF TG_OP = 'UPDATE' THEN
|
||||
v_pk := jsonb_build_object('id', NEW.id);
|
||||
INSERT INTO audit_log(table_name, op, pk, old_data, new_data, actor_id, ip, ua)
|
||||
VALUES (TG_TABLE_NAME, TG_OP, v_pk, to_jsonb(OLD), to_jsonb(NEW), v_actor_id, v_ip, v_ua);
|
||||
RETURN NEW;
|
||||
ELSE
|
||||
v_pk := jsonb_build_object('id', OLD.id);
|
||||
INSERT INTO audit_log(table_name, op, pk, old_data, new_data, actor_id, ip, ua)
|
||||
VALUES (TG_TABLE_NAME, TG_OP, v_pk, to_jsonb(OLD), NULL, v_actor_id, v_ip, v_ua);
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP TRIGGER IF EXISTS audit_sales_orders ON sales_orders;
|
||||
CREATE TRIGGER audit_sales_orders
|
||||
AFTER INSERT OR UPDATE OR DELETE ON sales_orders
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION audit_row_change();
|
||||
|
||||
DROP TRIGGER IF EXISTS audit_purchase_orders ON purchase_orders;
|
||||
CREATE TRIGGER audit_purchase_orders
|
||||
AFTER INSERT OR UPDATE OR DELETE ON purchase_orders
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION audit_row_change();
|
||||
|
||||
DROP TRIGGER IF EXISTS audit_stock_movements ON stock_movements;
|
||||
CREATE TRIGGER audit_stock_movements
|
||||
AFTER INSERT OR UPDATE OR DELETE ON stock_movements
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION audit_row_change();
|
||||
@@ -0,0 +1,2 @@
|
||||
DELETE FROM audit_log
|
||||
WHERE changed_at < (now() - interval '180 days');
|
||||
@@ -0,0 +1,12 @@
|
||||
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
|
||||
|
||||
SELECT
|
||||
calls,
|
||||
total_exec_time,
|
||||
mean_exec_time,
|
||||
max_exec_time,
|
||||
rows,
|
||||
query
|
||||
FROM pg_stat_statements
|
||||
ORDER BY mean_exec_time DESC
|
||||
LIMIT 50;
|
||||
Reference in New Issue
Block a user