commit
32a36cceff
444 files changed
+67186
No files matched your search
@@ -0,0 +1,15 @@
|
||||
-- Migration: Add chicken_count and population columns to mortality_records table
|
||||
-- Date: 2025-12-30
|
||||
-- Description: Adds chicken_count (from AI counting) and population (manual override) columns
|
||||
|
||||
-- Add chicken_count column (nullable)
|
||||
ALTER TABLE mortality_records
|
||||
ADD COLUMN IF NOT EXISTS chicken_count INTEGER;
|
||||
|
||||
-- Add population column (nullable)
|
||||
ALTER TABLE mortality_records
|
||||
ADD COLUMN IF NOT EXISTS population INTEGER;
|
||||
|
||||
-- Add comments for documentation
|
||||
COMMENT ON COLUMN mortality_records.chicken_count IS 'Chicken count from AI camera counting system';
|
||||
COMMENT ON COLUMN mortality_records.population IS 'Manual population override value';
|
||||
@@ -0,0 +1,10 @@
|
||||
-- Migration tracking table
|
||||
-- This table keeps track of which migrations have been applied
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
filename VARCHAR(255) UNIQUE NOT NULL,
|
||||
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create index for faster lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_schema_migrations_filename ON schema_migrations(filename);
|
||||
@@ -0,0 +1,74 @@
|
||||
-- Migration: Add kandang management
|
||||
-- Date: 2026-02-26
|
||||
-- Description: Adds kandangs table, kandang_cycles junction table, and kandang_id to mortality_records
|
||||
-- Also auto-assigns existing mortality records to a default "Kandang 1"
|
||||
|
||||
-- 1. Create kandangs table
|
||||
CREATE TABLE IF NOT EXISTS kandangs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 2. Create kandang_cycles junction table (doc_in_count per kandang per cycle)
|
||||
CREATE TABLE IF NOT EXISTS kandang_cycles (
|
||||
id SERIAL PRIMARY KEY,
|
||||
kandang_id INTEGER NOT NULL,
|
||||
cycle_id VARCHAR(255) NOT NULL,
|
||||
doc_in_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (kandang_id) REFERENCES kandangs(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (cycle_id) REFERENCES cycles(id) ON DELETE CASCADE,
|
||||
UNIQUE(kandang_id, cycle_id)
|
||||
);
|
||||
|
||||
-- 3. Add kandang_id column to mortality_records (nullable for backward compat)
|
||||
ALTER TABLE mortality_records
|
||||
ADD COLUMN IF NOT EXISTS kandang_id INTEGER REFERENCES kandangs(id) ON DELETE SET NULL;
|
||||
|
||||
-- 4. Add indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_mortality_kandang_id ON mortality_records(kandang_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_kandang_cycles_kandang ON kandang_cycles(kandang_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_kandang_cycles_cycle ON kandang_cycles(cycle_id);
|
||||
|
||||
-- 5. Add updated_at triggers
|
||||
DROP TRIGGER IF EXISTS update_kandangs_updated_at ON kandangs;
|
||||
CREATE TRIGGER update_kandangs_updated_at BEFORE UPDATE ON kandangs
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS update_kandang_cycles_updated_at ON kandang_cycles;
|
||||
CREATE TRIGGER update_kandang_cycles_updated_at BEFORE UPDATE ON kandang_cycles
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- 6. Auto-assign existing data: Create default "Kandang 1" if not exists and assign all unassigned mortality records
|
||||
-- Insert default kandang (only if no kandangs exist yet)
|
||||
INSERT INTO kandangs (name)
|
||||
SELECT 'Kandang 1'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM kandangs LIMIT 1);
|
||||
|
||||
-- Assign existing mortality records to the first kandang (only records with NULL kandang_id)
|
||||
UPDATE mortality_records
|
||||
SET kandang_id = (SELECT id FROM kandangs ORDER BY id LIMIT 1)
|
||||
WHERE kandang_id IS NULL
|
||||
AND EXISTS (SELECT 1 FROM kandangs LIMIT 1);
|
||||
|
||||
-- Create kandang_cycles entries for the default kandang for all cycles that have doc_in_count
|
||||
INSERT INTO kandang_cycles (kandang_id, cycle_id, doc_in_count)
|
||||
SELECT k.id, c.id, COALESCE(c.doc_in_count, 0)
|
||||
FROM kandangs k, cycles c
|
||||
WHERE k.id = (SELECT id FROM kandangs ORDER BY id LIMIT 1)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM kandang_cycles kc
|
||||
WHERE kc.kandang_id = k.id AND kc.cycle_id = c.id
|
||||
);
|
||||
|
||||
-- 7. Drop old unique constraint and add new one that includes kandang_id
|
||||
-- Note: The constraint name follows PostgreSQL naming convention
|
||||
ALTER TABLE mortality_records DROP CONSTRAINT IF EXISTS mortality_records_cycle_id_day_key;
|
||||
|
||||
-- Create new unique index that handles kandang_id (using COALESCE for NULL safety)
|
||||
DROP INDEX IF EXISTS idx_mortality_cycle_day_kandang;
|
||||
CREATE UNIQUE INDEX idx_mortality_cycle_day_kandang
|
||||
ON mortality_records(cycle_id, day, COALESCE(kandang_id, -1));
|
||||
@@ -0,0 +1,22 @@
|
||||
-- Migration: Simplify chicken counting to a single upsert table
|
||||
-- Date: 2026-03-05
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chicken_counting (
|
||||
id SERIAL PRIMARY KEY,
|
||||
date DATE NOT NULL,
|
||||
kandang_id INTEGER NOT NULL REFERENCES kandangs(id) ON DELETE CASCADE,
|
||||
filename TEXT,
|
||||
total_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(date, kandang_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_chicken_counting_date ON chicken_counting(date);
|
||||
CREATE INDEX IF NOT EXISTS idx_chicken_counting_kandang_id ON chicken_counting(kandang_id);
|
||||
|
||||
-- Auto-update updated_at trigger
|
||||
CREATE TRIGGER update_chicken_counting_updated_at
|
||||
BEFORE UPDATE ON chicken_counting
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Insert API key for chicken counting endpoint
|
||||
-- Key: cpa_e8cfeeabaa6997a1ecd4239cee6a9cc59cf43f96cfd58463
|
||||
INSERT INTO api_keys (id, key_hash, name, description, is_active, rate_limit)
|
||||
VALUES (
|
||||
'apk_chicken_counting',
|
||||
'7a98758e7e6e4dea9f69a06b98d1f56f9f9838dabb2e56c23214676d17a34cad',
|
||||
'Chicken Counting Service',
|
||||
'API key for chicken counting program to submit count data',
|
||||
true,
|
||||
1000
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Migration: Add deplesi tracking columns
|
||||
-- Date: 2026-04-19
|
||||
-- Description: Adds panen and keterangan columns to mortality_records for comprehensive daily tracking
|
||||
|
||||
-- Add new columns (safe: idempotent, non-destructive)
|
||||
ALTER TABLE mortality_records
|
||||
ADD COLUMN IF NOT EXISTS panen INTEGER DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS keterangan TEXT;
|
||||
|
||||
-- Optional: Add comment for documentation
|
||||
COMMENT ON COLUMN mortality_records.panen IS 'Daily harvest count (chickens sold/removed)';
|
||||
COMMENT ON COLUMN mortality_records.keterangan IS 'Daily notes or remarks about significant events';
|
||||
@@ -0,0 +1,10 @@
|
||||
-- Migration: Add afkir column
|
||||
-- Date: 2026-04-19
|
||||
-- Description: Adds afkir (culled chickens) column to mortality_records
|
||||
|
||||
-- Add new column (safe: idempotent, non-destructive)
|
||||
ALTER TABLE mortality_records
|
||||
ADD COLUMN IF NOT EXISTS afkir INTEGER DEFAULT 0;
|
||||
|
||||
-- Optional: Add comment for documentation
|
||||
COMMENT ON COLUMN mortality_records.afkir IS 'Daily culled chickens count (removed due to poor quality)';
|
||||
@@ -0,0 +1,29 @@
|
||||
-- Migration: Add manual_feed_sack_entries table
|
||||
-- Description: Store manual feed sack count entries separate from IOT camera data
|
||||
-- Date: 2026-04-19
|
||||
|
||||
CREATE TABLE IF NOT EXISTS manual_feed_sack_entries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
cycle_id VARCHAR(255) NOT NULL,
|
||||
day INTEGER NOT NULL,
|
||||
date DATE NOT NULL,
|
||||
camera_name VARCHAR(255) NOT NULL,
|
||||
count INTEGER NOT NULL CHECK(count >= 0),
|
||||
created_by VARCHAR(255),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (cycle_id) REFERENCES cycles(id) ON DELETE CASCADE,
|
||||
UNIQUE(cycle_id, day, camera_name)
|
||||
);
|
||||
|
||||
-- Create indexes for faster lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_feed_sack_cycle ON manual_feed_sack_entries(cycle_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_feed_sack_day ON manual_feed_sack_entries(day);
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_feed_sack_camera ON manual_feed_sack_entries(camera_name);
|
||||
|
||||
-- Trigger for automatic updated_at timestamp
|
||||
DROP TRIGGER IF EXISTS update_manual_feed_sack_entries_updated_at ON manual_feed_sack_entries;
|
||||
CREATE TRIGGER update_manual_feed_sack_entries_updated_at
|
||||
BEFORE UPDATE ON manual_feed_sack_entries
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,32 @@
|
||||
-- Migration: Update manual_feed_sack_entries to use date-based keys instead of cycle-based
|
||||
-- Description: Remove cycle_id and day dependencies, use date + camera_name as unique key
|
||||
-- Date: 2026-04-19
|
||||
|
||||
-- Drop old unique constraint
|
||||
ALTER TABLE manual_feed_sack_entries DROP CONSTRAINT IF EXISTS manual_feed_sack_entries_cycle_id_day_camera_name_key;
|
||||
|
||||
-- Drop foreign key constraint
|
||||
ALTER TABLE manual_feed_sack_entries DROP CONSTRAINT IF EXISTS manual_feed_sack_entries_cycle_id_fkey;
|
||||
|
||||
-- Drop cycle_id and day columns
|
||||
ALTER TABLE manual_feed_sack_entries DROP COLUMN IF EXISTS cycle_id;
|
||||
ALTER TABLE manual_feed_sack_entries DROP COLUMN IF EXISTS day;
|
||||
|
||||
-- Add new unique constraint on date + camera_name (only if it doesn't exist)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'manual_feed_sack_entries_date_camera_key'
|
||||
) THEN
|
||||
ALTER TABLE manual_feed_sack_entries
|
||||
ADD CONSTRAINT manual_feed_sack_entries_date_camera_key UNIQUE (date, camera_name);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Drop old index on cycle_id
|
||||
DROP INDEX IF EXISTS idx_manual_feed_sack_cycle;
|
||||
DROP INDEX IF EXISTS idx_manual_feed_sack_day;
|
||||
|
||||
-- Add index on date for faster lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_manual_feed_sack_date ON manual_feed_sack_entries(date);
|
||||
@@ -0,0 +1,23 @@
|
||||
-- Migration: Add type field to feed_sack_column_config
|
||||
-- Description: Add 'type' field to distinguish inbound vs usage columns
|
||||
-- Date: 2026-05-08
|
||||
-- Author: Claude Code
|
||||
|
||||
-- Update existing configs to add type field
|
||||
-- This uses PostgreSQL's JSONB operators to modify each array element
|
||||
UPDATE feed_sack_column_config
|
||||
SET config = (
|
||||
SELECT jsonb_agg(
|
||||
CASE
|
||||
-- Infer type from cameraName for backward compatibility
|
||||
WHEN elem->>'cameraName' LIKE '%karung_masuk%'
|
||||
THEN elem || '{"type": "inbound"}'
|
||||
ELSE elem || '{"type": "usage"}'
|
||||
END
|
||||
)
|
||||
FROM jsonb_array_elements(config) AS elem
|
||||
)
|
||||
WHERE config IS NOT NULL AND config != '[]'::jsonb;
|
||||
|
||||
-- Note: This migration is safe because it adds a new field with defaults
|
||||
-- All existing column configs will automatically get 'type' based on camera name pattern
|
||||
@@ -0,0 +1,20 @@
|
||||
-- Migration: Add keterangan column to manual_feed_sack_entries
|
||||
-- Description: Support per-camera notes/remarks for feed sack counting
|
||||
-- Date: 2026-05-14
|
||||
|
||||
-- Add keterangan column (nullable for backward compatibility)
|
||||
ALTER TABLE manual_feed_sack_entries
|
||||
ADD COLUMN IF NOT EXISTS keterangan TEXT;
|
||||
|
||||
-- Add CHECK constraint for max 200 characters (only if not exists)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'keterangan_max_length'
|
||||
AND conrelid = 'manual_feed_sack_entries'::regclass
|
||||
) THEN
|
||||
ALTER TABLE manual_feed_sack_entries
|
||||
ADD CONSTRAINT keterangan_max_length CHECK (char_length(keterangan) <= 200);
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -0,0 +1,23 @@
|
||||
-- Migration: Add cycle_feed_initial_balance table
|
||||
-- Description: Store initial feed sack balance (saldo karung awal) per cycle
|
||||
-- Date: 2026-05-26
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cycle_feed_initial_balance (
|
||||
id SERIAL PRIMARY KEY,
|
||||
cycle_id VARCHAR(255) NOT NULL UNIQUE,
|
||||
saldo_awal INTEGER NOT NULL DEFAULT 0 CHECK(saldo_awal >= 0),
|
||||
created_by VARCHAR(255),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (cycle_id) REFERENCES cycles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Create index for faster lookups by cycle
|
||||
CREATE INDEX IF NOT EXISTS idx_cycle_feed_initial_balance_cycle ON cycle_feed_initial_balance(cycle_id);
|
||||
|
||||
-- Trigger for automatic updated_at timestamp
|
||||
DROP TRIGGER IF EXISTS update_cycle_feed_initial_balance_updated_at ON cycle_feed_initial_balance;
|
||||
CREATE TRIGGER update_cycle_feed_initial_balance_updated_at
|
||||
BEFORE UPDATE ON cycle_feed_initial_balance
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,19 @@
|
||||
-- Create AI Insights table for caching AI-generated insights
|
||||
CREATE TABLE IF NOT EXISTS ai_insights (
|
||||
id SERIAL PRIMARY KEY,
|
||||
cycle_id VARCHAR(255) NOT NULL,
|
||||
kandang_id VARCHAR(255), -- NULL means "all kandangs"
|
||||
insight_text TEXT NOT NULL,
|
||||
version VARCHAR(50) NOT NULL DEFAULT 'v1',
|
||||
generated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create indexes for faster lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_insights_cycle_kandang ON ai_insights (cycle_id, kandang_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_insights_expires_at ON ai_insights (expires_at);
|
||||
|
||||
-- Add comment to table
|
||||
COMMENT ON TABLE ai_insights IS 'Stores AI-generated insights with 6-hour TTL for cross-device caching';
|
||||
@@ -0,0 +1,18 @@
|
||||
-- Migration 012: Add cage_uuid to kandangs table
|
||||
-- Add cage_uuid field for external scales API integration
|
||||
|
||||
-- Add cage_uuid column (nullable, unique)
|
||||
ALTER TABLE kandangs
|
||||
ADD COLUMN IF NOT EXISTS cage_uuid VARCHAR(36) UNIQUE;
|
||||
|
||||
-- Add comment explaining the field
|
||||
COMMENT ON COLUMN kandangs.cage_uuid IS 'External cage UUID for scales API (dashboard.cpsp.id/api/scales/cage/)';
|
||||
|
||||
-- Seed initial data for existing kandangs
|
||||
-- Kandang Atas gets the existing hardcoded UUID
|
||||
UPDATE kandangs
|
||||
SET cage_uuid = '9f556d2a-af5c-41b0-bcd0-6cea0dfa0fed'
|
||||
WHERE LOWER(name) LIKE '%atas%';
|
||||
|
||||
-- Kandang Bawah and others remain null (to be configured via settings UI)
|
||||
-- This is intentional - admins will configure via IoT Panel settings
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Migration: Add harvest weight tracking to mortality records
|
||||
-- Date: 2026-06-19
|
||||
-- Description: Adds berat_panen to mortality_records so harvest weight persists across reloads
|
||||
|
||||
ALTER TABLE mortality_records
|
||||
ADD COLUMN IF NOT EXISTS berat_panen NUMERIC(10,2) DEFAULT 0;
|
||||
|
||||
COMMENT ON COLUMN mortality_records.berat_panen IS 'Daily harvest weight in kilograms';
|
||||
Reference in new issue
Block a user