mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-25 07:45:44 +00:00
Merge branch 'development-after-sso' into 'development'
[FEAT/BE] merge feat recording, refactor chickin and implement auth middleware See merge request mbugroup/lti-api!54
This commit is contained in:
+1
-1
@@ -16,7 +16,7 @@ docker-compose.yaml
|
|||||||
Dockerfile.local
|
Dockerfile.local
|
||||||
# Go build cache
|
# Go build cache
|
||||||
.gocache/
|
.gocache/
|
||||||
vendor/
|
vendor
|
||||||
|
|
||||||
# Logs & reports
|
# Logs & reports
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
# ===============================
|
|
||||||
# LTI-API Makefile (Docker Setup)
|
|
||||||
# ===============================
|
|
||||||
|
|
||||||
APP_NAME := lti-api
|
|
||||||
COMPOSE := docker compose -f docker-compose.yaml
|
|
||||||
NETWORK := lti-network
|
|
||||||
ENV_FILE := .env.lti-api
|
|
||||||
|
|
||||||
include $(ENV_FILE)
|
|
||||||
export $(shell sed 's/=.*//' $(ENV_FILE))
|
|
||||||
|
|
||||||
MIGRATIONS_DIR := ./migrations
|
|
||||||
MIGRATE_IMAGE := migrate/migrate:v4.15.2
|
|
||||||
DB_URL := postgres://$(DB_USER):$(DB_PASSWORD)@lti-postgres:5432/$(DB_NAME)?sslmode=disable
|
|
||||||
|
|
||||||
# --- Docker ---
|
|
||||||
docker-local:
|
|
||||||
@echo "🚀 Starting $(APP_NAME) with local PostgreSQL & Redis..."
|
|
||||||
@$(COMPOSE) up --build -d
|
|
||||||
|
|
||||||
docker-down:
|
|
||||||
@$(COMPOSE) down --remove-orphans
|
|
||||||
|
|
||||||
docker-nuke:
|
|
||||||
@echo "💣 Removing all containers, images, and volumes..."
|
|
||||||
@$(COMPOSE) down --rmi all --volumes --remove-orphans
|
|
||||||
|
|
||||||
# --- Database / Migration ---
|
|
||||||
|
|
||||||
wait-db:
|
|
||||||
@echo "⏳ Waiting for database lti-postgres to be ready (inside Docker network)..."
|
|
||||||
@$(COMPOSE) run --rm app sh -c 'until nc -z lti-postgres 5432; do echo "Waiting for DB..."; sleep 2; done; echo "✅ Database is ready!"'
|
|
||||||
|
|
||||||
migrate-up: wait-db
|
|
||||||
@echo "⬆️ Running migrations..."
|
|
||||||
@docker run --rm -v $(MIGRATIONS_DIR):/migrations --network $(NETWORK) \
|
|
||||||
$(MIGRATE_IMAGE) -path=/migrations/ -database "$(DB_URL)" up
|
|
||||||
|
|
||||||
migrate-down: wait-db
|
|
||||||
@echo "⬇️ Rolling back all migrations..."
|
|
||||||
@docker run --rm -v $(MIGRATIONS_DIR):/migrations --network $(NETWORK) \
|
|
||||||
$(MIGRATE_IMAGE) -path=/migrations/ -database "$(DB_URL)" down -all
|
|
||||||
|
|
||||||
seed:
|
|
||||||
@echo "🌱 Running seed script..."
|
|
||||||
@$(COMPOSE) run --rm app go run cmd/seed/main.go
|
|
||||||
|
|
||||||
psql:
|
|
||||||
@docker exec -it lti-postgres psql -U $(DB_USER) -d $(DB_NAME)
|
|
||||||
|
|
||||||
logs:
|
|
||||||
@$(COMPOSE) logs -f app
|
|
||||||
|
|
||||||
restart:
|
|
||||||
@$(COMPOSE) restart
|
|
||||||
|
|
||||||
status:
|
|
||||||
@$(COMPOSE) ps
|
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
# --- Load .env kalau ada, dan export ke shell child ---
|
||||||
|
ifneq (,$(wildcard .env))
|
||||||
|
include .env
|
||||||
|
export
|
||||||
|
endif
|
||||||
|
|
||||||
|
# --- Konfigurasi umum ---
|
||||||
|
COMPOSE ?= docker compose -f docker-compose.local.yml
|
||||||
|
NETWORK ?= lti-api_go-network
|
||||||
|
MIGRATE_IMAGE ?= migrate/migrate
|
||||||
|
MIGRATIONS_DIR := $(PWD)/internal/database/migrations
|
||||||
|
|
||||||
|
# Fallback agar tetap jalan meski .env kosong
|
||||||
|
DB_HOST ?= postgresdb
|
||||||
|
DB_PORT ?= 5432
|
||||||
|
DB_USER ?= postgres
|
||||||
|
DB_PASSWORD ?= postgres
|
||||||
|
DB_NAME ?= db_lti_erp
|
||||||
|
|
||||||
|
DB_URL := postgres://$(DB_USER):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable
|
||||||
|
|
||||||
|
# Tunggu DB ready memakai pg_isready dari image postgres
|
||||||
|
WAIT_DB := docker run --rm --network $(NETWORK) postgres:alpine \
|
||||||
|
sh -c 'until pg_isready -h $(DB_HOST) -p $(DB_PORT) -U $(DB_USER) -d $(DB_NAME); do echo "waiting for postgres..."; sleep 1; done'
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
.DEFAULT_GOAL := start
|
||||||
|
|
||||||
|
# --- Daftar phony targets ---
|
||||||
|
.PHONY: start build test lint gen \
|
||||||
|
db-up wait-db \
|
||||||
|
migration-% migrate-up migrate-down migrate-fresh \
|
||||||
|
seed \
|
||||||
|
docker-local docker-down docker-nuke docker-cache psql
|
||||||
|
|
||||||
|
# --- Go workflow ---
|
||||||
|
start:
|
||||||
|
@go run cmd/api/main.go
|
||||||
|
|
||||||
|
build:
|
||||||
|
@go build -o tmp/app ./cmd/api
|
||||||
|
|
||||||
|
test:
|
||||||
|
@go test ./test/...
|
||||||
|
|
||||||
|
lint:
|
||||||
|
@golangci-lint run
|
||||||
|
|
||||||
|
# --- Compose / DB helpers ---
|
||||||
|
db-up:
|
||||||
|
@$(COMPOSE) up -d postgresdb
|
||||||
|
|
||||||
|
wait-db:
|
||||||
|
@$(WAIT_DB)
|
||||||
|
|
||||||
|
# --- Migration (pembuatan file) ---
|
||||||
|
# Contoh: make migration-create_users_table
|
||||||
|
# ":" akan diubah ke "_" (biar aman untuk nama file)
|
||||||
|
migration-%:
|
||||||
|
@migrate create -ext sql -dir $(MIGRATIONS_DIR) $(subst :,_,$*)
|
||||||
|
|
||||||
|
# --- Migration (apply via docker image 'migrate') ---
|
||||||
|
migrate-up: db-up wait-db
|
||||||
|
@docker run --rm -v $(MIGRATIONS_DIR):/migrations --network $(NETWORK) \
|
||||||
|
$(MIGRATE_IMAGE) -path=/migrations/ -database "$(DB_URL)" up
|
||||||
|
|
||||||
|
# Contoh:
|
||||||
|
# make migrate-down step=2 → rollback 2 step
|
||||||
|
# make migrate-down → rollback semua
|
||||||
|
|
||||||
|
migrate-down: db-up wait-db
|
||||||
|
@if [ -n "$(step)" ]; then \
|
||||||
|
echo "⬇️ Migrating down $(step) step(s)..."; \
|
||||||
|
docker run --rm -v $(MIGRATIONS_DIR):/migrations --network $(NETWORK) \
|
||||||
|
$(MIGRATE_IMAGE) -path=/migrations/ -database "$(DB_URL)" down $(step); \
|
||||||
|
else \
|
||||||
|
echo "⬇️ Migrating down ALL steps..."; \
|
||||||
|
docker run --rm -v $(MIGRATIONS_DIR):/migrations --network $(NETWORK) \
|
||||||
|
$(MIGRATE_IMAGE) -path=/migrations/ -database "$(DB_URL)" down -all; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
migrate-fresh: migrate-down migrate-up
|
||||||
|
@true
|
||||||
|
|
||||||
|
# Pakai: make migrate-force v=20250917120000
|
||||||
|
migrate-force:
|
||||||
|
@docker run --rm -v $(MIGRATIONS_DIR):/migrations --network $(NETWORK) \
|
||||||
|
$(MIGRATE_IMAGE) -path=/migrations/ -database "$(DB_URL)" force $(v)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Seeder ---
|
||||||
|
seed: db-up wait-db
|
||||||
|
@$(COMPOSE) run --rm app go run cmd/seed/main.go
|
||||||
|
|
||||||
|
# --- Docker orchestration convenience ---
|
||||||
|
docker-local:
|
||||||
|
@$(COMPOSE) up --build -d
|
||||||
|
|
||||||
|
docker-down:
|
||||||
|
@$(COMPOSE) down --remove-orphans
|
||||||
|
|
||||||
|
# ⚠️ Akan menghapus container, images dan volumes.
|
||||||
|
docker-nuke:
|
||||||
|
@$(COMPOSE) down --rmi all --volumes --remove-orphans
|
||||||
|
|
||||||
|
docker-cache:
|
||||||
|
@docker builder prune -f
|
||||||
|
|
||||||
|
# --- PSQL shell ke DB di container ---
|
||||||
|
psql: db-up
|
||||||
|
@$(COMPOSE) exec -it postgresdb psql -U $(DB_USER) -d $(DB_NAME)
|
||||||
|
|
||||||
|
# Single feature
|
||||||
|
# example: make gen feat=product-category
|
||||||
|
|
||||||
|
# Sub feature
|
||||||
|
# make gen feat=master/area
|
||||||
|
gen:
|
||||||
|
@go run tools/gen.go $(feat)
|
||||||
|
# @goimports -w internal
|
||||||
+27
-2
@@ -62,9 +62,34 @@ func setupRedis() *redis.Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setupSSO(ctx context.Context, rdb *redis.Client) {
|
func setupSSO(ctx context.Context, rdb *redis.Client) {
|
||||||
if err := sso.Init(ctx, config.SSOJWKSURL, config.SSOIssuer, config.SSOAllowedAudiences); err != nil {
|
const (
|
||||||
utils.Log.Fatalf("SSO initialization failed: %v", err)
|
maxAttempts = 12
|
||||||
|
retryDelay = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var lastErr error
|
||||||
|
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||||
|
if err := sso.Init(ctx, config.SSOJWKSURL, config.SSOIssuer, config.SSOAllowedAudiences); err != nil {
|
||||||
|
lastErr = err
|
||||||
|
utils.Log.WithError(err).Warnf("SSO initialization attempt %d/%d failed", attempt, maxAttempts)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
utils.Log.Fatalf("SSO initialization aborted: %v", ctx.Err())
|
||||||
|
case <-time.After(retryDelay):
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lastErr = nil
|
||||||
|
if attempt > 1 {
|
||||||
|
utils.Log.Infof("SSO initialization succeeded after %d attempts", attempt)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if lastErr != nil {
|
||||||
|
utils.Log.Fatalf("SSO initialization failed: %v", lastErr)
|
||||||
|
}
|
||||||
|
|
||||||
if rdb != nil {
|
if rdb != nil {
|
||||||
session.SetRevocationStore(session.NewRevocationStore(rdb, config.SSOTokenBlacklistPrefix))
|
session.SetRevocationStore(session.NewRevocationStore(rdb, config.SSOTokenBlacklistPrefix))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ services:
|
|||||||
working_dir: /lti-api
|
working_dir: /lti-api
|
||||||
volumes:
|
volumes:
|
||||||
- .:/lti-api
|
- .:/lti-api
|
||||||
- ./internal/config/jwtRS256.key:/run/keys/jwtRS256.key
|
|
||||||
- ./internal/config/jwtRS256.key.pub:/run/keys/jwtRS256.key.pub
|
|
||||||
command: air -c .air.toml
|
command: air -c .air.toml
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package capabilities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
recordings "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FromPermissions returns a filtered map of capabilities that the frontend can use
|
||||||
|
// to toggle features. Only permissions recognized by the application are exposed.
|
||||||
|
func FromPermissions(perms []string) map[string]bool {
|
||||||
|
if len(perms) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make(map[string]bool)
|
||||||
|
for _, perm := range perms {
|
||||||
|
if key, ok := normalizeAndAllow(perm); ok {
|
||||||
|
out[key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(out) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeAndAllow(perm string) (string, bool) {
|
||||||
|
perm = strings.ToLower(strings.TrimSpace(perm))
|
||||||
|
if perm == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if _, ok := allowed[perm]; !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return perm, true
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowed = map[string]struct{}{
|
||||||
|
recordings.PermissionRecordingRead: {},
|
||||||
|
recordings.PermissionRecordingCreate: {},
|
||||||
|
recordings.PermissionRecordingUpdate: {},
|
||||||
|
recordings.PermissionRecordingDelete: {},
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -32,3 +33,21 @@ func ExistsByName[T any](ctx context.Context, db *gorm.DB, name string, excludeI
|
|||||||
}
|
}
|
||||||
return count > 0, nil
|
return count > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExistsByField[T any](ctx context.Context, db *gorm.DB, field string, value any, excludeID *uint) (bool, error) {
|
||||||
|
if field == "" {
|
||||||
|
return false, fmt.Errorf("field is required")
|
||||||
|
}
|
||||||
|
var count int64
|
||||||
|
q := db.WithContext(ctx).
|
||||||
|
Model(new(T)).
|
||||||
|
Where(fmt.Sprintf("%s = ?", field), value).
|
||||||
|
Where("deleted_at IS NULL")
|
||||||
|
if excludeID != nil {
|
||||||
|
q = q.Where("id <> ?", *excludeID)
|
||||||
|
}
|
||||||
|
if err := q.Count(&count).Error; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SSOClientConfig struct {
|
type SSOClientConfig struct {
|
||||||
PublicID string `json:"public_id"`
|
PublicID string `json:"public_id"`
|
||||||
RedirectURI string `json:"redirect_uri"`
|
RedirectURI string `json:"redirect_uri"`
|
||||||
Scope string `json:"scope"`
|
Scope string `json:"scope"`
|
||||||
// Prompt string `json:"prompt"`
|
// Prompt string `json:"prompt"`
|
||||||
DefaultReturnURI string `json:"default_return_uri"`
|
DefaultReturnURI string `json:"default_return_uri"`
|
||||||
AllowedReturnOrigins []string `json:"allowed_return_origins"`
|
AllowedReturnOrigins []string `json:"allowed_return_origins"`
|
||||||
@@ -32,6 +32,10 @@ var (
|
|||||||
DBPassword string
|
DBPassword string
|
||||||
DBName string
|
DBName string
|
||||||
DBPort int
|
DBPort int
|
||||||
|
DBSSLMode string
|
||||||
|
DBSSLRootCert string
|
||||||
|
DBSSLCert string
|
||||||
|
DBSSLKey string
|
||||||
JWTSecret string
|
JWTSecret string
|
||||||
JWTAccessExp int
|
JWTAccessExp int
|
||||||
JWTRefreshExp int
|
JWTRefreshExp int
|
||||||
@@ -79,6 +83,10 @@ func init() {
|
|||||||
DBPassword = viper.GetString("DB_PASSWORD")
|
DBPassword = viper.GetString("DB_PASSWORD")
|
||||||
DBName = viper.GetString("DB_NAME")
|
DBName = viper.GetString("DB_NAME")
|
||||||
DBPort = viper.GetInt("DB_PORT")
|
DBPort = viper.GetInt("DB_PORT")
|
||||||
|
DBSSLMode = defaultString(viper.GetString("DB_SSLMODE"), "disable")
|
||||||
|
DBSSLRootCert = strings.TrimSpace(viper.GetString("DB_SSLROOTCERT"))
|
||||||
|
DBSSLCert = strings.TrimSpace(viper.GetString("DB_SSLCERT"))
|
||||||
|
DBSSLKey = strings.TrimSpace(viper.GetString("DB_SSLKEY"))
|
||||||
|
|
||||||
// jwt configuration
|
// jwt configuration
|
||||||
JWTSecret = viper.GetString("JWT_SECRET")
|
JWTSecret = viper.GetString("JWT_SECRET")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||||
@@ -13,10 +14,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Connect(dbHost, dbName string) *gorm.DB {
|
func Connect(dbHost, dbName string) *gorm.DB {
|
||||||
dsn := fmt.Sprintf(
|
parts := []string{
|
||||||
"host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai",
|
fmt.Sprintf("host=%s", dbHost),
|
||||||
dbHost, config.DBUser, config.DBPassword, dbName, config.DBPort,
|
fmt.Sprintf("user=%s", config.DBUser),
|
||||||
)
|
fmt.Sprintf("password=%s", config.DBPassword),
|
||||||
|
fmt.Sprintf("dbname=%s", dbName),
|
||||||
|
fmt.Sprintf("port=%d", config.DBPort),
|
||||||
|
fmt.Sprintf("sslmode=%s", config.DBSSLMode),
|
||||||
|
"TimeZone=Asia/Shanghai",
|
||||||
|
}
|
||||||
|
if config.DBSSLRootCert != "" {
|
||||||
|
parts = append(parts, fmt.Sprintf("sslrootcert=%s", config.DBSSLRootCert))
|
||||||
|
}
|
||||||
|
if config.DBSSLCert != "" {
|
||||||
|
parts = append(parts, fmt.Sprintf("sslcert=%s", config.DBSSLCert))
|
||||||
|
}
|
||||||
|
if config.DBSSLKey != "" {
|
||||||
|
parts = append(parts, fmt.Sprintf("sslkey=%s", config.DBSSLKey))
|
||||||
|
}
|
||||||
|
dsn := strings.Join(parts, " ")
|
||||||
|
|
||||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||||
Logger: logger.Default.LogMode(logger.Info),
|
Logger: logger.Default.LogMode(logger.Info),
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
--? Child Indexes(optional, biar rapi tapi klo gada juga ilang pas di drop)
|
||||||
|
DROP INDEX IF EXISTS idx_recording_stocks_product;
|
||||||
|
DROP INDEX IF EXISTS idx_recording_stocks_recording;
|
||||||
|
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_recording_depl_recording;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_recording_bws_recording;
|
||||||
|
|
||||||
|
--? Child Tables
|
||||||
|
DROP TABLE IF EXISTS recording_stocks;
|
||||||
|
DROP TABLE IF EXISTS recording_depletions;
|
||||||
|
DROP TABLE IF EXISTS recording_bws;
|
||||||
|
|
||||||
|
--? Parent Indexes ON recordings
|
||||||
|
DROP INDEX IF EXISTS uq_recordings_flock_record_date;
|
||||||
|
DROP INDEX IF EXISTS idx_recordings_flock_datetime;
|
||||||
|
|
||||||
|
--? Parent table
|
||||||
|
DROP TABLE IF EXISTS recordings;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
--? RECORDINGS (tabel induk recording harian)
|
||||||
|
CREATE TABLE IF NOT EXISTS recordings (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
project_flock_id BIGINT NOT NULL,
|
||||||
|
record_datetime TIMESTAMPTZ NOT NULL,
|
||||||
|
record_date DATE,
|
||||||
|
status INT NOT NULL DEFAULT 0, --? 0=draft,1=submitted,2=approved,3=rejected
|
||||||
|
ontime INT NOT NULL DEFAULT 0, --? 1=ontime,0=late (pakai INT/BOOLEAN sesuai preferensi)
|
||||||
|
day INT,
|
||||||
|
total_depletion INT,
|
||||||
|
cum_depletion_rate NUMERIC(7,3),
|
||||||
|
daily_gain NUMERIC(7,3),
|
||||||
|
avg_daily_gain NUMERIC(7,3),
|
||||||
|
cum_intake INT,
|
||||||
|
fcr_value NUMERIC(7,3),
|
||||||
|
total_chick BIGINT,
|
||||||
|
daily_depletion_rate NUMERIC(7,3),
|
||||||
|
cum_depletion INT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
created_by BIGINT,
|
||||||
|
|
||||||
|
CONSTRAINT fk_recordings_project_flock
|
||||||
|
FOREIGN KEY (project_flock_id) REFERENCES project_flock_kandangs(id),
|
||||||
|
|
||||||
|
CONSTRAINT fk_recordings_created_by
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users(id),
|
||||||
|
|
||||||
|
|
||||||
|
CONSTRAINT chk_recordings_status
|
||||||
|
CHECK (status IN (0,1,2,3)),
|
||||||
|
|
||||||
|
CONSTRAINT chk_recordings_ontime
|
||||||
|
CHECK (ontime IN (0,1)),
|
||||||
|
|
||||||
|
CONSTRAINT chk_recordings_day
|
||||||
|
CHECK (day IS NULL OR day >= 1),
|
||||||
|
|
||||||
|
CONSTRAINT chk_recordings_nonnegatives
|
||||||
|
CHECK (
|
||||||
|
(total_depletion IS NULL OR total_depletion >= 0) AND
|
||||||
|
(cum_depletion IS NULL OR cum_depletion >= 0) AND
|
||||||
|
(total_chick IS NULL OR total_chick >= 0) AND
|
||||||
|
(cum_intake IS NULL OR cum_intake >= 0) AND
|
||||||
|
(daily_gain IS NULL OR daily_gain >= 0) AND
|
||||||
|
(avg_daily_gain IS NULL OR avg_daily_gain >= 0) AND
|
||||||
|
(fcr_value IS NULL OR fcr_value > 0) AND
|
||||||
|
(daily_depletion_rate IS NULL OR daily_depletion_rate >= 0) AND
|
||||||
|
(cum_depletion_rate IS NULL OR cum_depletion_rate >= 0)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
--? Set record_date otomatis berdasarkan record_datetime (pakai zona Asia/Jakarta)
|
||||||
|
CREATE OR REPLACE FUNCTION trg_set_record_date() RETURNS trigger AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.record_date := (NEW.record_datetime AT TIME ZONE 'Asia/Jakarta')::date;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS recordings_set_record_date_trg ON recordings;
|
||||||
|
CREATE TRIGGER recordings_set_record_date_trg
|
||||||
|
BEFORE INSERT OR UPDATE OF record_datetime ON recordings
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION trg_set_record_date();
|
||||||
|
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_recordings_flock_datetime
|
||||||
|
ON recordings (project_flock_id, record_datetime);
|
||||||
|
|
||||||
|
--? Unique harian (1 recording per hari dan per flock)
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_recordings_flock_record_date
|
||||||
|
ON recordings (project_flock_id, record_date)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
--? RECORDING_BWS (BW per recording)
|
||||||
|
CREATE TABLE IF NOT EXISTS recording_bws (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
recording_id BIGINT NOT NULL,
|
||||||
|
weight NUMERIC(8,2) NOT NULL, --? bobot per ekor/kelompok
|
||||||
|
qty INT NOT NULL DEFAULT 1, --? jumlah ekor pada bobot ini
|
||||||
|
notes VARCHAR,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
|
||||||
|
CONSTRAINT fk_recording_bws_recording
|
||||||
|
FOREIGN KEY (recording_id) REFERENCES recordings(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
CONSTRAINT chk_recording_bws_nonneg
|
||||||
|
CHECK (weight >= 0 AND qty >= 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_recording_bws_recording
|
||||||
|
ON recording_bws (recording_id);
|
||||||
|
|
||||||
|
--? RECORDING_DEPLETIONS
|
||||||
|
CREATE TABLE IF NOT EXISTS recording_depletions (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
recording_id BIGINT NOT NULL,
|
||||||
|
product_warehouse_id BIGINT NOT NULL,
|
||||||
|
total BIGINT NOT NULL,
|
||||||
|
notes VARCHAR,
|
||||||
|
|
||||||
|
CONSTRAINT fk_recording_depl_recording
|
||||||
|
FOREIGN KEY (recording_id) REFERENCES recordings(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
CONSTRAINT fk_recording_depl_prodwh
|
||||||
|
FOREIGN KEY (product_warehouse_id) REFERENCES product_warehouses(id),
|
||||||
|
|
||||||
|
CONSTRAINT chk_recording_depl_total
|
||||||
|
CHECK (total >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_recording_depl_recording
|
||||||
|
ON recording_depletions (recording_id);
|
||||||
|
|
||||||
|
--? RECORDING_STOCKS
|
||||||
|
CREATE TABLE IF NOT EXISTS recording_stocks (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
recording_id BIGINT NOT NULL,
|
||||||
|
product_warehouse_id BIGINT NOT NULL,
|
||||||
|
increase NUMERIC(10,3), --? penambahan (boleh NULL)
|
||||||
|
decrease NUMERIC(10,3), --? pengurangan (boleh NULL)
|
||||||
|
usage_amount BIGINT, --? pemakaian (opsional, jika konsep dipisah dari decrease)
|
||||||
|
notes VARCHAR,
|
||||||
|
|
||||||
|
CONSTRAINT fk_recording_stocks_recording
|
||||||
|
FOREIGN KEY (recording_id) REFERENCES recordings(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
CONSTRAINT fk_recording_stocks_prodwh
|
||||||
|
FOREIGN KEY (product_warehouse_id) REFERENCES product_warehouses(id),
|
||||||
|
|
||||||
|
CONSTRAINT chk_recording_stocks_nonneg
|
||||||
|
CHECK (
|
||||||
|
(increase IS NULL OR increase >= 0) AND
|
||||||
|
(decrease IS NULL OR decrease >= 0) AND
|
||||||
|
(usage_amount IS NULL OR usage_amount >= 0)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_recording_stocks_recording
|
||||||
|
ON recording_stocks (recording_id);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_recording_stocks_product
|
||||||
|
ON recording_stocks (product_warehouse_id);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
ALTER TABLE kandangs
|
||||||
|
DROP CONSTRAINT IF EXISTS kandangs_project_flock_id_fkey;
|
||||||
|
|
||||||
|
ALTER TABLE kandangs
|
||||||
|
DROP COLUMN IF EXISTS project_flock_id;
|
||||||
|
|
||||||
|
ALTER TABLE project_chickins
|
||||||
|
DROP CONSTRAINT fk_project_flock_kandang_id,
|
||||||
|
ADD CONSTRAINT fk_project_flock_kandang_id
|
||||||
|
FOREIGN KEY (project_flock_kandang_id)
|
||||||
|
REFERENCES project_flock_kandangs(id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE project_flock_populations
|
||||||
|
DROP CONSTRAINT fk_project_flock_kandang_id,
|
||||||
|
ADD CONSTRAINT fk_project_flock_kandang_id
|
||||||
|
FOREIGN KEY (project_flock_kandang_id)
|
||||||
|
REFERENCES project_flock_kandangs(id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS project_flocks_base_period_unique;
|
||||||
|
|
||||||
|
ALTER TABLE project_flocks
|
||||||
|
ADD COLUMN IF NOT EXISTS flock_id BIGINT;
|
||||||
|
|
||||||
|
WITH normalized AS (
|
||||||
|
SELECT
|
||||||
|
pf.id,
|
||||||
|
COALESCE(
|
||||||
|
NULLIF(TRIM(regexp_replace(pf.flock_name, '\\s+\\d+(\\s+\\d+)*$', '', 'g')), ''),
|
||||||
|
CONCAT('Project Flock ', pf.id)
|
||||||
|
) AS normalized_name,
|
||||||
|
COALESCE(NULLIF(pf.created_by, 0), 1) AS created_by
|
||||||
|
FROM project_flocks pf
|
||||||
|
),
|
||||||
|
seed_flocks AS (
|
||||||
|
SELECT DISTINCT
|
||||||
|
n.normalized_name,
|
||||||
|
MIN(n.created_by) AS created_by
|
||||||
|
FROM normalized n
|
||||||
|
GROUP BY n.normalized_name
|
||||||
|
)
|
||||||
|
INSERT INTO flocks (name, created_by, created_at, updated_at)
|
||||||
|
SELECT sf.normalized_name, sf.created_by, NOW(), NOW()
|
||||||
|
FROM seed_flocks sf
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
WITH normalized AS (
|
||||||
|
SELECT
|
||||||
|
pf.id,
|
||||||
|
COALESCE(
|
||||||
|
NULLIF(TRIM(regexp_replace(pf.flock_name, '\\s+\\d+(\\s+\\d+)*$', '', 'g')), ''),
|
||||||
|
CONCAT('Project Flock ', pf.id)
|
||||||
|
) AS normalized_name
|
||||||
|
FROM project_flocks pf
|
||||||
|
),
|
||||||
|
resolved AS (
|
||||||
|
SELECT
|
||||||
|
n.id,
|
||||||
|
f.id AS flock_id
|
||||||
|
FROM normalized n
|
||||||
|
JOIN flocks f ON LOWER(f.name) = LOWER(n.normalized_name)
|
||||||
|
)
|
||||||
|
UPDATE project_flocks pf
|
||||||
|
SET flock_id = resolved.flock_id
|
||||||
|
FROM resolved
|
||||||
|
WHERE pf.id = resolved.id;
|
||||||
|
|
||||||
|
WITH missing AS (
|
||||||
|
SELECT
|
||||||
|
pf.id,
|
||||||
|
COALESCE(
|
||||||
|
NULLIF(TRIM(regexp_replace(pf.flock_name, '\\s+\\d+(\\s+\\d+)*$', '', 'g')), ''),
|
||||||
|
CONCAT('Project Flock ', pf.id)
|
||||||
|
) AS normalized_name,
|
||||||
|
COALESCE(NULLIF(pf.created_by, 0), 1) AS created_by
|
||||||
|
FROM project_flocks pf
|
||||||
|
WHERE pf.flock_id IS NULL
|
||||||
|
),
|
||||||
|
seed_missing AS (
|
||||||
|
SELECT DISTINCT normalized_name, created_by FROM missing
|
||||||
|
)
|
||||||
|
INSERT INTO flocks (name, created_by, created_at, updated_at)
|
||||||
|
SELECT sm.normalized_name, sm.created_by, NOW(), NOW()
|
||||||
|
FROM seed_missing sm
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
WITH missing AS (
|
||||||
|
SELECT
|
||||||
|
pf.id,
|
||||||
|
COALESCE(
|
||||||
|
NULLIF(TRIM(regexp_replace(pf.flock_name, '\\s+\\d+(\\s+\\d+)*$', '', 'g')), ''),
|
||||||
|
CONCAT('Project Flock ', pf.id)
|
||||||
|
) AS normalized_name
|
||||||
|
FROM project_flocks pf
|
||||||
|
WHERE pf.flock_id IS NULL
|
||||||
|
)
|
||||||
|
UPDATE project_flocks pf
|
||||||
|
SET flock_id = f.id
|
||||||
|
FROM missing m
|
||||||
|
JOIN flocks f ON LOWER(f.name) = LOWER(m.normalized_name)
|
||||||
|
WHERE pf.id = m.id;
|
||||||
|
|
||||||
|
ALTER TABLE project_flocks
|
||||||
|
ALTER COLUMN flock_id SET NOT NULL;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS project_flocks_flock_name_unique;
|
||||||
|
|
||||||
|
ALTER TABLE project_flocks
|
||||||
|
DROP COLUMN IF EXISTS flock_name;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS project_flocks_flock_period_unique
|
||||||
|
ON project_flocks (flock_id, period)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE project_flocks
|
||||||
|
ADD COLUMN IF NOT EXISTS flock_name VARCHAR(255);
|
||||||
|
|
||||||
|
WITH generated_names AS (
|
||||||
|
SELECT
|
||||||
|
pf.id,
|
||||||
|
COALESCE(f.name, CONCAT('Project Flock ', pf.id)) AS base_name,
|
||||||
|
pf.period,
|
||||||
|
ROW_NUMBER() OVER (PARTITION BY COALESCE(f.name, CONCAT('Project Flock ', pf.id)) ORDER BY pf.id) AS rn
|
||||||
|
FROM project_flocks pf
|
||||||
|
LEFT JOIN flocks f ON f.id = pf.flock_id
|
||||||
|
)
|
||||||
|
UPDATE project_flocks pf
|
||||||
|
SET flock_name = CASE
|
||||||
|
WHEN gn.period IS NOT NULL THEN
|
||||||
|
CASE
|
||||||
|
WHEN gn.rn = 1 THEN CONCAT(gn.base_name, ' ', gn.period)
|
||||||
|
ELSE CONCAT(gn.base_name, ' ', gn.period, ' ', gn.rn)
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
CASE
|
||||||
|
WHEN gn.rn = 1 THEN gn.base_name
|
||||||
|
ELSE CONCAT(gn.base_name, ' ', gn.rn)
|
||||||
|
END
|
||||||
|
END
|
||||||
|
FROM generated_names gn
|
||||||
|
WHERE pf.id = gn.id
|
||||||
|
AND (pf.flock_name IS NULL OR pf.flock_name = '');
|
||||||
|
|
||||||
|
UPDATE project_flocks
|
||||||
|
SET flock_name = CONCAT('Project Flock ', id)
|
||||||
|
WHERE flock_name IS NULL OR flock_name = '';
|
||||||
|
|
||||||
|
ALTER TABLE project_flocks
|
||||||
|
ALTER COLUMN flock_name SET NOT NULL;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS project_flocks_flock_name_unique
|
||||||
|
ON project_flocks (flock_name)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS project_flocks_flock_period_unique;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS project_flocks_base_period_unique
|
||||||
|
ON project_flocks (
|
||||||
|
LOWER(TRIM(regexp_replace(flock_name, '\\s+\\d+(\\s+\\d+)*$', '', 'g'))),
|
||||||
|
period
|
||||||
|
)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
ALTER TABLE project_flocks
|
||||||
|
DROP COLUMN IF EXISTS flock_id;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Drop newly introduced egg tables
|
||||||
|
DROP TABLE IF EXISTS grading_eggs;
|
||||||
|
DROP TABLE IF EXISTS recording_eggs;
|
||||||
|
|
||||||
|
-- Revert recording_stocks structure
|
||||||
|
ALTER TABLE recording_stocks
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_recording_stocks_nonneg;
|
||||||
|
|
||||||
|
ALTER TABLE recording_stocks
|
||||||
|
DROP COLUMN IF EXISTS usage_qty,
|
||||||
|
DROP COLUMN IF EXISTS pending_qty;
|
||||||
|
|
||||||
|
ALTER TABLE recording_stocks
|
||||||
|
ADD COLUMN increase NUMERIC(10,3),
|
||||||
|
ADD COLUMN decrease NUMERIC(10,3),
|
||||||
|
ADD COLUMN usage_amount BIGINT,
|
||||||
|
ADD COLUMN notes VARCHAR;
|
||||||
|
|
||||||
|
ALTER TABLE recording_stocks
|
||||||
|
ADD CONSTRAINT chk_recording_stocks_nonneg CHECK (
|
||||||
|
(increase IS NULL OR increase >= 0) AND
|
||||||
|
(decrease IS NULL OR decrease >= 0) AND
|
||||||
|
(usage_amount IS NULL OR usage_amount >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Revert recording_depletions structure
|
||||||
|
ALTER TABLE recording_depletions
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_recording_depl_qty;
|
||||||
|
|
||||||
|
ALTER TABLE recording_depletions
|
||||||
|
ALTER COLUMN qty TYPE BIGINT USING COALESCE(qty, 0)::BIGINT;
|
||||||
|
|
||||||
|
ALTER TABLE recording_depletions
|
||||||
|
RENAME COLUMN qty TO total;
|
||||||
|
|
||||||
|
ALTER TABLE recording_depletions
|
||||||
|
ADD COLUMN notes VARCHAR;
|
||||||
|
|
||||||
|
ALTER TABLE recording_depletions
|
||||||
|
ADD CONSTRAINT chk_recording_depl_total CHECK (total >= 0);
|
||||||
|
|
||||||
|
-- Revert recording_bws structure
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_recording_bws_nonneg;
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
ALTER COLUMN qty TYPE INT USING COALESCE(qty, 0)::INT;
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
DROP COLUMN IF EXISTS total_weight;
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
ALTER COLUMN avg_weight TYPE NUMERIC(8,2) USING COALESCE(avg_weight, 0)::NUMERIC(8,2);
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
RENAME COLUMN avg_weight TO weight;
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
ADD COLUMN notes VARCHAR;
|
||||||
|
|
||||||
|
UPDATE recording_bws
|
||||||
|
SET qty = GREATEST(qty, 1);
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
ADD CONSTRAINT chk_recording_bws_nonneg CHECK (weight >= 0 AND qty >= 1);
|
||||||
|
|
||||||
|
-- Revert recordings header
|
||||||
|
DROP INDEX IF EXISTS idx_recordings_flock_datetime;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
DROP CONSTRAINT IF EXISTS fk_recordings_project_flock_kandang,
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_recordings_nonnegatives_v2;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
ALTER COLUMN total_depletion_qty TYPE INT USING COALESCE(total_depletion_qty, 0)::INT,
|
||||||
|
ALTER COLUMN total_chick_qty TYPE BIGINT USING COALESCE(total_chick_qty, 0)::BIGINT;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
RENAME COLUMN total_depletion_qty TO total_depletion;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
RENAME COLUMN total_chick_qty TO total_chick;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
ADD COLUMN record_date DATE,
|
||||||
|
ADD COLUMN status INT NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN ontime INT NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN daily_depletion_rate NUMERIC(7,3),
|
||||||
|
ADD COLUMN cum_depletion INT;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
RENAME COLUMN project_flock_kandangs_id TO project_flock_id;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
ADD CONSTRAINT fk_recordings_project_flock
|
||||||
|
FOREIGN KEY (project_flock_id) REFERENCES project_flock_kandangs(id);
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
ADD CONSTRAINT chk_recordings_status CHECK (status IN (0,1,2,3));
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
ADD CONSTRAINT chk_recordings_ontime CHECK (ontime IN (0,1));
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
ADD CONSTRAINT chk_recordings_nonnegatives CHECK (
|
||||||
|
(total_depletion IS NULL OR total_depletion >= 0) AND
|
||||||
|
(cum_depletion IS NULL OR cum_depletion >= 0) AND
|
||||||
|
(total_chick IS NULL OR total_chick >= 0) AND
|
||||||
|
(cum_intake IS NULL OR cum_intake >= 0) AND
|
||||||
|
(daily_gain IS NULL OR daily_gain >= 0) AND
|
||||||
|
(avg_daily_gain IS NULL OR avg_daily_gain >= 0) AND
|
||||||
|
(fcr_value IS NULL OR fcr_value > 0) AND
|
||||||
|
(daily_depletion_rate IS NULL OR daily_depletion_rate >= 0) AND
|
||||||
|
(cum_depletion_rate IS NULL OR cum_depletion_rate >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Ensure new columns carry derived data
|
||||||
|
UPDATE recordings
|
||||||
|
SET record_date = (record_datetime AT TIME ZONE 'Asia/Jakarta')::date
|
||||||
|
WHERE record_date IS NULL;
|
||||||
|
|
||||||
|
-- Restore helper trigger/function and indexes
|
||||||
|
CREATE OR REPLACE FUNCTION trg_set_record_date() RETURNS trigger AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.record_date := (NEW.record_datetime AT TIME ZONE 'Asia/Jakarta')::date;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER recordings_set_record_date_trg
|
||||||
|
BEFORE INSERT OR UPDATE OF record_datetime ON recordings
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION trg_set_record_date();
|
||||||
|
|
||||||
|
CREATE INDEX idx_recordings_flock_datetime
|
||||||
|
ON recordings (project_flock_id, record_datetime);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX uq_recordings_flock_record_date
|
||||||
|
ON recordings (project_flock_id, record_date)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- Drop trigger & helper function tied to record_date before removing the column
|
||||||
|
DROP TRIGGER IF EXISTS recordings_set_record_date_trg ON recordings;
|
||||||
|
DROP FUNCTION IF EXISTS trg_set_record_date();
|
||||||
|
|
||||||
|
-- Drop indexes and constraints that reference legacy columns
|
||||||
|
DROP INDEX IF EXISTS uq_recordings_flock_record_date;
|
||||||
|
DROP INDEX IF EXISTS idx_recordings_flock_datetime;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
DROP CONSTRAINT IF EXISTS fk_recordings_project_flock,
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_recordings_status,
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_recordings_ontime,
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_recordings_nonnegatives;
|
||||||
|
|
||||||
|
-- Align recordings header with the new schema
|
||||||
|
ALTER TABLE recordings
|
||||||
|
RENAME COLUMN project_flock_id TO project_flock_kandangs_id;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
DROP COLUMN IF EXISTS record_date,
|
||||||
|
DROP COLUMN IF EXISTS status,
|
||||||
|
DROP COLUMN IF EXISTS ontime,
|
||||||
|
DROP COLUMN IF EXISTS daily_depletion_rate,
|
||||||
|
DROP COLUMN IF EXISTS cum_depletion;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
RENAME COLUMN total_depletion TO total_depletion_qty;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
RENAME COLUMN total_chick TO total_chick_qty;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
ALTER COLUMN total_depletion_qty TYPE NUMERIC(15,3) USING COALESCE(total_depletion_qty, 0)::NUMERIC(15,3),
|
||||||
|
ALTER COLUMN total_chick_qty TYPE NUMERIC(15,3) USING COALESCE(total_chick_qty, 0)::NUMERIC(15,3),
|
||||||
|
ALTER COLUMN cum_intake TYPE INT USING COALESCE(cum_intake, 0)::INT;
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
ADD CONSTRAINT fk_recordings_project_flock_kandang
|
||||||
|
FOREIGN KEY (project_flock_kandangs_id) REFERENCES project_flock_kandangs(id);
|
||||||
|
|
||||||
|
ALTER TABLE recordings
|
||||||
|
ADD CONSTRAINT chk_recordings_nonnegatives_v2 CHECK (
|
||||||
|
(total_depletion_qty IS NULL OR total_depletion_qty >= 0) AND
|
||||||
|
(cum_depletion_rate IS NULL OR cum_depletion_rate >= 0) AND
|
||||||
|
(daily_gain IS NULL OR daily_gain >= 0) AND
|
||||||
|
(avg_daily_gain IS NULL OR avg_daily_gain >= 0) AND
|
||||||
|
(cum_intake IS NULL OR cum_intake >= 0) AND
|
||||||
|
(fcr_value IS NULL OR fcr_value >= 0) AND
|
||||||
|
(total_chick_qty IS NULL OR total_chick_qty >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_recordings_flock_datetime
|
||||||
|
ON recordings (project_flock_kandangs_id, record_datetime);
|
||||||
|
|
||||||
|
-- recording_bws reshape
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
RENAME COLUMN weight TO avg_weight;
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
ALTER COLUMN avg_weight TYPE NUMERIC(8,2) USING COALESCE(avg_weight, 0)::NUMERIC(8,2);
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
ADD COLUMN total_weight NUMERIC(10,3);
|
||||||
|
|
||||||
|
UPDATE recording_bws
|
||||||
|
SET total_weight = COALESCE(avg_weight, 0) * COALESCE(qty, 0);
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
ALTER COLUMN total_weight SET NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
ALTER COLUMN qty TYPE NUMERIC(15,3) USING COALESCE(qty, 0)::NUMERIC(15,3);
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
DROP COLUMN IF EXISTS notes;
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_recording_bws_nonneg;
|
||||||
|
|
||||||
|
ALTER TABLE recording_bws
|
||||||
|
ADD CONSTRAINT chk_recording_bws_nonneg CHECK (
|
||||||
|
avg_weight >= 0 AND qty >= 0 AND total_weight >= 0
|
||||||
|
);
|
||||||
|
|
||||||
|
-- recording_depletions reshape
|
||||||
|
ALTER TABLE recording_depletions
|
||||||
|
RENAME COLUMN total TO qty;
|
||||||
|
|
||||||
|
ALTER TABLE recording_depletions
|
||||||
|
ALTER COLUMN qty TYPE NUMERIC(15,3) USING COALESCE(qty, 0)::NUMERIC(15,3);
|
||||||
|
|
||||||
|
ALTER TABLE recording_depletions
|
||||||
|
DROP COLUMN IF EXISTS notes;
|
||||||
|
|
||||||
|
ALTER TABLE recording_depletions
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_recording_depl_total;
|
||||||
|
|
||||||
|
ALTER TABLE recording_depletions
|
||||||
|
ADD CONSTRAINT chk_recording_depl_qty CHECK (qty >= 0);
|
||||||
|
|
||||||
|
-- recording_stocks reshape
|
||||||
|
ALTER TABLE recording_stocks
|
||||||
|
DROP CONSTRAINT IF EXISTS chk_recording_stocks_nonneg;
|
||||||
|
|
||||||
|
ALTER TABLE recording_stocks
|
||||||
|
DROP COLUMN IF EXISTS increase,
|
||||||
|
DROP COLUMN IF EXISTS decrease,
|
||||||
|
DROP COLUMN IF EXISTS usage_amount,
|
||||||
|
DROP COLUMN IF EXISTS notes;
|
||||||
|
|
||||||
|
ALTER TABLE recording_stocks
|
||||||
|
ADD COLUMN usage_qty NUMERIC(15,3),
|
||||||
|
ADD COLUMN pending_qty NUMERIC(15,3);
|
||||||
|
|
||||||
|
ALTER TABLE recording_stocks
|
||||||
|
ADD CONSTRAINT chk_recording_stocks_nonneg CHECK (
|
||||||
|
(usage_qty IS NULL OR usage_qty >= 0) AND
|
||||||
|
(pending_qty IS NULL OR pending_qty >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- recording_eggs table
|
||||||
|
CREATE TABLE recording_eggs (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
recording_id BIGINT NOT NULL,
|
||||||
|
product_warehouse_id BIGINT NOT NULL,
|
||||||
|
qty INT NOT NULL,
|
||||||
|
created_by BIGINT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
|
||||||
|
CONSTRAINT fk_recording_eggs_recording
|
||||||
|
FOREIGN KEY (recording_id) REFERENCES recordings(id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_recording_eggs_product_warehouse
|
||||||
|
FOREIGN KEY (product_warehouse_id) REFERENCES product_warehouses(id),
|
||||||
|
CONSTRAINT fk_recording_eggs_created_by
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users(id),
|
||||||
|
CONSTRAINT chk_recording_eggs_qty CHECK (qty >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_recording_eggs_recording
|
||||||
|
ON recording_eggs (recording_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_recording_eggs_product
|
||||||
|
ON recording_eggs (product_warehouse_id);
|
||||||
|
|
||||||
|
-- grading_eggs table
|
||||||
|
CREATE TABLE grading_eggs (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
recording_egg_id BIGINT NOT NULL,
|
||||||
|
qty NUMERIC(15,3) NOT NULL,
|
||||||
|
grade VARCHAR,
|
||||||
|
created_by BIGINT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
|
||||||
|
CONSTRAINT fk_grading_eggs_recording_egg
|
||||||
|
FOREIGN KEY (recording_egg_id) REFERENCES recording_eggs(id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_grading_eggs_created_by
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users(id),
|
||||||
|
CONSTRAINT chk_grading_eggs_qty CHECK (qty >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_grading_eggs_recording_egg
|
||||||
|
ON grading_eggs (recording_egg_id);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
+100
-380
@@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -41,22 +40,15 @@ func Run(db *gorm.DB) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
flocks, err := seedFlocks(tx, adminID)
|
if _, err := seedFlocks(tx, adminID); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fcrs, err := seedFcr(tx, adminID)
|
if _, err := seedFcr(tx, adminID); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
projectFlocks, err := seedProjectFlocks(tx, adminID, flocks, areas, fcrs, locations)
|
kandangs, err := seedKandangs(tx, adminID, locations, users)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
kandangs, err := seedKandangs(tx, adminID, locations, users, projectFlocks)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -93,10 +85,6 @@ func Run(db *gorm.DB) error {
|
|||||||
if err := seedTransferStock(tx, adminID); err != nil {
|
if err := seedTransferStock(tx, adminID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := seedChickin(tx, adminID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("✅ Master data seeding completed")
|
fmt.Println("✅ Master data seeding completed")
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -243,159 +231,16 @@ func seedFlocks(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func seedProjectFlocks(tx *gorm.DB, createdBy uint, flocks, areas, fcrs, locations map[string]uint) (map[string]uint, error) {
|
func seedKandangs(tx *gorm.DB, createdBy uint, locations map[string]uint, users map[string]uint) (map[string]uint, error) {
|
||||||
seeds := []struct {
|
seeds := []struct {
|
||||||
Key string
|
Name string
|
||||||
Flock string
|
Status utils.KandangStatus
|
||||||
Area string
|
|
||||||
Category utils.ProjectFlockCategory
|
|
||||||
Fcr string
|
|
||||||
Location string
|
Location string
|
||||||
Period int
|
PicKey string
|
||||||
}{
|
}{
|
||||||
{
|
{Name: "Singaparna 1", Status: utils.KandangStatusNonActive, Location: "Singaparna", PicKey: "admin"},
|
||||||
Key: "Singaparna Period 1",
|
|
||||||
Flock: "Flock Priangan",
|
|
||||||
Area: "Priangan",
|
|
||||||
Category: utils.ProjectFlockCategoryGrowing,
|
|
||||||
Fcr: "FCR Layer",
|
|
||||||
Location: "Singaparna",
|
|
||||||
Period: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "Cikaum Period 1",
|
|
||||||
Flock: "Flock Banten",
|
|
||||||
Area: "Banten",
|
|
||||||
Category: utils.ProjectFlockCategoryGrowing,
|
|
||||||
Fcr: "FCR Layer",
|
|
||||||
Location: "Cikaum",
|
|
||||||
Period: 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(map[string]uint, len(seeds))
|
|
||||||
|
|
||||||
for _, seed := range seeds {
|
|
||||||
flockID, ok := flocks[seed.Flock]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("floc %s not seeded", seed.Flock)
|
|
||||||
}
|
|
||||||
areaID, ok := areas[seed.Area]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("area %s not seeded", seed.Area)
|
|
||||||
}
|
|
||||||
fcrID, ok := fcrs[seed.Fcr]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("fcr %s not seeded", seed.Fcr)
|
|
||||||
}
|
|
||||||
locationID, ok := locations[seed.Location]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("location %s not seeded", seed.Location)
|
|
||||||
}
|
|
||||||
|
|
||||||
var projectFlock entity.ProjectFlock
|
|
||||||
err := tx.Where("flock_id = ? AND area_id = ? AND category = ? AND fcr_id = ? AND location_id = ? AND period = ?",
|
|
||||||
flockID, areaID, seed.Category, fcrID, locationID, seed.Period).First(&projectFlock).Error
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
projectFlock = entity.ProjectFlock{
|
|
||||||
FlockId: flockID,
|
|
||||||
AreaId: areaID,
|
|
||||||
Category: string(seed.Category),
|
|
||||||
FcrId: fcrID,
|
|
||||||
LocationId: locationID,
|
|
||||||
Period: seed.Period,
|
|
||||||
CreatedBy: createdBy,
|
|
||||||
}
|
|
||||||
if err := tx.Create(&projectFlock).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
if err := tx.Model(&entity.ProjectFlock{}).Where("id = ?", projectFlock.Id).Updates(map[string]any{
|
|
||||||
"flock_id": flockID,
|
|
||||||
"area_id": areaID,
|
|
||||||
"category": string(seed.Category),
|
|
||||||
"fcr_id": fcrID,
|
|
||||||
"location_id": locationID,
|
|
||||||
"period": seed.Period,
|
|
||||||
}).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ensureProjectFlockApprovals(tx, projectFlock.Id, createdBy); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result[seed.Key] = projectFlock.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureProjectFlockApprovals(tx *gorm.DB, projectFlockID uint, actorID uint) error {
|
|
||||||
if projectFlockID == 0 || actorID == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
workflow := utils.ApprovalWorkflowProjectFlock.String()
|
|
||||||
|
|
||||||
steps := []struct {
|
|
||||||
step approvalutils.ApprovalStep
|
|
||||||
action entity.ApprovalAction
|
|
||||||
}{
|
|
||||||
{step: utils.ProjectFlockStepPengajuan, action: entity.ApprovalActionCreated},
|
|
||||||
{step: utils.ProjectFlockStepAktif, action: entity.ApprovalActionApproved},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, cfg := range steps {
|
|
||||||
var count int64
|
|
||||||
if err := tx.Model(&entity.Approval{}).
|
|
||||||
Where("approvable_type = ? AND approvable_id = ? AND step_number = ?", workflow, projectFlockID, uint16(cfg.step)).
|
|
||||||
Count(&count).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
stepName, ok := utils.ProjectFlockApprovalSteps[cfg.step]
|
|
||||||
if !ok || strings.TrimSpace(stepName) == "" {
|
|
||||||
stepName = fmt.Sprintf("Step %d", cfg.step)
|
|
||||||
}
|
|
||||||
|
|
||||||
var actionPtr *entity.ApprovalAction
|
|
||||||
action := cfg.action
|
|
||||||
actionPtr = &action
|
|
||||||
|
|
||||||
record := entity.Approval{
|
|
||||||
ApprovableType: workflow,
|
|
||||||
ApprovableId: projectFlockID,
|
|
||||||
StepNumber: uint16(cfg.step),
|
|
||||||
StepName: stepName,
|
|
||||||
Action: actionPtr,
|
|
||||||
ActionBy: uintPtr(actorID),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Create(&record).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func seedKandangs(tx *gorm.DB, createdBy uint, locations map[string]uint, users map[string]uint, projectFlocks map[string]uint) (map[string]uint, error) {
|
|
||||||
seeds := []struct {
|
|
||||||
Name string
|
|
||||||
Status utils.KandangStatus
|
|
||||||
Location string
|
|
||||||
PicKey string
|
|
||||||
ProjectFlockKey *string
|
|
||||||
}{
|
|
||||||
{Name: "Singaparna 1", Status: utils.KandangStatusActive, Location: "Singaparna", PicKey: "admin", ProjectFlockKey: strPtr("Singaparna Period 1")},
|
|
||||||
{Name: "Singaparna 2", Status: utils.KandangStatusNonActive, Location: "Singaparna", PicKey: "admin"},
|
{Name: "Singaparna 2", Status: utils.KandangStatusNonActive, Location: "Singaparna", PicKey: "admin"},
|
||||||
{Name: "Cikaum 1", Status: utils.KandangStatusActive, Location: "Cikaum", PicKey: "admin", ProjectFlockKey: strPtr("Cikaum Period 1")},
|
{Name: "Cikaum 1", Status: utils.KandangStatusNonActive, Location: "Cikaum", PicKey: "admin"},
|
||||||
{Name: "Cikaum 2", Status: utils.KandangStatusNonActive, Location: "Cikaum", PicKey: "admin"},
|
{Name: "Cikaum 2", Status: utils.KandangStatusNonActive, Location: "Cikaum", PicKey: "admin"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,32 +256,19 @@ func seedKandangs(tx *gorm.DB, createdBy uint, locations map[string]uint, users
|
|||||||
return nil, fmt.Errorf("user %s not seeded", seed.PicKey)
|
return nil, fmt.Errorf("user %s not seeded", seed.PicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
var projectFlockID *uint
|
|
||||||
if seed.ProjectFlockKey != nil {
|
|
||||||
pfID, ok := projectFlocks[*seed.ProjectFlockKey]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("project flock %s not seeded", *seed.ProjectFlockKey)
|
|
||||||
}
|
|
||||||
projectFlockID = uintPtr(pfID)
|
|
||||||
}
|
|
||||||
|
|
||||||
var kandang entity.Kandang
|
var kandang entity.Kandang
|
||||||
err := tx.Where("name = ?", seed.Name).First(&kandang).Error
|
err := tx.Where("name = ?", seed.Name).First(&kandang).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
kandang = entity.Kandang{
|
kandang = entity.Kandang{
|
||||||
Name: seed.Name,
|
Name: seed.Name,
|
||||||
Status: string(seed.Status),
|
Status: string(seed.Status),
|
||||||
LocationId: locID,
|
LocationId: locID,
|
||||||
PicId: picID,
|
PicId: picID,
|
||||||
ProjectFlockId: projectFlockID,
|
CreatedBy: createdBy,
|
||||||
CreatedBy: createdBy,
|
|
||||||
}
|
}
|
||||||
if err := tx.Create(&kandang).Error; err != nil {
|
if err := tx.Create(&kandang).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := syncPivotRelation(tx, projectFlockID, kandang.Id); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
@@ -445,17 +277,9 @@ func seedKandangs(tx *gorm.DB, createdBy uint, locations map[string]uint, users
|
|||||||
"pic_id": picID,
|
"pic_id": picID,
|
||||||
"status": string(seed.Status),
|
"status": string(seed.Status),
|
||||||
}
|
}
|
||||||
if projectFlockID != nil {
|
|
||||||
updates["project_flock_id"] = *projectFlockID
|
|
||||||
} else {
|
|
||||||
updates["project_flock_id"] = nil
|
|
||||||
}
|
|
||||||
if err := tx.Model(&entity.Kandang{}).Where("id = ?", kandang.Id).Updates(updates).Error; err != nil {
|
if err := tx.Model(&entity.Kandang{}).Where("id = ?", kandang.Id).Updates(updates).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := syncPivotRelation(tx, projectFlockID, kandang.Id); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result[seed.Name] = kandang.Id
|
result[seed.Name] = kandang.Id
|
||||||
}
|
}
|
||||||
@@ -463,38 +287,6 @@ func seedKandangs(tx *gorm.DB, createdBy uint, locations map[string]uint, users
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func syncPivotRelation(tx *gorm.DB, projectFlockID *uint, kandangID uint) error {
|
|
||||||
if err := detachActivePivot(tx, kandangID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if projectFlockID == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ensureActivePivot(tx, *projectFlockID, kandangID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func detachActivePivot(tx *gorm.DB, kandangID uint) error {
|
|
||||||
return tx.Where("kandang_id = ?", kandangID).
|
|
||||||
Delete(&entity.ProjectFlockKandang{}).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureActivePivot(tx *gorm.DB, projectFlockID, kandangID uint) error {
|
|
||||||
var pivot entity.ProjectFlockKandang
|
|
||||||
err := tx.Where("project_flock_id = ? AND kandang_id = ?", projectFlockID, kandangID).
|
|
||||||
First(&pivot).Error
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newRecord := entity.ProjectFlockKandang{
|
|
||||||
ProjectFlockId: projectFlockID,
|
|
||||||
KandangId: kandangID,
|
|
||||||
}
|
|
||||||
return tx.Create(&newRecord).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func seedWarehouses(tx *gorm.DB, createdBy uint, areas map[string]uint, locations map[string]uint, kandangs map[string]uint) error {
|
func seedWarehouses(tx *gorm.DB, createdBy uint, areas map[string]uint, locations map[string]uint, kandangs map[string]uint) error {
|
||||||
seeds := []struct {
|
seeds := []struct {
|
||||||
Name string
|
Name string
|
||||||
@@ -571,8 +363,10 @@ func seedProductCategories(tx *gorm.DB, createdBy uint) (map[string]uint, error)
|
|||||||
Name string
|
Name string
|
||||||
Code string
|
Code string
|
||||||
}{
|
}{
|
||||||
|
{"Pullet", "PLT"},
|
||||||
{"Bahan Baku", "RAW"},
|
{"Bahan Baku", "RAW"},
|
||||||
{"Day Old Chick", "DOC"},
|
{"Day Old Chick", "DOC"},
|
||||||
|
{"Telur", "EGG"},
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make(map[string]uint, len(seeds))
|
result := make(map[string]uint, len(seeds))
|
||||||
@@ -776,6 +570,54 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories
|
|||||||
Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
|
Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
|
||||||
Flags: []utils.FlagType{utils.FlagDOC},
|
Flags: []utils.FlagType{utils.FlagDOC},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "Ayam Afkir",
|
||||||
|
Brand: "-",
|
||||||
|
Sku: "1",
|
||||||
|
Uom: "Ekor",
|
||||||
|
Category: "Day Old Chick",
|
||||||
|
Price: 1,
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Ayam Mati",
|
||||||
|
Brand: "-",
|
||||||
|
Sku: "2",
|
||||||
|
Uom: "Ekor",
|
||||||
|
Category: "Day Old Chick",
|
||||||
|
Price: 1,
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Ayam Culling",
|
||||||
|
Brand: "-",
|
||||||
|
Sku: "3",
|
||||||
|
Uom: "Ekor",
|
||||||
|
Category: "Day Old Chick",
|
||||||
|
Price: 1,
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Telur Konsumsi Baik",
|
||||||
|
Brand: "-",
|
||||||
|
Sku: "4",
|
||||||
|
Uom: "Unit",
|
||||||
|
Category: "Telur",
|
||||||
|
Price: 1,
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Telur Pecah",
|
||||||
|
Brand: "-",
|
||||||
|
Sku: "5",
|
||||||
|
Uom: "Unit",
|
||||||
|
Category: "Telur",
|
||||||
|
Price: 1,
|
||||||
|
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "281 SPECIAL STARTER",
|
Name: "281 SPECIAL STARTER",
|
||||||
Brand: "281 STARTER",
|
Brand: "281 STARTER",
|
||||||
@@ -1026,25 +868,44 @@ func seedBanks(tx *gorm.DB, createdBy uint) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func seedProductWarehouse(tx *gorm.DB, createdBy uint) error {
|
func seedProductWarehouse(tx *gorm.DB, createdBy uint) error {
|
||||||
|
|
||||||
seeds := []struct {
|
seeds := []struct {
|
||||||
ProductID uint
|
ProductName string
|
||||||
WarehouseID uint
|
WarehouseName string
|
||||||
Quantity float64
|
Quantity float64
|
||||||
}{
|
}{
|
||||||
{ProductID: 1, WarehouseID: 1, Quantity: 100},
|
{ProductName: "DOC Broiler", WarehouseName: "Gudang Priangan", Quantity: 100},
|
||||||
{ProductID: 2, WarehouseID: 2, Quantity: 200},
|
{ProductName: "281 SPECIAL STARTER", WarehouseName: "Gudang Singaparna", Quantity: 200},
|
||||||
{ProductID: 2, WarehouseID: 1, Quantity: 300},
|
{ProductName: "281 SPECIAL STARTER", WarehouseName: "Gudang Banten", Quantity: 300},
|
||||||
{ProductID: 1, WarehouseID: 3, Quantity: 5000},
|
{ProductName: "DOC Broiler", WarehouseName: "Gudang Singaparna 1", Quantity: 5000},
|
||||||
|
{ProductName: "Telur Konsumsi Baik", WarehouseName: "Gudang Singaparna 1", Quantity: 600},
|
||||||
|
{ProductName: "Telur Pecah", WarehouseName: "Gudang Singaparna 1", Quantity: 80},
|
||||||
|
{ProductName: "Telur Konsumsi Baik", WarehouseName: "Gudang Cikaum 1", Quantity: 450},
|
||||||
|
{ProductName: "Telur Pecah", WarehouseName: "Gudang Cikaum 1", Quantity: 60},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, seed := range seeds {
|
for _, seed := range seeds {
|
||||||
|
var product entity.Product
|
||||||
|
if err := tx.Where("name = ?", seed.ProductName).First(&product).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fmt.Errorf("product %q not found for product warehouse seeding", seed.ProductName)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var warehouse entity.Warehouse
|
||||||
|
if err := tx.Where("name = ?", seed.WarehouseName).First(&warehouse).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fmt.Errorf("warehouse %q not found for product warehouse seeding", seed.WarehouseName)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var productWarehouse entity.ProductWarehouse
|
var productWarehouse entity.ProductWarehouse
|
||||||
err := tx.Where("product_id = ? AND warehouse_id = ?", seed.ProductID, seed.WarehouseID).First(&productWarehouse).Error
|
err := tx.Where("product_id = ? AND warehouse_id = ?", product.Id, warehouse.Id).First(&productWarehouse).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
productWarehouse = entity.ProductWarehouse{
|
productWarehouse = entity.ProductWarehouse{
|
||||||
ProductId: seed.ProductID,
|
ProductId: product.Id,
|
||||||
WarehouseId: seed.WarehouseID,
|
WarehouseId: warehouse.Id,
|
||||||
Quantity: seed.Quantity,
|
Quantity: seed.Quantity,
|
||||||
CreatedBy: createdBy,
|
CreatedBy: createdBy,
|
||||||
}
|
}
|
||||||
@@ -1053,6 +914,12 @@ func seedProductWarehouse(tx *gorm.DB, createdBy uint) error {
|
|||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
|
} else {
|
||||||
|
if err := tx.Model(&productWarehouse).Updates(map[string]any{
|
||||||
|
"quantity": seed.Quantity,
|
||||||
|
}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1133,153 +1000,6 @@ func seedTransferStock(tx *gorm.DB, createdBy uint) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func seedChickin(tx *gorm.DB, createdBy uint) error {
|
|
||||||
seeds := []struct {
|
|
||||||
ProjectFlockKandangId uint
|
|
||||||
ChickInDate string
|
|
||||||
Quantity float64
|
|
||||||
Note string
|
|
||||||
}{
|
|
||||||
{ProjectFlockKandangId: 1, ChickInDate: "2025-10-20", Quantity: 100, Note: "Seeder chickin 1"},
|
|
||||||
{ProjectFlockKandangId: 2, ChickInDate: "2025-10-21", Quantity: 200, Note: "Seeder chickin 2"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, seed := range seeds {
|
|
||||||
chickinDate, err := time.Parse("2006-01-02", seed.ChickInDate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert ProjectChickin jika belum ada
|
|
||||||
var chickin entity.ProjectChickin
|
|
||||||
err = tx.Where("project_flock_kandang_id = ? AND chick_in_date = ?", seed.ProjectFlockKandangId, chickinDate).
|
|
||||||
First(&chickin).Error
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
chickin = entity.ProjectChickin{
|
|
||||||
ProjectFlockKandangId: seed.ProjectFlockKandangId,
|
|
||||||
ChickInDate: chickinDate,
|
|
||||||
Quantity: seed.Quantity,
|
|
||||||
Note: seed.Note,
|
|
||||||
CreatedBy: createdBy,
|
|
||||||
}
|
|
||||||
if err := tx.Create(&chickin).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var population entity.ProjectFlockPopulation
|
|
||||||
err = tx.Where("project_flock_kandang_id = ?", seed.ProjectFlockKandangId).First(&population).Error
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
population = entity.ProjectFlockPopulation{
|
|
||||||
ProjectFlockKandangId: seed.ProjectFlockKandangId,
|
|
||||||
InitialQuantity: seed.Quantity,
|
|
||||||
CurrentQuantity: seed.Quantity,
|
|
||||||
ReservedQuantity: 0,
|
|
||||||
CreatedBy: createdBy,
|
|
||||||
}
|
|
||||||
if err := tx.Create(&population).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
// Update population quantities
|
|
||||||
if err := tx.Model(&entity.ProjectFlockPopulation{}).
|
|
||||||
Where("id = ?", population.Id).
|
|
||||||
Updates(map[string]any{
|
|
||||||
"initial_quantity": population.InitialQuantity + seed.Quantity,
|
|
||||||
"current_quantity": population.CurrentQuantity + seed.Quantity,
|
|
||||||
"reserved_quantity": 0,
|
|
||||||
}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pfk entity.ProjectFlockKandang
|
|
||||||
if err := tx.Where("id = ?", seed.ProjectFlockKandangId).First(&pfk).Error; err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
// no pivot found; skip creating details
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var warehouse entity.Warehouse
|
|
||||||
if err := tx.Where("kandang_id = ?", pfk.KandangId).First(&warehouse).Error; err != nil {
|
|
||||||
// if warehouse not found, cannot create details
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var productWarehouses []entity.ProductWarehouse
|
|
||||||
err = tx.Table("product_warehouses").
|
|
||||||
Select("product_warehouses.*").
|
|
||||||
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
|
||||||
Joins("JOIN product_categories ON product_categories.id = products.product_category_id").
|
|
||||||
Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", warehouse.Id).
|
|
||||||
Order("product_warehouses.created_at DESC").
|
|
||||||
Find(&productWarehouses).Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no product warehouses found, keep existing chickin.Quantity and skip details
|
|
||||||
if len(productWarehouses) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// sum all pw quantities and set chickin.Quantity to that total (mimic CreateOne)
|
|
||||||
totalQty := 0.0
|
|
||||||
for _, pw := range productWarehouses {
|
|
||||||
totalQty += pw.Quantity
|
|
||||||
}
|
|
||||||
|
|
||||||
if chickin.Quantity != totalQty {
|
|
||||||
if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Update("quantity", totalQty).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
chickin.Quantity = totalQty
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pw := range productWarehouses {
|
|
||||||
// ensure detail exists or create it with full pw.Quantity
|
|
||||||
var detail entity.ProjectChickinDetail
|
|
||||||
err = tx.Where("project_chickin_id = ? AND product_warehouse_id = ?", chickin.Id, pw.Id).First(&detail).Error
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
detail = entity.ProjectChickinDetail{
|
|
||||||
ProjectChickinId: chickin.Id,
|
|
||||||
ProductWarehouseId: pw.Id,
|
|
||||||
Quantity: pw.Quantity,
|
|
||||||
CreatedBy: createdBy,
|
|
||||||
}
|
|
||||||
if err := tx.Create(&detail).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
if detail.Quantity != pw.Quantity {
|
|
||||||
if err := tx.Model(&entity.ProjectChickinDetail{}).Where("id = ?", detail.Id).Update("quantity", pw.Quantity).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// zero out pw quantity
|
|
||||||
if err := tx.Model(&entity.ProductWarehouse{}).Where("id = ?", pw.Id).Update("quantity", 0).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ptr[T any](v T) *T {
|
func ptr[T any](v T) *T {
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,18 +7,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Kandang struct {
|
type Kandang struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"not null;uniqueIndex:kandangs_name_unique,where:deleted_at IS NULL"`
|
Name string `gorm:"not null;uniqueIndex:kandangs_name_unique,where:deleted_at IS NULL"`
|
||||||
Status string `gorm:"type:varchar(50);not null"`
|
Status string `gorm:"type:varchar(50);not null"`
|
||||||
LocationId uint `gorm:"not null"`
|
LocationId uint `gorm:"not null"`
|
||||||
PicId uint `gorm:"not null"`
|
PicId uint `gorm:"not null"`
|
||||||
ProjectFlockId *uint `gorm:"column:project_flock_id"`
|
CreatedBy uint `gorm:"not null"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
Location Location `gorm:"foreignKey:LocationId;references:Id"`
|
||||||
Location Location `gorm:"foreignKey:LocationId;references:Id"`
|
Pic User `gorm:"foreignKey:PicId;references:Id"`
|
||||||
Pic User `gorm:"foreignKey:PicId;references:Id"`
|
ProjectFlockKandangs []ProjectFlockKandang `gorm:"foreignKey:KandangId;references:Id" json:"-"`
|
||||||
ProjectFlock *ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const ()
|
|||||||
|
|
||||||
type ProjectChickin struct {
|
type ProjectChickin struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
ProjectFlockKandangId uint `gorm:"not null"`
|
ProjectFlockKandangId uint `gorm:"not null;index;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||||
ChickInDate time.Time `gorm:"not null"`
|
ChickInDate time.Time `gorm:"not null"`
|
||||||
Quantity float64 `gorm:"not null"`
|
Quantity float64 `gorm:"not null"`
|
||||||
Note string `gorm:"type:text"`
|
Note string `gorm:"type:text"`
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
type ProjectFlockPopulation struct {
|
type ProjectFlockPopulation struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
ProjectFlockKandangId uint `gorm:"not null"`
|
ProjectFlockKandangId uint `gorm:"not null;index;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||||
InitialQuantity float64 `gorm:"type:numeric(15,3);not null"`
|
InitialQuantity float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
CurrentQuantity float64 `gorm:"type:numeric(15,3);not null"`
|
CurrentQuantity float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
ReservedQuantity float64 `gorm:"type:numeric(15,3)"`
|
ReservedQuantity float64 `gorm:"type:numeric(15,3)"`
|
||||||
@@ -18,5 +18,6 @@ type ProjectFlockPopulation struct {
|
|||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
|
||||||
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
||||||
|
|
||||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,23 +8,23 @@ import (
|
|||||||
|
|
||||||
type ProjectFlock struct {
|
type ProjectFlock struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
FlockId uint `gorm:"not null;uniqueIndex:project_flocks_flock_period_unique"`
|
FlockName string `gorm:"type:varchar(255);not null;uniqueIndex"`
|
||||||
AreaId uint `gorm:"not null"`
|
AreaId uint `gorm:"not null"`
|
||||||
Category string `gorm:"type:varchar(20);not null"`
|
Category string `gorm:"type:varchar(20);not null"`
|
||||||
FcrId uint `gorm:"not null"`
|
FcrId uint `gorm:"not null"`
|
||||||
LocationId uint `gorm:"not null"`
|
LocationId uint `gorm:"not null"`
|
||||||
Period int `gorm:"not null;uniqueIndex:project_flocks_flock_period_unique"`
|
Period int `gorm:"not null"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
CreatedBy uint `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
Flock Flock `gorm:"foreignKey:FlockId;references:Id"`
|
|
||||||
Area Area `gorm:"foreignKey:AreaId;references:Id"`
|
Area Area `gorm:"foreignKey:AreaId;references:Id"`
|
||||||
Fcr Fcr `gorm:"foreignKey:FcrId;references:Id"`
|
Fcr Fcr `gorm:"foreignKey:FcrId;references:Id"`
|
||||||
Location Location `gorm:"foreignKey:LocationId;references:Id"`
|
Location Location `gorm:"foreignKey:LocationId;references:Id"`
|
||||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
Kandangs []Kandang `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
Kandangs []Kandang `gorm:"many2many:project_flock_kandangs;joinTableForeignKey:project_flock_id;joinTableReferences:kandang_id" json:"kandangs,omitempty"`
|
||||||
KandangHistory []ProjectFlockKandang `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
KandangHistory []ProjectFlockKandang `gorm:"foreignKey:ProjectFlockId;references:Id" json:"-"`
|
||||||
LatestApproval *Approval `gorm:"-" json:"-"`
|
|
||||||
|
LatestApproval *Approval `gorm:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ type ProjectFlockKandang struct {
|
|||||||
ProjectFlockId uint `gorm:"not null;index:idx_project_flock_kandangs_project;uniqueIndex:idx_project_flock_kandangs_unique"`
|
ProjectFlockId uint `gorm:"not null;index:idx_project_flock_kandangs_project;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||||
KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"`
|
KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
|
||||||
Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
|
||||||
|
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
||||||
|
Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Recording struct {
|
type Recording struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"not null;uniqueIndex:idx_name,where:deleted_at IS NULL"`
|
ProjectFlockKandangId uint `gorm:"column:project_flock_kandangs_id;not null;index"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
RecordDatetime time.Time `gorm:"column:record_datetime;not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
Day *int `gorm:"column:day"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
TotalDepletionQty *float64 `gorm:"column:total_depletion_qty"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
CumDepletionRate *float64 `gorm:"column:cum_depletion_rate"`
|
||||||
|
DailyGain *float64 `gorm:"column:daily_gain"`
|
||||||
|
AvgDailyGain *float64 `gorm:"column:avg_daily_gain"`
|
||||||
|
CumIntake *int `gorm:"column:cum_intake"`
|
||||||
|
FcrValue *float64 `gorm:"column:fcr_value"`
|
||||||
|
TotalChickQty *float64 `gorm:"column:total_chick_qty"`
|
||||||
|
CreatedBy uint `gorm:"column:created_by"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
||||||
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
|
BodyWeights []RecordingBW `gorm:"foreignKey:RecordingId;references:Id"`
|
||||||
|
Depletions []RecordingDepletion `gorm:"foreignKey:RecordingId;references:Id"`
|
||||||
|
Stocks []RecordingStock `gorm:"foreignKey:RecordingId;references:Id"`
|
||||||
|
Eggs []RecordingEgg `gorm:"foreignKey:RecordingId;references:Id"`
|
||||||
|
|
||||||
|
LatestApproval *Approval `gorm:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type RecordingBW struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
RecordingId uint `gorm:"column:recording_id;not null;index"`
|
||||||
|
AvgWeight float64 `gorm:"column:avg_weight;not null"`
|
||||||
|
Qty float64 `gorm:"column:qty;not null"`
|
||||||
|
TotalWeight float64 `gorm:"column:total_weight;not null"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
|
||||||
|
Recording Recording `gorm:"foreignKey:RecordingId;references:Id"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
type RecordingDepletion struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
RecordingId uint `gorm:"column:recording_id;not null;index"`
|
||||||
|
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"`
|
||||||
|
Qty float64 `gorm:"column:qty;not null"`
|
||||||
|
|
||||||
|
Recording Recording `gorm:"foreignKey:RecordingId;references:Id"`
|
||||||
|
ProductWarehouse ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type RecordingEgg struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
RecordingId uint `gorm:"column:recording_id;not null;index"`
|
||||||
|
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"`
|
||||||
|
Qty int `gorm:"column:qty;not null"`
|
||||||
|
CreatedBy uint `gorm:"column:created_by"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
GradingEggs []GradingEgg `gorm:"foreignKey:RecordingEggId;references:Id"`
|
||||||
|
ProductWarehouse ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||||
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
|
Recording Recording `gorm:"foreignKey:RecordingId;references:Id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GradingEgg struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
RecordingEggId uint `gorm:"column:recording_egg_id;not null;index"`
|
||||||
|
Qty float64 `gorm:"column:qty;not null"`
|
||||||
|
Grade string `gorm:"column:grade;type:varchar(50)"`
|
||||||
|
CreatedBy uint `gorm:"column:created_by"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
|
||||||
|
RecordingEgg RecordingEgg `gorm:"foreignKey:RecordingEggId;references:Id"`
|
||||||
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
type RecordingStock struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
RecordingId uint `gorm:"column:recording_id;not null;index"`
|
||||||
|
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"`
|
||||||
|
UsageQty *float64 `gorm:"column:usage_qty"`
|
||||||
|
PendingQty *float64 `gorm:"column:pending_qty"`
|
||||||
|
|
||||||
|
Recording Recording `gorm:"foreignKey:RecordingId;references:Id"`
|
||||||
|
ProductWarehouse ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||||
|
}
|
||||||
+177
-85
@@ -1,101 +1,193 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
// import (
|
import (
|
||||||
// "strings"
|
"strings"
|
||||||
|
|
||||||
// "gitlab.com/mbugroup/lti-api.git/internal/config"
|
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||||
// service "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
// "gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/sso/session"
|
||||||
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/sso"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
// "github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
// )
|
)
|
||||||
|
|
||||||
// func Auth(userService service.UserService, requiredRights ...string) fiber.Handler {
|
const (
|
||||||
// return func(c *fiber.Ctx) error {
|
authContextLocalsKey = "auth.context"
|
||||||
// authHeader := c.Get("Authorization")
|
authUserLocalsKey = "auth.user"
|
||||||
// token := strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer "))
|
)
|
||||||
|
|
||||||
// if token == "" {
|
// AuthContext keeps authentication details captured by the middleware.
|
||||||
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
type AuthContext struct {
|
||||||
// }
|
Token string
|
||||||
|
Verification *sso.VerificationResult
|
||||||
|
User *entity.User
|
||||||
|
Roles []sso.Role
|
||||||
|
Permissions map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
// userID, err := utils.VerifyToken(token, config.JWTSecret, config.TokenTypeAccess)
|
// Auth validates the incoming request against the central SSO access token and
|
||||||
// if err != nil {
|
// loads the corresponding local user. Optional scopes can be provided to enforce
|
||||||
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
// fine-grained authorization using the SSO access token scopes.
|
||||||
// }
|
func Auth(userService service.UserService, requiredScopes ...string) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
token := bearerToken(c)
|
||||||
|
if token == "" {
|
||||||
|
token = strings.TrimSpace(c.Cookies(config.SSOAccessCookieName))
|
||||||
|
}
|
||||||
|
if token == "" {
|
||||||
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
|
}
|
||||||
|
|
||||||
// // Only end-user subjects are allowed by this middleware. Service tokens
|
verification, err := sso.VerifyAccessToken(token)
|
||||||
// if verification.UserID == 0 {
|
if err != nil {
|
||||||
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
utils.Log.WithError(err).Warn("auth: token verification failed")
|
||||||
// }
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
|
}
|
||||||
|
|
||||||
// // Fail-closed on revocation check errors for stricter security posture.
|
if verification.UserID == 0 {
|
||||||
// if revoker := session.GetRevocationStore(); revoker != nil {
|
return fiber.NewError(fiber.StatusForbidden, "Service authentication is not permitted for this endpoint")
|
||||||
// if fingerprint := session.TokenFingerprint(token); fingerprint != "" {
|
}
|
||||||
// revoked, err := revoker.IsRevoked(c.Context(), fingerprint)
|
|
||||||
// if err != nil {
|
|
||||||
// utils.Log.WithError(err).Warn("failed to check token revocation")
|
|
||||||
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
|
||||||
// }
|
|
||||||
// if revoked {
|
|
||||||
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// user, err := userService.GetBySSOUserID(c, verification.UserID)
|
if err := ensureNotRevoked(c, token, verification); err != nil {
|
||||||
// if err != nil || user == nil {
|
return err
|
||||||
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// if len(requiredRights) > 0 && verification.Claims != nil {
|
user, err := userService.GetBySSOUserID(c, verification.UserID)
|
||||||
// if !hasAllScopes(verification.Claims.Scopes(), requiredRights) {
|
if err != nil || user == nil {
|
||||||
// return fiber.NewError(fiber.StatusForbidden, "Insufficient scope")
|
utils.Log.WithError(err).Warn("auth: failed to resolve user from repository")
|
||||||
// }
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
// }
|
}
|
||||||
|
|
||||||
// c.Locals("user", user)
|
if len(requiredScopes) > 0 {
|
||||||
|
if verification.Claims == nil || !hasAllScopes(verification.Claims.Scopes(), requiredScopes) {
|
||||||
|
return fiber.NewError(fiber.StatusForbidden, "Insufficient scope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// // if len(requiredRights) > 0 {
|
var roles []sso.Role
|
||||||
// // userRights, hasRights := config.RoleRights[user.Role]
|
permissions := make(map[string]struct{})
|
||||||
// // if (!hasRights || !hasAllRights(userRights, requiredRights)) && c.Params("userId") != userID {
|
if verification.UserID != 0 {
|
||||||
// // return fiber.NewError(fiber.StatusForbidden, "You don't have permission to access this resource")
|
if profile, err := sso.FetchProfile(c.Context(), token, verification); err != nil {
|
||||||
// // }
|
utils.Log.WithError(err).Warn("auth: failed to fetch sso profile")
|
||||||
// // }
|
} else if profile != nil {
|
||||||
|
roles = profile.Roles
|
||||||
|
for _, perm := range profile.PermissionNames() {
|
||||||
|
if perm != "" {
|
||||||
|
permissions[perm] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// return c.Next()
|
ctx := &AuthContext{
|
||||||
// }
|
Token: token,
|
||||||
// }
|
Verification: verification,
|
||||||
|
User: user,
|
||||||
|
Roles: roles,
|
||||||
|
Permissions: permissions,
|
||||||
|
}
|
||||||
|
|
||||||
// // bearerToken extracts a Bearer token from the Authorization header using
|
c.Locals(authContextLocalsKey, ctx)
|
||||||
// // case-insensitive scheme matching and tolerant whitespace handling.
|
c.Locals(authUserLocalsKey, user)
|
||||||
// func bearerToken(c *fiber.Ctx) string {
|
|
||||||
// parts := strings.Fields(c.Get("Authorization"))
|
|
||||||
// if len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") {
|
|
||||||
// return strings.TrimSpace(parts[1])
|
|
||||||
// }
|
|
||||||
// return ""
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func hasAllScopes(have, required []string) bool {
|
return c.Next()
|
||||||
// if len(required) == 0 {
|
}
|
||||||
// return true
|
}
|
||||||
// }
|
|
||||||
// set := make(map[string]struct{}, len(have))
|
// AuthenticatedUser returns the authenticated user populated by Auth.
|
||||||
// for _, s := range have {
|
func AuthenticatedUser(c *fiber.Ctx) (*entity.User, bool) {
|
||||||
// s = strings.ToLower(strings.TrimSpace(s))
|
value := c.Locals(authUserLocalsKey)
|
||||||
// if s != "" {
|
if user, ok := value.(*entity.User); ok && user != nil {
|
||||||
// set[s] = struct{}{}
|
return user, true
|
||||||
// }
|
}
|
||||||
// }
|
return nil, false
|
||||||
// for _, r := range required {
|
}
|
||||||
// r = strings.ToLower(strings.TrimSpace(r))
|
|
||||||
// if r == "" {
|
// AuthDetails returns the full authentication context (token, claims, user).
|
||||||
// continue
|
func AuthDetails(c *fiber.Ctx) (*AuthContext, bool) {
|
||||||
// }
|
value := c.Locals(authContextLocalsKey)
|
||||||
// if _, ok := set[r]; !ok {
|
if ctx, ok := value.(*AuthContext); ok && ctx != nil {
|
||||||
// return false
|
return ctx, true
|
||||||
// }
|
}
|
||||||
// }
|
return nil, false
|
||||||
// return true
|
}
|
||||||
// }
|
|
||||||
|
// ensureNotRevoked ensures the token is not revoked or superseded by a forced logout.
|
||||||
|
func ensureNotRevoked(c *fiber.Ctx, token string, verification *sso.VerificationResult) error {
|
||||||
|
revoker := session.GetRevocationStore()
|
||||||
|
if revoker == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if fingerprint := session.TokenFingerprint(token); fingerprint != "" {
|
||||||
|
revoked, err := revoker.IsRevoked(c.Context(), fingerprint)
|
||||||
|
if err != nil {
|
||||||
|
utils.Log.WithError(err).Warn("auth: token revocation check failed")
|
||||||
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
|
}
|
||||||
|
if revoked {
|
||||||
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if verification.UserID == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logoutAt, err := revoker.UserLogoutTime(c.Context(), verification.UserID)
|
||||||
|
if err != nil {
|
||||||
|
utils.Log.WithError(err).Warn("auth: failed to load user logout marker")
|
||||||
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
|
}
|
||||||
|
if logoutAt.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := verification.Claims
|
||||||
|
if claims == nil || claims.IssuedAt == nil {
|
||||||
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
|
}
|
||||||
|
|
||||||
|
issuedAt := claims.IssuedAt.Time
|
||||||
|
// Treat tokens issued at or before the forced logout timestamp as invalid.
|
||||||
|
if !issuedAt.After(logoutAt) {
|
||||||
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bearerToken extracts a Bearer token from the Authorization header using
|
||||||
|
// case-insensitive scheme matching and tolerant whitespace handling.
|
||||||
|
func bearerToken(c *fiber.Ctx) string {
|
||||||
|
parts := strings.Fields(c.Get("Authorization"))
|
||||||
|
if len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") {
|
||||||
|
return strings.TrimSpace(parts[1])
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasAllScopes(have, required []string) bool {
|
||||||
|
if len(required) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
set := make(map[string]struct{}, len(have))
|
||||||
|
for _, s := range have {
|
||||||
|
s = strings.ToLower(strings.TrimSpace(s))
|
||||||
|
if s != "" {
|
||||||
|
set[s] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, r := range required {
|
||||||
|
r = strings.ToLower(strings.TrimSpace(r))
|
||||||
|
if r == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := set[r]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequirePermissions ensures the authenticated user possesses all specified permissions.
|
||||||
|
func RequirePermissions(perms ...string) fiber.Handler {
|
||||||
|
required := canonicalPermissions(perms)
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
if len(required) == 0 {
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, ok := AuthDetails(c)
|
||||||
|
if !ok || ctx == nil {
|
||||||
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
|
}
|
||||||
|
|
||||||
|
userPerms := ctx.permissionSet()
|
||||||
|
if len(userPerms) == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusForbidden, "Insufficient permission")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, perm := range required {
|
||||||
|
if _, has := userPerms[perm]; !has {
|
||||||
|
return fiber.NewError(fiber.StatusForbidden, "Insufficient permission")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPermission reports whether the current request context includes the given permission.
|
||||||
|
func HasPermission(c *fiber.Ctx, perm string) bool {
|
||||||
|
ctx, ok := AuthDetails(c)
|
||||||
|
if !ok || ctx == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
perm = canonicalPermission(perm)
|
||||||
|
if perm == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, has := ctx.permissionSet()[perm]
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AuthContext) permissionSet() map[string]struct{} {
|
||||||
|
if a == nil || a.Permissions == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return a.Permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
func canonicalPermissions(perms []string) []string {
|
||||||
|
out := make([]string, 0, len(perms))
|
||||||
|
seen := make(map[string]struct{}, len(perms))
|
||||||
|
for _, perm := range perms {
|
||||||
|
if canonical := canonicalPermission(perm); canonical != "" {
|
||||||
|
if _, ok := seen[canonical]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[canonical] = struct{}{}
|
||||||
|
out = append(out, canonical)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func canonicalPermission(perm string) string {
|
||||||
|
return strings.ToLower(strings.TrimSpace(perm))
|
||||||
|
}
|
||||||
@@ -202,21 +202,7 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu
|
|||||||
if query.TransactionType != "" {
|
if query.TransactionType != "" {
|
||||||
db = db.Where("transaction_type = ?", strings.ToUpper(query.TransactionType))
|
db = db.Where("transaction_type = ?", strings.ToUpper(query.TransactionType))
|
||||||
}
|
}
|
||||||
if query.ProductID > 0 {
|
db = s.StockLogsRepository.ApplyProductWarehouseFilters(db, uint(query.ProductID), uint(query.WarehouseID))
|
||||||
db = db.Joins("JOIN product_warehouses ON product_warehouses.id = stock_logs.product_warehouse_id").
|
|
||||||
Where("product_warehouses.product_id = ?", query.ProductID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if query.WarehouseID > 0 {
|
|
||||||
if query.ProductID > 0 {
|
|
||||||
|
|
||||||
db = db.Where("product_warehouses.warehouse_id = ?", query.WarehouseID)
|
|
||||||
} else {
|
|
||||||
|
|
||||||
db = db.Joins("JOIN product_warehouses ON product_warehouses.id = stock_logs.product_warehouse_id").
|
|
||||||
Where("product_warehouses.warehouse_id = ?", query.WarehouseID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.Order("created_at DESC")
|
return db.Order("created_at DESC")
|
||||||
})
|
})
|
||||||
|
|||||||
+5
-2
@@ -28,6 +28,11 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error {
|
|||||||
Limit: c.QueryInt("limit", 10),
|
Limit: c.QueryInt("limit", 10),
|
||||||
ProductId: uint(c.QueryInt("product_id", 0)),
|
ProductId: uint(c.QueryInt("product_id", 0)),
|
||||||
WarehouseId: uint(c.QueryInt("warehouse_id", 0)),
|
WarehouseId: uint(c.QueryInt("warehouse_id", 0)),
|
||||||
|
Flags: c.Query("flags", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.ProductWarehouseService.GetAll(c, query)
|
result, totalResults, err := u.ProductWarehouseService.GetAll(c, query)
|
||||||
@@ -71,5 +76,3 @@ func (u *ProductWarehouseController) GetOne(c *fiber.Ctx) error {
|
|||||||
Data: dto.ToProductWarehouseListDTO(*result),
|
Data: dto.ToProductWarehouseListDTO(*result),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ func (ProductWarehouseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, v
|
|||||||
|
|
||||||
ProductWarehouseRoutes(router, userService, productWarehouseService)
|
ProductWarehouseRoutes(router, userService, productWarehouseService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+89
-15
@@ -2,6 +2,7 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
@@ -16,23 +17,36 @@ type ProductWarehouseRepository interface {
|
|||||||
ProductWarehouseExistByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (bool, error)
|
ProductWarehouseExistByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (bool, error)
|
||||||
ExistsByID(ctx context.Context, id uint) (bool, error)
|
ExistsByID(ctx context.Context, id uint) (bool, error)
|
||||||
GetProductWarehouseByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (*entity.ProductWarehouse, error)
|
GetProductWarehouseByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (*entity.ProductWarehouse, error)
|
||||||
|
GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error)
|
||||||
|
GetLatestByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint, db *gorm.DB) (*entity.ProductWarehouse, error)
|
||||||
|
ApplyFlagsFilter(db *gorm.DB, flags []string) *gorm.DB
|
||||||
|
AdjustQuantities(ctx context.Context, deltas map[uint]float64, modifier func(*gorm.DB) *gorm.DB) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductWarehouseRepositoryImpl struct {
|
type ProductWarehouseRepositoryImpl struct {
|
||||||
*repository.BaseRepositoryImpl[entity.ProductWarehouse]
|
*repository.BaseRepositoryImpl[entity.ProductWarehouse]
|
||||||
db *gorm.DB
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProductWarehouseRepository(db *gorm.DB) ProductWarehouseRepository {
|
func NewProductWarehouseRepository(db *gorm.DB) ProductWarehouseRepository {
|
||||||
return &ProductWarehouseRepositoryImpl{
|
return &ProductWarehouseRepositoryImpl{
|
||||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProductWarehouse](db),
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProductWarehouse](db),
|
||||||
db: db,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ProductWarehouseRepositoryImpl) IsProductExist(ctx context.Context, productId uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.Product](ctx, r.DB(), productId)
|
||||||
|
}
|
||||||
|
func (r *ProductWarehouseRepositoryImpl) IsWarehouseExist(ctx context.Context, warehouseId uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.Warehouse](ctx, r.DB(), warehouseId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProductWarehouseRepositoryImpl) ExistsByID(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.ProductWarehouse](ctx, r.DB(), id)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExists(ctx context.Context, productId, warehouseId uint, excludeID *uint) (bool, error) {
|
func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExists(ctx context.Context, productId, warehouseId uint, excludeID *uint) (bool, error) {
|
||||||
var count int64
|
var count int64
|
||||||
query := r.db.WithContext(ctx).Model(&entity.ProductWarehouse{}).
|
query := r.DB().WithContext(ctx).Model(&entity.ProductWarehouse{}).
|
||||||
Where("product_id = ? AND warehouse_id = ?", productId, warehouseId)
|
Where("product_id = ? AND warehouse_id = ?", productId, warehouseId)
|
||||||
if excludeID != nil {
|
if excludeID != nil {
|
||||||
query = query.Where("id != ?", *excludeID)
|
query = query.Where("id != ?", *excludeID)
|
||||||
@@ -43,20 +57,9 @@ func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExists(ctx context.Cont
|
|||||||
return count > 0, nil
|
return count > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProductWarehouseRepositoryImpl) IsProductExist(ctx context.Context, productId uint) (bool, error) {
|
|
||||||
return repository.Exists[entity.Product](ctx, r.db, productId)
|
|
||||||
}
|
|
||||||
func (r *ProductWarehouseRepositoryImpl) IsWarehouseExist(ctx context.Context, warehouseId uint) (bool, error) {
|
|
||||||
return repository.Exists[entity.Warehouse](ctx, r.db, warehouseId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ProductWarehouseRepositoryImpl) ExistsByID(ctx context.Context, id uint) (bool, error) {
|
|
||||||
return repository.Exists[entity.ProductWarehouse](ctx, r.db, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExistByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (bool, error) {
|
func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExistByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (bool, error) {
|
||||||
var count int64
|
var count int64
|
||||||
if err := r.db.WithContext(ctx).
|
if err := r.DB().WithContext(ctx).
|
||||||
Model(&entity.ProductWarehouse{}).
|
Model(&entity.ProductWarehouse{}).
|
||||||
Where("product_id = ? AND warehouse_id = ?", productId, warehouseId).
|
Where("product_id = ? AND warehouse_id = ?", productId, warehouseId).
|
||||||
Count(&count).Error; err != nil {
|
Count(&count).Error; err != nil {
|
||||||
@@ -72,3 +75,74 @@ func (r *ProductWarehouseRepositoryImpl) GetProductWarehouseByProductAndWarehous
|
|||||||
}
|
}
|
||||||
return &productWarehouse, nil
|
return &productWarehouse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ProductWarehouseRepositoryImpl) GetByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint) ([]entity.ProductWarehouse, error) {
|
||||||
|
var productWarehouses []entity.ProductWarehouse
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Table("product_warehouses").
|
||||||
|
Select("product_warehouses.*").
|
||||||
|
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
||||||
|
Joins("JOIN product_categories ON product_categories.id = products.product_category_id").
|
||||||
|
Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", categoryCode, warehouseId).
|
||||||
|
Order("product_warehouses.created_at DESC").
|
||||||
|
Find(&productWarehouses).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return productWarehouses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProductWarehouseRepositoryImpl) GetLatestByCategoryCodeAndWarehouseID(ctx context.Context, categoryCode string, warehouseId uint, db *gorm.DB) (*entity.ProductWarehouse, error) {
|
||||||
|
var productWarehouse entity.ProductWarehouse
|
||||||
|
query := r.DB()
|
||||||
|
if db != nil {
|
||||||
|
query = db
|
||||||
|
}
|
||||||
|
fmt.Println(warehouseId)
|
||||||
|
err := query.WithContext(ctx).
|
||||||
|
Table("product_warehouses").
|
||||||
|
Select("product_warehouses.*").
|
||||||
|
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
||||||
|
Joins("JOIN product_categories ON product_categories.id = products.product_category_id").
|
||||||
|
Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", categoryCode, warehouseId).
|
||||||
|
Order("product_warehouses.created_at DESC").
|
||||||
|
First(&productWarehouse).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &productWarehouse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProductWarehouseRepositoryImpl) ApplyFlagsFilter(db *gorm.DB, flags []string) *gorm.DB {
|
||||||
|
if len(flags) == 0 {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.
|
||||||
|
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
||||||
|
Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = ?", "products").
|
||||||
|
Where("flags.name IN ?", flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProductWarehouseRepositoryImpl) AdjustQuantities(ctx context.Context, deltas map[uint]float64, modifier func(*gorm.DB) *gorm.DB) error {
|
||||||
|
if len(deltas) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
base := r.DB().WithContext(ctx)
|
||||||
|
if modifier != nil {
|
||||||
|
base = modifier(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, delta := range deltas {
|
||||||
|
if delta == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := base.Model(&entity.ProductWarehouse{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("quantity", gorm.Expr("COALESCE(quantity,0) + ?", delta)).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package productWarehouses
|
package productWarehouses
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/controllers"
|
||||||
productWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/services"
|
productWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func ProductWarehouseRoutes(v1 fiber.Router, u user.UserService, s productWareho
|
|||||||
ctrl := controller.NewProductWarehouseController(s)
|
ctrl := controller.NewProductWarehouseController(s)
|
||||||
|
|
||||||
route := v1.Group("/product-warehouses")
|
route := v1.Group("/product-warehouses")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Get("/:id", ctrl.GetOne)
|
route.Get("/:id", ctrl.GetOne)
|
||||||
|
|||||||
@@ -49,8 +49,30 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.ProductId > 0 {
|
||||||
|
isProductExist, err := s.Repository.IsProductExist(c.Context(), params.ProductId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if !isProductExist {
|
||||||
|
return nil, 0, fiber.NewError(fiber.StatusNotFound, "Product not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.WarehouseId > 0 {
|
||||||
|
isWarehouseExist, err := s.Repository.IsWarehouseExist(c.Context(), params.WarehouseId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if !isWarehouseExist {
|
||||||
|
return nil, 0, fiber.NewError(fiber.StatusNotFound, "Warehouse not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
offset := (params.Page - 1) * params.Limit
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
|
cleanFlags := utils.ParseFlags(params.Flags)
|
||||||
|
|
||||||
productWarehouses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
productWarehouses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
db = s.withRelations(db)
|
db = s.withRelations(db)
|
||||||
|
|
||||||
@@ -62,6 +84,8 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
|
|||||||
db = db.Where("warehouse_id = ?", params.WarehouseId)
|
db = db.Where("warehouse_id = ?", params.WarehouseId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db = s.Repository.ApplyFlagsFilter(db, cleanFlags)
|
||||||
|
|
||||||
return db.Order("created_at DESC").Order("updated_at DESC")
|
return db.Order("created_at DESC").Order("updated_at DESC")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -13,8 +13,9 @@ type Update struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
Page int `query:"page" validate:"omitempty,number,min=1"`
|
Page int `query:"page" validate:"omitempty,number,min=1"`
|
||||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
||||||
ProductId uint `query:"product_id" validate:"omitempty,number,min=1"`
|
ProductId uint `query:"product_id" validate:"omitempty,number,min=1"`
|
||||||
WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"`
|
WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"`
|
||||||
|
Flags string `query:"flags" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
productWarehouses "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses"
|
|
||||||
adjustments "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments"
|
adjustments "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments"
|
||||||
|
productWarehouses "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses"
|
||||||
transfers "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers"
|
transfers "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers"
|
||||||
// MODULE IMPORTS
|
// MODULE IMPORTS
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package transfers
|
package transfers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/controllers"
|
||||||
transfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services"
|
transfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func TransferRoutes(v1 fiber.Router, u user.UserService, s transfer.TransferServ
|
|||||||
ctrl := controller.NewTransferController(s)
|
ctrl := controller.NewTransferController(s)
|
||||||
|
|
||||||
route := v1.Group("/transfers")
|
route := v1.Group("/transfers")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ func (u *AreaController) GetAll(c *fiber.Ctx) error {
|
|||||||
Search: c.Query("search", ""),
|
Search: c.Query("search", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.AreaService.GetAll(c, query)
|
result, totalResults, err := u.AreaService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ func (AreaModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *val
|
|||||||
|
|
||||||
AreaRoutes(router, userService, areaService)
|
AreaRoutes(router, userService, areaService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package areas
|
package areas
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/controllers"
|
||||||
area "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/services"
|
area "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func AreaRoutes(v1 fiber.Router, u user.UserService, s area.AreaService) {
|
|||||||
ctrl := controller.NewAreaController(s)
|
ctrl := controller.NewAreaController(s)
|
||||||
|
|
||||||
route := v1.Group("/areas")
|
route := v1.Group("/areas")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ func (u *BankController) GetAll(c *fiber.Ctx) error {
|
|||||||
Search: c.Query("search", ""),
|
Search: c.Query("search", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.BankService.GetAll(c, query)
|
result, totalResults, err := u.BankService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ func (BankModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *val
|
|||||||
|
|
||||||
BankRoutes(router, userService, bankService)
|
BankRoutes(router, userService, bankService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
type BankRepository interface {
|
type BankRepository interface {
|
||||||
repository.BaseRepository[entity.Bank]
|
repository.BaseRepository[entity.Bank]
|
||||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||||
|
AccountNumberExists(ctx context.Context, accountNumber string, excludeID *uint) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BankRepositoryImpl struct {
|
type BankRepositoryImpl struct {
|
||||||
@@ -28,3 +29,7 @@ func NewBankRepository(db *gorm.DB) BankRepository {
|
|||||||
func (r *BankRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
func (r *BankRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
||||||
return repository.ExistsByName[entity.Bank](ctx, r.db, name, excludeID)
|
return repository.ExistsByName[entity.Bank](ctx, r.db, name, excludeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *BankRepositoryImpl) AccountNumberExists(ctx context.Context, accountNumber string, excludeID *uint) (bool, error) {
|
||||||
|
return repository.ExistsByField[entity.Bank](ctx, r.db, "account_number", accountNumber, excludeID)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package banks
|
package banks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/banks/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/banks/controllers"
|
||||||
bank "gitlab.com/mbugroup/lti-api.git/internal/modules/master/banks/services"
|
bank "gitlab.com/mbugroup/lti-api.git/internal/modules/master/banks/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func BankRoutes(v1 fiber.Router, u user.UserService, s bank.BankService) {
|
|||||||
ctrl := controller.NewBankController(s)
|
ctrl := controller.NewBankController(s)
|
||||||
|
|
||||||
route := v1.Group("/banks")
|
route := v1.Group("/banks")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -87,6 +87,13 @@ func (s *bankService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.B
|
|||||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Bank with name %s already exists", req.Name))
|
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Bank with name %s already exists", req.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if exists, err := s.Repository.AccountNumberExists(c.Context(), req.AccountNumber, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to check bank account number: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check bank account number")
|
||||||
|
} else if exists {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Bank with account number %s already exists", req.AccountNumber))
|
||||||
|
}
|
||||||
|
|
||||||
createBody := &entity.Bank{
|
createBody := &entity.Bank{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Alias: req.Alias,
|
Alias: req.Alias,
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ func (u *CustomerController) GetAll(c *fiber.Ctx) error {
|
|||||||
Search: c.Query("search", ""),
|
Search: c.Query("search", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.CustomerService.GetAll(c, query)
|
result, totalResults, err := u.CustomerService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ func (CustomerModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
|
|
||||||
CustomerRoutes(router, userService, customerService)
|
CustomerRoutes(router, userService, customerService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package customers
|
package customers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/controllers"
|
||||||
customer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/services"
|
customer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func CustomerRoutes(v1 fiber.Router, u user.UserService, s customer.CustomerServ
|
|||||||
ctrl := controller.NewCustomerController(s)
|
ctrl := controller.NewCustomerController(s)
|
||||||
|
|
||||||
route := v1.Group("/customers")
|
route := v1.Group("/customers")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ func (u *FcrController) GetAll(c *fiber.Ctx) error {
|
|||||||
Search: c.Query("search", ""),
|
Search: c.Query("search", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.FcrService.GetAll(c, query)
|
result, totalResults, err := u.FcrService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package fcrs
|
package fcrs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/controllers"
|
||||||
fcr "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/services"
|
fcr "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func FcrRoutes(v1 fiber.Router, u user.UserService, s fcr.FcrService) {
|
|||||||
ctrl := controller.NewFcrController(s)
|
ctrl := controller.NewFcrController(s)
|
||||||
|
|
||||||
route := v1.Group("/fcrs")
|
route := v1.Group("/fcrs")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ func (u *FlockController) GetAll(c *fiber.Ctx) error {
|
|||||||
Search: c.Query("search", ""),
|
Search: c.Query("search", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.FlockService.GetAll(c, query)
|
result, totalResults, err := u.FlockService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ func ToFlockListDTO(e entity.Flock) FlockListDTO {
|
|||||||
|
|
||||||
return FlockListDTO{
|
return FlockListDTO{
|
||||||
FlockBaseDTO: ToFlockBaseDTO(e),
|
FlockBaseDTO: ToFlockBaseDTO(e),
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
type FlockRepository interface {
|
type FlockRepository interface {
|
||||||
repository.BaseRepository[entity.Flock]
|
repository.BaseRepository[entity.Flock]
|
||||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||||
|
GetByName(ctx context.Context, name string) (*entity.Flock, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlockRepositoryImpl struct {
|
type FlockRepositoryImpl struct {
|
||||||
@@ -28,3 +29,15 @@ func NewFlockRepository(db *gorm.DB) FlockRepository {
|
|||||||
func (r *FlockRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
func (r *FlockRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
||||||
return repository.ExistsByName[entity.Flock](ctx, r.db, name, excludeID)
|
return repository.ExistsByName[entity.Flock](ctx, r.db, name, excludeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *FlockRepositoryImpl) GetByName(ctx context.Context, name string) (*entity.Flock, error) {
|
||||||
|
var flock entity.Flock
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("LOWER(name) = LOWER(?)", name).
|
||||||
|
Where("deleted_at IS NULL").
|
||||||
|
First(&flock).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &flock, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package flocks
|
package flocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/controllers"
|
||||||
flock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/services"
|
flock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func FlockRoutes(v1 fiber.Router, u user.UserService, s flock.FlockService) {
|
|||||||
ctrl := controller.NewFlockController(s)
|
ctrl := controller.NewFlockController(s)
|
||||||
|
|
||||||
route := v1.Group("/flocks")
|
route := v1.Group("/flocks")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty"`
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ func (u *KandangController) GetAll(c *fiber.Ctx) error {
|
|||||||
PicId: c.QueryInt("pic_id", 0),
|
PicId: c.QueryInt("pic_id", 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.KandangService.GetAll(c, query)
|
result, totalResults, err := u.KandangService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ func (KandangModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
|||||||
|
|
||||||
KandangRoutes(router, userService, kandangService)
|
KandangRoutes(router, userService, kandangService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
@@ -18,6 +19,8 @@ type KandangRepository interface {
|
|||||||
GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.Kandang, error)
|
GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.Kandang, error)
|
||||||
HasActiveKandangForProjectFlock(ctx context.Context, projectFlockID uint, excludeID *uint) (bool, error)
|
HasActiveKandangForProjectFlock(ctx context.Context, projectFlockID uint, excludeID *uint) (bool, error)
|
||||||
UpdateStatusByProjectFlockID(ctx context.Context, projectFlockID uint, status utils.KandangStatus) error
|
UpdateStatusByProjectFlockID(ctx context.Context, projectFlockID uint, status utils.KandangStatus) error
|
||||||
|
UpsertProjectFlockKandang(ctx context.Context, projectFlockID, kandangID uint) error
|
||||||
|
UpdateStatusByIDs(ctx context.Context, kandangIDs []uint, status utils.KandangStatus) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type KandangRepositoryImpl struct {
|
type KandangRepositoryImpl struct {
|
||||||
@@ -59,12 +62,13 @@ func (r *KandangRepositoryImpl) ProjectFlockExists(ctx context.Context, projectF
|
|||||||
func (r *KandangRepositoryImpl) HasActiveKandangForProjectFlock(ctx context.Context, projectFlockID uint, excludeID *uint) (bool, error) {
|
func (r *KandangRepositoryImpl) HasActiveKandangForProjectFlock(ctx context.Context, projectFlockID uint, excludeID *uint) (bool, error) {
|
||||||
var count int64
|
var count int64
|
||||||
q := r.db.WithContext(ctx).
|
q := r.db.WithContext(ctx).
|
||||||
Model(&entity.Kandang{}).
|
Table("kandangs k").
|
||||||
Where("project_flock_id = ?", projectFlockID).
|
Joins("JOIN project_flock_kandangs pfk ON pfk.kandang_id = k.id").
|
||||||
Where("status = ?", utils.KandangStatusActive).
|
Where("pfk.project_flock_id = ?", projectFlockID).
|
||||||
Where("deleted_at IS NULL")
|
Where("k.status = ?", utils.KandangStatusActive).
|
||||||
|
Where("k.deleted_at IS NULL")
|
||||||
if excludeID != nil {
|
if excludeID != nil {
|
||||||
q = q.Where("id <> ?", *excludeID)
|
q = q.Where("k.id <> ?", *excludeID)
|
||||||
}
|
}
|
||||||
if err := q.Count(&count).Error; err != nil {
|
if err := q.Count(&count).Error; err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -75,17 +79,58 @@ func (r *KandangRepositoryImpl) HasActiveKandangForProjectFlock(ctx context.Cont
|
|||||||
func (r *KandangRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.Kandang, error) {
|
func (r *KandangRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.Kandang, error) {
|
||||||
kandang := new(entity.Kandang)
|
kandang := new(entity.Kandang)
|
||||||
err := r.db.WithContext(ctx).
|
err := r.db.WithContext(ctx).
|
||||||
Where("project_flock_id = ?", projectFlockID).
|
Table("kandangs k").
|
||||||
First(kandang).Error
|
Joins("JOIN project_flock_kandangs pfk ON pfk.kandang_id = k.id").
|
||||||
|
Where("pfk.project_flock_id = ?", projectFlockID).
|
||||||
|
Where("k.deleted_at IS NULL").
|
||||||
|
Order("k.id ASC").
|
||||||
|
Limit(1).
|
||||||
|
Find(kandang).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if kandang.Id == 0 {
|
||||||
|
return nil, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
return kandang, nil
|
return kandang, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *KandangRepositoryImpl) UpdateStatusByProjectFlockID(ctx context.Context, projectFlockID uint, status utils.KandangStatus) error {
|
func (r *KandangRepositoryImpl) UpdateStatusByProjectFlockID(ctx context.Context, projectFlockID uint, status utils.KandangStatus) error {
|
||||||
|
sub := r.db.WithContext(ctx).
|
||||||
|
Table("project_flock_kandangs").
|
||||||
|
Select("kandang_id").
|
||||||
|
Where("project_flock_id = ?", projectFlockID)
|
||||||
|
|
||||||
return r.db.WithContext(ctx).
|
return r.db.WithContext(ctx).
|
||||||
Model(&entity.Kandang{}).
|
Model(&entity.Kandang{}).
|
||||||
Where("project_flock_id = ?", projectFlockID).
|
Where("id IN (?)", sub).
|
||||||
|
Where("deleted_at IS NULL").
|
||||||
|
Update("status", string(status)).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *KandangRepositoryImpl) UpsertProjectFlockKandang(ctx context.Context, projectFlockID, kandangID uint) error {
|
||||||
|
var link entity.ProjectFlockKandang
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("project_flock_id = ? AND kandang_id = ?", projectFlockID, kandangID).
|
||||||
|
First(&link).Error
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
link = entity.ProjectFlockKandang{
|
||||||
|
ProjectFlockId: projectFlockID,
|
||||||
|
KandangId: kandangID,
|
||||||
|
}
|
||||||
|
return r.db.WithContext(ctx).Create(&link).Error
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *KandangRepositoryImpl) UpdateStatusByIDs(ctx context.Context, kandangIDs []uint, status utils.KandangStatus) error {
|
||||||
|
if len(kandangIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return r.db.WithContext(ctx).
|
||||||
|
Model(&entity.Kandang{}).
|
||||||
|
Where("id IN ?", kandangIDs).
|
||||||
|
Where("deleted_at IS NULL").
|
||||||
Update("status", string(status)).Error
|
Update("status", string(status)).Error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package kandangs
|
package kandangs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/controllers"
|
||||||
kandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/services"
|
kandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func KandangRoutes(v1 fiber.Router, u user.UserService, s kandang.KandangService
|
|||||||
ctrl := controller.NewKandangController(s)
|
ctrl := controller.NewKandangController(s)
|
||||||
|
|
||||||
route := v1.Group("/kandangs")
|
route := v1.Group("/kandangs")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ func NewKandangService(repo repository.KandangRepository, validate *validator.Va
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s kandangService) withRelations(db *gorm.DB) *gorm.DB {
|
func (s kandangService) withRelations(db *gorm.DB) *gorm.DB {
|
||||||
return db.Preload("CreatedUser").Preload("Location").Preload("Pic")
|
return db.Preload("CreatedUser").Preload("Location").Preload("Pic").Preload("ProjectFlockKandangs.ProjectFlock")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s kandangService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Kandang, int64, error) {
|
func (s kandangService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Kandang, int64, error) {
|
||||||
@@ -110,7 +111,6 @@ func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang status")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang status")
|
||||||
}
|
}
|
||||||
|
|
||||||
var projectFlockID *uint
|
|
||||||
if req.ProjectFlockId != nil {
|
if req.ProjectFlockId != nil {
|
||||||
if exists, err := s.Repository.ProjectFlockExists(c.Context(), *req.ProjectFlockId); err != nil {
|
if exists, err := s.Repository.ProjectFlockExists(c.Context(), *req.ProjectFlockId); err != nil {
|
||||||
s.Log.Errorf("Failed to check project flock existence: %+v", err)
|
s.Log.Errorf("Failed to check project flock existence: %+v", err)
|
||||||
@@ -128,18 +128,15 @@ func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
idCopy := *req.ProjectFlockId
|
|
||||||
projectFlockID = &idCopy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: created by dummy
|
//TODO: created by dummy
|
||||||
createBody := &entity.Kandang{
|
createBody := &entity.Kandang{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
LocationId: req.LocationId,
|
LocationId: req.LocationId,
|
||||||
Status: status,
|
Status: status,
|
||||||
PicId: req.PicId,
|
PicId: req.PicId,
|
||||||
ProjectFlockId: projectFlockID,
|
CreatedBy: 1,
|
||||||
CreatedBy: 1,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
@@ -147,6 +144,12 @@ func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.ProjectFlockId != nil {
|
||||||
|
if err := s.Repository.UpsertProjectFlockKandang(c.Context(), *req.ProjectFlockId, createBody.Id); err != nil {
|
||||||
|
s.Log.Errorf("Failed to link kandang to project_flock via pivot: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to link kandang to project flock")
|
||||||
|
}
|
||||||
|
}
|
||||||
return s.GetOne(c, createBody.Id)
|
return s.GetOne(c, createBody.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +204,6 @@ func (s kandangService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
finalStatus = status
|
finalStatus = status
|
||||||
}
|
}
|
||||||
|
|
||||||
projectFlockIDToUse := existing.ProjectFlockId
|
|
||||||
if req.ProjectFlockId != nil {
|
if req.ProjectFlockId != nil {
|
||||||
if exists, err := s.Repository.ProjectFlockExists(c.Context(), *req.ProjectFlockId); err != nil {
|
if exists, err := s.Repository.ProjectFlockExists(c.Context(), *req.ProjectFlockId); err != nil {
|
||||||
s.Log.Errorf("Failed to check project flock existence: %+v", err)
|
s.Log.Errorf("Failed to check project flock existence: %+v", err)
|
||||||
@@ -209,30 +211,33 @@ func (s kandangService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
} else if !exists {
|
} else if !exists {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Project flock with id %d not found", *req.ProjectFlockId))
|
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Project flock with id %d not found", *req.ProjectFlockId))
|
||||||
}
|
}
|
||||||
idCopy := *req.ProjectFlockId
|
|
||||||
projectFlockIDToUse = &idCopy
|
|
||||||
updateBody["project_flock_id"] = idCopy
|
|
||||||
}
|
|
||||||
|
|
||||||
if projectFlockIDToUse != nil && finalStatus == string(utils.KandangStatusActive) {
|
// Kalau status jadi ACTIVE, pastikan tidak ada kandang aktif lain pada project flock tsb (hitung via pivot)
|
||||||
if active, err := s.Repository.HasActiveKandangForProjectFlock(c.Context(), *projectFlockIDToUse, &id); err != nil {
|
if finalStatus == string(utils.KandangStatusActive) {
|
||||||
s.Log.Errorf("Failed to check kandang activity for project flock %d: %+v", *projectFlockIDToUse, err)
|
if active, err := s.Repository.HasActiveKandangForProjectFlock(c.Context(), *req.ProjectFlockId, &id); err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check active kandang for project flock")
|
s.Log.Errorf("Failed to check kandang activity for project flock %d: %+v", *req.ProjectFlockId, err)
|
||||||
} else if active {
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check active kandang for project flock")
|
||||||
return nil, fiber.NewError(fiber.StatusConflict, "Project flock already has an active kandang")
|
} else if active {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, "Project flock already has an active kandang")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(updateBody) == 0 {
|
if len(updateBody) > 0 {
|
||||||
return s.GetOne(c, id)
|
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Kandang not found")
|
||||||
|
}
|
||||||
|
s.Log.Errorf("Failed to update kandang: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
if req.ProjectFlockId != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if err := s.Repository.UpsertProjectFlockKandang(c.Context(), *req.ProjectFlockId, id); err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Kandang not found")
|
s.Log.Errorf("Failed to upsert pivot kandang-project_flock: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to link kandang to project flock")
|
||||||
}
|
}
|
||||||
s.Log.Errorf("Failed to update kandang: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.GetOne(c, id)
|
return s.GetOne(c, id)
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ func (u *LocationController) GetAll(c *fiber.Ctx) error {
|
|||||||
AreaId: c.QueryInt("area_id", 0),
|
AreaId: c.QueryInt("area_id", 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.LocationService.GetAll(c, query)
|
result, totalResults, err := u.LocationService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ func (LocationModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
|
|
||||||
LocationRoutes(router, userService, locationService)
|
LocationRoutes(router, userService, locationService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package locations
|
package locations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/controllers"
|
||||||
location "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/services"
|
location "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func LocationRoutes(v1 fiber.Router, u user.UserService, s location.LocationServ
|
|||||||
ctrl := controller.NewLocationController(s)
|
ctrl := controller.NewLocationController(s)
|
||||||
|
|
||||||
route := v1.Group("/locations")
|
route := v1.Group("/locations")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ func (u *NonstockController) GetAll(c *fiber.Ctx) error {
|
|||||||
Search: c.Query("search", ""),
|
Search: c.Query("search", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.NonstockService.GetAll(c, query)
|
result, totalResults, err := u.NonstockService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ func (NonstockModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
|
|
||||||
NonstockRoutes(router, userService, nonstockService)
|
NonstockRoutes(router, userService, nonstockService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package nonstocks
|
package nonstocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks/controllers"
|
||||||
nonstock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks/services"
|
nonstock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func NonstockRoutes(v1 fiber.Router, u user.UserService, s nonstock.NonstockServ
|
|||||||
ctrl := controller.NewNonstockController(s)
|
ctrl := controller.NewNonstockController(s)
|
||||||
|
|
||||||
route := v1.Group("/nonstocks")
|
route := v1.Group("/nonstocks")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ func (u *ProductCategoryController) GetAll(c *fiber.Ctx) error {
|
|||||||
Search: c.Query("search", ""),
|
Search: c.Query("search", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.ProductCategoryService.GetAll(c, query)
|
result, totalResults, err := u.ProductCategoryService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package productcategories
|
package productcategories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/controllers"
|
||||||
productCategory "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/services"
|
productCategory "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func ProductCategoryRoutes(v1 fiber.Router, u user.UserService, s productCategor
|
|||||||
ctrl := controller.NewProductCategoryController(s)
|
ctrl := controller.NewProductCategoryController(s)
|
||||||
|
|
||||||
route := v1.Group("/product-categories")
|
route := v1.Group("/product-categories")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ func (u *ProductController) GetAll(c *fiber.Ctx) error {
|
|||||||
ProductCategoryID: c.QueryInt("product_category_id", 0),
|
ProductCategoryID: c.QueryInt("product_category_id", 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.ProductService.GetAll(c, query)
|
result, totalResults, err := u.ProductService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ func (ProductModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
|||||||
|
|
||||||
ProductRoutes(router, userService, productService)
|
ProductRoutes(router, userService, productService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package products
|
package products
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/controllers"
|
||||||
product "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/services"
|
product "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func ProductRoutes(v1 fiber.Router, u user.UserService, s product.ProductService
|
|||||||
ctrl := controller.NewProductController(s)
|
ctrl := controller.NewProductController(s)
|
||||||
|
|
||||||
route := v1.Group("/products")
|
route := v1.Group("/products")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
banks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/banks"
|
banks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/banks"
|
||||||
customers "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers"
|
customers "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers"
|
||||||
fcrs "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs"
|
fcrs "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs"
|
||||||
|
flocks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks"
|
||||||
kandangs "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs"
|
kandangs "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs"
|
||||||
locations "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations"
|
locations "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations"
|
||||||
nonstocks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks"
|
nonstocks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks"
|
||||||
@@ -19,7 +20,6 @@ import (
|
|||||||
suppliers "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers"
|
suppliers "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers"
|
||||||
uoms "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms"
|
uoms "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms"
|
||||||
warehouses "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses"
|
warehouses "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses"
|
||||||
flocks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks"
|
|
||||||
// MODULE IMPORTS
|
// MODULE IMPORTS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ func (u *SupplierController) GetAll(c *fiber.Ctx) error {
|
|||||||
Search: c.Query("search", ""),
|
Search: c.Query("search", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.SupplierService.GetAll(c, query)
|
result, totalResults, err := u.SupplierService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ func (SupplierModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
|
|
||||||
SupplierRoutes(router, userService, supplierService)
|
SupplierRoutes(router, userService, supplierService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
type SupplierRepository interface {
|
type SupplierRepository interface {
|
||||||
repository.BaseRepository[entity.Supplier]
|
repository.BaseRepository[entity.Supplier]
|
||||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||||
|
AliasExists(ctx context.Context, alias string, excludeID *uint) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SupplierRepositoryImpl struct {
|
type SupplierRepositoryImpl struct {
|
||||||
@@ -29,3 +29,7 @@ func NewSupplierRepository(db *gorm.DB) SupplierRepository {
|
|||||||
func (r *SupplierRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
func (r *SupplierRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
||||||
return repository.ExistsByName[entity.Supplier](ctx, r.db, name, excludeID)
|
return repository.ExistsByName[entity.Supplier](ctx, r.db, name, excludeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *SupplierRepositoryImpl) AliasExists(ctx context.Context, alias string, excludeID *uint) (bool, error) {
|
||||||
|
return repository.ExistsByField[entity.Supplier](ctx, r.db, "alias", alias, excludeID)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package suppliers
|
package suppliers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/controllers"
|
||||||
supplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/services"
|
supplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func SupplierRoutes(v1 fiber.Router, u user.UserService, s supplier.SupplierServ
|
|||||||
ctrl := controller.NewSupplierController(s)
|
ctrl := controller.NewSupplierController(s)
|
||||||
|
|
||||||
route := v1.Group("/suppliers")
|
route := v1.Group("/suppliers")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -88,6 +88,13 @@ func (s *supplierService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti
|
|||||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Supplier with name %s already exists", req.Name))
|
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Supplier with name %s already exists", req.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if exists, err := s.Repository.AliasExists(c.Context(), strings.TrimSpace(strings.ToUpper(req.Alias)), nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to check supplier alias: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check supplier alias")
|
||||||
|
} else if exists {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Supplier with alias %s already exists", strings.TrimSpace(strings.ToUpper(req.Alias))))
|
||||||
|
}
|
||||||
|
|
||||||
typ := strings.ToUpper(req.Type)
|
typ := strings.ToUpper(req.Type)
|
||||||
if !utils.IsValidCustomerSupplierType(typ) {
|
if !utils.IsValidCustomerSupplierType(typ) {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid supplier type")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid supplier type")
|
||||||
@@ -143,6 +150,12 @@ func (s supplierService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint
|
|||||||
}
|
}
|
||||||
|
|
||||||
if req.Alias != nil {
|
if req.Alias != nil {
|
||||||
|
if exists, err := s.Repository.AliasExists(c.Context(), strings.TrimSpace(strings.ToUpper(*req.Alias)), &id); err != nil {
|
||||||
|
s.Log.Errorf("Failed to check supplier alias: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check supplier alias")
|
||||||
|
} else if exists {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Supplier with alias %s already exists", strings.TrimSpace(strings.ToUpper(*req.Alias))))
|
||||||
|
}
|
||||||
updateBody["alias"] = strings.TrimSpace(strings.ToUpper(*req.Alias))
|
updateBody["alias"] = strings.TrimSpace(strings.ToUpper(*req.Alias))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ func (u *UomController) GetAll(c *fiber.Ctx) error {
|
|||||||
Search: c.Query("search", ""),
|
Search: c.Query("search", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.UomService.GetAll(c, query)
|
result, totalResults, err := u.UomService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ func (UomModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *vali
|
|||||||
|
|
||||||
UomRoutes(router, userService, uomService)
|
UomRoutes(router, userService, uomService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package uoms
|
package uoms
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/controllers"
|
||||||
uom "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/services"
|
uom "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func UomRoutes(v1 fiber.Router, u user.UserService, s uom.UomService) {
|
|||||||
ctrl := controller.NewUomController(s)
|
ctrl := controller.NewUomController(s)
|
||||||
|
|
||||||
route := v1.Group("/uoms")
|
route := v1.Group("/uoms")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ func (u *WarehouseController) GetAll(c *fiber.Ctx) error {
|
|||||||
AreaId: c.QueryInt("area_id", 0),
|
AreaId: c.QueryInt("area_id", 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.WarehouseService.GetAll(c, query)
|
result, totalResults, err := u.WarehouseService.GetAll(c, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ func (WarehouseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
|
|
||||||
WarehouseRoutes(router, userService, warehouseService)
|
WarehouseRoutes(router, userService, warehouseService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package warehouses
|
package warehouses
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/controllers"
|
||||||
warehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/services"
|
warehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func WarehouseRoutes(v1 fiber.Router, u user.UserService, s warehouse.WarehouseS
|
|||||||
ctrl := controller.NewWarehouseController(s)
|
ctrl := controller.NewWarehouseController(s)
|
||||||
|
|
||||||
route := v1.Group("/warehouses")
|
route := v1.Group("/warehouses")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
flockBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
flockBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
||||||
kandangBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
kandangBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||||
locationBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
locationBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||||
|
pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
|
||||||
userBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
userBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -88,9 +89,9 @@ func ToUserBaseDTO(e entity.User) userBaseDTO.UserBaseDTO {
|
|||||||
|
|
||||||
func ToProjectFlockDTO(e entity.ProjectFlock) ProjectFlockDTO {
|
func ToProjectFlockDTO(e entity.ProjectFlock) ProjectFlockDTO {
|
||||||
var flock *flockBaseDTO.FlockBaseDTO
|
var flock *flockBaseDTO.FlockBaseDTO
|
||||||
if e.Flock.Id != 0 {
|
if base := pfutils.DeriveBaseName(e.FlockName); base != "" {
|
||||||
mapped := flockBaseDTO.ToFlockBaseDTO(e.Flock)
|
summary := flockBaseDTO.FlockBaseDTO{Id: 0, Name: base}
|
||||||
flock = &mapped
|
flock = &summary
|
||||||
}
|
}
|
||||||
var area *areaBaseDTO.AreaBaseDTO
|
var area *areaBaseDTO.AreaBaseDTO
|
||||||
if e.Area.Id != 0 {
|
if e.Area.Id != 0 {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package chickins
|
package chickins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/controllers"
|
||||||
chickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/services"
|
chickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,12 +13,7 @@ func ChickinRoutes(v1 fiber.Router, u user.UserService, s chickin.ChickinService
|
|||||||
ctrl := controller.NewChickinController(s)
|
ctrl := controller.NewChickinController(s)
|
||||||
|
|
||||||
route := v1.Group("/chickins")
|
route := v1.Group("/chickins")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ func (s chickinService) withRelations(db *gorm.DB) *gorm.DB {
|
|||||||
Preload("ProjectFlockKandang.Kandang.Location.Area").
|
Preload("ProjectFlockKandang.Kandang.Location.Area").
|
||||||
Preload("ProjectFlockKandang.Kandang.Pic").
|
Preload("ProjectFlockKandang.Kandang.Pic").
|
||||||
Preload("ProjectFlockKandang.ProjectFlock").
|
Preload("ProjectFlockKandang.ProjectFlock").
|
||||||
Preload("ProjectFlockKandang.ProjectFlock.Flock").
|
|
||||||
Preload("ProjectFlockKandang.ProjectFlock.Area").
|
Preload("ProjectFlockKandang.ProjectFlock.Area").
|
||||||
Preload("ProjectFlockKandang.ProjectFlock.Fcr").
|
Preload("ProjectFlockKandang.ProjectFlock.Fcr").
|
||||||
Preload("ProjectFlockKandang.ProjectFlock.Location").
|
Preload("ProjectFlockKandang.ProjectFlock.Location").
|
||||||
@@ -121,14 +120,8 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var productWarehouses []entity.ProductWarehouse
|
// move complex DB query into repository for cleaner service
|
||||||
err = s.ProductWarehouseRepo.DB().
|
productWarehouses, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(c.Context(), "DOC", warehouse.Id)
|
||||||
WithContext(c.Context()).
|
|
||||||
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
|
||||||
Joins("JOIN product_categories ON product_categories.id = products.product_category_id").
|
|
||||||
Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", warehouse.Id).
|
|
||||||
Order("created_at DESC").
|
|
||||||
Find(&productWarehouses).Error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to get product warehouses: %+v", err)
|
s.Log.Errorf("Failed to get product warehouses: %+v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -136,8 +129,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
if len(productWarehouses) == 0 {
|
if len(productWarehouses) == 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jumlahkan semua quantity DOC
|
|
||||||
totalQuantity := 0.0
|
totalQuantity := 0.0
|
||||||
for _, pw := range productWarehouses {
|
for _, pw := range productWarehouses {
|
||||||
totalQuantity += pw.Quantity
|
totalQuantity += pw.Quantity
|
||||||
@@ -147,7 +138,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Insufficient quantity in Product Warehouses")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Insufficient quantity in Product Warehouses")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buat satu chickin dengan total quantity
|
|
||||||
chickinDate, err := utils.ParseDateString(req.ChickInDate)
|
chickinDate, err := utils.ParseDateString(req.ChickInDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to parse chickin date: %+v", err)
|
s.Log.Errorf("Failed to parse chickin date: %+v", err)
|
||||||
@@ -157,7 +147,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
ProjectFlockKandangId: projectflockkandang.Id,
|
ProjectFlockKandangId: projectflockkandang.Id,
|
||||||
ChickInDate: chickinDate,
|
ChickInDate: chickinDate,
|
||||||
Quantity: totalQuantity,
|
Quantity: totalQuantity,
|
||||||
Note: "",
|
Note: req.Note,
|
||||||
CreatedBy: 1, //todo: ganti dengan user login
|
CreatedBy: 1, //todo: ganti dengan user login
|
||||||
}
|
}
|
||||||
err = s.Repository.CreateOne(c.Context(), newChickin, nil)
|
err = s.Repository.CreateOne(c.Context(), newChickin, nil)
|
||||||
@@ -176,7 +166,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// add ke detail chickin
|
|
||||||
newChickinDetail := &entity.ProjectChickinDetail{
|
newChickinDetail := &entity.ProjectChickinDetail{
|
||||||
ProjectChickinId: newChickin.Id,
|
ProjectChickinId: newChickin.Id,
|
||||||
ProductWarehouseId: pw.Id,
|
ProductWarehouseId: pw.Id,
|
||||||
@@ -232,6 +221,9 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
if req.ChickInDate != "" {
|
if req.ChickInDate != "" {
|
||||||
updateBody["chick_in_date"] = req.ChickInDate
|
updateBody["chick_in_date"] = req.ChickInDate
|
||||||
}
|
}
|
||||||
|
if req.Note != "" {
|
||||||
|
updateBody["note"] = req.Note
|
||||||
|
}
|
||||||
if len(updateBody) == 0 {
|
if len(updateBody) == 0 {
|
||||||
return s.GetOne(c, id)
|
return s.GetOne(c, id)
|
||||||
}
|
}
|
||||||
@@ -293,7 +285,6 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
return rollback(err)
|
return rollback(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper: restore quantities from details; returns (restored bool, error)
|
|
||||||
restoreFromDetails := func() (bool, error) {
|
restoreFromDetails := func() (bool, error) {
|
||||||
var details []entity.ProjectChickinDetail
|
var details []entity.ProjectChickinDetail
|
||||||
if err := tx.WithContext(c.Context()).Where("project_chickin_id = ?", chickin.Id).Find(&details).Error; err != nil {
|
if err := tx.WithContext(c.Context()).Where("project_chickin_id = ?", chickin.Id).Find(&details).Error; err != nil {
|
||||||
@@ -348,15 +339,12 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
return rollback(err)
|
return rollback(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var productWarehouse entity.ProductWarehouse
|
productWarehouse, err := s.ProductWarehouseRepo.GetLatestByCategoryCodeAndWarehouseID(
|
||||||
err = tx.WithContext(c.Context()).Table("product_warehouses").
|
c.Context(),
|
||||||
Select("product_warehouses.*").
|
"DOC",
|
||||||
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
warehouse.Id,
|
||||||
Joins("JOIN product_categories ON product_categories.id = products.product_category_id").
|
tx,
|
||||||
Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", warehouse.Id).
|
)
|
||||||
Order("product_warehouses.created_at DESC").
|
|
||||||
First(&productWarehouse).Error
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return rollback(fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse"))
|
return rollback(fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse"))
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package validation
|
|||||||
type Create struct {
|
type Create struct {
|
||||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required,number,min=1"`
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required,number,min=1"`
|
||||||
ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"`
|
ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"`
|
||||||
|
Note string `json:"note" validate:"omitempty`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"`
|
ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"`
|
||||||
|
Note string `json:"note" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
|
|||||||
@@ -222,11 +222,11 @@ func (u *ProjectflockController) Approval(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ProjectflockController) GetFlockPeriodSummary(c *fiber.Ctx) error {
|
func (u *ProjectflockController) GetFlockPeriodSummary(c *fiber.Ctx) error {
|
||||||
param := c.Params("flock_id")
|
param := c.Params("project_flock_kandang_id")
|
||||||
|
|
||||||
id, err := strconv.Atoi(param)
|
id, err := strconv.Atoi(param)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Flock Id")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_kandang_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
summary, err := u.ProjectflockService.GetFlockPeriodSummary(c, uint(id))
|
summary, err := u.ProjectflockService.GetFlockPeriodSummary(c, uint(id))
|
||||||
@@ -246,17 +246,39 @@ func (u *ProjectflockController) GetFlockPeriodSummary(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error {
|
func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error {
|
||||||
projectFlockIdStr := c.Query("project_flock_id", "")
|
projectFlockId := c.QueryInt("project_flock_id", 0)
|
||||||
kandangIdStr := c.Query("kandang_id", "")
|
kandangId := c.QueryInt("kandang_id", 0)
|
||||||
|
|
||||||
result, err := u.ProjectflockService.GetProjectFlockKandangByParams(c, "", projectFlockIdStr, kandangIdStr)
|
if projectFlockId == 0 || kandangId == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id or kandang_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, availableStock, err := u.ProjectflockService.GetProjectFlockKandangByProjectAndKandang(c, uint(projectFlockId), uint(kandangId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dtoResult := dto.ToProjectFlockKandangDTO(*result)
|
||||||
|
dtoResult.AvailableQuantity = float64(availableStock)
|
||||||
|
|
||||||
|
// populate available quantity for each kandang inside project_flock
|
||||||
|
if dtoResult.ProjectFlock != nil {
|
||||||
|
for i := range dtoResult.ProjectFlock.Kandangs {
|
||||||
|
kand := &dtoResult.ProjectFlock.Kandangs[i]
|
||||||
|
if kand.Id == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if q, qerr := u.ProjectflockService.GetAvailableDocQuantity(c, kand.Id); qerr == nil {
|
||||||
|
kand.AvailableQuantity = q
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove inner kandangs from project_flock to avoid duplication
|
||||||
|
dtoResult.ProjectFlock.Kandangs = nil
|
||||||
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).
|
return c.Status(fiber.StatusOK).
|
||||||
JSON(response.Success{Code: fiber.StatusOK,
|
JSON(response.Success{Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Get projectflock kandang successfully",
|
Message: "Get projectflock kandang successfully",
|
||||||
Data: dto.ToProjectFlockKandangDTO(*result)})
|
Data: dtoResult})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,19 +10,21 @@ import (
|
|||||||
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
||||||
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||||
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||||
|
// pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
|
||||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProjectFlockBaseDTO struct {
|
type ProjectFlockBaseDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Period int `json:"period"`
|
Period int `json:"period"`
|
||||||
|
FlockName string `json:"flock_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectFlockListDTO struct {
|
type ProjectFlockListDTO struct {
|
||||||
ProjectFlockBaseDTO
|
ProjectFlockBaseDTO
|
||||||
Flock *flockDTO.FlockBaseDTO `json:"flock,omitempty"`
|
// Flock *flockDTO.FlockBaseDTO `json:"flock,omitempty"`
|
||||||
Area *areaDTO.AreaBaseDTO `json:"area,omitempty"`
|
Area *areaDTO.AreaBaseDTO `json:"area,omitempty"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
Fcr *fcrDTO.FcrBaseDTO `json:"fcr,omitempty"`
|
Fcr *fcrDTO.FcrBaseDTO `json:"fcr,omitempty"`
|
||||||
@@ -58,11 +60,11 @@ func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var flockSummary *flockDTO.FlockBaseDTO
|
// var flockSummary *flockDTO.FlockBaseDTO
|
||||||
if e.Flock.Id != 0 {
|
// if baseName := pfutils.DeriveBaseName(e.FlockName); baseName != "" {
|
||||||
mapped := flockDTO.ToFlockBaseDTO(e.Flock)
|
// summary := flockDTO.FlockBaseDTO{Id: 0, Name: baseName}
|
||||||
flockSummary = &mapped
|
// flockSummary = &summary
|
||||||
}
|
// }
|
||||||
|
|
||||||
var areaSummary *areaDTO.AreaBaseDTO
|
var areaSummary *areaDTO.AreaBaseDTO
|
||||||
if e.Area.Id != 0 {
|
if e.Area.Id != 0 {
|
||||||
@@ -90,7 +92,7 @@ func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO {
|
|||||||
|
|
||||||
return ProjectFlockListDTO{
|
return ProjectFlockListDTO{
|
||||||
ProjectFlockBaseDTO: createProjectFlockBaseDTO(e),
|
ProjectFlockBaseDTO: createProjectFlockBaseDTO(e),
|
||||||
Flock: flockSummary,
|
// Flock: flockSummary,
|
||||||
Area: areaSummary,
|
Area: areaSummary,
|
||||||
Kandangs: kandangSummaries,
|
Kandangs: kandangSummaries,
|
||||||
Category: e.Category,
|
Category: e.Category,
|
||||||
@@ -144,8 +146,9 @@ func defaultProjectFlockLatestApproval(e entity.ProjectFlock) approvalDTO.Approv
|
|||||||
|
|
||||||
func createProjectFlockBaseDTO(e entity.ProjectFlock) ProjectFlockBaseDTO {
|
func createProjectFlockBaseDTO(e entity.ProjectFlock) ProjectFlockBaseDTO {
|
||||||
return ProjectFlockBaseDTO{
|
return ProjectFlockBaseDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Period: e.Period,
|
Period: e.Period,
|
||||||
|
FlockName: e.FlockName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import (
|
|||||||
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
||||||
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||||
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||||
|
pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
|
||||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// internal DTO used only for lookup response: project flock with kandangs carrying pivot ids
|
|
||||||
type KandangWithPivotDTO struct {
|
type KandangWithPivotDTO struct {
|
||||||
kandangDTO.KandangBaseDTO
|
kandangDTO.KandangBaseDTO
|
||||||
ProjectFlockKandangId *uint `json:"project_flock_kandang_id,omitempty"`
|
AvailableQuantity float64 `json:"available_quantity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectFlockWithPivotDTO struct {
|
type ProjectFlockWithPivotDTO struct {
|
||||||
@@ -28,11 +28,13 @@ type ProjectFlockWithPivotDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProjectFlockKandangDTO struct {
|
type ProjectFlockKandangDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
ProjectFlockId uint `json:"project_flock_id"`
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||||
KandangId uint `json:"kandang_id"`
|
ProjectFlockId uint `json:"project_flock_id"`
|
||||||
Kandang *kandangDTO.KandangBaseDTO `json:"kandang,omitempty"`
|
KandangId uint `json:"kandang_id"`
|
||||||
ProjectFlock *ProjectFlockWithPivotDTO `json:"project_flock,omitempty"`
|
Kandang *kandangDTO.KandangBaseDTO `json:"kandang,omitempty"`
|
||||||
|
ProjectFlock *ProjectFlockWithPivotDTO `json:"project_flock,omitempty"`
|
||||||
|
AvailableQuantity float64 `json:"available_quantity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO {
|
func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO {
|
||||||
@@ -44,19 +46,19 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
|||||||
|
|
||||||
var pf *ProjectFlockWithPivotDTO
|
var pf *ProjectFlockWithPivotDTO
|
||||||
if e.ProjectFlock.Id != 0 {
|
if e.ProjectFlock.Id != 0 {
|
||||||
// build project flock with kandangs that include pivot ids
|
|
||||||
pfLocal := ProjectFlockWithPivotDTO{
|
pfLocal := ProjectFlockWithPivotDTO{
|
||||||
ProjectFlockBaseDTO: ProjectFlockBaseDTO{
|
ProjectFlockBaseDTO: ProjectFlockBaseDTO{
|
||||||
Id: e.ProjectFlock.Id,
|
Id: e.ProjectFlock.Id,
|
||||||
Period: e.ProjectFlock.Period,
|
Period: e.ProjectFlock.Period,
|
||||||
|
FlockName: e.ProjectFlock.FlockName,
|
||||||
},
|
},
|
||||||
Category: e.ProjectFlock.Category,
|
Category: e.ProjectFlock.Category,
|
||||||
}
|
}
|
||||||
|
|
||||||
// fill related small summaries
|
if base := pfutils.DeriveBaseName(e.ProjectFlock.FlockName); base != "" {
|
||||||
if e.ProjectFlock.Flock.Id != 0 {
|
summary := flockDTO.FlockBaseDTO{Id: 0, Name: base}
|
||||||
mapped := ToFlockSummaryDTO(e.ProjectFlock.Flock)
|
pfLocal.Flock = &summary
|
||||||
pfLocal.Flock = &mapped
|
|
||||||
}
|
}
|
||||||
if e.ProjectFlock.Area.Id != 0 {
|
if e.ProjectFlock.Area.Id != 0 {
|
||||||
mapped := areaDTO.ToAreaBaseDTO(e.ProjectFlock.Area)
|
mapped := areaDTO.ToAreaBaseDTO(e.ProjectFlock.Area)
|
||||||
@@ -75,23 +77,11 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
|||||||
pfLocal.CreatedUser = &mapped
|
pfLocal.CreatedUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
// build pivot map
|
|
||||||
pivotMap := make(map[uint]uint)
|
|
||||||
for _, ph := range e.ProjectFlock.KandangHistory {
|
|
||||||
pivotMap[ph.KandangId] = ph.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
// populate kandangs with pivot ids
|
|
||||||
for _, k := range e.ProjectFlock.Kandangs {
|
for _, k := range e.ProjectFlock.Kandangs {
|
||||||
kb := kandangDTO.ToKandangBaseDTO(k)
|
kb := kandangDTO.ToKandangBaseDTO(k)
|
||||||
var pid *uint
|
|
||||||
if v, ok := pivotMap[k.Id]; ok {
|
|
||||||
vv := v
|
|
||||||
pid = &vv
|
|
||||||
}
|
|
||||||
pfLocal.Kandangs = append(pfLocal.Kandangs, KandangWithPivotDTO{
|
pfLocal.Kandangs = append(pfLocal.Kandangs, KandangWithPivotDTO{
|
||||||
KandangBaseDTO: kb,
|
KandangBaseDTO: kb,
|
||||||
ProjectFlockKandangId: pid,
|
AvailableQuantity: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,10 +89,12 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ProjectFlockKandangDTO{
|
return ProjectFlockKandangDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
ProjectFlockId: e.ProjectFlockId,
|
ProjectFlockKandangId: e.Id,
|
||||||
KandangId: e.KandangId,
|
ProjectFlockId: e.ProjectFlockId,
|
||||||
Kandang: kandang,
|
KandangId: e.KandangId,
|
||||||
ProjectFlock: pf,
|
Kandang: kandang,
|
||||||
|
ProjectFlock: pf,
|
||||||
|
AvailableQuantity: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import (
|
|||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
rFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
rFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
||||||
rKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
rKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||||
|
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||||
rProjectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
rProjectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
|
|
||||||
sProjectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
|
sProjectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
|
||||||
@@ -27,6 +29,8 @@ func (ProjectflockModule) RegisterRoutes(router fiber.Router, db *gorm.DB, valid
|
|||||||
kandangRepo := rKandang.NewKandangRepository(db)
|
kandangRepo := rKandang.NewKandangRepository(db)
|
||||||
projectflockRepo := rProjectflock.NewProjectflockRepository(db)
|
projectflockRepo := rProjectflock.NewProjectflockRepository(db)
|
||||||
projectflockKandangRepo := rProjectflock.NewProjectFlockKandangRepository(db)
|
projectflockKandangRepo := rProjectflock.NewProjectFlockKandangRepository(db)
|
||||||
|
warehouseRepo := rWarehouse.NewWarehouseRepository(db)
|
||||||
|
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||||
userRepo := rUser.NewUserRepository(db)
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
|
||||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||||
@@ -35,7 +39,7 @@ func (ProjectflockModule) RegisterRoutes(router fiber.Router, db *gorm.DB, valid
|
|||||||
panic(fmt.Sprintf("failed to register project flock approval workflow: %v", err))
|
panic(fmt.Sprintf("failed to register project flock approval workflow: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
projectflockService := sProjectflock.NewProjectflockService(projectflockRepo, flockRepo, kandangRepo, projectflockKandangRepo, approvalService, validate)
|
projectflockService := sProjectflock.NewProjectflockService(projectflockRepo, flockRepo, kandangRepo, projectflockKandangRepo, warehouseRepo, productWarehouseRepo, approvalService, validate)
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
ProjectflockRoutes(router, userService, projectflockService)
|
ProjectflockRoutes(router, userService, projectflockService)
|
||||||
|
|||||||
+184
-12
@@ -3,19 +3,30 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const baseNameExpression = "LOWER(TRIM(regexp_replace(flock_name, '\\\\s+\\\\d+(\\\\s+\\\\d+)*$', '', 'g')))"
|
||||||
|
|
||||||
type ProjectflockRepository interface {
|
type ProjectflockRepository interface {
|
||||||
repository.BaseRepository[entity.ProjectFlock]
|
repository.BaseRepository[entity.ProjectFlock]
|
||||||
GetAllByFlock(ctx context.Context, flockID uint) ([]entity.ProjectFlock, error)
|
GetAllByBaseName(ctx context.Context, baseName string) ([]entity.ProjectFlock, error)
|
||||||
GetActiveByFlock(ctx context.Context, flockID uint) (*entity.ProjectFlock, error)
|
GetActiveByBaseName(ctx context.Context, baseName string) (*entity.ProjectFlock, error)
|
||||||
GetMaxPeriodByFlock(ctx context.Context, flockID uint) (int, error)
|
GetMaxPeriodByBaseName(ctx context.Context, baseName string) (int, error)
|
||||||
GetNextPeriodForFlock(ctx context.Context, flockID uint) (int, error)
|
GetNextSequenceForBase(ctx context.Context, baseName string) (int, error)
|
||||||
|
GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error)
|
||||||
|
WithDefaultRelations() func(*gorm.DB) *gorm.DB
|
||||||
|
ExistsByFlockName(ctx context.Context, flockName string, excludeID *uint) (bool, error)
|
||||||
|
AreaExists(ctx context.Context, id uint) (bool, error)
|
||||||
|
FcrExists(ctx context.Context, id uint) (bool, error)
|
||||||
|
LocationExists(ctx context.Context, id uint) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectflockRepositoryImpl struct {
|
type ProjectflockRepositoryImpl struct {
|
||||||
@@ -28,11 +39,11 @@ func NewProjectflockRepository(db *gorm.DB) ProjectflockRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProjectflockRepositoryImpl) GetAllByFlock(ctx context.Context, flockID uint) ([]entity.ProjectFlock, error) {
|
func (r *ProjectflockRepositoryImpl) GetAllByBaseName(ctx context.Context, baseName string) ([]entity.ProjectFlock, error) {
|
||||||
var records []entity.ProjectFlock
|
var records []entity.ProjectFlock
|
||||||
if err := r.DB().WithContext(ctx).
|
if err := r.DB().WithContext(ctx).
|
||||||
Unscoped().
|
Unscoped().
|
||||||
Where("flock_id = ?", flockID).
|
Where(baseNameExpression+" = LOWER(?)", baseName).
|
||||||
Order("period ASC").
|
Order("period ASC").
|
||||||
Find(&records).Error; err != nil {
|
Find(&records).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -40,10 +51,10 @@ func (r *ProjectflockRepositoryImpl) GetAllByFlock(ctx context.Context, flockID
|
|||||||
return records, nil
|
return records, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProjectflockRepositoryImpl) GetActiveByFlock(ctx context.Context, flockID uint) (*entity.ProjectFlock, error) {
|
func (r *ProjectflockRepositoryImpl) GetActiveByBaseName(ctx context.Context, baseName string) (*entity.ProjectFlock, error) {
|
||||||
var record entity.ProjectFlock
|
var record entity.ProjectFlock
|
||||||
err := r.DB().WithContext(ctx).
|
err := r.DB().WithContext(ctx).
|
||||||
Where("flock_id = ?", flockID).
|
Where(baseNameExpression+" = LOWER(?)", baseName).
|
||||||
Order("period DESC").
|
Order("period DESC").
|
||||||
First(&record).Error
|
First(&record).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -55,11 +66,11 @@ func (r *ProjectflockRepositoryImpl) GetActiveByFlock(ctx context.Context, flock
|
|||||||
return &record, nil
|
return &record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProjectflockRepositoryImpl) GetMaxPeriodByFlock(ctx context.Context, flockID uint) (int, error) {
|
func (r *ProjectflockRepositoryImpl) GetMaxPeriodByBaseName(ctx context.Context, baseName string) (int, error) {
|
||||||
var max int
|
var max int
|
||||||
if err := r.DB().WithContext(ctx).
|
if err := r.DB().WithContext(ctx).
|
||||||
Model(&entity.ProjectFlock{}).
|
Model(&entity.ProjectFlock{}).
|
||||||
Where("flock_id = ?", flockID).
|
Where(baseNameExpression+" = LOWER(?)", baseName).
|
||||||
Select("COALESCE(MAX(period), 0)").
|
Select("COALESCE(MAX(period), 0)").
|
||||||
Scan(&max).Error; err != nil {
|
Scan(&max).Error; err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -67,13 +78,13 @@ func (r *ProjectflockRepositoryImpl) GetMaxPeriodByFlock(ctx context.Context, fl
|
|||||||
return max, nil
|
return max, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProjectflockRepositoryImpl) GetNextPeriodForFlock(ctx context.Context, flockID uint) (int, error) {
|
func (r *ProjectflockRepositoryImpl) GetNextSequenceForBase(ctx context.Context, baseName string) (int, error) {
|
||||||
var payload struct {
|
var payload struct {
|
||||||
Period int
|
Period int
|
||||||
}
|
}
|
||||||
if err := r.DB().WithContext(ctx).
|
if err := r.DB().WithContext(ctx).
|
||||||
Model(&entity.ProjectFlock{}).
|
Model(&entity.ProjectFlock{}).
|
||||||
Where("flock_id = ?", flockID).
|
Where(baseNameExpression+" = LOWER(?)", baseName).
|
||||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
Order("period DESC").
|
Order("period DESC").
|
||||||
Limit(1).
|
Limit(1).
|
||||||
@@ -86,3 +97,164 @@ func (r *ProjectflockRepositoryImpl) GetNextPeriodForFlock(ctx context.Context,
|
|||||||
}
|
}
|
||||||
return payload.Period + 1, nil
|
return payload.Period + 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
|
||||||
|
return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB {
|
||||||
|
db = r.withDefaultRelations(db)
|
||||||
|
return r.applyQueryFilters(db, params)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) WithDefaultRelations() func(*gorm.DB) *gorm.DB {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return r.withDefaultRelations(db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) withDefaultRelations(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.
|
||||||
|
Preload("CreatedUser").
|
||||||
|
Preload("Area").
|
||||||
|
Preload("Fcr").
|
||||||
|
Preload("Location").
|
||||||
|
Preload("Kandangs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) applyQueryFilters(db *gorm.DB, params *validation.Query) *gorm.DB {
|
||||||
|
if params == nil {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.AreaId > 0 {
|
||||||
|
db = db.Where("project_flocks.area_id = ?", params.AreaId)
|
||||||
|
}
|
||||||
|
if params.LocationId > 0 {
|
||||||
|
db = db.Where("project_flocks.location_id = ?", params.LocationId)
|
||||||
|
}
|
||||||
|
if params.Period > 0 {
|
||||||
|
db = db.Where("project_flocks.period = ?", params.Period)
|
||||||
|
}
|
||||||
|
if len(params.KandangIds) > 0 {
|
||||||
|
db = db.Where(`
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM project_flock_kandangs pfk
|
||||||
|
WHERE pfk.project_flock_id = project_flocks.id
|
||||||
|
AND pfk.kandang_id IN ?
|
||||||
|
)`, params.KandangIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
db = r.applySearchFilters(db, params.Search)
|
||||||
|
|
||||||
|
for _, expr := range r.buildOrderExpressions(params.SortBy, params.SortOrder) {
|
||||||
|
db = db.Order(expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) applySearchFilters(db *gorm.DB, rawSearch string) *gorm.DB {
|
||||||
|
if rawSearch == "" {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized := strings.ToLower(strings.TrimSpace(rawSearch))
|
||||||
|
if normalized == "" {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
likeQuery := "%" + normalized + "%"
|
||||||
|
return db.
|
||||||
|
Joins("LEFT JOIN areas ON areas.id = project_flocks.area_id").
|
||||||
|
Joins("LEFT JOIN fcrs ON fcrs.id = project_flocks.fcr_id").
|
||||||
|
Joins("LEFT JOIN locations ON locations.id = project_flocks.location_id").
|
||||||
|
Joins("LEFT JOIN users AS created_users ON created_users.id = project_flocks.created_by").
|
||||||
|
Where(`
|
||||||
|
LOWER(areas.name) LIKE ?
|
||||||
|
OR LOWER(project_flocks.category) LIKE ?
|
||||||
|
OR LOWER(fcrs.name) LIKE ?
|
||||||
|
OR LOWER(locations.name) LIKE ?
|
||||||
|
OR LOWER(locations.address) LIKE ?
|
||||||
|
OR LOWER(created_users.name) LIKE ?
|
||||||
|
OR LOWER(created_users.email) LIKE ?
|
||||||
|
OR LOWER(project_flocks.flock_name) LIKE ?
|
||||||
|
OR LOWER(TRIM(regexp_replace(project_flocks.flock_name, '\\s+\\d+(\\s+\\d+)*$', '', 'g'))) LIKE ?
|
||||||
|
OR LOWER(CAST(project_flocks.period AS TEXT)) LIKE ?
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1 FROM kandangs
|
||||||
|
WHERE kandangs.project_flock_id = project_flocks.id
|
||||||
|
AND LOWER(kandangs.name) LIKE ?
|
||||||
|
)
|
||||||
|
`,
|
||||||
|
likeQuery,
|
||||||
|
likeQuery,
|
||||||
|
likeQuery,
|
||||||
|
likeQuery,
|
||||||
|
likeQuery,
|
||||||
|
likeQuery,
|
||||||
|
likeQuery,
|
||||||
|
likeQuery,
|
||||||
|
likeQuery,
|
||||||
|
likeQuery,
|
||||||
|
likeQuery,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) AreaExists(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.Area](ctx, r.DB(), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) FcrExists(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.Fcr](ctx, r.DB(), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) LocationExists(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.Location](ctx, r.DB(), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) buildOrderExpressions(sortBy, sortOrder string) []string {
|
||||||
|
direction := "ASC"
|
||||||
|
if strings.ToLower(sortOrder) == "desc" {
|
||||||
|
direction = "DESC"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch sortBy {
|
||||||
|
case "area":
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf("(SELECT name FROM areas WHERE areas.id = project_flocks.area_id) %s", direction),
|
||||||
|
fmt.Sprintf("project_flocks.id %s", direction),
|
||||||
|
}
|
||||||
|
case "location":
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf("(SELECT name FROM locations WHERE locations.id = project_flocks.location_id) %s", direction),
|
||||||
|
fmt.Sprintf("project_flocks.id %s", direction),
|
||||||
|
}
|
||||||
|
case "kandangs":
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf("(SELECT COUNT(*) FROM project_flock_kandangs pfk WHERE pfk.project_flock_id = project_flocks.id) %s", direction),
|
||||||
|
fmt.Sprintf("project_flocks.id %s", direction),
|
||||||
|
}
|
||||||
|
case "period":
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf("project_flocks.period %s", direction),
|
||||||
|
fmt.Sprintf("project_flocks.id %s", direction),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return []string{
|
||||||
|
"project_flocks.created_at DESC",
|
||||||
|
"project_flocks.updated_at DESC",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) ExistsByFlockName(ctx context.Context, flockName string, excludeID *uint) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
q := r.DB().WithContext(ctx).Model(&entity.ProjectFlock{}).Where("flock_name = ?", flockName)
|
||||||
|
if excludeID != nil && *excludeID != 0 {
|
||||||
|
q = q.Where("id <> ?", *excludeID)
|
||||||
|
}
|
||||||
|
if err := q.Count(&count).Error; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|||||||
+66
-3
@@ -2,6 +2,7 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -13,6 +14,10 @@ type ProjectFlockKandangRepository interface {
|
|||||||
CreateMany(ctx context.Context, records []*entity.ProjectFlockKandang) error
|
CreateMany(ctx context.Context, records []*entity.ProjectFlockKandang) error
|
||||||
DeleteMany(ctx context.Context, projectFlockID uint, kandangIDs []uint) error
|
DeleteMany(ctx context.Context, projectFlockID uint, kandangIDs []uint) error
|
||||||
GetAll(ctx context.Context) ([]entity.ProjectFlockKandang, error)
|
GetAll(ctx context.Context) ([]entity.ProjectFlockKandang, error)
|
||||||
|
ListExistingKandangIDs(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]uint, error)
|
||||||
|
HasKandangsLinkedToOtherProject(ctx context.Context, kandangIDs []uint, exceptProjectID *uint) (bool, error)
|
||||||
|
FindKandangsWithRecordings(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]entity.Kandang, error)
|
||||||
|
MaxPeriodByBaseName(ctx context.Context, baseName string) (int, error)
|
||||||
WithTx(tx *gorm.DB) ProjectFlockKandangRepository
|
WithTx(tx *gorm.DB) ProjectFlockKandangRepository
|
||||||
DB() *gorm.DB
|
DB() *gorm.DB
|
||||||
}
|
}
|
||||||
@@ -21,6 +26,8 @@ type projectFlockKandangRepositoryImpl struct {
|
|||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const flockBaseNameExpression = "LOWER(TRIM(regexp_replace(project_flocks.flock_name, '\\s+\\d+(\\s+\\d+)*$', '', 'g')))"
|
||||||
|
|
||||||
func NewProjectFlockKandangRepository(db *gorm.DB) ProjectFlockKandangRepository {
|
func NewProjectFlockKandangRepository(db *gorm.DB) ProjectFlockKandangRepository {
|
||||||
return &projectFlockKandangRepositoryImpl{db: db}
|
return &projectFlockKandangRepositoryImpl{db: db}
|
||||||
}
|
}
|
||||||
@@ -45,7 +52,6 @@ func (r *projectFlockKandangRepositoryImpl) GetAll(ctx context.Context) ([]entit
|
|||||||
var records []entity.ProjectFlockKandang
|
var records []entity.ProjectFlockKandang
|
||||||
if err := r.db.WithContext(ctx).
|
if err := r.db.WithContext(ctx).
|
||||||
Preload("ProjectFlock").
|
Preload("ProjectFlock").
|
||||||
Preload("ProjectFlock.Flock").
|
|
||||||
Preload("ProjectFlock.Fcr").
|
Preload("ProjectFlock.Fcr").
|
||||||
Preload("ProjectFlock.Area").
|
Preload("ProjectFlock.Area").
|
||||||
Preload("ProjectFlock.Location").
|
Preload("ProjectFlock.Location").
|
||||||
@@ -72,7 +78,6 @@ func (r *projectFlockKandangRepositoryImpl) GetByID(ctx context.Context, id uint
|
|||||||
record := new(entity.ProjectFlockKandang)
|
record := new(entity.ProjectFlockKandang)
|
||||||
if err := r.db.WithContext(ctx).
|
if err := r.db.WithContext(ctx).
|
||||||
Preload("ProjectFlock").
|
Preload("ProjectFlock").
|
||||||
Preload("ProjectFlock.Flock").
|
|
||||||
Preload("ProjectFlock.Fcr").
|
Preload("ProjectFlock.Fcr").
|
||||||
Preload("ProjectFlock.Area").
|
Preload("ProjectFlock.Area").
|
||||||
Preload("ProjectFlock.Location").
|
Preload("ProjectFlock.Location").
|
||||||
@@ -91,7 +96,6 @@ func (r *projectFlockKandangRepositoryImpl) GetByProjectFlockAndKandang(ctx cont
|
|||||||
if err := r.db.WithContext(ctx).
|
if err := r.db.WithContext(ctx).
|
||||||
Where("project_flock_id = ? AND kandang_id = ?", projectFlockID, kandangID).
|
Where("project_flock_id = ? AND kandang_id = ?", projectFlockID, kandangID).
|
||||||
Preload("ProjectFlock").
|
Preload("ProjectFlock").
|
||||||
Preload("ProjectFlock.Flock").
|
|
||||||
Preload("ProjectFlock.Fcr").
|
Preload("ProjectFlock.Fcr").
|
||||||
Preload("ProjectFlock.Area").
|
Preload("ProjectFlock.Area").
|
||||||
Preload("ProjectFlock.Location").
|
Preload("ProjectFlock.Location").
|
||||||
@@ -104,3 +108,62 @@ func (r *projectFlockKandangRepositoryImpl) GetByProjectFlockAndKandang(ctx cont
|
|||||||
}
|
}
|
||||||
return record, nil
|
return record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *projectFlockKandangRepositoryImpl) ListExistingKandangIDs(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]uint, error) {
|
||||||
|
if len(kandangIDs) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var existing []uint
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Table("project_flock_kandangs").
|
||||||
|
Where("project_flock_id = ? AND kandang_id IN ?", projectFlockID, kandangIDs).
|
||||||
|
Pluck("kandang_id", &existing).Error
|
||||||
|
return existing, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *projectFlockKandangRepositoryImpl) HasKandangsLinkedToOtherProject(ctx context.Context, kandangIDs []uint, exceptProjectID *uint) (bool, error) {
|
||||||
|
if len(kandangIDs) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
q := r.db.WithContext(ctx).
|
||||||
|
Table("project_flock_kandangs").
|
||||||
|
Where("kandang_id IN ?", kandangIDs)
|
||||||
|
if exceptProjectID != nil {
|
||||||
|
q = q.Where("project_flock_id <> ?", *exceptProjectID)
|
||||||
|
}
|
||||||
|
var count int64
|
||||||
|
if err := q.Count(&count).Error; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *projectFlockKandangRepositoryImpl) FindKandangsWithRecordings(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]entity.Kandang, error) {
|
||||||
|
if len(kandangIDs) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var kandangs []entity.Kandang
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Table("recordings AS r").
|
||||||
|
Select("pfk.kandang_id AS id, COALESCE(k.name, '') AS name").
|
||||||
|
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
||||||
|
Joins("LEFT JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||||
|
Where("pfk.project_flock_id = ? AND pfk.kandang_id IN ?", projectFlockID, kandangIDs).
|
||||||
|
Group("pfk.kandang_id, k.name").
|
||||||
|
Scan(&kandangs).Error
|
||||||
|
return kandangs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *projectFlockKandangRepositoryImpl) MaxPeriodByBaseName(ctx context.Context, baseName string) (int, error) {
|
||||||
|
if strings.TrimSpace(baseName) == "" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
var max int
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Table("project_flock_kandangs pfk").
|
||||||
|
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
||||||
|
Where(flockBaseNameExpression+" = LOWER(?)", baseName).
|
||||||
|
Select("COALESCE(MAX(pf.period), 0)").
|
||||||
|
Scan(&max).Error
|
||||||
|
return max, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package project_flocks
|
package project_flocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/controllers"
|
||||||
projectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
|
projectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -12,13 +12,8 @@ import (
|
|||||||
func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.ProjectflockService) {
|
func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.ProjectflockService) {
|
||||||
ctrl := controller.NewProjectflockController(s)
|
ctrl := controller.NewProjectflockController(s)
|
||||||
|
|
||||||
route := v1.Group("/project_flocks")
|
route := v1.Group("/project-flocks")
|
||||||
|
route.Use(m.Auth(u))
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
|
||||||
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
|
||||||
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
@@ -27,5 +22,6 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj
|
|||||||
route.Delete("/:id", ctrl.DeleteOne)
|
route.Delete("/:id", ctrl.DeleteOne)
|
||||||
route.Get("/kandangs/lookup", ctrl.LookupProjectFlockKandang)
|
route.Get("/kandangs/lookup", ctrl.LookupProjectFlockKandang)
|
||||||
route.Post("/approvals", ctrl.Approval)
|
route.Post("/approvals", ctrl.Approval)
|
||||||
route.Get("/flocks/:flock_id/periods", ctrl.GetFlockPeriodSummary)
|
route.Get("/kandangs/:project_flock_kandang_id/periods", ctrl.GetFlockPeriodSummary)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ import (
|
|||||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
authmiddleware "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
|
productWarehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
||||||
kandangRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
kandangRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||||
|
warehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
|
pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
||||||
utils "gitlab.com/mbugroup/lti-api.git/internal/utils"
|
utils "gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||||
@@ -28,21 +32,24 @@ type ProjectflockService interface {
|
|||||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, error)
|
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, error)
|
||||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectFlock, error)
|
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectFlock, error)
|
||||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error)
|
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error)
|
||||||
|
GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error)
|
||||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||||
GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, error)
|
GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, float64, error)
|
||||||
GetFlockPeriodSummary(ctx *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error)
|
GetFlockPeriodSummary(ctx *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error)
|
||||||
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error)
|
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type projectflockService struct {
|
type projectflockService struct {
|
||||||
Log *logrus.Logger
|
Log *logrus.Logger
|
||||||
Validate *validator.Validate
|
Validate *validator.Validate
|
||||||
Repository repository.ProjectflockRepository
|
Repository repository.ProjectflockRepository
|
||||||
FlockRepo flockRepository.FlockRepository
|
FlockRepo flockRepository.FlockRepository
|
||||||
KandangRepo kandangRepository.KandangRepository
|
KandangRepo kandangRepository.KandangRepository
|
||||||
PivotRepo repository.ProjectFlockKandangRepository
|
WarehouseRepo warehouseRepository.WarehouseRepository
|
||||||
ApprovalSvc commonSvc.ApprovalService
|
ProductWarehouseRepo productWarehouseRepository.ProductWarehouseRepository
|
||||||
approvalWorkflow approvalutils.ApprovalWorkflowKey
|
PivotRepo repository.ProjectFlockKandangRepository
|
||||||
|
ApprovalSvc commonSvc.ApprovalService
|
||||||
|
approvalWorkflow approvalutils.ApprovalWorkflowKey
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlockPeriodSummary struct {
|
type FlockPeriodSummary struct {
|
||||||
@@ -55,31 +62,25 @@ func NewProjectflockService(
|
|||||||
flockRepo flockRepository.FlockRepository,
|
flockRepo flockRepository.FlockRepository,
|
||||||
kandangRepo kandangRepository.KandangRepository,
|
kandangRepo kandangRepository.KandangRepository,
|
||||||
pivotRepo repository.ProjectFlockKandangRepository,
|
pivotRepo repository.ProjectFlockKandangRepository,
|
||||||
|
warehouseRepo warehouseRepository.WarehouseRepository,
|
||||||
|
productWarehouseRepo productWarehouseRepository.ProductWarehouseRepository,
|
||||||
approvalSvc commonSvc.ApprovalService,
|
approvalSvc commonSvc.ApprovalService,
|
||||||
validate *validator.Validate,
|
validate *validator.Validate,
|
||||||
) ProjectflockService {
|
) ProjectflockService {
|
||||||
return &projectflockService{
|
return &projectflockService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
Repository: repo,
|
Repository: repo,
|
||||||
FlockRepo: flockRepo,
|
FlockRepo: flockRepo,
|
||||||
KandangRepo: kandangRepo,
|
KandangRepo: kandangRepo,
|
||||||
PivotRepo: pivotRepo,
|
WarehouseRepo: warehouseRepo,
|
||||||
ApprovalSvc: approvalSvc,
|
ProductWarehouseRepo: productWarehouseRepo,
|
||||||
approvalWorkflow: utils.ApprovalWorkflowProjectFlock,
|
PivotRepo: pivotRepo,
|
||||||
|
ApprovalSvc: approvalSvc,
|
||||||
|
approvalWorkflow: utils.ApprovalWorkflowProjectFlock,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB {
|
|
||||||
return db.
|
|
||||||
Preload("CreatedUser").
|
|
||||||
Preload("Flock").
|
|
||||||
Preload("Area").
|
|
||||||
Preload("Fcr").
|
|
||||||
Preload("Location").
|
|
||||||
Preload("Kandangs")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
|
func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
|
||||||
if err := s.Validate.Struct(params); err != nil {
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
@@ -94,74 +95,11 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
|
|||||||
|
|
||||||
offset := (params.Page - 1) * params.Limit
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
projectflocks, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
projectflocks, total, err := s.Repository.GetAllWithFilters(c.Context(), offset, params.Limit, params)
|
||||||
db = s.withRelations(db)
|
|
||||||
|
|
||||||
if params.AreaId > 0 {
|
|
||||||
db = db.Where("project_flocks.area_id = ?", params.AreaId)
|
|
||||||
}
|
|
||||||
if params.LocationId > 0 {
|
|
||||||
db = db.Where("project_flocks.location_id = ?", params.LocationId)
|
|
||||||
}
|
|
||||||
if params.Period > 0 {
|
|
||||||
db = db.Where("project_flocks.period = ?", params.Period)
|
|
||||||
}
|
|
||||||
if len(params.KandangIds) > 0 {
|
|
||||||
db = db.Where("EXISTS (SELECT 1 FROM kandangs WHERE kandangs.project_flock_id = project_flocks.id AND kandangs.id IN ?)", params.KandangIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
if params.Search != "" {
|
|
||||||
normalizedSearch := strings.ToLower(strings.TrimSpace(params.Search))
|
|
||||||
if normalizedSearch == "" {
|
|
||||||
for _, expr := range s.buildOrderExpressions(params.SortBy, params.SortOrder) {
|
|
||||||
db = db.Order(expr)
|
|
||||||
}
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
likeQuery := "%" + normalizedSearch + "%"
|
|
||||||
db = db.
|
|
||||||
Joins("LEFT JOIN flocks ON flocks.id = project_flocks.flock_id").
|
|
||||||
Joins("LEFT JOIN areas ON areas.id = project_flocks.area_id").
|
|
||||||
Joins("LEFT JOIN fcrs ON fcrs.id = project_flocks.fcr_id").
|
|
||||||
Joins("LEFT JOIN locations ON locations.id = project_flocks.location_id").
|
|
||||||
Joins("LEFT JOIN users AS created_users ON created_users.id = project_flocks.created_by").
|
|
||||||
Where(`
|
|
||||||
LOWER(flocks.name) LIKE ?
|
|
||||||
OR LOWER(areas.name) LIKE ?
|
|
||||||
OR LOWER(project_flocks.category) LIKE ?
|
|
||||||
OR LOWER(fcrs.name) LIKE ?
|
|
||||||
OR LOWER(locations.name) LIKE ?
|
|
||||||
OR LOWER(locations.address) LIKE ?
|
|
||||||
OR LOWER(created_users.name) LIKE ?
|
|
||||||
OR LOWER(created_users.email) LIKE ?
|
|
||||||
OR LOWER(CAST(project_flocks.period AS TEXT)) LIKE ?
|
|
||||||
OR EXISTS (
|
|
||||||
SELECT 1 FROM kandangs
|
|
||||||
WHERE kandangs.project_flock_id = project_flocks.id
|
|
||||||
AND LOWER(kandangs.name) LIKE ?
|
|
||||||
)
|
|
||||||
`,
|
|
||||||
likeQuery,
|
|
||||||
likeQuery,
|
|
||||||
likeQuery,
|
|
||||||
likeQuery,
|
|
||||||
likeQuery,
|
|
||||||
likeQuery,
|
|
||||||
likeQuery,
|
|
||||||
likeQuery,
|
|
||||||
likeQuery,
|
|
||||||
likeQuery,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
for _, expr := range s.buildOrderExpressions(params.SortBy, params.SortOrder) {
|
|
||||||
db = db.Order(expr)
|
|
||||||
}
|
|
||||||
return db
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to get projectflocks: %+v", err)
|
s.Log.Errorf("Failed to get projectflocks: %+v", err)
|
||||||
return nil, 0, err
|
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flocks")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.ApprovalSvc != nil && len(projectflocks) > 0 {
|
if s.ApprovalSvc != nil && len(projectflocks) > 0 {
|
||||||
@@ -188,13 +126,13 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock, error) {
|
func (s projectflockService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock, error) {
|
||||||
projectflock, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
projectflock, err := s.Repository.GetByID(c.Context(), id, s.Repository.WithDefaultRelations())
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed get projectflock by id: %+v", err)
|
s.Log.Errorf("Failed get projectflock by id: %+v", err)
|
||||||
return nil, err
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.ApprovalSvc != nil {
|
if s.ApprovalSvc != nil {
|
||||||
@@ -221,6 +159,11 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actorID, err := actorIDFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
cat := strings.ToUpper(req.Category)
|
cat := strings.ToUpper(req.Category)
|
||||||
if !utils.IsValidProjectFlockCategory(cat) {
|
if !utils.IsValidProjectFlockCategory(cat) {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid category")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid category")
|
||||||
@@ -230,15 +173,28 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
|||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids is required")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
baseName := strings.TrimSpace(req.FlockName)
|
||||||
|
if baseName == "" {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Flock name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Flock", ID: &req.FlockId, Exists: relationExistsChecker[entity.Flock](s.Repository.DB())},
|
commonSvc.RelationCheck{Name: "Area", ID: &req.AreaId, Exists: s.Repository.AreaExists},
|
||||||
commonSvc.RelationCheck{Name: "Area", ID: &req.AreaId, Exists: relationExistsChecker[entity.Area](s.Repository.DB())},
|
commonSvc.RelationCheck{Name: "FCR", ID: &req.FcrId, Exists: s.Repository.FcrExists},
|
||||||
commonSvc.RelationCheck{Name: "FCR", ID: &req.FcrId, Exists: relationExistsChecker[entity.Fcr](s.Repository.DB())},
|
commonSvc.RelationCheck{Name: "Location", ID: &req.LocationId, Exists: s.Repository.LocationExists},
|
||||||
commonSvc.RelationCheck{Name: "Location", ID: &req.LocationId, Exists: relationExistsChecker[entity.Location](s.Repository.DB())},
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canonicalBase := baseName
|
||||||
|
if s.FlockRepo != nil {
|
||||||
|
baseFlock, err := s.ensureFlockByName(c.Context(), actorID, baseName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
canonicalBase = baseFlock.Name
|
||||||
|
}
|
||||||
|
|
||||||
kandangIDs := uniqueUintSlice(req.KandangIds)
|
kandangIDs := uniqueUintSlice(req.KandangIds)
|
||||||
kandangs, err := s.KandangRepo.GetByIDs(c.Context(), kandangIDs, nil)
|
kandangs, err := s.KandangRepo.GetByIDs(c.Context(), kandangIDs, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -250,29 +206,34 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
|||||||
if len(kandangs) != len(kandangIDs) {
|
if len(kandangs) != len(kandangIDs) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
||||||
}
|
}
|
||||||
for _, kandang := range kandangs {
|
// larang kalau ada yg sudah terikat ke project lain
|
||||||
if kandang.ProjectFlockId != nil {
|
if linked, err := s.pivotRepo().HasKandangsLinkedToOtherProject(c.Context(), kandangIDs, nil); err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Kandang %s sudah memiliki project flock", kandang.Name))
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandangs linkage")
|
||||||
}
|
} else if linked {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, "Beberapa kandang sudah terikat dengan project flock lain")
|
||||||
}
|
}
|
||||||
|
|
||||||
createBody := &entity.ProjectFlock{
|
createBody := &entity.ProjectFlock{
|
||||||
FlockId: req.FlockId,
|
|
||||||
AreaId: req.AreaId,
|
AreaId: req.AreaId,
|
||||||
Category: cat,
|
Category: cat,
|
||||||
FcrId: req.FcrId,
|
FcrId: req.FcrId,
|
||||||
LocationId: req.LocationId,
|
LocationId: req.LocationId,
|
||||||
CreatedBy: 1,
|
CreatedBy: actorID,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
projectRepo := repository.NewProjectflockRepository(dbTransaction)
|
projectRepo := repository.NewProjectflockRepository(dbTransaction)
|
||||||
|
|
||||||
period, err := projectRepo.GetNextPeriodForFlock(c.Context(), req.FlockId)
|
nextSeq, err := projectRepo.GetNextSequenceForBase(c.Context(), canonicalBase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
createBody.Period = period
|
generatedName, seq, err := s.generateSequentialFlockName(c.Context(), projectRepo, canonicalBase, nextSeq, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
createBody.FlockName = generatedName
|
||||||
|
createBody.Period = seq
|
||||||
|
|
||||||
if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil {
|
if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -282,7 +243,6 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID := uint(1) //TODO: Change From Auth
|
|
||||||
action := entity.ApprovalActionCreated
|
action := entity.ApprovalActionCreated
|
||||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
_, err = approvalSvcTx.CreateApproval(
|
_, err = approvalSvcTx.CreateApproval(
|
||||||
@@ -298,11 +258,14 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||||
|
return nil, fiberErr
|
||||||
|
}
|
||||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
return nil, fiber.NewError(fiber.StatusConflict, "Project flock period already exists")
|
return nil, fiber.NewError(fiber.StatusConflict, "Project flock period already exists")
|
||||||
}
|
}
|
||||||
s.Log.Errorf("Failed to create projectflock: %+v", err)
|
s.Log.Errorf("Failed to create projectflock: %+v", err)
|
||||||
return nil, err
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create project flock")
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.GetOne(c, createBody.Id)
|
return s.GetOne(c, createBody.Id)
|
||||||
@@ -313,7 +276,12 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
existing, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
actorID, err := actorIDFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
existing, err := s.Repository.GetByID(c.Context(), id, s.Repository.WithDefaultRelations())
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||||
}
|
}
|
||||||
@@ -324,15 +292,28 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
updateBody := make(map[string]any)
|
updateBody := make(map[string]any)
|
||||||
hasBodyChanges := false
|
hasBodyChanges := false
|
||||||
var relationChecks []commonSvc.RelationCheck
|
var relationChecks []commonSvc.RelationCheck
|
||||||
|
existingBase := pfutils.DeriveBaseName(existing.FlockName)
|
||||||
|
targetBaseName := existingBase
|
||||||
|
needFlockNameRegenerate := false
|
||||||
|
|
||||||
if req.FlockId != nil {
|
if req.FlockName != nil {
|
||||||
updateBody["flock_id"] = *req.FlockId
|
trimmed := strings.TrimSpace(*req.FlockName)
|
||||||
hasBodyChanges = true
|
if trimmed == "" {
|
||||||
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Flock name cannot be empty")
|
||||||
Name: "Flock",
|
}
|
||||||
ID: req.FlockId,
|
canonicalBase := trimmed
|
||||||
Exists: relationExistsChecker[entity.Flock](s.Repository.DB()),
|
if s.FlockRepo != nil {
|
||||||
})
|
flockEntity, err := s.ensureFlockByName(c.Context(), actorID, trimmed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
canonicalBase = flockEntity.Name
|
||||||
|
}
|
||||||
|
if !strings.EqualFold(canonicalBase, existingBase) {
|
||||||
|
needFlockNameRegenerate = true
|
||||||
|
targetBaseName = canonicalBase
|
||||||
|
hasBodyChanges = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if req.AreaId != nil {
|
if req.AreaId != nil {
|
||||||
updateBody["area_id"] = *req.AreaId
|
updateBody["area_id"] = *req.AreaId
|
||||||
@@ -340,7 +321,7 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
||||||
Name: "Area",
|
Name: "Area",
|
||||||
ID: req.AreaId,
|
ID: req.AreaId,
|
||||||
Exists: relationExistsChecker[entity.Area](s.Repository.DB()),
|
Exists: s.Repository.AreaExists,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if req.Category != nil {
|
if req.Category != nil {
|
||||||
@@ -357,7 +338,7 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
||||||
Name: "FCR",
|
Name: "FCR",
|
||||||
ID: req.FcrId,
|
ID: req.FcrId,
|
||||||
Exists: relationExistsChecker[entity.Fcr](s.Repository.DB()),
|
Exists: s.Repository.FcrExists,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if req.LocationId != nil {
|
if req.LocationId != nil {
|
||||||
@@ -366,7 +347,7 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
relationChecks = append(relationChecks, commonSvc.RelationCheck{
|
||||||
Name: "Location",
|
Name: "Location",
|
||||||
ID: req.LocationId,
|
ID: req.LocationId,
|
||||||
Exists: relationExistsChecker[entity.Location](s.Repository.DB()),
|
Exists: s.Repository.LocationExists,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,11 +375,12 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
if len(kandangs) != len(newKandangIDs) {
|
if len(kandangs) != len(newKandangIDs) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
||||||
}
|
}
|
||||||
for _, k := range kandangs {
|
if linked, err := s.pivotRepo().HasKandangsLinkedToOtherProject(c.Context(), newKandangIDs, &id); err != nil {
|
||||||
if k.ProjectFlockId != nil && *k.ProjectFlockId != id {
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandangs linkage")
|
||||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Kandang %s sudah terikat dengan project flock lain", k.Name))
|
} else if linked {
|
||||||
}
|
return nil, fiber.NewError(fiber.StatusConflict, "Beberapa kandang sudah terikat dengan project flock lain")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasChanges := hasBodyChanges || hasKandangChanges
|
hasChanges := hasBodyChanges || hasKandangChanges
|
||||||
@@ -409,6 +391,29 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
projectRepo := repository.NewProjectflockRepository(dbTransaction)
|
projectRepo := repository.NewProjectflockRepository(dbTransaction)
|
||||||
|
|
||||||
|
baseForGeneration := targetBaseName
|
||||||
|
if strings.TrimSpace(baseForGeneration) == "" {
|
||||||
|
baseForGeneration = existingBase
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(baseForGeneration) == "" {
|
||||||
|
baseForGeneration = strings.TrimSpace(existing.FlockName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if needFlockNameRegenerate {
|
||||||
|
nextSeq, err := projectRepo.GetNextSequenceForBase(c.Context(), baseForGeneration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newName, seq, err := s.generateSequentialFlockName(c.Context(), projectRepo, baseForGeneration, nextSeq, &id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
updateBody["flock_name"] = newName
|
||||||
|
if seq != existing.Period {
|
||||||
|
updateBody["period"] = seq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(updateBody) > 0 {
|
if len(updateBody) > 0 {
|
||||||
if err := projectRepo.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
if err := projectRepo.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -457,7 +462,6 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hasChanges {
|
if hasChanges {
|
||||||
actorID := uint(1) //TODO: Change From Auth
|
|
||||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
if approvalSvc != nil {
|
if approvalSvc != nil {
|
||||||
latestBeforeReset, err := approvalSvc.LatestByTarget(c.Context(), s.approvalWorkflow, id, nil)
|
latestBeforeReset, err := approvalSvc.LatestByTarget(c.Context(), s.approvalWorkflow, id, nil)
|
||||||
@@ -497,7 +501,10 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||||
}
|
}
|
||||||
s.Log.Errorf("Failed to update projectflock %d: %+v", id, err)
|
s.Log.Errorf("Failed to update projectflock %d: %+v", id, err)
|
||||||
return nil, err
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, "Project flock period already exists")
|
||||||
|
}
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update project flock")
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.GetOne(c, id)
|
return s.GetOne(c, id)
|
||||||
@@ -508,7 +515,11 @@ func (s projectflockService) Approval(c *fiber.Ctx, req *validation.Approve) ([]
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID := uint(1) // TODO: change from auth context
|
actorID, err := actorIDFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var action entity.ApprovalAction
|
var action entity.ApprovalAction
|
||||||
switch strings.ToUpper(strings.TrimSpace(req.Action)) {
|
switch strings.ToUpper(strings.TrimSpace(req.Action)) {
|
||||||
case string(entity.ApprovalActionRejected):
|
case string(entity.ApprovalActionRejected):
|
||||||
@@ -529,7 +540,7 @@ func (s projectflockService) Approval(c *fiber.Ctx, req *validation.Approve) ([]
|
|||||||
step = utils.ProjectFlockStepAktif
|
step = utils.ProjectFlockStepAktif
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
kandangRepoTx := kandangRepository.NewKandangRepository(dbTransaction)
|
kandangRepoTx := kandangRepository.NewKandangRepository(dbTransaction)
|
||||||
projectRepoTx := repository.NewProjectflockRepository(dbTransaction)
|
projectRepoTx := repository.NewProjectflockRepository(dbTransaction)
|
||||||
@@ -601,7 +612,7 @@ func (s projectflockService) Approval(c *fiber.Ctx, req *validation.Approve) ([]
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error {
|
func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||||
existing, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
existing, err := s.Repository.GetByID(c.Context(), id, s.Repository.WithDefaultRelations())
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
return fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||||
}
|
}
|
||||||
@@ -635,30 +646,32 @@ func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
return fiberErr
|
return fiberErr
|
||||||
}
|
}
|
||||||
s.Log.Errorf("Failed to delete projectflock %d: %+v", id, err)
|
s.Log.Errorf("Failed to delete projectflock %d: %+v", id, err)
|
||||||
return err
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete project flock")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) GetProjectFlockKandang(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, error) {
|
func (s projectflockService) GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, float64, error) {
|
||||||
// keep for backward compatibility; delegate to new consolidated method
|
|
||||||
return s.GetProjectFlockKandangByParams(ctx, fmt.Sprintf("%d", id), "", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s projectflockService) GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error) {
|
|
||||||
|
|
||||||
pfk, err := s.PivotRepo.GetByProjectFlockAndKandang(ctx.Context(), projectFlockID, kandangID)
|
pfk, err := s.PivotRepo.GetByProjectFlockAndKandang(ctx.Context(), projectFlockID, kandangID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
|
return nil, 0, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
|
||||||
}
|
}
|
||||||
return nil, err
|
s.Log.Errorf("Failed to fetch project_flock_kandang by project %d and kandang %d: %+v", projectFlockID, kandangID, err)
|
||||||
|
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang")
|
||||||
}
|
}
|
||||||
return pfk, nil
|
|
||||||
|
availableQuantity, err := s.GetAvailableDocQuantity(ctx, pfk.KandangId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pfk, availableQuantity, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, error) {
|
func (s projectflockService) GetProjectFlockKandangByParams(ctx *fiber.Ctx, idStr string, projectFlockIdStr string, kandangIdStr string) (*entity.ProjectFlockKandang, float64, error) {
|
||||||
idStr = strings.TrimSpace(idStr)
|
idStr = strings.TrimSpace(idStr)
|
||||||
projectFlockIdStr = strings.TrimSpace(projectFlockIdStr)
|
projectFlockIdStr = strings.TrimSpace(projectFlockIdStr)
|
||||||
kandangIdStr = strings.TrimSpace(kandangIdStr)
|
kandangIdStr = strings.TrimSpace(kandangIdStr)
|
||||||
@@ -666,52 +679,107 @@ func (s projectflockService) GetProjectFlockKandangByParams(ctx *fiber.Ctx, idSt
|
|||||||
if idStr != "" {
|
if idStr != "" {
|
||||||
id, err := strconv.Atoi(idStr)
|
id, err := strconv.Atoi(idStr)
|
||||||
if err != nil || id <= 0 {
|
if err != nil || id <= 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
}
|
}
|
||||||
pfk, err := s.PivotRepo.GetByID(ctx.Context(), uint(id))
|
pfk, err := s.PivotRepo.GetByID(ctx.Context(), uint(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
|
return nil, 0, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
|
||||||
}
|
}
|
||||||
return nil, err
|
s.Log.Errorf("Failed to fetch project_flock_kandang %d: %+v", id, err)
|
||||||
|
return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang")
|
||||||
}
|
}
|
||||||
return pfk, nil
|
|
||||||
|
availableQuantity, err := s.GetAvailableDocQuantity(ctx, pfk.KandangId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pfk, availableQuantity, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if projectFlockIdStr == "" || kandangIdStr == "" {
|
if projectFlockIdStr == "" || kandangIdStr == "" {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Missing lookup parameters")
|
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Missing lookup parameters")
|
||||||
}
|
}
|
||||||
pfid, err := strconv.Atoi(projectFlockIdStr)
|
pfid, err := strconv.Atoi(projectFlockIdStr)
|
||||||
if err != nil || pfid <= 0 {
|
if err != nil || pfid <= 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id")
|
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id")
|
||||||
}
|
}
|
||||||
kid, err := strconv.Atoi(kandangIdStr)
|
kid, err := strconv.Atoi(kandangIdStr)
|
||||||
if err != nil || kid <= 0 {
|
if err != nil || kid <= 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang_id")
|
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang_id")
|
||||||
}
|
}
|
||||||
return s.GetProjectFlockKandangByProjectAndKandang(ctx, uint(pfid), uint(kid))
|
return s.GetProjectFlockKandangByProjectAndKandang(ctx, uint(pfid), uint(kid))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error) {
|
func (s projectflockService) GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error) {
|
||||||
flock, err := s.FlockRepo.GetByID(c.Context(), flockID, func(db *gorm.DB) *gorm.DB {
|
|
||||||
return db.Preload("CreatedUser")
|
wh, err := s.WarehouseRepo.GetByKandangID(ctx.Context(), kandangID)
|
||||||
})
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Flock not found")
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed get flock %d for period summary: %+v", flockID, err)
|
return 0, err
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch flock")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
maxPeriod, err := s.Repository.GetMaxPeriodByFlock(c.Context(), flockID)
|
productWarehouses, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(ctx.Context(), "DOC", wh.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to compute next period for flock %d: %+v", flockID, err)
|
return 0, err
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to compute next period")
|
}
|
||||||
|
|
||||||
|
total := 0.0
|
||||||
|
for _, pw := range productWarehouses {
|
||||||
|
total += pw.Quantity
|
||||||
|
}
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, projectFlockKandangID uint) (*FlockPeriodSummary, error) {
|
||||||
|
if projectFlockKandangID == 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
pivot, err := s.pivotRepo().GetByID(c.Context(), projectFlockKandangID)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Project flock kandang not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to fetch project_flock_kandang %d: %+v", projectFlockKandangID, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock kandang")
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseName string
|
||||||
|
var referenceFlock *entity.Flock
|
||||||
|
if pivot.ProjectFlock.Id != 0 {
|
||||||
|
baseName = pfutils.DeriveBaseName(pivot.ProjectFlock.FlockName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(baseName) != "" {
|
||||||
|
referenceFlock, err = s.FlockRepo.GetByName(c.Context(), baseName)
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
s.Log.Errorf("Failed to fetch flock %q: %+v", baseName, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch flock")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if referenceFlock == nil {
|
||||||
|
referenceFlock = &entity.Flock{Name: pivot.ProjectFlock.FlockName}
|
||||||
|
}
|
||||||
|
|
||||||
|
maxPeriod := pivot.ProjectFlock.Period
|
||||||
|
if strings.TrimSpace(baseName) != "" {
|
||||||
|
if headerMax, err := s.Repository.GetMaxPeriodByBaseName(c.Context(), baseName); err != nil {
|
||||||
|
s.Log.Warnf("Unable to compute header period for base %q: %+v", baseName, err)
|
||||||
|
} else if headerMax > maxPeriod {
|
||||||
|
maxPeriod = headerMax
|
||||||
|
}
|
||||||
|
|
||||||
|
if pivotMax, err := s.pivotRepo().MaxPeriodByBaseName(c.Context(), baseName); err != nil {
|
||||||
|
s.Log.Warnf("Unable to compute pivot period for base %q: %+v", baseName, err)
|
||||||
|
} else if pivotMax > maxPeriod {
|
||||||
|
maxPeriod = pivotMax
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &FlockPeriodSummary{
|
return &FlockPeriodSummary{
|
||||||
Flock: *flock,
|
Flock: *referenceFlock,
|
||||||
NextPeriod: maxPeriod + 1,
|
NextPeriod: maxPeriod + 1,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -729,45 +797,64 @@ func uniqueUintSlice(values []uint) []uint {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func relationExistsChecker[T any](db *gorm.DB) func(context.Context, uint) (bool, error) {
|
func (s projectflockService) generateSequentialFlockName(ctx context.Context, repo repository.ProjectflockRepository, baseName string, startNumber int, excludeID *uint) (string, int, error) {
|
||||||
return func(ctx context.Context, id uint) (bool, error) {
|
name := strings.TrimSpace(baseName)
|
||||||
return commonRepo.Exists[T](ctx, db, id)
|
if name == "" {
|
||||||
|
return "", 0, fiber.NewError(fiber.StatusBadRequest, "Base flock name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
number := startNumber
|
||||||
|
if number <= 0 {
|
||||||
|
number = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
attempts := 0
|
||||||
|
for {
|
||||||
|
candidate := fmt.Sprintf("%s %03d", name, number)
|
||||||
|
exists, err := repo.ExistsByFlockName(ctx, candidate, excludeID)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed checking project flock name uniqueness for %q: %+v", candidate, err)
|
||||||
|
return "", 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate flock name")
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return candidate, number, nil
|
||||||
|
}
|
||||||
|
number++
|
||||||
|
attempts++
|
||||||
|
if attempts > 9999 {
|
||||||
|
return "", 0, fiber.NewError(fiber.StatusInternalServerError, "Unable to generate unique flock name")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) buildOrderExpressions(sortBy, sortOrder string) []string {
|
func (s projectflockService) ensureFlockByName(ctx context.Context, actorID uint, name string) (*entity.Flock, error) {
|
||||||
direction := "ASC"
|
trimmed := strings.TrimSpace(name)
|
||||||
if strings.ToLower(sortOrder) == "desc" {
|
if trimmed == "" {
|
||||||
direction = "DESC"
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Flock name cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch sortBy {
|
flock, err := s.FlockRepo.GetByName(ctx, trimmed)
|
||||||
case "area":
|
if err == nil {
|
||||||
return []string{
|
return flock, nil
|
||||||
fmt.Sprintf("(SELECT name FROM areas WHERE areas.id = project_flocks.area_id) %s", direction),
|
|
||||||
fmt.Sprintf("project_flocks.id %s", direction),
|
|
||||||
}
|
|
||||||
case "location":
|
|
||||||
return []string{
|
|
||||||
fmt.Sprintf("(SELECT name FROM locations WHERE locations.id = project_flocks.location_id) %s", direction),
|
|
||||||
fmt.Sprintf("project_flocks.id %s", direction),
|
|
||||||
}
|
|
||||||
case "kandangs":
|
|
||||||
return []string{
|
|
||||||
fmt.Sprintf("(SELECT COUNT(*) FROM kandangs WHERE kandangs.project_flock_id = project_flocks.id) %s", direction),
|
|
||||||
fmt.Sprintf("project_flocks.id %s", direction),
|
|
||||||
}
|
|
||||||
case "period":
|
|
||||||
return []string{
|
|
||||||
fmt.Sprintf("project_flocks.period %s", direction),
|
|
||||||
fmt.Sprintf("project_flocks.id %s", direction),
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return []string{
|
|
||||||
"project_flocks.created_at DESC",
|
|
||||||
"project_flocks.updated_at DESC",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
s.Log.Errorf("Failed to fetch flock by name %q: %+v", trimmed, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to prepare flock data")
|
||||||
|
}
|
||||||
|
|
||||||
|
newFlock := &entity.Flock{
|
||||||
|
Name: trimmed,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
}
|
||||||
|
if err := s.FlockRepo.CreateOne(ctx, newFlock, nil); err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
return s.FlockRepo.GetByName(ctx, trimmed)
|
||||||
|
}
|
||||||
|
s.Log.Errorf("Failed to create flock %q: %+v", trimmed, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to prepare flock data")
|
||||||
|
}
|
||||||
|
|
||||||
|
return newFlock, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *gorm.DB, projectFlockID uint, kandangIDs []uint) error {
|
func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *gorm.DB, projectFlockID uint, kandangIDs []uint) error {
|
||||||
@@ -775,24 +862,45 @@ func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dbTransaction.Model(&entity.Kandang{}).
|
if err := s.kandangRepoWithTx(dbTransaction).UpdateStatusByIDs(ctx, kandangIDs, utils.KandangStatusPengajuan); err != nil {
|
||||||
Where("id IN ?", kandangIDs).
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs status")
|
||||||
Updates(map[string]any{
|
|
||||||
"project_flock_id": projectFlockID,
|
|
||||||
"status": string(utils.KandangStatusPengajuan),
|
|
||||||
}).Error; err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pivotRepo := s.pivotRepoWithTx(dbTransaction)
|
already, err := s.pivotRepoWithTx(dbTransaction).ListExistingKandangIDs(ctx, projectFlockID, kandangIDs)
|
||||||
records := make([]*entity.ProjectFlockKandang, len(kandangIDs))
|
if err != nil {
|
||||||
for i, id := range kandangIDs {
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check existing pivot")
|
||||||
records[i] = &entity.ProjectFlockKandang{
|
}
|
||||||
ProjectFlockId: projectFlockID,
|
exists := make(map[uint]struct{}, len(already))
|
||||||
KandangId: id,
|
for _, id := range already {
|
||||||
|
exists[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var toAttach []uint
|
||||||
|
seen := make(map[uint]struct{}, len(kandangIDs))
|
||||||
|
for _, id := range kandangIDs {
|
||||||
|
if _, ok := seen[id]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[id] = struct{}{}
|
||||||
|
if _, ok := exists[id]; !ok {
|
||||||
|
toAttach = append(toAttach, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := pivotRepo.CreateMany(ctx, records); err != nil {
|
if len(toAttach) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
records := make([]*entity.ProjectFlockKandang, 0, len(toAttach))
|
||||||
|
for _, id := range toAttach {
|
||||||
|
records = append(records, &entity.ProjectFlockKandang{
|
||||||
|
ProjectFlockId: projectFlockID,
|
||||||
|
KandangId: id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := s.pivotRepoWithTx(dbTransaction).CreateMany(ctx, records); err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
return fiber.NewError(fiber.StatusConflict, "Beberapa kandang sudah terhubung dengan project flock ini")
|
||||||
|
}
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to persist project flock history")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to persist project flock history")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -803,15 +911,27 @@ func (s projectflockService) detachKandangs(ctx context.Context, dbTransaction *
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
updates := map[string]any{"project_flock_id": nil}
|
blocked, err := s.pivotRepoWithTx(dbTransaction).FindKandangsWithRecordings(ctx, projectFlockID, kandangIDs)
|
||||||
if resetStatus {
|
if err != nil {
|
||||||
updates["status"] = string(utils.KandangStatusNonActive)
|
s.Log.Errorf("Failed to check recordings before detaching kandangs: %+v", err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to validate kandang detachment")
|
||||||
|
}
|
||||||
|
if len(blocked) > 0 {
|
||||||
|
names := make([]string, 0, len(blocked))
|
||||||
|
for _, item := range blocked {
|
||||||
|
label := fmt.Sprintf("ID %d", item.Id)
|
||||||
|
if strings.TrimSpace(item.Name) != "" {
|
||||||
|
label = fmt.Sprintf("%s (%s)", label, item.Name)
|
||||||
|
}
|
||||||
|
names = append(names, label)
|
||||||
|
}
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Tidak dapat melepas kandang karena sudah memiliki recording: %s", strings.Join(names, ", ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := dbTransaction.Model(&entity.Kandang{}).
|
if resetStatus {
|
||||||
Where("id IN ?", kandangIDs).
|
if err := s.kandangRepoWithTx(dbTransaction).UpdateStatusByIDs(ctx, kandangIDs, utils.KandangStatusNonActive); err != nil {
|
||||||
Updates(updates).Error; err != nil {
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs status")
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.pivotRepoWithTx(dbTransaction).DeleteMany(ctx, projectFlockID, kandangIDs); err != nil {
|
if err := s.pivotRepoWithTx(dbTransaction).DeleteMany(ctx, projectFlockID, kandangIDs); err != nil {
|
||||||
@@ -821,8 +941,33 @@ func (s projectflockService) detachKandangs(ctx context.Context, dbTransaction *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) pivotRepoWithTx(dbTransaction *gorm.DB) repository.ProjectFlockKandangRepository {
|
func (s projectflockService) pivotRepoWithTx(dbTransaction *gorm.DB) repository.ProjectFlockKandangRepository {
|
||||||
if s.PivotRepo == nil {
|
if dbTransaction == nil {
|
||||||
return repository.NewProjectFlockKandangRepository(dbTransaction)
|
return s.pivotRepo()
|
||||||
}
|
}
|
||||||
return s.PivotRepo.WithTx(dbTransaction)
|
return s.pivotRepo().WithTx(dbTransaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) pivotRepo() repository.ProjectFlockKandangRepository {
|
||||||
|
if s.PivotRepo != nil {
|
||||||
|
return s.PivotRepo
|
||||||
|
}
|
||||||
|
return repository.NewProjectFlockKandangRepository(s.Repository.DB())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) kandangRepoWithTx(tx *gorm.DB) kandangRepository.KandangRepository {
|
||||||
|
if tx != nil {
|
||||||
|
return kandangRepository.NewKandangRepository(tx)
|
||||||
|
}
|
||||||
|
if s.KandangRepo != nil {
|
||||||
|
return s.KandangRepo
|
||||||
|
}
|
||||||
|
return kandangRepository.NewKandangRepository(s.Repository.DB())
|
||||||
|
}
|
||||||
|
|
||||||
|
func actorIDFromContext(c *fiber.Ctx) (uint, error) {
|
||||||
|
user, ok := authmiddleware.AuthenticatedUser(c)
|
||||||
|
if !ok || user == nil || user.Id == 0 {
|
||||||
|
return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
|
}
|
||||||
|
return user.Id, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeriveBaseName removes trailing numeric tokens from the flock name.
|
||||||
|
func DeriveBaseName(name string) string {
|
||||||
|
trimmed := strings.TrimSpace(name)
|
||||||
|
if trimmed == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Fields(trimmed)
|
||||||
|
for len(parts) > 0 {
|
||||||
|
if _, err := strconv.Atoi(parts[len(parts)-1]); err == nil {
|
||||||
|
parts = parts[:len(parts)-1]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(strings.Join(parts, " "))
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
FlockId uint `json:"flock_id" validate:"required_strict,number,gt=0"`
|
FlockName string `json:"flock_name" validate:"required_strict"`
|
||||||
AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"`
|
AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"`
|
||||||
Category string `json:"category" validate:"required_strict"`
|
Category string `json:"category" validate:"required_strict"`
|
||||||
FcrId uint `json:"fcr_id" validate:"required_strict,number,gt=0"`
|
FcrId uint `json:"fcr_id" validate:"required_strict,number,gt=0"`
|
||||||
@@ -10,7 +10,7 @@ type Create struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
FlockId *uint `json:"flock_id,omitempty" validate:"omitempty,number,gt=0"`
|
FlockName *string `json:"flock_name,omitempty" validate:"omitempty"`
|
||||||
AreaId *uint `json:"area_id,omitempty" validate:"omitempty,number,gt=0"`
|
AreaId *uint `json:"area_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
Category *string `json:"category,omitempty" validate:"omitempty"`
|
Category *string `json:"category,omitempty" validate:"omitempty"`
|
||||||
FcrId *uint `json:"fcr_id,omitempty" validate:"omitempty,number,gt=0"`
|
FcrId *uint `json:"fcr_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
|
|||||||
@@ -23,10 +23,14 @@ func NewRecordingController(recordingService service.RecordingService) *Recordin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
||||||
|
projectFlockID := c.QueryInt("project_flock_kandang_id", 0)
|
||||||
|
|
||||||
query := &validation.Query{
|
query := &validation.Query{
|
||||||
Page: c.QueryInt("page", 1),
|
Page: c.QueryInt("page", 1),
|
||||||
Limit: c.QueryInt("limit", 10),
|
Limit: c.QueryInt("limit", 10),
|
||||||
Search: c.Query("search", ""),
|
}
|
||||||
|
if projectFlockID > 0 {
|
||||||
|
query.ProjectFlockKandangId = uint(projectFlockID)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, totalResults, err := u.RecordingService.GetAll(c, query)
|
result, totalResults, err := u.RecordingService.GetAll(c, query)
|
||||||
@@ -67,7 +71,30 @@ func (u *RecordingController) GetOne(c *fiber.Ctx) error {
|
|||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Get recording successfully",
|
Message: "Get recording successfully",
|
||||||
Data: dto.ToRecordingListDTO(*result),
|
Data: dto.ToRecordingDetailDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) GetNextDay(c *fiber.Ctx) error {
|
||||||
|
projectFlockID := c.QueryInt("project_flock_kandang_id", 0)
|
||||||
|
if projectFlockID <= 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
nextDay, err := u.RecordingService.GetNextDay(c, uint(projectFlockID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get next recording day successfully",
|
||||||
|
Data: fiber.Map{
|
||||||
|
"project_flock_kandang_id": projectFlockID,
|
||||||
|
"next_day": nextDay,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +115,7 @@ func (u *RecordingController) CreateOne(c *fiber.Ctx) error {
|
|||||||
Code: fiber.StatusCreated,
|
Code: fiber.StatusCreated,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Create recording successfully",
|
Message: "Create recording successfully",
|
||||||
Data: dto.ToRecordingListDTO(*result),
|
Data: dto.ToRecordingDetailDTO(*result),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +142,61 @@ func (u *RecordingController) UpdateOne(c *fiber.Ctx) error {
|
|||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Update recording successfully",
|
Message: "Update recording successfully",
|
||||||
Data: dto.ToRecordingListDTO(*result),
|
Data: dto.ToRecordingDetailDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) SubmitGrading(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.SubmitGrading)
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.RecordingService.SubmitGrading(c, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Submit grading eggs successfully",
|
||||||
|
Data: dto.ToRecordingDetailDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) Approve(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Approve)
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := u.RecordingService.Approval(c, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
data interface{}
|
||||||
|
message = "Submit recording approvals successfully"
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(results) == 1 {
|
||||||
|
message = "Submit recording approval successfully"
|
||||||
|
data = dto.ToRecordingDetailDTO(results[0])
|
||||||
|
} else {
|
||||||
|
data = dto.ToRecordingListDTOs(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: message,
|
||||||
|
Data: data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user