mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-21 13:55:43 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f003c512d | |||
| 3ca95750a7 | |||
| 5dd2dbfa98 | |||
| 00bfd5ca45 | |||
| 3e8bcb3f64 | |||
| cfc4846170 | |||
| c8f96c6086 | |||
| 930fe25589 | |||
| 4137008b92 | |||
| 477f65554e | |||
| 6fcbcfd554 | |||
| 99c34c5793 | |||
| ecc9a51ea7 | |||
| 75ade0d8b3 | |||
| b6a60d5009 |
@@ -1,13 +0,0 @@
|
|||||||
# .air.toml
|
|
||||||
root = "."
|
|
||||||
tmp_dir = "tmp"
|
|
||||||
|
|
||||||
[build]
|
|
||||||
cmd = "go build -o ./tmp/main ./cmd/api"
|
|
||||||
bin = "tmp/main"
|
|
||||||
full_bin = "APP_ENV=dev ./tmp/main"
|
|
||||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
|
||||||
exclude_dir = ["vendor", "tmp"]
|
|
||||||
|
|
||||||
[log]
|
|
||||||
time = true
|
|
||||||
+54
-73
@@ -1,90 +1,71 @@
|
|||||||
stages:
|
stages:
|
||||||
|
- build
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
deploy-dev:
|
variables:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
DOCKER_DRIVER: overlay2
|
||||||
|
DOCKER_HOST: tcp://docker:2375
|
||||||
|
DOCKER_TLS_CERTDIR: ""
|
||||||
|
|
||||||
|
IMAGE_TAG: "stg-ec2_${CI_COMMIT_SHORT_SHA}"
|
||||||
|
IMAGE_NAME: "${CI_REGISTRY_IMAGE}:${IMAGE_TAG}"
|
||||||
|
IMAGE_LATEST_STG_EC2: "${CI_REGISTRY_IMAGE}:stg-ec2_latest"
|
||||||
|
|
||||||
|
build:stg-ec2:
|
||||||
|
stage: build
|
||||||
|
image: docker:27.0.3
|
||||||
|
services:
|
||||||
|
- name: docker:27.0.3-dind
|
||||||
|
command: ["--mtu=1460"]
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_BRANCH == "stg-ec2"'
|
||||||
|
before_script:
|
||||||
|
- docker info
|
||||||
|
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
|
||||||
|
script:
|
||||||
|
- docker build -t "$IMAGE_NAME" -f Dockerfile .
|
||||||
|
- docker push "$IMAGE_NAME"
|
||||||
|
- docker tag "$IMAGE_NAME" "$IMAGE_LATEST_STG_EC2"
|
||||||
|
- docker push "$IMAGE_LATEST_STG_EC2"
|
||||||
|
|
||||||
|
deploy:stg-ec2:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
image: alpine:3.20
|
image: alpine:3.20
|
||||||
variables:
|
rules:
|
||||||
DEPLOY_APP: "LTI-MBUGROUP"
|
- if: '$CI_COMMIT_BRANCH == "stg-ec2"'
|
||||||
# Opsional: kalau pakai submodule, ini bikin clone submodule pakai SSH juga
|
needs:
|
||||||
GIT_SUBMODULE_STRATEGY: recursive
|
- job: build:stg-ec2
|
||||||
GIT_DEPTH: "1"
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- echo "🧰 Installing dependencies..."
|
- apk add --no-cache openssh-client bash ca-certificates
|
||||||
- apk update && apk add --no-cache openssh git curl bash
|
|
||||||
|
|
||||||
# Setup SSH di runner
|
|
||||||
- mkdir -p ~/.ssh
|
- mkdir -p ~/.ssh
|
||||||
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
|
- chmod 700 ~/.ssh
|
||||||
|
|
||||||
|
# SSH_PRIVATE_KEY = multiline private key (bukan File)
|
||||||
|
- printf "%s\n" "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||||
|
- sed -i 's/\r$//' ~/.ssh/id_rsa
|
||||||
- chmod 600 ~/.ssh/id_rsa
|
- chmod 600 ~/.ssh/id_rsa
|
||||||
|
|
||||||
|
- head -n 1 ~/.ssh/id_rsa
|
||||||
|
- tail -n 1 ~/.ssh/id_rsa
|
||||||
|
|
||||||
- eval "$(ssh-agent -s)"
|
- eval "$(ssh-agent -s)"
|
||||||
- ssh-add ~/.ssh/id_rsa
|
- ssh-add ~/.ssh/id_rsa
|
||||||
|
|
||||||
# Trust host keys (server + gitlab) biar SSH gak nanya interaktif
|
|
||||||
- ssh-keyscan -H "$SERVER_IP" >> ~/.ssh/known_hosts
|
- ssh-keyscan -H "$SERVER_IP" >> ~/.ssh/known_hosts
|
||||||
- ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- echo "🚀 Deploying latest code to $SERVER_USER@$SERVER_IP"
|
|
||||||
|
|
||||||
- >
|
- >
|
||||||
if ssh -o StrictHostKeyChecking=no "$SERVER_USER@$SERVER_IP" "
|
ssh "$SERVER_USER@$SERVER_IP"
|
||||||
set -e
|
"export CI_REGISTRY_USER='$CI_REGISTRY_USER';
|
||||||
|
export CI_REGISTRY_PASSWORD='$CI_REGISTRY_PASSWORD';
|
||||||
cd /home/devops/docker/deployment/development/lti-api
|
export CI_REGISTRY='$CI_REGISTRY';
|
||||||
|
set -e;
|
||||||
# Pastikan remote origin SSH (antisipasi kalau pernah ke-set HTTPS)
|
cd /home/ubuntu/docker/deployment/staging/stg-lti-api;
|
||||||
git remote set-url origin git@gitlab.com:mbugroup/lti-api.git
|
echo \"\$CI_REGISTRY_PASSWORD\" | docker login -u \"\$CI_REGISTRY_USER\" --password-stdin \"\$CI_REGISTRY\";
|
||||||
|
docker compose pull;
|
||||||
# Pastikan server percaya gitlab.com juga (untuk git fetch via SSH)
|
docker compose up -d;
|
||||||
mkdir -p ~/.ssh
|
docker image prune -f"
|
||||||
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
|
|
||||||
|
|
||||||
# Fetch/reset pakai SSH
|
|
||||||
GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' git fetch origin development
|
|
||||||
git reset --hard origin/development
|
|
||||||
|
|
||||||
docker compose restart dev-api-lti || docker compose up -d dev-api-lti
|
|
||||||
"; then
|
|
||||||
STATUS='success';
|
|
||||||
else
|
|
||||||
STATUS='failed';
|
|
||||||
fi;
|
|
||||||
|
|
||||||
RUN_URL="${CI_PROJECT_URL}/-/pipelines/${CI_PIPELINE_ID}";
|
|
||||||
|
|
||||||
if [ "$STATUS" = "success" ]; then
|
|
||||||
COLOR=3066993;
|
|
||||||
TITLE="✅ Deployment API Succeeded";
|
|
||||||
DESC="Deployment job on branch \`${CI_COMMIT_REF_NAME}\` completed successfully.";
|
|
||||||
else
|
|
||||||
COLOR=15158332;
|
|
||||||
TITLE="❌ Deployment API Failed Gaes";
|
|
||||||
DESC="Deployment job on branch \`${CI_COMMIT_REF_NAME}\` failed.";
|
|
||||||
fi;
|
|
||||||
|
|
||||||
echo "{
|
|
||||||
\"username\": \"CI Bot\",
|
|
||||||
\"embeds\": [{
|
|
||||||
\"title\": \"$TITLE\",
|
|
||||||
\"description\": \"$DESC\",
|
|
||||||
\"color\": $COLOR,
|
|
||||||
\"fields\": [
|
|
||||||
{\"name\": \"Repository\", \"value\": \"${CI_PROJECT_PATH}\", \"inline\": true},
|
|
||||||
{\"name\": \"Actor\", \"value\": \"${GITLAB_USER_LOGIN}\", \"inline\": true},
|
|
||||||
{\"name\": \"Commit\", \"value\": \"${CI_COMMIT_SHA}\", \"inline\": false},
|
|
||||||
{\"name\": \"Pipeline\", \"value\": \"[Open run](${RUN_URL})\", \"inline\": false}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}" > payload.json;
|
|
||||||
|
|
||||||
echo "📡 Sending notification to Discord...";
|
|
||||||
curl -sS -H "Content-Type: application/json" \
|
|
||||||
-d @payload.json "$DISCORD_WEBHOOK_URL";
|
|
||||||
|
|
||||||
only:
|
|
||||||
- development
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
name: development
|
name: staging
|
||||||
+26
-11
@@ -1,20 +1,35 @@
|
|||||||
FROM golang:1.23-alpine
|
# =========================
|
||||||
|
# Builder stage
|
||||||
|
# =========================
|
||||||
|
FROM golang:1.23-alpine AS builder
|
||||||
|
|
||||||
# Install dependensi dasar
|
RUN apk add --no-cache git ca-certificates tzdata
|
||||||
RUN apk add --no-cache git curl bash build-base
|
WORKDIR /app
|
||||||
|
|
||||||
# Install Air (pakai repo baru air-verse)
|
|
||||||
RUN go install github.com/air-verse/air@v1.52.3
|
|
||||||
|
|
||||||
WORKDIR /lti-api
|
|
||||||
|
|
||||||
# Cache dependencies
|
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
# Copy source code
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Build binary dari cmd/api
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
|
||||||
|
go build -trimpath -ldflags="-s -w" -o lti-api ./cmd/api
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Runtime stage
|
||||||
|
# =========================
|
||||||
|
FROM alpine:3.20
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata curl \
|
||||||
|
&& adduser -D -H -u 10001 appuser
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /app/lti-api /app/lti-api
|
||||||
|
|
||||||
|
USER appuser
|
||||||
|
|
||||||
|
# Samakan dengan APP_PORT default kamu (8081)
|
||||||
EXPOSE 8081
|
EXPOSE 8081
|
||||||
|
|
||||||
CMD ["air", "-c", ".air.toml"]
|
CMD ["/app/lti-api"]
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
POSTGRES_USER=postgres
|
|
||||||
POSTGRES_PASSWORD=Postgres@Secure2025!
|
|
||||||
POSTGRES_DB=db_lti_erp
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
-- ============================================================
|
|
||||||
-- 🧩 INIT SCRIPT: CREATE LIMITED APP USER FOR LTI API
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
-- Buat user aplikasi jika belum ada
|
|
||||||
DO
|
|
||||||
$$
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'app_lti_user') THEN
|
|
||||||
CREATE ROLE app_lti_user WITH LOGIN PASSWORD 'AppLti@Secure2025!' NOINHERIT NOCREATEROLE NOCREATEDB NOSUPERUSER;
|
|
||||||
RAISE NOTICE '✅ Role app_lti_user created successfully.';
|
|
||||||
ELSE
|
|
||||||
RAISE NOTICE 'ℹ️ Role app_lti_user already exists.';
|
|
||||||
END IF;
|
|
||||||
END
|
|
||||||
$$;
|
|
||||||
|
|
||||||
-- Buat database jika belum ada
|
|
||||||
DO
|
|
||||||
$$
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (SELECT FROM pg_database WHERE datname = 'db_lti_erp') THEN
|
|
||||||
CREATE DATABASE db_lti_erp OWNER app_lti_user;
|
|
||||||
RAISE NOTICE '✅ Database db_lti_erp created and owned by app_lti_user.';
|
|
||||||
ELSE
|
|
||||||
RAISE NOTICE 'ℹ️ Database db_lti_erp already exists.';
|
|
||||||
END IF;
|
|
||||||
END
|
|
||||||
$$;
|
|
||||||
|
|
||||||
\connect db_lti_erp
|
|
||||||
|
|
||||||
-- Beri hak CRUD untuk app_lti_user
|
|
||||||
GRANT CONNECT ON DATABASE db_lti_erp TO app_lti_user;
|
|
||||||
GRANT USAGE ON SCHEMA public TO app_lti_user;
|
|
||||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_lti_user;
|
|
||||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_lti_user;
|
|
||||||
|
|
||||||
-- Set default privileges agar tabel baru juga bisa diakses
|
|
||||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
||||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_lti_user;
|
|
||||||
|
|
||||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
|
||||||
GRANT USAGE, SELECT ON SEQUENCES TO app_lti_user;
|
|
||||||
|
|
||||||
-- Tampilkan hasil
|
|
||||||
\du app_lti_user
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
services:
|
|
||||||
postgresdb:
|
|
||||||
image: postgres:alpine
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- "${DB_PORT_HOST:-5542}:5432"
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: ${DB_USER:-postgres}
|
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
|
|
||||||
POSTGRES_DB: ${DB_NAME:-db_lti_erp}
|
|
||||||
volumes:
|
|
||||||
- dbdata:/var/lib/postgresql/data
|
|
||||||
- ./internal/database/init:/docker-entrypoint-initdb.d
|
|
||||||
networks: [go-network]
|
|
||||||
healthcheck:
|
|
||||||
test:
|
|
||||||
[
|
|
||||||
"CMD-SHELL",
|
|
||||||
"pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-db_lti_erp}",
|
|
||||||
]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
redis:
|
|
||||||
image: redis:7-alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "${REDIS_PORT_HOST:-6381}:6379"
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 10
|
|
||||||
networks: [go-network]
|
|
||||||
|
|
||||||
app:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile.local
|
|
||||||
image: cosmtrek/air:v1.52.3
|
|
||||||
working_dir: /lti-api
|
|
||||||
volumes:
|
|
||||||
- .:/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
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
environment:
|
|
||||||
DB_HOST: postgresdb
|
|
||||||
DB_PORT: 5432
|
|
||||||
DB_USER: ${DB_USER:-postgres}
|
|
||||||
DB_PASSWORD: ${DB_PASSWORD:-postgres}
|
|
||||||
DB_NAME: ${DB_NAME:-db_lti_erp}
|
|
||||||
REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
|
|
||||||
ports:
|
|
||||||
- "${APP_PORT:-8081}:8081"
|
|
||||||
depends_on:
|
|
||||||
postgresdb:
|
|
||||||
condition: service_healthy
|
|
||||||
networks: [go-network]
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "wget -qO- http://localhost:8081/healthz || exit 1"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 10
|
|
||||||
start_period: 10s
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
dbdata:
|
|
||||||
go-mod-cache:
|
|
||||||
go-build-cache:
|
|
||||||
|
|
||||||
networks:
|
|
||||||
go-network:
|
|
||||||
name: lti-api_go-network
|
|
||||||
driver: bridge
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
services:
|
|
||||||
dev-api-lti:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
container_name: dev-api-lti
|
|
||||||
working_dir: /lti-api
|
|
||||||
command: ["/bin/sh", "scripts/entrypoint.sh"]
|
|
||||||
ports:
|
|
||||||
- "8081:8081"
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
environment:
|
|
||||||
# override agar koneksi ke container internal
|
|
||||||
DB_HOST: dev-postgres-lti
|
|
||||||
DB_PORT: 5432
|
|
||||||
REDIS_URL: redis://dev-redis-lti:6379/0
|
|
||||||
volumes:
|
|
||||||
- .:/lti-api
|
|
||||||
- ./.air.toml:/lti-api/.air.toml:ro
|
|
||||||
- ./internal/config/jwtRS256.key:/run/keys/jwtRS256.key
|
|
||||||
- ./internal/config/jwtRS256.key.pub:/run/keys/jwtRS256.key.pub
|
|
||||||
depends_on:
|
|
||||||
- dev-postgres-lti
|
|
||||||
- dev-redis-lti
|
|
||||||
networks:
|
|
||||||
- lti-network
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "wget -qO- http://localhost:8081/healthz || exit 1"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 10
|
|
||||||
start_period: 10s
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpus: "2.0"
|
|
||||||
memory: 2G
|
|
||||||
reservations:
|
|
||||||
cpus: "1.0"
|
|
||||||
memory: 512M
|
|
||||||
|
|
||||||
dev-postgres-lti:
|
|
||||||
image: postgres:15-alpine
|
|
||||||
container_name: dev-postgres-lti
|
|
||||||
restart: always
|
|
||||||
env_file:
|
|
||||||
- credential/.env.db
|
|
||||||
ports:
|
|
||||||
- "5433:5432"
|
|
||||||
volumes:
|
|
||||||
- dev-postgres-lti-data:/var/lib/postgresql/data
|
|
||||||
- ./credential:/docker-entrypoint-initdb.d:ro
|
|
||||||
networks:
|
|
||||||
- lti-network
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-db_lti_erp}"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
start_period: 5s
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpus: "1.0"
|
|
||||||
memory: 2G
|
|
||||||
reservations:
|
|
||||||
cpus: "0.5"
|
|
||||||
memory: 512M
|
|
||||||
|
|
||||||
dev-redis-lti:
|
|
||||||
image: redis:7-alpine
|
|
||||||
container_name: dev-redis-lti
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- "6380:6379"
|
|
||||||
networks:
|
|
||||||
- lti-network
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 3s
|
|
||||||
retries: 10
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpus: "0.5"
|
|
||||||
memory: 512M
|
|
||||||
reservations:
|
|
||||||
cpus: "0.2"
|
|
||||||
memory: 256M
|
|
||||||
|
|
||||||
networks:
|
|
||||||
lti-network:
|
|
||||||
driver: bridge
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
dev-postgres-lti-data:
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StockAllocationRepository interface {
|
|
||||||
BaseRepository[entity.StockAllocation]
|
|
||||||
FindActiveByUsable(ctx context.Context, usableType string, usableID uint, modifier func(*gorm.DB) *gorm.DB) ([]entity.StockAllocation, error)
|
|
||||||
ReleaseByUsable(ctx context.Context, usableType string, usableID uint, note *string, modifier func(*gorm.DB) *gorm.DB) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type StockAllocationRepositoryImpl struct {
|
|
||||||
*BaseRepositoryImpl[entity.StockAllocation]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStockAllocationRepository(db *gorm.DB) StockAllocationRepository {
|
|
||||||
return &StockAllocationRepositoryImpl{
|
|
||||||
BaseRepositoryImpl: NewBaseRepository[entity.StockAllocation](db),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *StockAllocationRepositoryImpl) FindActiveByUsable(
|
|
||||||
ctx context.Context,
|
|
||||||
usableType string,
|
|
||||||
usableID uint,
|
|
||||||
modifier func(*gorm.DB) *gorm.DB,
|
|
||||||
) ([]entity.StockAllocation, error) {
|
|
||||||
var allocations []entity.StockAllocation
|
|
||||||
|
|
||||||
q := r.DB().WithContext(ctx).
|
|
||||||
Where("usable_type = ? AND usable_id = ? AND status = ?", usableType, usableID, entity.StockAllocationStatusActive)
|
|
||||||
|
|
||||||
if modifier != nil {
|
|
||||||
q = modifier(q)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := q.Order("created_at ASC").Find(&allocations).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return allocations, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *StockAllocationRepositoryImpl) ReleaseByUsable(
|
|
||||||
ctx context.Context,
|
|
||||||
usableType string,
|
|
||||||
usableID uint,
|
|
||||||
note *string,
|
|
||||||
modifier func(*gorm.DB) *gorm.DB,
|
|
||||||
) error {
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
updates := map[string]any{
|
|
||||||
"status": entity.StockAllocationStatusReleased,
|
|
||||||
"released_at": now,
|
|
||||||
}
|
|
||||||
if note != nil {
|
|
||||||
updates["note"] = *note
|
|
||||||
}
|
|
||||||
|
|
||||||
q := r.DB().WithContext(ctx).
|
|
||||||
Model(&entity.StockAllocation{}).
|
|
||||||
Where("usable_type = ? AND usable_id = ? AND status = ?", usableType, usableID, entity.StockAllocationStatusActive)
|
|
||||||
|
|
||||||
if modifier != nil {
|
|
||||||
q = modifier(q)
|
|
||||||
}
|
|
||||||
|
|
||||||
return q.Updates(updates).Error
|
|
||||||
}
|
|
||||||
@@ -1,820 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
||||||
productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FifoService interface {
|
|
||||||
RegisterStockable(cfg fifo.StockableConfig) error
|
|
||||||
RegisterUsable(cfg fifo.UsableConfig) error
|
|
||||||
|
|
||||||
Replenish(ctx context.Context, req StockReplenishRequest) (*StockReplenishResult, error)
|
|
||||||
Consume(ctx context.Context, req StockConsumeRequest) (*StockConsumeResult, error)
|
|
||||||
ReleaseUsage(ctx context.Context, req StockReleaseRequest) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type fifoService struct {
|
|
||||||
db *gorm.DB
|
|
||||||
logger *logrus.Logger
|
|
||||||
allocations commonRepo.StockAllocationRepository
|
|
||||||
productWarehouseRepo productWarehouseRepo.ProductWarehouseRepository
|
|
||||||
defaultOrderBy []string
|
|
||||||
pendingBatchPerUsable int
|
|
||||||
maxLotsPerStockable int
|
|
||||||
defaultAllocationNotes string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFifoService(
|
|
||||||
db *gorm.DB,
|
|
||||||
allocations commonRepo.StockAllocationRepository,
|
|
||||||
productWarehouseRepo productWarehouseRepo.ProductWarehouseRepository,
|
|
||||||
logger *logrus.Logger,
|
|
||||||
) FifoService {
|
|
||||||
if logger == nil {
|
|
||||||
logger = logrus.StandardLogger()
|
|
||||||
}
|
|
||||||
return &fifoService{
|
|
||||||
db: db,
|
|
||||||
logger: logger,
|
|
||||||
allocations: allocations,
|
|
||||||
productWarehouseRepo: productWarehouseRepo,
|
|
||||||
defaultOrderBy: []string{"created_at ASC", "id ASC"},
|
|
||||||
pendingBatchPerUsable: 25,
|
|
||||||
maxLotsPerStockable: 50,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) withTransaction(
|
|
||||||
ctx context.Context,
|
|
||||||
tx *gorm.DB,
|
|
||||||
fn func(*gorm.DB) error,
|
|
||||||
) error {
|
|
||||||
if tx != nil {
|
|
||||||
return fn(tx.WithContext(ctx))
|
|
||||||
}
|
|
||||||
return s.db.WithContext(ctx).Transaction(func(inner *gorm.DB) error {
|
|
||||||
return fn(inner)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) txOrDB(tx, db *gorm.DB) *gorm.DB {
|
|
||||||
if tx != nil {
|
|
||||||
return tx
|
|
||||||
}
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) RegisterStockable(cfg fifo.StockableConfig) error {
|
|
||||||
return fifo.RegisterStockable(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) RegisterUsable(cfg fifo.UsableConfig) error {
|
|
||||||
return fifo.RegisterUsable(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
type StockReplenishRequest struct {
|
|
||||||
StockableKey fifo.StockableKey
|
|
||||||
StockableID uint
|
|
||||||
ProductWarehouseID uint
|
|
||||||
Quantity float64
|
|
||||||
Note *string
|
|
||||||
Tx *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
type PendingResolution struct {
|
|
||||||
UsableKey fifo.UsableKey
|
|
||||||
UsableID uint
|
|
||||||
Quantity float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type StockReplenishResult struct {
|
|
||||||
AddedQuantity float64
|
|
||||||
PendingResolved []PendingResolution
|
|
||||||
RemainingPending float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type StockConsumeRequest struct {
|
|
||||||
UsableKey fifo.UsableKey
|
|
||||||
UsableID uint
|
|
||||||
ProductWarehouseID uint
|
|
||||||
Quantity float64
|
|
||||||
AllowPending bool
|
|
||||||
Note *string
|
|
||||||
Tx *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
type AllocationDetail struct {
|
|
||||||
StockableKey fifo.StockableKey
|
|
||||||
StockableID uint
|
|
||||||
Quantity float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type StockConsumeResult struct {
|
|
||||||
RequestedQuantity float64
|
|
||||||
UsageQuantity float64
|
|
||||||
PendingQuantity float64
|
|
||||||
AddedAllocations []AllocationDetail
|
|
||||||
ReleasedQuantity float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type StockReleaseRequest struct {
|
|
||||||
UsableKey fifo.UsableKey
|
|
||||||
UsableID uint
|
|
||||||
Reason *string
|
|
||||||
Tx *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) Replenish(ctx context.Context, req StockReplenishRequest) (*StockReplenishResult, error) {
|
|
||||||
if req.StockableID == 0 || strings.TrimSpace(req.StockableKey.String()) == "" {
|
|
||||||
return nil, errors.New("stockable key and id are required")
|
|
||||||
}
|
|
||||||
if req.ProductWarehouseID == 0 {
|
|
||||||
return nil, errors.New("product warehouse id is required")
|
|
||||||
}
|
|
||||||
if req.Quantity <= 0 {
|
|
||||||
return nil, errors.New("quantity must be greater than zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, ok := fifo.Stockable(req.StockableKey)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("stockable %q is not registered", req.StockableKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &StockReplenishResult{
|
|
||||||
AddedQuantity: req.Quantity,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.withTransaction(ctx, req.Tx, func(tx *gorm.DB) error {
|
|
||||||
if err := s.incrementStockableQty(ctx, tx, cfg, req.StockableID, req.Quantity); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.productWarehouseRepo.AdjustQuantities(ctx, map[uint]float64{
|
|
||||||
req.ProductWarehouseID: req.Quantity,
|
|
||||||
}, func(db *gorm.DB) *gorm.DB {
|
|
||||||
return s.txOrDB(tx, db)
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resolved, err := s.resolvePendingForWarehouse(ctx, tx, req.ProductWarehouseID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
result.PendingResolved = resolved
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) Consume(ctx context.Context, req StockConsumeRequest) (*StockConsumeResult, error) {
|
|
||||||
if req.UsableID == 0 || strings.TrimSpace(req.UsableKey.String()) == "" {
|
|
||||||
return nil, errors.New("usable key and id are required")
|
|
||||||
}
|
|
||||||
if req.Quantity < 0 {
|
|
||||||
return nil, errors.New("quantity must be zero or greater")
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, ok := fifo.Usable(req.UsableKey)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("usable %q is not registered", req.UsableKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &StockConsumeResult{
|
|
||||||
RequestedQuantity: req.Quantity,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.withTransaction(ctx, req.Tx, func(tx *gorm.DB) error {
|
|
||||||
ctxRow, err := s.loadUsableContext(ctx, tx, cfg, req.UsableID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
productWarehouseID := ctxRow.ProductWarehouseID
|
|
||||||
if productWarehouseID == 0 {
|
|
||||||
return fmt.Errorf("usable %q (id: %d) has no product warehouse reference", req.UsableKey, req.UsableID)
|
|
||||||
}
|
|
||||||
if req.ProductWarehouseID != 0 && req.ProductWarehouseID != productWarehouseID {
|
|
||||||
return fmt.Errorf("usable %q (id: %d) references product warehouse %d but %d was provided", req.UsableKey, req.UsableID, productWarehouseID, req.ProductWarehouseID)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentUsage := ctxRow.UsageQty
|
|
||||||
currentPending := ctxRow.PendingQty
|
|
||||||
currentTotal := currentUsage + currentPending
|
|
||||||
delta := req.Quantity - currentTotal
|
|
||||||
|
|
||||||
var (
|
|
||||||
usageDelta float64
|
|
||||||
pendingDelta float64
|
|
||||||
addedAlloc []AllocationDetail
|
|
||||||
releasedAmount float64
|
|
||||||
)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case delta > 0:
|
|
||||||
allocationRes, err := s.allocateFromStock(ctx, tx, productWarehouseID, req.UsableKey, req.UsableID, delta)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if allocationRes.pending > 0 && !req.AllowPending {
|
|
||||||
return fmt.Errorf("insufficient stock: requested %.3f, allocated %.3f", req.Quantity, currentUsage+allocationRes.allocated)
|
|
||||||
}
|
|
||||||
|
|
||||||
usageDelta += allocationRes.allocated
|
|
||||||
pendingDelta += allocationRes.pending
|
|
||||||
addedAlloc = allocationRes.allocations
|
|
||||||
|
|
||||||
if allocationRes.allocated > 0 {
|
|
||||||
if err := s.productWarehouseRepo.AdjustQuantities(ctx, map[uint]float64{
|
|
||||||
productWarehouseID: -allocationRes.allocated,
|
|
||||||
}, func(db *gorm.DB) *gorm.DB {
|
|
||||||
return s.txOrDB(tx, db)
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case delta < 0:
|
|
||||||
reductionTarget := -delta
|
|
||||||
|
|
||||||
if currentPending > 0 {
|
|
||||||
pendingReduction := math.Min(currentPending, reductionTarget)
|
|
||||||
if pendingReduction > 0 {
|
|
||||||
pendingDelta -= pendingReduction
|
|
||||||
reductionTarget -= pendingReduction
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if reductionTarget > 0 {
|
|
||||||
released, err := s.releaseUsagePortion(ctx, tx, req.UsableKey, req.UsableID, reductionTarget)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if released+1e-6 < reductionTarget {
|
|
||||||
return fmt.Errorf("unable to release %.3f from usable %d, only %.3f available", reductionTarget, req.UsableID, released)
|
|
||||||
}
|
|
||||||
usageDelta -= released
|
|
||||||
releasedAmount = released
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// no change
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.applyUsableDeltas(ctx, tx, cfg, req.UsableID, usageDelta, pendingDelta); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result.AddedAllocations = addedAlloc
|
|
||||||
result.ReleasedQuantity = releasedAmount
|
|
||||||
result.UsageQuantity = currentUsage + usageDelta
|
|
||||||
result.PendingQuantity = currentPending + pendingDelta
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) ReleaseUsage(ctx context.Context, req StockReleaseRequest) error {
|
|
||||||
if req.UsableID == 0 || strings.TrimSpace(req.UsableKey.String()) == "" {
|
|
||||||
return errors.New("usable key and id are required")
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.withTransaction(ctx, req.Tx, func(tx *gorm.DB) error {
|
|
||||||
cfg, ok := fifo.Usable(req.UsableKey)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("usable %q is not registered", req.UsableKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctxRow, err := s.loadUsableContext(ctx, tx, cfg, req.UsableID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var usageDelta, pendingDelta float64
|
|
||||||
if ctxRow.UsageQty > 0 {
|
|
||||||
if _, err := s.releaseUsagePortion(ctx, tx, req.UsableKey, req.UsableID, ctxRow.UsageQty); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
usageDelta -= ctxRow.UsageQty
|
|
||||||
}
|
|
||||||
if ctxRow.PendingQty > 0 {
|
|
||||||
pendingDelta -= ctxRow.PendingQty
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.applyUsableDeltas(ctx, tx, cfg, req.UsableID, usageDelta, pendingDelta); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.allocations.ReleaseByUsable(ctx, req.UsableKey.String(), req.UsableID, req.Reason, func(db *gorm.DB) *gorm.DB {
|
|
||||||
return s.txOrDB(tx, db)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- helpers ---
|
|
||||||
|
|
||||||
type usableContextRow struct {
|
|
||||||
ProductWarehouseID uint
|
|
||||||
UsageQty float64
|
|
||||||
PendingQty float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) loadUsableContext(ctx context.Context, tx *gorm.DB, cfg fifo.UsableConfig, id uint) (*usableContextRow, error) {
|
|
||||||
var row usableContextRow
|
|
||||||
|
|
||||||
query := tx.Table(cfg.Table).
|
|
||||||
Select(fmt.Sprintf("%s AS product_warehouse_id, COALESCE(%s,0) AS usage_qty, COALESCE(%s,0) AS pending_qty", cfg.Columns.ProductWarehouseID, cfg.Columns.UsageQuantity, cfg.Columns.PendingQuantity)).
|
|
||||||
Where(fmt.Sprintf("%s = ?", cfg.Columns.ID), id).
|
|
||||||
Clauses(clause.Locking{Strength: "UPDATE"})
|
|
||||||
|
|
||||||
if cfg.Scope != nil {
|
|
||||||
query = cfg.Scope(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := query.Take(&row).Error; err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, fmt.Errorf("usable record %d not found", id)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &row, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) incrementStockableQty(ctx context.Context, tx *gorm.DB, cfg fifo.StockableConfig, id uint, qty float64) error {
|
|
||||||
column := cfg.Columns.TotalQuantity
|
|
||||||
|
|
||||||
query := tx.Table(cfg.Table).
|
|
||||||
Where(fmt.Sprintf("%s = ?", cfg.Columns.ID), id)
|
|
||||||
if cfg.Scope != nil {
|
|
||||||
query = cfg.Scope(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
updates := map[string]any{
|
|
||||||
column: gorm.Expr(fmt.Sprintf("COALESCE(%s,0) + ?", column), qty),
|
|
||||||
}
|
|
||||||
if cfg.Columns.TotalUsedQuantity != "" {
|
|
||||||
updates[cfg.Columns.TotalUsedQuantity] = gorm.Expr(fmt.Sprintf("COALESCE(%s,0)", cfg.Columns.TotalUsedQuantity))
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.Updates(updates).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) incrementStockableUsage(ctx context.Context, tx *gorm.DB, cfg fifo.StockableConfig, id uint, qty float64) error {
|
|
||||||
if qty == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
column := cfg.Columns.TotalUsedQuantity
|
|
||||||
query := tx.Table(cfg.Table).
|
|
||||||
Where(fmt.Sprintf("%s = ?", cfg.Columns.ID), id)
|
|
||||||
if cfg.Scope != nil {
|
|
||||||
query = cfg.Scope(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.Update(column, gorm.Expr(fmt.Sprintf("COALESCE(%s,0) + ?", column), qty)).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
type allocationOutcome struct {
|
|
||||||
allocated float64
|
|
||||||
pending float64
|
|
||||||
allocations []AllocationDetail
|
|
||||||
}
|
|
||||||
|
|
||||||
type stockLot struct {
|
|
||||||
StockableKey fifo.StockableKey
|
|
||||||
RecordID uint
|
|
||||||
AvailableQty float64
|
|
||||||
CreatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) allocateFromStock(
|
|
||||||
ctx context.Context,
|
|
||||||
tx *gorm.DB,
|
|
||||||
productWarehouseID uint,
|
|
||||||
usableKey fifo.UsableKey,
|
|
||||||
usableID uint,
|
|
||||||
requestQty float64,
|
|
||||||
) (*allocationOutcome, error) {
|
|
||||||
lots, err := s.fetchStockLots(ctx, tx, productWarehouseID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(lots) == 0 {
|
|
||||||
return &allocationOutcome{pending: requestQty}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
remaining = requestQty
|
|
||||||
applied float64
|
|
||||||
allocations []*entities.StockAllocation
|
|
||||||
allocationSummaries []AllocationDetail
|
|
||||||
usageAdjustments = make(map[fifo.StockableKey]map[uint]float64)
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, lot := range lots {
|
|
||||||
if remaining <= 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if lot.AvailableQty <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
portion := lot.AvailableQty
|
|
||||||
if portion > remaining {
|
|
||||||
portion = remaining
|
|
||||||
}
|
|
||||||
|
|
||||||
applied += portion
|
|
||||||
remaining -= portion
|
|
||||||
|
|
||||||
allocationSummaries = append(allocationSummaries, AllocationDetail{
|
|
||||||
StockableKey: lot.StockableKey,
|
|
||||||
StockableID: lot.RecordID,
|
|
||||||
Quantity: portion,
|
|
||||||
})
|
|
||||||
|
|
||||||
allocations = append(allocations, &entities.StockAllocation{
|
|
||||||
ProductWarehouseId: productWarehouseID,
|
|
||||||
StockableType: lot.StockableKey.String(),
|
|
||||||
StockableId: lot.RecordID,
|
|
||||||
UsableType: usableKey.String(),
|
|
||||||
UsableId: usableID,
|
|
||||||
Qty: portion,
|
|
||||||
Status: entities.StockAllocationStatusActive,
|
|
||||||
})
|
|
||||||
|
|
||||||
if _, ok := usageAdjustments[lot.StockableKey]; !ok {
|
|
||||||
usageAdjustments[lot.StockableKey] = make(map[uint]float64)
|
|
||||||
}
|
|
||||||
usageAdjustments[lot.StockableKey][lot.RecordID] += portion
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(allocations) > 0 {
|
|
||||||
if err := s.allocations.CreateMany(ctx, allocations, func(db *gorm.DB) *gorm.DB {
|
|
||||||
return s.txOrDB(tx, db)
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, deltas := range usageAdjustments {
|
|
||||||
cfg, ok := fifo.Stockable(key)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for id, qty := range deltas {
|
|
||||||
if err := s.incrementStockableUsage(ctx, tx, cfg, id, qty); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &allocationOutcome{
|
|
||||||
allocated: applied,
|
|
||||||
pending: remaining,
|
|
||||||
allocations: allocationSummaries,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) fetchStockLots(ctx context.Context, tx *gorm.DB, productWarehouseID uint) ([]stockLot, error) {
|
|
||||||
configs := fifo.Stockables()
|
|
||||||
if len(configs) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var lots []stockLot
|
|
||||||
for key, cfg := range configs {
|
|
||||||
selectStmt := fmt.Sprintf(
|
|
||||||
"%s AS id, %s AS available_qty, %s AS created_at",
|
|
||||||
cfg.Columns.ID,
|
|
||||||
fmt.Sprintf("%s - COALESCE(%s,0)", cfg.Columns.TotalQuantity, cfg.Columns.TotalUsedQuantity),
|
|
||||||
cfg.Columns.CreatedAt,
|
|
||||||
)
|
|
||||||
|
|
||||||
var rows []struct {
|
|
||||||
ID uint
|
|
||||||
AvailableQty float64
|
|
||||||
CreatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
query := tx.Table(cfg.Table).
|
|
||||||
Select(selectStmt).
|
|
||||||
Where(fmt.Sprintf("%s = ?", cfg.Columns.ProductWarehouseID), productWarehouseID).
|
|
||||||
Where(fmt.Sprintf("%s > %s", cfg.Columns.TotalQuantity, cfg.Columns.TotalUsedQuantity))
|
|
||||||
|
|
||||||
if cfg.Scope != nil {
|
|
||||||
query = cfg.Scope(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, order := range s.orderClauses(cfg.OrderBy) {
|
|
||||||
query = query.Order(order)
|
|
||||||
}
|
|
||||||
query = query.Limit(s.maxLotsPerStockable)
|
|
||||||
|
|
||||||
if err := query.Find(&rows).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, row := range rows {
|
|
||||||
if row.AvailableQty <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
lots = append(lots, stockLot{
|
|
||||||
StockableKey: key,
|
|
||||||
RecordID: row.ID,
|
|
||||||
AvailableQty: row.AvailableQty,
|
|
||||||
CreatedAt: row.CreatedAt,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(lots) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.SliceStable(lots, func(i, j int) bool {
|
|
||||||
if lots[i].CreatedAt.Equal(lots[j].CreatedAt) {
|
|
||||||
return lots[i].RecordID < lots[j].RecordID
|
|
||||||
}
|
|
||||||
return lots[i].CreatedAt.Before(lots[j].CreatedAt)
|
|
||||||
})
|
|
||||||
|
|
||||||
return lots, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) applyUsableDeltas(ctx context.Context, tx *gorm.DB, cfg fifo.UsableConfig, id uint, usageDelta, pendingDelta float64) error {
|
|
||||||
if usageDelta == 0 && pendingDelta == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
updates := map[string]any{}
|
|
||||||
if usageDelta != 0 {
|
|
||||||
updates[cfg.Columns.UsageQuantity] = gorm.Expr(fmt.Sprintf("COALESCE(%s,0) + ?", cfg.Columns.UsageQuantity), usageDelta)
|
|
||||||
}
|
|
||||||
if pendingDelta != 0 {
|
|
||||||
updates[cfg.Columns.PendingQuantity] = gorm.Expr(fmt.Sprintf("COALESCE(%s,0) + ?", cfg.Columns.PendingQuantity), pendingDelta)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := tx.Table(cfg.Table).Where(fmt.Sprintf("%s = ?", cfg.Columns.ID), id)
|
|
||||||
if cfg.Scope != nil {
|
|
||||||
query = cfg.Scope(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.Updates(updates).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
type pendingCandidate struct {
|
|
||||||
UsableKey fifo.UsableKey
|
|
||||||
Config fifo.UsableConfig
|
|
||||||
UsableID uint
|
|
||||||
Pending float64
|
|
||||||
CreatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) resolvePendingForWarehouse(ctx context.Context, tx *gorm.DB, productWarehouseID uint) ([]PendingResolution, error) {
|
|
||||||
candidates, err := s.fetchPendingCandidates(ctx, tx, productWarehouseID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(candidates) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var resolutions []PendingResolution
|
|
||||||
|
|
||||||
for _, candidate := range candidates {
|
|
||||||
if candidate.Pending <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
outcome, err := s.allocateFromStock(ctx, tx, productWarehouseID, candidate.UsableKey, candidate.UsableID, candidate.Pending)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if outcome.allocated <= 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.applyUsableDeltas(ctx, tx, candidate.Config, candidate.UsableID, outcome.allocated, -outcome.allocated); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.productWarehouseRepo.AdjustQuantities(ctx, map[uint]float64{
|
|
||||||
productWarehouseID: -outcome.allocated,
|
|
||||||
}, func(db *gorm.DB) *gorm.DB {
|
|
||||||
return s.txOrDB(tx, db)
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resolutions = append(resolutions, PendingResolution{
|
|
||||||
UsableKey: candidate.UsableKey,
|
|
||||||
UsableID: candidate.UsableID,
|
|
||||||
Quantity: outcome.allocated,
|
|
||||||
})
|
|
||||||
|
|
||||||
if outcome.pending > 0 {
|
|
||||||
// No more stock available for this warehouse at the moment.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolutions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) releaseUsagePortion(
|
|
||||||
ctx context.Context,
|
|
||||||
tx *gorm.DB,
|
|
||||||
usableKey fifo.UsableKey,
|
|
||||||
usableID uint,
|
|
||||||
target float64,
|
|
||||||
) (float64, error) {
|
|
||||||
if target <= 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
allocations, err := s.allocations.FindActiveByUsable(ctx, usableKey.String(), usableID, func(db *gorm.DB) *gorm.DB {
|
|
||||||
target := s.txOrDB(tx, db)
|
|
||||||
return target.Clauses(clause.Locking{Strength: "UPDATE"})
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if len(allocations) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
remaining = target
|
|
||||||
totalReleased float64
|
|
||||||
warehouseAdjustments = make(map[uint]float64)
|
|
||||||
stockableAdjustments = make(map[fifo.StockableKey]map[uint]float64)
|
|
||||||
)
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
for i := len(allocations) - 1; i >= 0 && remaining > 0; i-- {
|
|
||||||
allocation := allocations[i]
|
|
||||||
releaseAmt := allocation.Qty
|
|
||||||
if releaseAmt > remaining {
|
|
||||||
releaseAmt = remaining
|
|
||||||
}
|
|
||||||
|
|
||||||
remaining -= releaseAmt
|
|
||||||
totalReleased += releaseAmt
|
|
||||||
warehouseAdjustments[allocation.ProductWarehouseId] += releaseAmt
|
|
||||||
|
|
||||||
key := fifo.StockableKey(allocation.StockableType)
|
|
||||||
if _, ok := stockableAdjustments[key]; !ok {
|
|
||||||
stockableAdjustments[key] = make(map[uint]float64)
|
|
||||||
}
|
|
||||||
stockableAdjustments[key][allocation.StockableId] += releaseAmt
|
|
||||||
|
|
||||||
if releaseAmt == allocation.Qty {
|
|
||||||
if err := s.allocations.PatchOne(ctx, allocation.Id, map[string]any{
|
|
||||||
"status": entities.StockAllocationStatusReleased,
|
|
||||||
"released_at": now,
|
|
||||||
}, func(db *gorm.DB) *gorm.DB {
|
|
||||||
return s.txOrDB(tx, db)
|
|
||||||
}); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := s.allocations.PatchOne(ctx, allocation.Id, map[string]any{
|
|
||||||
"quantity": allocation.Qty - releaseAmt,
|
|
||||||
}, func(db *gorm.DB) *gorm.DB {
|
|
||||||
return s.txOrDB(tx, db)
|
|
||||||
}); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if totalReleased == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, deltas := range stockableAdjustments {
|
|
||||||
cfg, ok := fifo.Stockable(key)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for id, qty := range deltas {
|
|
||||||
if err := s.incrementStockableUsage(ctx, tx, cfg, id, -qty); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(warehouseAdjustments) > 0 {
|
|
||||||
if err := s.productWarehouseRepo.AdjustQuantities(ctx, warehouseAdjustments, func(db *gorm.DB) *gorm.DB {
|
|
||||||
return s.txOrDB(tx, db)
|
|
||||||
}); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for warehouseID := range warehouseAdjustments {
|
|
||||||
if _, err := s.resolvePendingForWarehouse(ctx, tx, warehouseID); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalReleased, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) fetchPendingCandidates(ctx context.Context, tx *gorm.DB, productWarehouseID uint) ([]pendingCandidate, error) {
|
|
||||||
configs := fifo.Usables()
|
|
||||||
if len(configs) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var candidates []pendingCandidate
|
|
||||||
|
|
||||||
for key, cfg := range configs {
|
|
||||||
selectStmt := fmt.Sprintf(
|
|
||||||
"%s AS id, %s AS pending_qty, %s AS created_at",
|
|
||||||
cfg.Columns.ID,
|
|
||||||
cfg.Columns.PendingQuantity,
|
|
||||||
cfg.Columns.CreatedAt,
|
|
||||||
)
|
|
||||||
|
|
||||||
var rows []struct {
|
|
||||||
ID uint
|
|
||||||
Pending float64
|
|
||||||
CreatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
query := tx.Table(cfg.Table).
|
|
||||||
Select(selectStmt).
|
|
||||||
Where(fmt.Sprintf("%s = ?", cfg.Columns.ProductWarehouseID), productWarehouseID).
|
|
||||||
Where(fmt.Sprintf("%s > 0", cfg.Columns.PendingQuantity)).
|
|
||||||
Limit(s.pendingBatchPerUsable)
|
|
||||||
|
|
||||||
if cfg.Scope != nil {
|
|
||||||
query = cfg.Scope(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, order := range s.orderClauses(cfg.OrderBy) {
|
|
||||||
query = query.Order(order)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := query.Find(&rows).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, row := range rows {
|
|
||||||
if row.Pending <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
candidates = append(candidates, pendingCandidate{
|
|
||||||
UsableKey: key,
|
|
||||||
Config: cfg,
|
|
||||||
UsableID: row.ID,
|
|
||||||
Pending: row.Pending,
|
|
||||||
CreatedAt: row.CreatedAt,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(candidates) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.SliceStable(candidates, func(i, j int) bool {
|
|
||||||
if candidates[i].CreatedAt.Equal(candidates[j].CreatedAt) {
|
|
||||||
return candidates[i].UsableID < candidates[j].UsableID
|
|
||||||
}
|
|
||||||
return candidates[i].CreatedAt.Before(candidates[j].CreatedAt)
|
|
||||||
})
|
|
||||||
|
|
||||||
return candidates, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fifoService) orderClauses(custom []string) []string {
|
|
||||||
if len(custom) > 0 {
|
|
||||||
return custom
|
|
||||||
}
|
|
||||||
return s.defaultOrderBy
|
|
||||||
}
|
|
||||||
@@ -2,42 +2,42 @@
|
|||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
id_user BIGINT NOT NULL,
|
id_user BIGINT NOT NULL,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
email VARCHAR(50) NOT NULL,
|
email VARCHAR NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ
|
deleted_at TIMESTAMPTZ
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX users_id_user_unique ON users (id_user)
|
CREATE UNIQUE INDEX users_id_user_unique ON users (id_user) WHERE deleted_at IS NULL;
|
||||||
WHERE
|
|
||||||
deleted_at IS NULL;
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX users_email_unique ON users (email)
|
CREATE UNIQUE INDEX users_email_unique ON users (email) WHERE deleted_at IS NULL;
|
||||||
WHERE
|
|
||||||
deleted_at IS NULL;
|
|
||||||
|
|
||||||
-- FLAGS
|
-- FLAGS
|
||||||
CREATE TABLE flags (
|
CREATE TABLE flags (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
flagable_id BIGINT NOT NULL,
|
flagable_id BIGINT NOT NULL,
|
||||||
flagable_type VARCHAR(50) NOT NULL,
|
flagable_type VARCHAR(50) NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW ()
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX flags_unique_flagable ON flags (name, flagable_id, flagable_type);
|
CREATE UNIQUE INDEX flags_unique_flagable ON flags (
|
||||||
|
name,
|
||||||
|
flagable_id,
|
||||||
|
flagable_type
|
||||||
|
);
|
||||||
|
|
||||||
CREATE INDEX flags_flagable_lookup ON flags (flagable_type, flagable_id);
|
CREATE INDEX flags_flagable_lookup ON flags (flagable_type, flagable_id);
|
||||||
|
|
||||||
-- PRODUCT CATEGORIES
|
-- PRODUCT CATEGORIES
|
||||||
CREATE TABLE product_categories (
|
CREATE TABLE product_categories (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
code VARCHAR(10) NOT NULL,
|
code VARCHAR(10) NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -53,9 +53,9 @@ WHERE
|
|||||||
-- UOM
|
-- UOM
|
||||||
CREATE TABLE uoms (
|
CREATE TABLE uoms (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -67,12 +67,12 @@ WHERE
|
|||||||
-- BANKS
|
-- BANKS
|
||||||
CREATE TABLE banks (
|
CREATE TABLE banks (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
alias VARCHAR(5) NOT NULL,
|
alias VARCHAR(5) NOT NULL,
|
||||||
owner VARCHAR(50),
|
owner VARCHAR,
|
||||||
account_number VARCHAR(50) NOT NULL,
|
account_number VARCHAR(50) NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -84,9 +84,9 @@ WHERE
|
|||||||
-- AREAS
|
-- AREAS
|
||||||
CREATE TABLE areas (
|
CREATE TABLE areas (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -98,11 +98,11 @@ WHERE
|
|||||||
-- LOCATIONS
|
-- LOCATIONS
|
||||||
CREATE TABLE locations (
|
CREATE TABLE locations (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
address TEXT NOT NULL,
|
address TEXT NOT NULL,
|
||||||
area_id BIGINT NOT NULL REFERENCES areas (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
area_id BIGINT NOT NULL REFERENCES areas (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -114,11 +114,11 @@ WHERE
|
|||||||
-- KANDANG
|
-- KANDANG
|
||||||
CREATE TABLE kandangs (
|
CREATE TABLE kandangs (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
location_id BIGINT NOT NULL REFERENCES locations (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
location_id BIGINT NOT NULL REFERENCES locations (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
pic_id BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE,
|
pic_id BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -130,13 +130,13 @@ WHERE
|
|||||||
-- WAREHOUSES
|
-- WAREHOUSES
|
||||||
CREATE TABLE warehouses (
|
CREATE TABLE warehouses (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
type VARCHAR(50) NOT NULL,
|
type VARCHAR(50) NOT NULL,
|
||||||
area_id BIGINT NOT NULL REFERENCES areas (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
area_id BIGINT NOT NULL REFERENCES areas (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
location_id BIGINT REFERENCES locations (id) ON DELETE SET NULL ON UPDATE CASCADE,
|
location_id BIGINT REFERENCES locations (id) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
kandang_id BIGINT REFERENCES kandangs (id) ON DELETE SET NULL ON UPDATE CASCADE,
|
kandang_id BIGINT REFERENCES kandangs (id) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -148,16 +148,16 @@ WHERE
|
|||||||
-- CUSTOMERS
|
-- CUSTOMERS
|
||||||
CREATE TABLE customers (
|
CREATE TABLE customers (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
pic_id BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE,
|
pic_id BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
type VARCHAR(50) NOT NULL,
|
type VARCHAR(50) NOT NULL,
|
||||||
address TEXT NOT NULL,
|
address TEXT NOT NULL,
|
||||||
phone VARCHAR(20) NOT NULL,
|
phone VARCHAR(20) NOT NULL,
|
||||||
email VARCHAR(50) NOT NULL,
|
email VARCHAR NOT NULL,
|
||||||
account_number VARCHAR(50) NOT NULL,
|
account_number VARCHAR(50) NOT NULL,
|
||||||
balance NUMERIC(15, 3) DEFAULT 0,
|
balance NUMERIC(15, 3) DEFAULT 0,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -169,10 +169,10 @@ WHERE
|
|||||||
-- NONSTOCK
|
-- NONSTOCK
|
||||||
CREATE TABLE nonstocks (
|
CREATE TABLE nonstocks (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
uom_id BIGINT NOT NULL REFERENCES uoms (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
uom_id BIGINT NOT NULL REFERENCES uoms (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -184,9 +184,9 @@ WHERE
|
|||||||
-- FCR
|
-- FCR
|
||||||
CREATE TABLE fcrs (
|
CREATE TABLE fcrs (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -201,29 +201,29 @@ CREATE TABLE fcr_standards (
|
|||||||
weight NUMERIC(15, 3) NOT NULL,
|
weight NUMERIC(15, 3) NOT NULL,
|
||||||
fcr_number NUMERIC(15, 3) NOT NULL,
|
fcr_number NUMERIC(15, 3) NOT NULL,
|
||||||
mortality NUMERIC(15, 3) NOT NULL,
|
mortality NUMERIC(15, 3) NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ
|
deleted_at TIMESTAMPTZ
|
||||||
);
|
);
|
||||||
|
|
||||||
-- SUPPLIERS
|
-- SUPPLIERS
|
||||||
CREATE TABLE suppliers (
|
CREATE TABLE suppliers (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
alias VARCHAR(5) NOT NULL,
|
alias VARCHAR(5) NOT NULL,
|
||||||
pic VARCHAR(50) NOT NULL,
|
pic VARCHAR NOT NULL,
|
||||||
type VARCHAR(50) NOT NULL,
|
type VARCHAR(50) NOT NULL,
|
||||||
category VARCHAR(20) NOT NULL,
|
category VARCHAR(20) NOT NULL,
|
||||||
hatchery VARCHAR(50),
|
hatchery VARCHAR,
|
||||||
phone VARCHAR(20) NOT NULL,
|
phone VARCHAR(20) NOT NULL,
|
||||||
email VARCHAR(50) NOT NULL,
|
email VARCHAR NOT NULL,
|
||||||
address TEXT NOT NULL,
|
address TEXT NOT NULL,
|
||||||
npwp VARCHAR(50),
|
npwp VARCHAR(50),
|
||||||
account_number VARCHAR(50),
|
account_number VARCHAR(50),
|
||||||
balance NUMERIC(15, 3) DEFAULT 0,
|
balance NUMERIC(15, 3) DEFAULT 0,
|
||||||
due_date INT NOT NULL,
|
due_date INT NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -235,15 +235,15 @@ WHERE
|
|||||||
CREATE TABLE nonstock_suppliers (
|
CREATE TABLE nonstock_suppliers (
|
||||||
nonstock_id BIGINT NOT NULL REFERENCES nonstocks (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
nonstock_id BIGINT NOT NULL REFERENCES nonstocks (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
supplier_id BIGINT NOT NULL REFERENCES suppliers (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
supplier_id BIGINT NOT NULL REFERENCES suppliers (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
PRIMARY KEY (nonstock_id, supplier_id)
|
PRIMARY KEY (nonstock_id, supplier_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- PRODUCTS
|
-- PRODUCTS
|
||||||
CREATE TABLE products (
|
CREATE TABLE products (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR NOT NULL,
|
||||||
brand VARCHAR(50) NOT NULL,
|
brand VARCHAR NOT NULL,
|
||||||
sku VARCHAR(100),
|
sku VARCHAR(100),
|
||||||
uom_id BIGINT NOT NULL REFERENCES uoms (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
uom_id BIGINT NOT NULL REFERENCES uoms (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
product_category_id BIGINT NOT NULL REFERENCES product_categories (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
product_category_id BIGINT NOT NULL REFERENCES product_categories (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
@@ -251,8 +251,8 @@ CREATE TABLE products (
|
|||||||
selling_price NUMERIC(15, 3),
|
selling_price NUMERIC(15, 3),
|
||||||
tax NUMERIC(15, 3),
|
tax NUMERIC(15, 3),
|
||||||
expiry_period INT,
|
expiry_period INT,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -268,15 +268,15 @@ WHERE
|
|||||||
CREATE TABLE product_suppliers (
|
CREATE TABLE product_suppliers (
|
||||||
product_id BIGINT NOT NULL REFERENCES products (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
product_id BIGINT NOT NULL REFERENCES products (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
supplier_id BIGINT NOT NULL REFERENCES suppliers (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
supplier_id BIGINT NOT NULL REFERENCES suppliers (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
PRIMARY KEY (product_id, supplier_id)
|
PRIMARY KEY (product_id, supplier_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- PROJECTS
|
-- PROJECTS
|
||||||
CREATE TABLE projects (
|
CREATE TABLE projects (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ,
|
deleted_at TIMESTAMPTZ,
|
||||||
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
@@ -288,8 +288,8 @@ CREATE TABLE product_warehouses (
|
|||||||
warehouse_id BIGINT NOT NULL REFERENCES warehouses (id),
|
warehouse_id BIGINT NOT NULL REFERENCES warehouses (id),
|
||||||
quantity INTEGER NOT NULL DEFAULT 0,
|
quantity INTEGER NOT NULL DEFAULT 0,
|
||||||
created_by BIGINT NOT NULL REFERENCES users (id),
|
created_by BIGINT NOT NULL REFERENCES users (id),
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ
|
deleted_at TIMESTAMPTZ
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -316,8 +316,8 @@ CREATE TABLE stock_logs (
|
|||||||
note TEXT,
|
note TEXT,
|
||||||
product_warehouse_id BIGINT NOT NULL REFERENCES product_warehouses (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
product_warehouse_id BIGINT NOT NULL REFERENCES product_warehouses (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
created_by BIGINT NOT NULL REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE,
|
created_by BIGINT NOT NULL REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW (),
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ DEFAULT NOW (),
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
deleted_at TIMESTAMPTZ
|
deleted_at TIMESTAMPTZ
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
DROP INDEX IF EXISTS stock_allocations_released_at_idx;
|
|
||||||
DROP INDEX IF EXISTS stock_allocations_status_idx;
|
|
||||||
DROP INDEX IF EXISTS stock_allocations_usage_lookup;
|
|
||||||
DROP INDEX IF EXISTS stock_allocations_lookup;
|
|
||||||
DROP INDEX IF EXISTS stock_allocations_product_warehouse_id_idx;
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS stock_allocations;
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS stock_allocations (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
product_warehouse_id BIGINT NOT NULL REFERENCES product_warehouses(id),
|
|
||||||
stockable_type VARCHAR(100) NOT NULL,
|
|
||||||
stockable_id BIGINT NOT NULL,
|
|
||||||
usable_type VARCHAR(100) NOT NULL,
|
|
||||||
usable_id BIGINT NOT NULL,
|
|
||||||
qty NUMERIC(15,3) NOT NULL,
|
|
||||||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
|
||||||
note TEXT NULL,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
released_at TIMESTAMPTZ NULL,
|
|
||||||
deleted_at TIMESTAMPTZ NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS stock_allocations_product_warehouse_id_idx
|
|
||||||
ON stock_allocations (product_warehouse_id);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS stock_allocations_lookup
|
|
||||||
ON stock_allocations (stockable_type, stockable_id);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS stock_allocations_usage_lookup
|
|
||||||
ON stock_allocations (usable_type, usable_id);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS stock_allocations_status_idx
|
|
||||||
ON stock_allocations (status);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS stock_allocations_released_at_idx
|
|
||||||
ON stock_allocations (released_at);
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
CREATE TABLE expenses (
|
CREATE TABLE expenses (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
reference_number VARCHAR(50) UNIQUE NOT NULL,
|
reference_number VARCHAR(50) UNIQUE NOT NULL,
|
||||||
supplier_id BIGINT NOT NULL,
|
supplier_id BIGINT NULL,
|
||||||
category VARCHAR(50) NOT NULL CHECK (
|
category VARCHAR(50) NOT NULL CHECK (
|
||||||
category IN ('BOP', 'NON-BOP')
|
category IN ('BOP', 'NON-BOP')
|
||||||
),
|
),
|
||||||
|
|||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
-- ============================
|
|
||||||
-- EXPENSES
|
|
||||||
-- ============================
|
|
||||||
ALTER TABLE expenses DROP COLUMN IF EXISTS grand_total;
|
|
||||||
|
|
||||||
ALTER TABLE expenses RENAME COLUMN note TO notes;
|
|
||||||
|
|
||||||
ALTER TABLE expenses RENAME COLUMN expense_date TO transaction_date;
|
|
||||||
|
|
||||||
-- ============================
|
|
||||||
-- EXPENSE_REALIZATIONS
|
|
||||||
-- ============================
|
|
||||||
ALTER TABLE expense_realizations
|
|
||||||
RENAME COLUMN realization_qty TO qty;
|
|
||||||
|
|
||||||
ALTER TABLE expense_realizations
|
|
||||||
RENAME COLUMN realization_unit_price TO price;
|
|
||||||
|
|
||||||
ALTER TABLE expense_realizations RENAME COLUMN note TO notes;
|
|
||||||
|
|
||||||
ALTER TABLE expense_realizations
|
|
||||||
DROP COLUMN IF EXISTS realization_total_price;
|
|
||||||
|
|
||||||
ALTER TABLE expense_realizations
|
|
||||||
DROP COLUMN IF EXISTS realization_date;
|
|
||||||
|
|
||||||
ALTER TABLE expense_realizations DROP COLUMN IF EXISTS created_by;
|
|
||||||
|
|
||||||
ALTER TABLE expense_realizations
|
|
||||||
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ DEFAULT NOW();
|
|
||||||
|
|
||||||
-- ============================
|
|
||||||
-- EXPENSE_NONSTOCKS
|
|
||||||
-- ============================
|
|
||||||
ALTER TABLE expense_nonstocks RENAME COLUMN note TO notes;
|
|
||||||
|
|
||||||
ALTER TABLE expense_nonstocks DROP COLUMN IF EXISTS total_price;
|
|
||||||
|
|
||||||
ALTER TABLE expense_nonstocks RENAME COLUMN unit_price TO price;
|
|
||||||
|
|
||||||
ALTER TABLE expense_nonstocks DROP COLUMN IF EXISTS created_by;
|
|
||||||
|
|
||||||
ALTER TABLE expense_nonstocks
|
|
||||||
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ DEFAULT NOW();
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
DROP Table IF EXISTS project_budgets;
|
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
CREATE TABLE project_budgets (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
project_flock_id BIGINT NOT NULL,
|
|
||||||
nonstock_id BIGINT NOT NULL,
|
|
||||||
qty NUMERIC(15, 3) NOT NULL,
|
|
||||||
price NUMERIC(15, 3) NOT NULL,
|
|
||||||
created_at TIMESTAMPTZ DEFAULT now()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Tambahkan Foreign Key ke project_flocks
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_flocks') THEN
|
|
||||||
ALTER TABLE project_budgets
|
|
||||||
ADD CONSTRAINT fk_project_budgets_project_flock_id
|
|
||||||
FOREIGN KEY (project_flock_id) REFERENCES project_flocks(id);
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
-- Tambahkan Foreign Key ke nonstocks
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'nonstocks') THEN
|
|
||||||
ALTER TABLE project_budgets
|
|
||||||
ADD CONSTRAINT fk_project_budgets_nonstock_id
|
|
||||||
FOREIGN KEY (nonstock_id) REFERENCES nonstocks(id);
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
-- Index
|
|
||||||
CREATE INDEX idx_project_budgets_project_flock_id ON project_budgets (project_flock_id);
|
|
||||||
|
|
||||||
CREATE INDEX idx_project_budgets_nonstock_id ON project_budgets (nonstock_id);
|
|
||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
type Area struct {
|
type Area struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);not null;uniqueIndex:areas_name_unique,where:deleted_at IS NULL"`
|
Name string `gorm:"not null;uniqueIndex:areas_name_unique,where:deleted_at IS 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"`
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
|
|
||||||
type Bank struct {
|
type Bank struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);not null;uniqueIndex:banks_name_unique,where:deleted_at IS NULL"`
|
Name string `gorm:"not null;uniqueIndex:banks_name_unique,where:deleted_at IS NULL"`
|
||||||
Alias string `gorm:"not null;size:5"`
|
Alias string `gorm:"not null;size:5"`
|
||||||
Owner *string `gorm:"type:varchar(50)"`
|
Owner *string `gorm:""`
|
||||||
AccountNumber string `gorm:"not null;size:50"`
|
AccountNumber string `gorm:"not null;size:50"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
CreatedBy uint `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import (
|
|||||||
|
|
||||||
type Customer struct {
|
type Customer struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);not null;uniqueIndex:customers_name_unique,where:deleted_at IS NULL"`
|
Name string `gorm:"not null;uniqueIndex:customers_name_unique,where:deleted_at IS NULL"`
|
||||||
PicId uint `gorm:"not null"`
|
PicId uint `gorm:"not null"`
|
||||||
Type string `gorm:"not null;size:50"`
|
Type string `gorm:"not null;size:50"`
|
||||||
Address string `gorm:"not null"`
|
Address string `gorm:"not null"`
|
||||||
Phone string `gorm:"not null;size:20"`
|
Phone string `gorm:"not null;size:20"`
|
||||||
Email string `gorm:"type:varchar(50);not null"`
|
Email string `gorm:"not null"`
|
||||||
AccountNumber string `gorm:"not null;size:50"`
|
AccountNumber string `gorm:"not null;size:50"`
|
||||||
Balance float64 `gorm:"default:0"`
|
Balance float64 `gorm:"default:0"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
CreatedBy uint `gorm:"not null"`
|
||||||
|
|||||||
@@ -13,16 +13,18 @@ type Expense struct {
|
|||||||
SupplierId uint64 `gorm:""`
|
SupplierId uint64 `gorm:""`
|
||||||
Category string `gorm:"type:varchar(50);not null"`
|
Category string `gorm:"type:varchar(50);not null"`
|
||||||
PoNumber string `gorm:"type:varchar(50)"`
|
PoNumber string `gorm:"type:varchar(50)"`
|
||||||
DocumentPath sql.NullString `gorm:"type:json"`
|
DocumentPath sql.NullString `gorm:"type:json"` // Dokumen pengajuan
|
||||||
RealizationDocumentPath sql.NullString `gorm:"type:json;column:realization_document_path"`
|
RealizationDocumentPath sql.NullString `gorm:"type:json;column:realization_document_path"` // Dokumen realisasi
|
||||||
RealizationDate time.Time `gorm:"type:date;column:realization_date"`
|
RealizationDate time.Time `gorm:"type:date;column:realization_date"` // Tanggal realisasi
|
||||||
TransactionDate time.Time `gorm:"type:date;not null"`
|
ExpenseDate time.Time `gorm:"type:date;not null"`
|
||||||
Notes string `gorm:"type:text;column:notes"`
|
GrandTotal float64 `gorm:"type:numeric(15,3);default:0"`
|
||||||
|
Note string `gorm:"type:text"`
|
||||||
CreatedBy uint64 `gorm:""`
|
CreatedBy uint64 `gorm:""`
|
||||||
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:"-"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
Supplier *Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
Supplier *Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
||||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
Nonstocks []ExpenseNonstock `gorm:"foreignKey:ExpenseId;references:Id"`
|
Nonstocks []ExpenseNonstock `gorm:"foreignKey:ExpenseId;references:Id"`
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
package entities
|
package entities
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ExpenseNonstock struct {
|
type ExpenseNonstock struct {
|
||||||
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
ExpenseId *uint64 `gorm:""`
|
ExpenseId *uint64 `gorm:""`
|
||||||
@@ -11,13 +7,14 @@ type ExpenseNonstock struct {
|
|||||||
KandangId *uint64 `gorm:""`
|
KandangId *uint64 `gorm:""`
|
||||||
NonstockId *uint64 `gorm:""`
|
NonstockId *uint64 `gorm:""`
|
||||||
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
Price float64 `gorm:"type:numeric(15,3);not null;column:price"`
|
UnitPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
Notes string `gorm:"type:text;column:notes"`
|
TotalPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
CreatedAt time.Time `gorm:"type:timestamptz;default:CURRENT_TIMESTAMP"`
|
Note string `gorm:"type:text"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
Expense *Expense `gorm:"foreignKey:ExpenseId;references:Id"`
|
Expense *Expense `gorm:"foreignKey:ExpenseId;references:Id"`
|
||||||
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
||||||
Kandang *Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
Kandang *Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
||||||
Nonstock *Nonstock `gorm:"foreignKey:NonstockId;references:Id"`
|
Nonstock *Nonstock `gorm:"foreignKey:NonstockId;references:Id"`
|
||||||
Realization *ExpenseRealization `gorm:"foreignKey:Id;references:ExpenseNonstockId"`
|
Realization *ExpenseRealization `gorm:"foreignKey:ExpenseNonstockId;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,14 @@ import (
|
|||||||
type ExpenseRealization struct {
|
type ExpenseRealization struct {
|
||||||
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
ExpenseNonstockId *uint64 `gorm:""`
|
ExpenseNonstockId *uint64 `gorm:""`
|
||||||
Qty float64 `gorm:"type:numeric(15,3);not null;"`
|
RealizationQty float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
Price float64 `gorm:"type:numeric(15,3);not null;"`
|
RealizationUnitPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
Notes string `gorm:"type:text;"`
|
RealizationTotalPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
CreatedAt time.Time `gorm:"type:timestamptz;default:CURRENT_TIMESTAMP"`
|
RealizationDate time.Time `gorm:"type:date;not null"`
|
||||||
|
Note *string `gorm:"type:text"`
|
||||||
|
CreatedBy *uint64 `gorm:""`
|
||||||
|
|
||||||
|
// Relations
|
||||||
ExpenseNonstock *ExpenseNonstock `gorm:"foreignKey:ExpenseNonstockId;references:Id"`
|
ExpenseNonstock *ExpenseNonstock `gorm:"foreignKey:ExpenseNonstockId;references:Id"`
|
||||||
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
type Fcr struct {
|
type Fcr struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);not null;uniqueIndex:idx_suppliers_name,where:deleted_at IS NULL"`
|
Name string `gorm:"not null;uniqueIndex:idx_suppliers_name,where:deleted_at IS 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"`
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const (
|
|||||||
|
|
||||||
type Flag struct {
|
type Flag struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);size:50;not null;uniqueIndex:flags_unique_flagable"`
|
Name string `gorm:"not null;uniqueIndex:flags_unique_flagable"`
|
||||||
FlagableID uint `gorm:"not null;uniqueIndex:flags_unique_flagable;index:flags_flagable_lookup,priority:2"`
|
FlagableID uint `gorm:"not null;uniqueIndex:flags_unique_flagable;index:flags_flagable_lookup,priority:2"`
|
||||||
FlagableType string `gorm:"size:50;not null;uniqueIndex:flags_unique_flagable;index:flags_flagable_lookup,priority:1"`
|
FlagableType string `gorm:"size:50;not null;uniqueIndex:flags_unique_flagable;index:flags_flagable_lookup,priority:1"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
type Kandang struct {
|
type Kandang struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);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"`
|
||||||
Capacity float64 `gorm:"not null"`
|
Capacity float64 `gorm:"not null"`
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
type Location struct {
|
type Location struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);not null;uniqueIndex:locations_name_unique,where:deleted_at IS NULL"`
|
Name string `gorm:"not null;uniqueIndex:locations_name_unique,where:deleted_at IS NULL"`
|
||||||
Address string `gorm:"not null"`
|
Address string `gorm:"not null"`
|
||||||
AreaId uint `gorm:"not null"`
|
AreaId uint `gorm:"not null"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
CreatedBy uint `gorm:"not null"`
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
type Nonstock struct {
|
type Nonstock struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);not null;uniqueIndex:nonstocks_name_unique,where:deleted_at IS NULL"`
|
Name string `gorm:"not null;uniqueIndex:nonstocks_name_unique,where:deleted_at IS NULL"`
|
||||||
UomId uint `gorm:"not null"`
|
UomId uint `gorm:"not null"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
CreatedBy uint `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
type ProductCategory struct {
|
type ProductCategory struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);not null;uniqueIndex:product_categories_name_unique,where:deleted_at IS NULL"`
|
Name string `gorm:"not null;uniqueIndex:product_categories_name_unique,where:deleted_at IS NULL"`
|
||||||
Code string `gorm:"not null;size:10;uniqueIndex:product_categories_code_unique,where:deleted_at IS NULL"`
|
Code string `gorm:"not null;size:10;uniqueIndex:product_categories_code_unique,where:deleted_at IS NULL"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
CreatedBy uint `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
type Product struct {
|
type Product struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);not null;uniqueIndex:products_name_unique,where:deleted_at IS NULL"`
|
Name string `gorm:"not null;uniqueIndex:products_name_unique,where:deleted_at IS NULL"`
|
||||||
Brand string `gorm:"type:varchar(50);not null"`
|
Brand string `gorm:"not null"`
|
||||||
Sku *string `gorm:"size:100;uniqueIndex:products_sku_unique,where:deleted_at IS NULL"`
|
Sku *string `gorm:"size:100;uniqueIndex:products_sku_unique,where:deleted_at IS NULL"`
|
||||||
UomId uint `gorm:"not null"`
|
UomId uint `gorm:"not null"`
|
||||||
ProductCategoryId uint `gorm:"not null"`
|
ProductCategoryId uint `gorm:"not null"`
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package entities
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProjectBudget struct {
|
|
||||||
Id uint `gorm:"primaryKey"`
|
|
||||||
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
|
||||||
Price float64 `gorm:"type:numeric(15,3);not null"`
|
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
|
||||||
|
|
||||||
Nonstock *Nonstock `gorm:"foreignKey:Id;references:Id"`
|
|
||||||
ProjectFlock *ProjectFlock `gorm:"foreignKey:Id;references:Id"`
|
|
||||||
}
|
|
||||||
@@ -5,11 +5,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Purchase struct {
|
type Purchase struct {
|
||||||
Id uint `gorm:"primaryKey;autoIncrement"`
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
PrNumber string `gorm:"not null"`
|
PrNumber string `gorm:"not null"`
|
||||||
PoNumber *string
|
PoNumber *string
|
||||||
PoDate *time.Time
|
PoDate *time.Time
|
||||||
SupplierId uint `gorm:"not null"`
|
SupplierId uint64 `gorm:"not null"`
|
||||||
CreditTerm *int
|
CreditTerm *int
|
||||||
DueDate *time.Time
|
DueDate *time.Time
|
||||||
GrandTotal float64 `gorm:"type:numeric(15,3);default:0"`
|
GrandTotal float64 `gorm:"type:numeric(15,3);default:0"`
|
||||||
@@ -17,7 +17,7 @@ type Purchase struct {
|
|||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
DeletedAt *time.Time `gorm:"index"`
|
DeletedAt *time.Time `gorm:"index"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
CreatedBy uint64 `gorm:"not null"`
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
Supplier Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
Supplier Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type PurchaseItem struct {
|
type PurchaseItem struct {
|
||||||
Id uint `gorm:"primaryKey;autoIncrement"`
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
PurchaseId uint `gorm:"not null"`
|
PurchaseId uint64 `gorm:"not null"`
|
||||||
ProductId uint `gorm:"not null"`
|
ProductId uint64 `gorm:"not null"`
|
||||||
WarehouseId uint `gorm:"not null"`
|
WarehouseId uint64 `gorm:"not null"`
|
||||||
ProductWarehouseId *uint
|
ProductWarehouseId *uint64
|
||||||
ReceivedDate *time.Time
|
ReceivedDate *time.Time
|
||||||
TravelNumber *string
|
TravelNumber *string
|
||||||
TravelNumberDocs *string
|
TravelNumberDocs *string
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
package entities
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
StockAllocationStatusPending = "PENDING"
|
|
||||||
StockAllocationStatusActive = "ACTIVE"
|
|
||||||
StockAllocationStatusReleased = "RELEASED"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StockAllocation links a usable record (consumption) with an incoming stock record.
|
|
||||||
// The combination lets us trace FIFO deductions while keeping each module focused on its own fields.
|
|
||||||
type StockAllocation struct {
|
|
||||||
Id uint `gorm:"primaryKey"`
|
|
||||||
ProductWarehouseId uint `gorm:"not null;index"`
|
|
||||||
StockableType string `gorm:"size:100;not null;index:stock_allocations_lookup,priority:1"`
|
|
||||||
StockableId uint `gorm:"not null;index:stock_allocations_lookup,priority:2"`
|
|
||||||
UsableType string `gorm:"size:100;not null;index:stock_allocations_usage_lookup,priority:1"`
|
|
||||||
UsableId uint `gorm:"not null;index:stock_allocations_usage_lookup,priority:2"`
|
|
||||||
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
|
||||||
Status string `gorm:"size:20;not null;default:ACTIVE"`
|
|
||||||
Note *string `gorm:"type:text"`
|
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
|
||||||
ReleasedAt *time.Time `gorm:"index"`
|
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
||||||
|
|
||||||
ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
|
||||||
}
|
|
||||||
@@ -8,14 +8,14 @@ import (
|
|||||||
|
|
||||||
type Supplier struct {
|
type Supplier struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);not null;uniqueIndex:suppliers_name_unique,where:deleted_at IS NULL"`
|
Name string `gorm:"not null;uniqueIndex:suppliers_name_unique,where:deleted_at IS NULL"`
|
||||||
Alias string `gorm:"not null;size:5"`
|
Alias string `gorm:"not null;size:5"`
|
||||||
Pic string `gorm:"type:varchar(50);not null"`
|
Pic string `gorm:"not null"`
|
||||||
Type string `gorm:"not null;size:50"`
|
Type string `gorm:"not null;size:50"`
|
||||||
Category string `gorm:"not null;size:20"`
|
Category string `gorm:"not null;size:20"`
|
||||||
Hatchery *string `gorm:"type:varchar(50)"`
|
Hatchery *string `gorm:"size:255"`
|
||||||
Phone string `gorm:"not null;size:20"`
|
Phone string `gorm:"not null;size:20"`
|
||||||
Email string `gorm:"type:varchar(50);not null"`
|
Email string `gorm:"not null"`
|
||||||
Address string `gorm:"not null"`
|
Address string `gorm:"not null"`
|
||||||
Npwp *string `gorm:"size:50"`
|
Npwp *string `gorm:"size:50"`
|
||||||
AccountNumber *string `gorm:"size:50"`
|
AccountNumber *string `gorm:"size:50"`
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
type Uom struct {
|
type Uom struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);not null;uniqueIndex:uoms_name_unique,where:deleted_at IS NULL"`
|
Name string `gorm:"not null;uniqueIndex:uoms_name_unique,where:deleted_at IS 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"`
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
type User struct {
|
type User struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
IdUser int64 `gorm:"uniqueIndex"`
|
IdUser int64 `gorm:"uniqueIndex"`
|
||||||
Email string `gorm:"type:varchar(50);uniqueIndex"`
|
Email string `gorm:"uniqueIndex"`
|
||||||
Name string `gorm:"type:varchar(50);not null"`
|
Name string `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:"-"`
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
type Warehouse struct {
|
type Warehouse struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"type:varchar(50);not null"`
|
Name string `gorm:"not null"`
|
||||||
Type string `gorm:"not null"`
|
Type string `gorm:"not null"`
|
||||||
AreaId uint `gorm:"not null"`
|
AreaId uint `gorm:"not null"`
|
||||||
LocationId *uint
|
LocationId *uint
|
||||||
|
|||||||
@@ -105,14 +105,6 @@ func AuthenticatedUser(c *fiber.Ctx) (*entity.User, bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func ActorIDFromContext(c *fiber.Ctx) (uint, error) {
|
|
||||||
user, ok := AuthenticatedUser(c)
|
|
||||||
if !ok || user == nil || user.Id == 0 {
|
|
||||||
return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
|
||||||
}
|
|
||||||
return user.Id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthDetails returns the full authentication context (token, claims, user).
|
// AuthDetails returns the full authentication context (token, claims, user).
|
||||||
func AuthDetails(c *fiber.Ctx) (*AuthContext, bool) {
|
func AuthDetails(c *fiber.Ctx) (*AuthContext, bool) {
|
||||||
value := c.Locals(authContextLocalsKey)
|
value := c.Locals(authContextLocalsKey)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package approvals
|
|||||||
import (
|
import (
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/controllers"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
func ApprovalRoutes(v1 fiber.Router, u user.UserService, s common.ApprovalService) {
|
func ApprovalRoutes(v1 fiber.Router, u user.UserService, s common.ApprovalService) {
|
||||||
_ = u
|
_ = u
|
||||||
ctrl := controller.NewApprovalController(s)
|
ctrl := controller.NewApprovalController(s)
|
||||||
|
|
||||||
route := v1.Group("/approvals")
|
route := v1.Group("/approvals")
|
||||||
route.Use(m.Auth(u))
|
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto"
|
|
||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services"
|
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations"
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ClosingController struct {
|
|
||||||
ClosingService service.ClosingService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClosingController(closingService service.ClosingService) *ClosingController {
|
|
||||||
return &ClosingController{
|
|
||||||
ClosingService: closingService,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ClosingController) GetAll(c *fiber.Ctx) error {
|
|
||||||
query := &validation.Query{
|
|
||||||
Page: c.QueryInt("page", 1),
|
|
||||||
Limit: c.QueryInt("limit", 10),
|
|
||||||
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.ClosingService.GetAll(c, query)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).
|
|
||||||
JSON(response.SuccessWithPaginate[dto.ClosingListDTO]{
|
|
||||||
Code: fiber.StatusOK,
|
|
||||||
Status: "success",
|
|
||||||
Message: "Get all closings successfully",
|
|
||||||
Meta: response.Meta{
|
|
||||||
Page: query.Page,
|
|
||||||
Limit: query.Limit,
|
|
||||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
|
||||||
TotalResults: totalResults,
|
|
||||||
},
|
|
||||||
Data: dto.ToClosingListDTOs(result),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *ClosingController) GetOne(c *fiber.Ctx) error {
|
|
||||||
param := c.Params("id")
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(param)
|
|
||||||
if err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := u.ClosingService.GetOne(c, uint(id))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).
|
|
||||||
JSON(response.Success{
|
|
||||||
Code: fiber.StatusOK,
|
|
||||||
Status: "success",
|
|
||||||
Message: "Get closing successfully",
|
|
||||||
Data: dto.ToClosingListDTO(*result),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package dto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
||||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// === DTO Structs ===
|
|
||||||
|
|
||||||
type ClosingRelationDTO struct {
|
|
||||||
Id uint `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClosingListDTO struct {
|
|
||||||
Id uint `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClosingDetailDTO struct {
|
|
||||||
ClosingListDTO
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Mapper Functions ===
|
|
||||||
|
|
||||||
func ToClosingRelationDTO(e entity.ProjectFlock) ClosingRelationDTO {
|
|
||||||
return ClosingRelationDTO{
|
|
||||||
Id: e.Id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToClosingListDTO(e entity.ProjectFlock) ClosingListDTO {
|
|
||||||
var createdUser *userDTO.UserRelationDTO
|
|
||||||
if e.CreatedUser.Id != 0 {
|
|
||||||
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
|
||||||
createdUser = &mapped
|
|
||||||
}
|
|
||||||
|
|
||||||
return ClosingListDTO{
|
|
||||||
Id: e.Id,
|
|
||||||
CreatedAt: e.CreatedAt,
|
|
||||||
UpdatedAt: e.UpdatedAt,
|
|
||||||
CreatedUser: createdUser,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToClosingListDTOs(e []entity.ProjectFlock) []ClosingListDTO {
|
|
||||||
result := make([]ClosingListDTO, len(e))
|
|
||||||
for i, r := range e {
|
|
||||||
result[i] = ToClosingListDTO(r)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToClosingDetailDTO(e entity.ProjectFlock) ClosingDetailDTO {
|
|
||||||
return ClosingDetailDTO{
|
|
||||||
ClosingListDTO: ToClosingListDTO(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package closings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
|
|
||||||
rClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories"
|
|
||||||
sClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services"
|
|
||||||
|
|
||||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
|
||||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ClosingModule struct{}
|
|
||||||
|
|
||||||
func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
|
||||||
closingRepo := rClosing.NewClosingRepository(db)
|
|
||||||
userRepo := rUser.NewUserRepository(db)
|
|
||||||
|
|
||||||
closingService := sClosing.NewClosingService(closingRepo, validate)
|
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
|
||||||
|
|
||||||
ClosingRoutes(router, userService, closingService)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ClosingRepository interface {
|
|
||||||
repository.BaseRepository[entity.ProjectFlock]
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClosingRepositoryImpl struct {
|
|
||||||
*repository.BaseRepositoryImpl[entity.ProjectFlock]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClosingRepository(db *gorm.DB) ClosingRepository {
|
|
||||||
return &ClosingRepositoryImpl{
|
|
||||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlock](db),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package closings
|
|
||||||
|
|
||||||
import (
|
|
||||||
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/controllers"
|
|
||||||
closing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services"
|
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService) {
|
|
||||||
ctrl := controller.NewClosingController(s)
|
|
||||||
|
|
||||||
route := v1.Group("/closings")
|
|
||||||
|
|
||||||
// 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("/:id", ctrl.GetOne)
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories"
|
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations"
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ClosingService interface {
|
|
||||||
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error)
|
|
||||||
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type closingService struct {
|
|
||||||
Log *logrus.Logger
|
|
||||||
Validate *validator.Validate
|
|
||||||
Repository repository.ClosingRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClosingService(repo repository.ClosingRepository, validate *validator.Validate) ClosingService {
|
|
||||||
return &closingService{
|
|
||||||
Log: utils.Log,
|
|
||||||
Validate: validate,
|
|
||||||
Repository: repo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s closingService) withRelations(db *gorm.DB) *gorm.DB {
|
|
||||||
return db.Preload("CreatedUser")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
|
|
||||||
if err := s.Validate.Struct(params); err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
offset := (params.Page - 1) * params.Limit
|
|
||||||
|
|
||||||
closings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
|
||||||
db = s.withRelations(db)
|
|
||||||
if params.Search != "" {
|
|
||||||
return db.Where("name LIKE ?", "%"+params.Search+"%")
|
|
||||||
}
|
|
||||||
return db.Order("created_at DESC").Order("updated_at DESC")
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
s.Log.Errorf("Failed to get closings: %+v", err)
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
return closings, total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s closingService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock, error) {
|
|
||||||
closing, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Closing not found")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
s.Log.Errorf("Failed get closing by id: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return closing, nil
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package validation
|
|
||||||
|
|
||||||
type Create struct {
|
|
||||||
Name string `json:"name" validate:"required_strict,min=3"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Update struct {
|
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Query struct {
|
|
||||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
|
||||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
|
||||||
Search string `query:"search" validate:"omitempty,max=50"`
|
|
||||||
}
|
|
||||||
@@ -96,30 +96,30 @@ func (u *ExpenseController) CreateOne(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
req.Documents = form.File["documents"]
|
req.Documents = form.File["documents"]
|
||||||
|
|
||||||
expenseNonstocksJSON := c.FormValue("expense_nonstocks")
|
costPerKandangJSON := c.FormValue("cost_per_kandangs")
|
||||||
if expenseNonstocksJSON != "" {
|
if costPerKandangJSON != "" {
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(expenseNonstocksJSON), &req.ExpenseNonstocks); err != nil {
|
if err := json.Unmarshal([]byte(costPerKandangJSON), &req.CostPerKandangs); err != nil {
|
||||||
|
|
||||||
var singleExpenseNonstock validation.ExpenseNonstock
|
var singleCostPerKandang validation.CostPerKandang
|
||||||
if err := json.Unmarshal([]byte(expenseNonstocksJSON), &singleExpenseNonstock); err != nil {
|
if err := json.Unmarshal([]byte(costPerKandangJSON), &singleCostPerKandang); err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid expense_nonstocks JSON: %v", err))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid cost_per_kandangs JSON: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if singleExpenseNonstock.KandangID == 0 {
|
if singleCostPerKandang.KandangID == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Field KandangID is required")
|
return fiber.NewError(fiber.StatusBadRequest, "Field KandangID is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
req.ExpenseNonstocks = []validation.ExpenseNonstock{singleExpenseNonstock}
|
req.CostPerKandangs = []validation.CostPerKandang{singleCostPerKandang}
|
||||||
} else {
|
} else {
|
||||||
for i, expenseNonstock := range req.ExpenseNonstocks {
|
for i, costPerKandang := range req.CostPerKandangs {
|
||||||
if expenseNonstock.KandangID == 0 {
|
if costPerKandang.KandangID == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Field KandangID is required for expense_nonstocks[%d]", i))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Field KandangID is required for cost_per_kandangs[%d]", i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Field expense_nonstocks is required")
|
return fiber.NewError(fiber.StatusBadRequest, "Field cost_per_kandangs is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := u.ExpenseService.CreateOne(c, req)
|
result, err := u.ExpenseService.CreateOne(c, req)
|
||||||
@@ -155,32 +155,20 @@ func (u *ExpenseController) UpdateOne(c *fiber.Ctx) error {
|
|||||||
req.TransactionDate = &transactionDate
|
req.TransactionDate = &transactionDate
|
||||||
}
|
}
|
||||||
|
|
||||||
categoryVal := c.FormValue("category")
|
costPerKandangJSON := c.FormValue("cost_per_kandang")
|
||||||
req.Category = &categoryVal
|
if costPerKandangJSON != "" {
|
||||||
|
var costPerKandang []validation.CostPerKandang
|
||||||
supplierIDVal := c.FormValue("supplier_id")
|
if err := json.Unmarshal([]byte(costPerKandangJSON), &costPerKandang); err != nil {
|
||||||
if supplierIDVal != "" {
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid cost_per_kandang JSON: %v", err))
|
||||||
supplierID, err := strconv.ParseUint(supplierIDVal, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid supplier_id format")
|
|
||||||
}
|
|
||||||
req.SupplierID = &supplierID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expenseNonstocksJSON := c.FormValue("expense_nonstocks")
|
for i, costPerKandang := range costPerKandang {
|
||||||
if expenseNonstocksJSON != "" {
|
if costPerKandang.KandangID == 0 {
|
||||||
var expenseNonstocks []validation.ExpenseNonstock
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Field KandangID is required for cost_per_kandang[%d]", i))
|
||||||
if err := json.Unmarshal([]byte(expenseNonstocksJSON), &expenseNonstocks); err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid expense_nonstocks JSON: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, expenseNonstock := range expenseNonstocks {
|
|
||||||
if expenseNonstock.KandangID == 0 {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Field KandangID is required for expense_nonstocks[%d]", i))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req.ExpenseNonstocks = &expenseNonstocks
|
req.CostPerKandang = &costPerKandang
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := u.ExpenseService.UpdateOne(c, req, uint(id))
|
result, err := u.ExpenseService.UpdateOne(c, req, uint(id))
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import (
|
|||||||
type ExpenseRelationDTO struct {
|
type ExpenseRelationDTO struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
PoNumber string `json:"po_number"`
|
PoNumber string `json:"po_number"`
|
||||||
TransactionDate time.Time `json:"transaction_date"`
|
ExpenseDate time.Time `json:"expense_date"`
|
||||||
|
GrandTotal float64 `json:"grand_total"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExpenseBaseDTO struct {
|
type ExpenseBaseDTO struct {
|
||||||
@@ -27,7 +28,8 @@ type ExpenseBaseDTO struct {
|
|||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier,omitempty"`
|
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier,omitempty"`
|
||||||
RealizationDate *time.Time `json:"realization_date,omitempty"`
|
RealizationDate *time.Time `json:"realization_date,omitempty"`
|
||||||
TransactionDate time.Time `json:"transaction_date"`
|
ExpenseDate time.Time `json:"expense_date"`
|
||||||
|
GrandTotal float64 `json:"grand_total"`
|
||||||
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,25 +56,20 @@ type ExpenseDetailDTO struct {
|
|||||||
|
|
||||||
type ExpenseNonstockDTO struct {
|
type ExpenseNonstockDTO struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
ExpenseId *uint64 `json:"expense_id,omitempty"`
|
|
||||||
ProjectFlockKandangId *uint64 `json:"project_flock_kandang_id,omitempty"`
|
|
||||||
KandangId *uint64 `json:"kandang_id,omitempty"`
|
|
||||||
NonstockId *uint64 `json:"nonstock_id,omitempty"`
|
|
||||||
Qty float64 `json:"qty"`
|
Qty float64 `json:"qty"`
|
||||||
Price float64 `json:"price"`
|
UnitPrice float64 `json:"unit_price"`
|
||||||
Notes string `json:"notes"`
|
TotalPrice float64 `json:"total_price"`
|
||||||
|
Note *string `json:"note,omitempty"`
|
||||||
Nonstock *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"`
|
Nonstock *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExpenseRealizationDTO struct {
|
type ExpenseRealizationDTO struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
ExpenseNonstockId *uint64 `json:"expense_nonstock_id,omitempty"`
|
|
||||||
Qty float64 `json:"qty"`
|
Qty float64 `json:"qty"`
|
||||||
Price float64 `json:"price"`
|
UnitPrice float64 `json:"unit_price"`
|
||||||
Notes string `json:"notes"`
|
TotalPrice float64 `json:"total_price"`
|
||||||
|
Note *string `json:"note,omitempty"`
|
||||||
Nonstock *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"`
|
Nonstock *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type KandangGroupDTO struct {
|
type KandangGroupDTO struct {
|
||||||
@@ -94,7 +91,8 @@ func ToExpenseRelationDTO(e entity.Expense) ExpenseRelationDTO {
|
|||||||
return ExpenseRelationDTO{
|
return ExpenseRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
PoNumber: e.PoNumber,
|
PoNumber: e.PoNumber,
|
||||||
TransactionDate: e.TransactionDate,
|
ExpenseDate: e.ExpenseDate,
|
||||||
|
GrandTotal: e.GrandTotal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +124,8 @@ func ToExpenseBaseDTO(e *entity.Expense) ExpenseBaseDTO {
|
|||||||
Category: e.Category,
|
Category: e.Category,
|
||||||
Supplier: supplier,
|
Supplier: supplier,
|
||||||
RealizationDate: realizationDate,
|
RealizationDate: realizationDate,
|
||||||
TransactionDate: e.TransactionDate,
|
ExpenseDate: e.ExpenseDate,
|
||||||
|
GrandTotal: e.GrandTotal,
|
||||||
Location: location,
|
Location: location,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,9 +192,10 @@ func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
|
|||||||
|
|
||||||
for _, ns := range e.Nonstocks {
|
for _, ns := range e.Nonstocks {
|
||||||
pengajuanDTO := ToExpenseNonstockDTO(ns)
|
pengajuanDTO := ToExpenseNonstockDTO(ns)
|
||||||
|
|
||||||
pengajuans = append(pengajuans, pengajuanDTO)
|
pengajuans = append(pengajuans, pengajuanDTO)
|
||||||
|
|
||||||
if ns.Realization != nil {
|
if ns.Realization != nil && ns.Realization.Id != 0 {
|
||||||
var nonstock *nonstockDTO.NonstockRelationDTO
|
var nonstock *nonstockDTO.NonstockRelationDTO
|
||||||
if ns.Nonstock != nil && ns.Nonstock.Id != 0 {
|
if ns.Nonstock != nil && ns.Nonstock.Id != 0 {
|
||||||
mapped := nonstockDTO.ToNonstockRelationDTO(*ns.Nonstock)
|
mapped := nonstockDTO.ToNonstockRelationDTO(*ns.Nonstock)
|
||||||
@@ -204,12 +204,11 @@ func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
|
|||||||
|
|
||||||
realisasiDTO := ExpenseRealizationDTO{
|
realisasiDTO := ExpenseRealizationDTO{
|
||||||
Id: ns.Realization.Id,
|
Id: ns.Realization.Id,
|
||||||
ExpenseNonstockId: ns.Realization.ExpenseNonstockId,
|
Qty: ns.Realization.RealizationQty,
|
||||||
Qty: ns.Realization.Qty,
|
UnitPrice: ns.Realization.RealizationUnitPrice,
|
||||||
Price: ns.Realization.Price,
|
TotalPrice: ns.Realization.RealizationTotalPrice,
|
||||||
Notes: ns.Realization.Notes,
|
Note: ns.Realization.Note,
|
||||||
Nonstock: nonstock,
|
Nonstock: nonstock,
|
||||||
CreatedAt: ns.Realization.CreatedAt,
|
|
||||||
}
|
}
|
||||||
realisasi = append(realisasi, realisasiDTO)
|
realisasi = append(realisasi, realisasiDTO)
|
||||||
}
|
}
|
||||||
@@ -218,12 +217,12 @@ func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
|
|||||||
|
|
||||||
var totalPengajuan float64
|
var totalPengajuan float64
|
||||||
for _, p := range pengajuans {
|
for _, p := range pengajuans {
|
||||||
totalPengajuan += p.Qty * p.Price
|
totalPengajuan += p.TotalPrice
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalRealisasi float64
|
var totalRealisasi float64
|
||||||
for _, r := range realisasi {
|
for _, r := range realisasi {
|
||||||
totalRealisasi += r.Qty * r.Price
|
totalRealisasi += r.TotalPrice
|
||||||
}
|
}
|
||||||
kandangs := ToKandangGroupDTO(pengajuans, realisasi, e.Nonstocks)
|
kandangs := ToKandangGroupDTO(pengajuans, realisasi, e.Nonstocks)
|
||||||
|
|
||||||
@@ -250,15 +249,11 @@ func ToExpenseNonstockDTO(ns entity.ExpenseNonstock) ExpenseNonstockDTO {
|
|||||||
|
|
||||||
return ExpenseNonstockDTO{
|
return ExpenseNonstockDTO{
|
||||||
Id: ns.Id,
|
Id: ns.Id,
|
||||||
ExpenseId: ns.ExpenseId,
|
|
||||||
ProjectFlockKandangId: ns.ProjectFlockKandangId,
|
|
||||||
KandangId: ns.KandangId,
|
|
||||||
NonstockId: ns.NonstockId,
|
|
||||||
Qty: ns.Qty,
|
Qty: ns.Qty,
|
||||||
Price: ns.Price,
|
UnitPrice: ns.UnitPrice,
|
||||||
Notes: ns.Notes,
|
TotalPrice: ns.TotalPrice,
|
||||||
|
Note: &ns.Note,
|
||||||
Nonstock: nonstock,
|
Nonstock: nonstock,
|
||||||
CreatedAt: ns.CreatedAt,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,15 +264,13 @@ func ToKandangGroupDTO(pengajuans []ExpenseNonstockDTO, realisasi []ExpenseReali
|
|||||||
var kandangId uint64
|
var kandangId uint64
|
||||||
var kandangName string
|
var kandangName string
|
||||||
|
|
||||||
if p.KandangId != nil {
|
|
||||||
kandangId = *p.KandangId
|
|
||||||
for _, ns := range nonstocks {
|
for _, ns := range nonstocks {
|
||||||
if ns.Id == p.Id && ns.Kandang != nil {
|
if ns.Id == p.Id && ns.Kandang != nil {
|
||||||
|
kandangId = uint64(ns.Kandang.Id)
|
||||||
kandangName = ns.Kandang.Name
|
kandangName = ns.Kandang.Name
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if kandangId > 0 {
|
if kandangId > 0 {
|
||||||
if kandangMap[kandangId] == nil {
|
if kandangMap[kandangId] == nil {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
type ExpenseRepository interface {
|
type ExpenseRepository interface {
|
||||||
repository.BaseRepository[entity.Expense]
|
repository.BaseRepository[entity.Expense]
|
||||||
IdExists(ctx context.Context, id uint) (bool, error)
|
IdExists(ctx context.Context, id uint64) (bool, error)
|
||||||
GetNextSequence(ctx context.Context) (int, error)
|
GetNextSequence(ctx context.Context) (int, error)
|
||||||
GetWithSupplier(ctx context.Context, id uint64) (*entity.Expense, error)
|
GetWithSupplier(ctx context.Context, id uint64) (*entity.Expense, error)
|
||||||
}
|
}
|
||||||
@@ -25,8 +25,8 @@ func NewExpenseRepository(db *gorm.DB) ExpenseRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ExpenseRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
func (r *ExpenseRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) {
|
||||||
return repository.Exists[entity.Expense](ctx, r.DB(), id)
|
return repository.Exists[entity.Expense](ctx, r.DB(), uint(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ExpenseRepositoryImpl) GetNextSequence(ctx context.Context) (int, error) {
|
func (r *ExpenseRepositoryImpl) GetNextSequence(ctx context.Context) (int, error) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package expenses
|
package expenses
|
||||||
|
|
||||||
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/expenses/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/controllers"
|
||||||
expense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/services"
|
expense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,7 +13,7 @@ func ExpenseRoutes(v1 fiber.Router, u user.UserService, s expense.ExpenseService
|
|||||||
ctrl := controller.NewExpenseController(s)
|
ctrl := controller.NewExpenseController(s)
|
||||||
|
|
||||||
route := v1.Group("/expenses")
|
route := v1.Group("/expenses")
|
||||||
route.Use(m.Auth(u))
|
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
"time"
|
||||||
|
|
||||||
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"
|
||||||
@@ -147,8 +148,8 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, expenseNonstock := range req.ExpenseNonstocks {
|
for _, costPerKandang := range req.CostPerKandangs {
|
||||||
for _, costItem := range expenseNonstock.CostItems {
|
for _, costItem := range costPerKandang.CostItems {
|
||||||
nonstockId := uint(costItem.NonstockID)
|
nonstockId := uint(costItem.NonstockID)
|
||||||
|
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
@@ -188,13 +189,21 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate reference number")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate reference number")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var grandTotal float64
|
||||||
|
for _, costPerKandang := range req.CostPerKandangs {
|
||||||
|
for _, costItem := range costPerKandang.CostItems {
|
||||||
|
grandTotal += costItem.TotalCost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
createdBy := uint64(1) //todo get from auth
|
createdBy := uint64(1) //todo get from auth
|
||||||
expense = &entity.Expense{
|
expense = &entity.Expense{
|
||||||
ReferenceNumber: referenceNumber,
|
ReferenceNumber: referenceNumber,
|
||||||
PoNumber: req.PoNumber,
|
PoNumber: req.PoNumber,
|
||||||
Category: req.Category,
|
Category: req.Category,
|
||||||
SupplierId: req.SupplierID,
|
SupplierId: req.SupplierID,
|
||||||
TransactionDate: expenseDate,
|
ExpenseDate: expenseDate,
|
||||||
|
GrandTotal: grandTotal,
|
||||||
CreatedBy: createdBy,
|
CreatedBy: createdBy,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,15 +211,15 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create expense")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create expense")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req.ExpenseNonstocks) > 0 {
|
if len(req.CostPerKandangs) > 0 {
|
||||||
|
|
||||||
for _, expenseNonstock := range req.ExpenseNonstocks {
|
for _, costPerKandang := range req.CostPerKandangs {
|
||||||
|
|
||||||
var projectFlockKandangId *uint64
|
var projectFlockKandangId *uint64
|
||||||
|
|
||||||
if req.Category == "BOP" {
|
if req.Category == "BOP" {
|
||||||
|
|
||||||
projectFlockKandang, err := projectFlockKandangRepoTx.GetActiveByKandangID(c.Context(), uint(expenseNonstock.KandangID))
|
projectFlockKandang, err := projectFlockKandangRepoTx.GetActiveByKandangID(c.Context(), uint(costPerKandang.KandangID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, "No active project flock kandang found for this kandang")
|
return fiber.NewError(fiber.StatusNotFound, "No active project flock kandang found for this kandang")
|
||||||
@@ -221,16 +230,16 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
|
|||||||
projectFlockKandangId = &id
|
projectFlockKandangId = &id
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, costItem := range expenseNonstock.CostItems {
|
for _, costItem := range costPerKandang.CostItems {
|
||||||
|
|
||||||
nonstockId := costItem.NonstockID
|
nonstockId := costItem.NonstockID
|
||||||
var kandangId *uint64
|
var kandangId *uint64
|
||||||
if req.Category == "NON-BOP" {
|
if req.Category == "NON-BOP" {
|
||||||
id := uint64(expenseNonstock.KandangID)
|
id := uint64(costPerKandang.KandangID)
|
||||||
kandangId = &id
|
kandangId = &id
|
||||||
} else if req.Category == "BOP" {
|
} else if req.Category == "BOP" {
|
||||||
if projectFlockKandangId != nil {
|
if projectFlockKandangId != nil {
|
||||||
kandangId = &expenseNonstock.KandangID
|
kandangId = &costPerKandang.KandangID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,8 +249,8 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
|
|||||||
KandangId: kandangId,
|
KandangId: kandangId,
|
||||||
NonstockId: &nonstockId,
|
NonstockId: &nonstockId,
|
||||||
Qty: costItem.Quantity,
|
Qty: costItem.Quantity,
|
||||||
Price: costItem.Price,
|
TotalPrice: costItem.TotalCost,
|
||||||
Notes: costItem.Notes,
|
Note: costItem.Notes,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := expenseNonstockRepoTx.CreateOne(c.Context(), expenseNonstock, nil); err != nil {
|
if err := expenseNonstockRepoTx.CreateOne(c.Context(), expenseNonstock, nil); err != nil {
|
||||||
@@ -293,7 +302,9 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Expense", ID: &id, Exists: s.Repository.IdExists},
|
commonSvc.RelationCheck{Name: "Expense", ID: &id, Exists: func(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return s.Repository.IdExists(ctx, uint64(id))
|
||||||
|
}},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -317,27 +328,10 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transaction_date format")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transaction_date format")
|
||||||
}
|
}
|
||||||
updateBody["transaction_date"] = expenseDate
|
updateBody["expense_date"] = expenseDate
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Category != nil {
|
if len(updateBody) == 0 && req.CostPerKandang == nil && len(req.Documents) == 0 {
|
||||||
updateBody["category"] = *req.Category
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.SupplierID != nil {
|
|
||||||
supplierID := uint(*req.SupplierID)
|
|
||||||
supplierExistsFunc := func(ctx context.Context, id uint) (bool, error) {
|
|
||||||
return commonRepo.Exists[entity.Supplier](ctx, s.SupplierRepo.DB(), id)
|
|
||||||
}
|
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
|
||||||
commonSvc.RelationCheck{Name: "Supplier", ID: &supplierID, Exists: supplierExistsFunc},
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
updateBody["supplier_id"] = *req.SupplierID
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(updateBody) == 0 && req.ExpenseNonstocks == nil && len(req.Documents) == 0 {
|
|
||||||
|
|
||||||
responseDTO, err := s.GetOne(c, id)
|
responseDTO, err := s.GetOne(c, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -352,21 +346,6 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx))
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx))
|
||||||
expenseNonstockRepoTx := repository.NewExpenseNonstockRepository(tx)
|
expenseNonstockRepoTx := repository.NewExpenseNonstockRepository(tx)
|
||||||
|
|
||||||
currentExpense, err := expenseRepoTx.GetByID(c.Context(), id, nil)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Expense not found")
|
|
||||||
}
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get expense")
|
|
||||||
}
|
|
||||||
|
|
||||||
categoryChanged := false
|
|
||||||
var newCategory string
|
|
||||||
if req.Category != nil && *req.Category != currentExpense.Category {
|
|
||||||
categoryChanged = true
|
|
||||||
newCategory = *req.Category
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(updateBody) > 0 {
|
if len(updateBody) > 0 {
|
||||||
if err := expenseRepoTx.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
if err := expenseRepoTx.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -376,79 +355,41 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if categoryChanged {
|
if req.CostPerKandang != nil {
|
||||||
if currentExpense.Category == "BOP" && newCategory == "NON-BOP" {
|
|
||||||
|
|
||||||
var existingExpenseNonstocks []entity.ExpenseNonstock
|
if err := tx.Where("expense_id = ?", id).Delete(&entity.ExpenseNonstock{}).Error; err != nil {
|
||||||
if err := tx.Where("expense_id = ?", id).Find(&existingExpenseNonstocks).Error; err != nil {
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update expense items")
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get expense nonstocks")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ens := range existingExpenseNonstocks {
|
var grandTotal float64
|
||||||
updateData := map[string]interface{}{
|
for _, cpk := range *req.CostPerKandang {
|
||||||
"project_flock_kandang_id": nil,
|
for _, costItem := range cpk.CostItems {
|
||||||
}
|
grandTotal += costItem.TotalCost
|
||||||
if err := expenseNonstockRepoTx.PatchOne(c.Context(), uint(ens.Id), updateData, nil); err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update project flock kandang id to null")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if currentExpense.Category == "NON-BOP" && newCategory == "BOP" {
|
|
||||||
|
|
||||||
var existingExpenseNonstocks []entity.ExpenseNonstock
|
|
||||||
if err := tx.Where("expense_id = ?", id).Find(&existingExpenseNonstocks).Error; err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get expense nonstocks")
|
|
||||||
}
|
|
||||||
|
|
||||||
projectFlockKandangRepoTx := projectFlockKandangRepo.NewProjectFlockKandangRepository(tx)
|
|
||||||
for _, ens := range existingExpenseNonstocks {
|
|
||||||
if ens.KandangId != nil {
|
|
||||||
projectFlockKandang, err := projectFlockKandangRepoTx.GetActiveByKandangID(c.Context(), uint(*ens.KandangId))
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return fiber.NewError(fiber.StatusNotFound, "No active project flock kandang found for this kandang")
|
|
||||||
}
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to find project flock kandang for this kandang")
|
|
||||||
}
|
|
||||||
projectFlockKandangId := uint64(projectFlockKandang.Id)
|
|
||||||
|
|
||||||
updateData := map[string]interface{}{
|
|
||||||
"project_flock_kandang_id": projectFlockKandangId,
|
|
||||||
}
|
|
||||||
if err := expenseNonstockRepoTx.PatchOne(c.Context(), uint(ens.Id), updateData, nil); err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update project flock kandang id")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.ExpenseNonstocks != nil {
|
if err := expenseRepoTx.PatchOne(c.Context(), id, map[string]interface{}{
|
||||||
|
"grand_total": grandTotal,
|
||||||
|
}, nil); err != nil {
|
||||||
|
|
||||||
var existingExpenseNonstocks []entity.ExpenseNonstock
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update expense grand total")
|
||||||
if err := tx.Where("expense_id = ?", id).Find(&existingExpenseNonstocks).Error; err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get expense nonstocks for deletion")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ens := range existingExpenseNonstocks {
|
for _, cpk := range *req.CostPerKandang {
|
||||||
if err := expenseNonstockRepoTx.DeleteOne(c.Context(), uint(ens.Id)); err != nil {
|
var projectFlockKandangId *uint64
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete expense nonstock")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedExpense, err := expenseRepoTx.GetByID(c.Context(), id, nil)
|
expense, err := expenseRepoTx.GetByID(c.Context(), id, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Expense not found")
|
return fiber.NewError(fiber.StatusNotFound, "Expense not found")
|
||||||
}
|
}
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get updated expense")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get expense")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, expenseNonstock := range *req.ExpenseNonstocks {
|
if expense.Category == "BOP" {
|
||||||
var projectFlockKandangId *uint64
|
|
||||||
|
|
||||||
if updatedExpense.Category == "BOP" {
|
|
||||||
projectFlockKandangRepoTx := projectFlockKandangRepo.NewProjectFlockKandangRepository(tx)
|
projectFlockKandangRepoTx := projectFlockKandangRepo.NewProjectFlockKandangRepository(tx)
|
||||||
projectFlockKandang, err := projectFlockKandangRepoTx.GetActiveByKandangID(c.Context(), uint(expenseNonstock.KandangID))
|
projectFlockKandang, err := projectFlockKandangRepoTx.GetActiveByKandangID(c.Context(), uint(cpk.KandangID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, "No active project flock kandang found for this kandang")
|
return fiber.NewError(fiber.StatusNotFound, "No active project flock kandang found for this kandang")
|
||||||
@@ -459,7 +400,7 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
projectFlockKandangId = &id
|
projectFlockKandangId = &id
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, costItem := range expenseNonstock.CostItems {
|
for _, costItem := range cpk.CostItems {
|
||||||
|
|
||||||
nonstockId := uint(costItem.NonstockID)
|
nonstockId := uint(costItem.NonstockID)
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
@@ -469,12 +410,13 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var kandangId *uint64
|
var kandangId *uint64
|
||||||
if updatedExpense.Category == "NON-BOP" {
|
if expense.Category == "NON-BOP" {
|
||||||
id := uint64(expenseNonstock.KandangID)
|
id := uint64(cpk.KandangID)
|
||||||
kandangId = &id
|
kandangId = &id
|
||||||
} else if updatedExpense.Category == "BOP" {
|
} else if expense.Category == "BOP" {
|
||||||
|
|
||||||
if projectFlockKandangId != nil {
|
if projectFlockKandangId != nil {
|
||||||
kandangId = &expenseNonstock.KandangID
|
kandangId = &cpk.KandangID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,8 +427,8 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
KandangId: kandangId,
|
KandangId: kandangId,
|
||||||
NonstockId: &costItem.NonstockID,
|
NonstockId: &costItem.NonstockID,
|
||||||
Qty: costItem.Quantity,
|
Qty: costItem.Quantity,
|
||||||
Price: costItem.Price,
|
TotalPrice: costItem.TotalCost,
|
||||||
Notes: costItem.Notes,
|
Note: costItem.Notes,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := expenseNonstockRepoTx.CreateOne(c.Context(), expenseNonstock, nil); err != nil {
|
if err := expenseNonstockRepoTx.CreateOne(c.Context(), expenseNonstock, nil); err != nil {
|
||||||
@@ -539,7 +481,9 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
func (s expenseService) DeleteOne(c *fiber.Ctx, id uint) error {
|
func (s expenseService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||||
|
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Expense", ID: &id, Exists: s.Repository.IdExists},
|
commonSvc.RelationCheck{Name: "Expense", ID: &id, Exists: func(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return s.Repository.IdExists(ctx, uint64(id))
|
||||||
|
}},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -562,7 +506,9 @@ func (s *expenseService) CreateRealization(c *fiber.Ctx, expenseID uint, req *va
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Expense", ID: &expenseID, Exists: s.Repository.IdExists},
|
commonSvc.RelationCheck{Name: "Expense", ID: &expenseID, Exists: func(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return s.Repository.IdExists(ctx, uint64(id))
|
||||||
|
}},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -572,6 +518,8 @@ func (s *expenseService) CreateRealization(c *fiber.Ctx, expenseID uint, req *va
|
|||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid realization_date format")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid realization_date format")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createdBy := uint64(1) // TODO: replace with authenticated user id
|
||||||
|
|
||||||
if err := s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
if err := s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||||
|
|
||||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx))
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx))
|
||||||
@@ -596,13 +544,12 @@ func (s *expenseService) CreateRealization(c *fiber.Ctx, expenseID uint, req *va
|
|||||||
|
|
||||||
realization := &entity.ExpenseRealization{
|
realization := &entity.ExpenseRealization{
|
||||||
ExpenseNonstockId: &expenseNonstockID,
|
ExpenseNonstockId: &expenseNonstockID,
|
||||||
Qty: realizationItem.Qty,
|
RealizationQty: realizationItem.Qty,
|
||||||
Price: realizationItem.Price,
|
RealizationUnitPrice: realizationItem.UnitPrice,
|
||||||
Notes: "",
|
RealizationTotalPrice: realizationItem.TotalPrice,
|
||||||
}
|
RealizationDate: realizationDate,
|
||||||
|
Note: realizationItem.Notes,
|
||||||
if realizationItem.Notes != nil {
|
CreatedBy: &createdBy,
|
||||||
realization.Notes = *realizationItem.Notes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := realizationRepoTx.CreateOne(c.Context(), realization, nil); err != nil {
|
if err := realizationRepoTx.CreateOne(c.Context(), realization, nil); err != nil {
|
||||||
@@ -629,7 +576,7 @@ func (s *expenseService) CreateRealization(c *fiber.Ctx, expenseID uint, req *va
|
|||||||
expenseID,
|
expenseID,
|
||||||
utils.ExpenseStepRealisasi,
|
utils.ExpenseStepRealisasi,
|
||||||
&approvalAction,
|
&approvalAction,
|
||||||
uint(1), // TODO: replace with authenticated user id
|
uint(createdBy),
|
||||||
nil); err != nil {
|
nil); err != nil {
|
||||||
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create realization approval")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create realization approval")
|
||||||
@@ -650,7 +597,9 @@ func (s *expenseService) CreateRealization(c *fiber.Ctx, expenseID uint, req *va
|
|||||||
func (s *expenseService) CompleteExpense(c *fiber.Ctx, id uint, notes *string) (*expenseDto.ExpenseDetailDTO, error) {
|
func (s *expenseService) CompleteExpense(c *fiber.Ctx, id uint, notes *string) (*expenseDto.ExpenseDetailDTO, error) {
|
||||||
|
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Expense", ID: &id, Exists: s.Repository.IdExists},
|
commonSvc.RelationCheck{Name: "Expense", ID: &id, Exists: func(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return s.Repository.IdExists(ctx, uint64(id))
|
||||||
|
}},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -703,12 +652,14 @@ func (s *expenseService) CompleteExpense(c *fiber.Ctx, id uint, notes *string) (
|
|||||||
|
|
||||||
func (s *expenseService) UpdateRealization(c *fiber.Ctx, expenseID uint, req *validation.UpdateRealization) (*expenseDto.ExpenseDetailDTO, error) {
|
func (s *expenseService) UpdateRealization(c *fiber.Ctx, expenseID uint, req *validation.UpdateRealization) (*expenseDto.ExpenseDetailDTO, error) {
|
||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
s.Log.Errorf("Validation failed for UpdateRealization: %+v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Expense", ID: &expenseID, Exists: s.Repository.IdExists},
|
commonSvc.RelationCheck{Name: "Expense", ID: &expenseID, Exists: func(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return s.Repository.IdExists(ctx, uint64(id))
|
||||||
|
}},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -718,23 +669,27 @@ func (s *expenseService) UpdateRealization(c *fiber.Ctx, expenseID uint, req *va
|
|||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate workflow")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate workflow")
|
||||||
}
|
}
|
||||||
|
|
||||||
if latestApproval != nil && (latestApproval.StepNumber < uint16(utils.ExpenseStepRealisasi)) {
|
if latestApproval != nil && latestApproval.StepNumber != uint16(utils.ExpenseStepRealisasi) {
|
||||||
currentStepName := utils.ExpenseApprovalSteps[approvalutils.ApprovalStep(latestApproval.StepNumber)]
|
currentStepName := utils.ExpenseApprovalSteps[approvalutils.ApprovalStep(latestApproval.StepNumber)]
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest,
|
return nil, fiber.NewError(fiber.StatusBadRequest,
|
||||||
fmt.Sprintf("tidak bisa update realisasi pada step %s. Harus pada step Realisasi atau selesai", currentStepName))
|
fmt.Sprintf("Cannot update realization at %s step. Must be at Realisasi step", currentStepName))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
var realizationDate *time.Time
|
||||||
|
if req.RealizationDate != "" {
|
||||||
|
parsedDate, err := utils.ParseDateString(req.RealizationDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid realization_date format")
|
||||||
|
}
|
||||||
|
realizationDate = &parsedDate
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
|
||||||
|
|
||||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx))
|
|
||||||
realizationRepoTx := repository.NewExpenseRealizationRepository(tx)
|
realizationRepoTx := repository.NewExpenseRealizationRepository(tx)
|
||||||
expenseNonstockRepoTx := repository.NewExpenseNonstockRepository(tx)
|
expenseNonstockRepoTx := repository.NewExpenseNonstockRepository(tx)
|
||||||
expenseRepoTx := repository.NewExpenseRepository(tx)
|
expenseRepoTx := repository.NewExpenseRepository(tx)
|
||||||
|
|
||||||
// Check if only updating documents
|
|
||||||
updateDataOnly := len(req.Realizations) == 0 && len(req.Documents) > 0
|
|
||||||
|
|
||||||
if len(req.Realizations) > 0 {
|
|
||||||
for _, realizationItem := range req.Realizations {
|
for _, realizationItem := range req.Realizations {
|
||||||
|
|
||||||
expenseNonstockID := realizationItem.ExpenseNonstockID
|
expenseNonstockID := realizationItem.ExpenseNonstockID
|
||||||
@@ -754,12 +709,14 @@ func (s *expenseService) UpdateRealization(c *fiber.Ctx, expenseID uint, req *va
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateData := map[string]interface{}{
|
updateData := map[string]interface{}{
|
||||||
"qty": realizationItem.Qty,
|
"realization_qty": realizationItem.Qty,
|
||||||
"price": realizationItem.Price,
|
"realization_unit_price": realizationItem.UnitPrice,
|
||||||
|
"realization_total_price": realizationItem.TotalPrice,
|
||||||
|
"realization_date": *realizationDate,
|
||||||
}
|
}
|
||||||
|
|
||||||
if realizationItem.Notes != nil {
|
if realizationItem.Notes != nil {
|
||||||
updateData["notes"] = *realizationItem.Notes
|
updateData["note"] = *realizationItem.Notes
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := realizationRepoTx.PatchOne(c.Context(), uint(existingRealization.Id), updateData, nil); err != nil {
|
if err := realizationRepoTx.PatchOne(c.Context(), uint(existingRealization.Id), updateData, nil); err != nil {
|
||||||
@@ -768,6 +725,10 @@ func (s *expenseService) UpdateRealization(c *fiber.Ctx, expenseID uint, req *va
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := expenseRepoTx.PatchOne(c.Context(), expenseID, map[string]interface{}{
|
||||||
|
"realization_date": *realizationDate,
|
||||||
|
}, nil); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update realization date")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req.Documents) > 0 {
|
if len(req.Documents) > 0 {
|
||||||
@@ -776,28 +737,9 @@ func (s *expenseService) UpdateRealization(c *fiber.Ctx, expenseID uint, req *va
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !updateDataOnly && *latestApproval.Action == entity.ApprovalActionUpdated {
|
|
||||||
actorID := uint(1) // TODO: replace with authenticated user id
|
|
||||||
approvalAction := entity.ApprovalActionUpdated
|
|
||||||
if _, err := approvalSvcTx.CreateApproval(
|
|
||||||
c.Context(),
|
|
||||||
utils.ApprovalWorkflowExpense,
|
|
||||||
expenseID,
|
|
||||||
utils.ExpenseStepRealisasi,
|
|
||||||
&approvalAction,
|
|
||||||
actorID,
|
|
||||||
nil); err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create realization approval")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
if fiberErr, ok := err.(*fiber.Error); ok {
|
|
||||||
return nil, fiberErr
|
|
||||||
}
|
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "gagal update realisasi expense")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
responseDTO, err := s.GetOne(c, expenseID)
|
responseDTO, err := s.GetOne(c, expenseID)
|
||||||
@@ -883,7 +825,9 @@ func (s *expenseService) processDocuments(ctx *fiber.Ctx, expenseRepoTx reposito
|
|||||||
func (s *expenseService) DeleteDocument(ctx *fiber.Ctx, expenseID uint, documentID uint64, isRealization bool) error {
|
func (s *expenseService) DeleteDocument(ctx *fiber.Ctx, expenseID uint, documentID uint64, isRealization bool) error {
|
||||||
|
|
||||||
if err := commonSvc.EnsureRelations(ctx.Context(),
|
if err := commonSvc.EnsureRelations(ctx.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Expense", ID: &expenseID, Exists: s.Repository.IdExists},
|
commonSvc.RelationCheck{Name: "Expense", ID: &expenseID, Exists: func(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return s.Repository.IdExists(ctx, uint64(id))
|
||||||
|
}},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -965,7 +909,9 @@ func (s *expenseService) Approval(c *fiber.Ctx, req *validation.ApprovalRequest,
|
|||||||
|
|
||||||
for _, id := range req.ApprovableIds {
|
for _, id := range req.ApprovableIds {
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Expense", ID: &id, Exists: s.Repository.IdExists},
|
commonSvc.RelationCheck{Name: "Expense", ID: &id, Exists: func(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return s.Repository.IdExists(ctx, uint64(id))
|
||||||
|
}},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ type Create struct {
|
|||||||
Category string `form:"category" json:"category" validate:"required,oneof=BOP NON-BOP"`
|
Category string `form:"category" json:"category" validate:"required,oneof=BOP NON-BOP"`
|
||||||
SupplierID uint64 `form:"supplier_id" json:"supplier_id" validate:"required,gt=0"`
|
SupplierID uint64 `form:"supplier_id" json:"supplier_id" validate:"required,gt=0"`
|
||||||
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
|
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
|
||||||
ExpenseNonstocks []ExpenseNonstock `form:"expense_nonstocks" json:"expense_nonstocks" validate:"required,min=1,dive"`
|
CostPerKandangs []CostPerKandang `form:"cost_per_kandangs" json:"cost_per_kandangs" validate:"required,min=1,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExpenseNonstock struct {
|
type CostPerKandang struct {
|
||||||
KandangID uint64 `form:"kandang_id" json:"kandang_id" validate:"required,gt=0"`
|
KandangID uint64 `form:"kandang_id" json:"kandang_id" validate:"required,gt=0"`
|
||||||
CostItems []CostItem `form:"cost_items" json:"cost_items" validate:"required,min=1,dive"`
|
CostItems []CostItem `form:"cost_items" json:"cost_items" validate:"required,min=1,dive"`
|
||||||
}
|
}
|
||||||
@@ -21,15 +21,13 @@ type ExpenseNonstock struct {
|
|||||||
type CostItem struct {
|
type CostItem struct {
|
||||||
NonstockID uint64 `form:"nonstock_id" json:"nonstock_id" validate:"required,gt=0"`
|
NonstockID uint64 `form:"nonstock_id" json:"nonstock_id" validate:"required,gt=0"`
|
||||||
Quantity float64 `form:"quantity" json:"quantity" validate:"required,gt=0"`
|
Quantity float64 `form:"quantity" json:"quantity" validate:"required,gt=0"`
|
||||||
Price float64 `form:"price" json:"price" validate:"required,gt=0"`
|
TotalCost float64 `form:"total_cost" json:"total_cost" validate:"required,gt=0"`
|
||||||
Notes string `form:"notes" json:"notes" validate:"required,max=500"`
|
Notes string `form:"notes" json:"notes" validate:"required,max=500"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
TransactionDate *string `form:"transaction_date" json:"transaction_date" validate:"omitempty,datetime=2006-01-02"`
|
TransactionDate *string `form:"transaction_date" json:"transaction_date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
Category *string `form:"category" json:"category" validate:"omitempty,oneof=BOP NON-BOP"`
|
CostPerKandang *[]CostPerKandang `form:"cost_per_kandang" json:"cost_per_kandang" validate:"omitempty,min=1,dive"`
|
||||||
SupplierID *uint64 `form:"supplier_id" json:"supplier_id" validate:"omitempty,gt=0"`
|
|
||||||
ExpenseNonstocks *[]ExpenseNonstock `form:"expense_nonstocks" json:"expense_nonstocks" validate:"omitempty,min=1,dive"`
|
|
||||||
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
|
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +52,8 @@ type UpdateRealization struct {
|
|||||||
type RealizationItem struct {
|
type RealizationItem struct {
|
||||||
ExpenseNonstockID uint64 `form:"expense_nonstock_id" json:"expense_nonstock_id" validate:"required,gt=0"`
|
ExpenseNonstockID uint64 `form:"expense_nonstock_id" json:"expense_nonstock_id" validate:"required,gt=0"`
|
||||||
Qty float64 `form:"qty" json:"qty" validate:"required,gt=0"`
|
Qty float64 `form:"qty" json:"qty" validate:"required,gt=0"`
|
||||||
Price float64 `form:"price" json:"price" validate:"required,gt=0"`
|
UnitPrice float64 `form:"unit_price" json:"unit_price" validate:"required,gt=0"`
|
||||||
|
TotalPrice float64 `form:"total_price" json:"total_price" validate:"required,gt=0"`
|
||||||
Notes *string `form:"notes" json:"notes" validate:"omitempty,max=500"`
|
Notes *string `form:"notes" json:"notes" validate:"omitempty,max=500"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
common "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"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/validations"
|
||||||
ProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
ProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
productRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories"
|
productRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories"
|
||||||
@@ -78,10 +78,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ctx := c.Context()
|
ctx := c.Context()
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := common.EnsureRelations(c.Context(),
|
if err := common.EnsureRelations(c.Context(),
|
||||||
common.RelationCheck{Name: "Product", ID: &req.ProductID, Exists: s.ProductRepo.IdExists},
|
common.RelationCheck{Name: "Product", ID: &req.ProductID, Exists: s.ProductRepo.IdExists},
|
||||||
common.RelationCheck{Name: "Warehouse", ID: &req.WarehouseID, Exists: s.WarehouseRepo.IdExists},
|
common.RelationCheck{Name: "Warehouse", ID: &req.WarehouseID, Exists: s.WarehouseRepo.IdExists},
|
||||||
@@ -110,7 +107,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
|||||||
ProductId: uint(req.ProductID),
|
ProductId: uint(req.ProductID),
|
||||||
WarehouseId: uint(req.WarehouseID),
|
WarehouseId: uint(req.WarehouseID),
|
||||||
Quantity: 0,
|
Quantity: 0,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1, // TODO: should Get from auth middleware
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.ProductWarehouseRepo.CreateOne(ctx, newPW, nil); err != nil {
|
if err := s.ProductWarehouseRepo.CreateOne(ctx, newPW, nil); err != nil {
|
||||||
@@ -146,7 +143,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
|||||||
LogId: 0,
|
LogId: 0,
|
||||||
Note: req.Note,
|
Note: req.Note,
|
||||||
ProductWarehouseId: productWarehouse.Id,
|
ProductWarehouseId: productWarehouse.Id,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1, // TODO: should Get from auth middleware
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.StockLogsRepository.WithTx(tx).CreateOne(ctx, newLog, nil); err != nil {
|
if err := s.StockLogsRepository.WithTx(tx).CreateOne(ctx, newLog, nil); err != nil {
|
||||||
|
|||||||
+2
-2
@@ -27,7 +27,7 @@ type ProductWarehouseRepository interface {
|
|||||||
GetDetailByID(ctx context.Context, id uint) (*entity.ProductWarehouse, error)
|
GetDetailByID(ctx context.Context, id uint) (*entity.ProductWarehouse, error)
|
||||||
IdExists(ctx context.Context, id uint) (bool, error)
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
CleanupEmpty(ctx context.Context, affected map[uint]struct{}) error
|
CleanupEmpty(ctx context.Context, affected map[uint]struct{}) error
|
||||||
EnsureProductWarehouse(ctx context.Context, productID, warehouseID uint, createdBy uint) (uint, error)
|
EnsureProductWarehouse(ctx context.Context, productID, warehouseID uint, createdBy uint64) (uint, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductWarehouseRepositoryImpl struct {
|
type ProductWarehouseRepositoryImpl struct {
|
||||||
@@ -199,7 +199,7 @@ func (r *ProductWarehouseRepositoryImpl) EnsureProductWarehouse(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
productID uint,
|
productID uint,
|
||||||
warehouseID uint,
|
warehouseID uint,
|
||||||
createdBy uint,
|
createdBy uint64,
|
||||||
) (uint, error) {
|
) (uint, error) {
|
||||||
record, err := r.GetProductWarehouseByProductAndWarehouseID(ctx, productID, warehouseID)
|
record, err := r.GetProductWarehouseByProductAndWarehouseID(ctx, productID, warehouseID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories"
|
rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/validations"
|
||||||
rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories"
|
rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories"
|
||||||
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -127,10 +127,6 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d di gudang asal tidak cukup", product.ProductID))
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d di gudang asal tidak cukup", product.ProductID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// validasi total qty harus lebih besar dari atau sama dengan total qty di delivery compare berdasarkan productid
|
// validasi total qty harus lebih besar dari atau sama dengan total qty di delivery compare berdasarkan productid
|
||||||
deliveryQtyMap := make(map[uint]float64)
|
deliveryQtyMap := make(map[uint]float64)
|
||||||
@@ -178,7 +174,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
Reason: req.TransferReason,
|
Reason: req.TransferReason,
|
||||||
TransferDate: transferDate,
|
TransferDate: transferDate,
|
||||||
MovementNumber: movementNumber,
|
MovementNumber: movementNumber,
|
||||||
CreatedBy: uint64(actorID),
|
CreatedBy: 1, //todo: get from token
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the transfer entity to the database
|
// Save the transfer entity to the database
|
||||||
@@ -281,7 +277,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
LogId: uint(entityTransfer.Id),
|
LogId: uint(entityTransfer.Id),
|
||||||
Note: "",
|
Note: "",
|
||||||
ProductWarehouseId: sourcePW.Id,
|
ProductWarehouseId: sourcePW.Id,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
if err := s.StockLogsRepository.WithTx(tx).CreateOne(c.Context(), decreaseLog, nil); err != nil {
|
if err := s.StockLogsRepository.WithTx(tx).CreateOne(c.Context(), decreaseLog, nil); err != nil {
|
||||||
s.Log.Errorf("Failed to create stock log decrease: %+v", err)
|
s.Log.Errorf("Failed to create stock log decrease: %+v", err)
|
||||||
@@ -302,7 +298,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
ProductId: uint(product.ProductID),
|
ProductId: uint(product.ProductID),
|
||||||
WarehouseId: uint(req.DestinationWarehouseID),
|
WarehouseId: uint(req.DestinationWarehouseID),
|
||||||
Quantity: 0,
|
Quantity: 0,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1, // TODO: should Get from auth middleware
|
||||||
}
|
}
|
||||||
if err := s.ProductWarehouseRepo.WithTx(tx).CreateOne(c.Context(), destPW, nil); err != nil {
|
if err := s.ProductWarehouseRepo.WithTx(tx).CreateOne(c.Context(), destPW, nil); err != nil {
|
||||||
s.Log.Errorf("Failed to create destination product warehouse: %+v", err)
|
s.Log.Errorf("Failed to create destination product warehouse: %+v", err)
|
||||||
@@ -329,7 +325,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
LogId: uint(entityTransfer.Id),
|
LogId: uint(entityTransfer.Id),
|
||||||
Note: "",
|
Note: "",
|
||||||
ProductWarehouseId: destPW.Id,
|
ProductWarehouseId: destPW.Id,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
if err := s.StockLogsRepository.WithTx(tx).CreateOne(c.Context(), increaseLog, nil); err != nil {
|
if err := s.StockLogsRepository.WithTx(tx).CreateOne(c.Context(), increaseLog, nil); err != nil {
|
||||||
s.Log.Errorf("Failed to create stock log increase: %+v", err)
|
s.Log.Errorf("Failed to create stock log increase: %+v", err)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ 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"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
marketingDeliveryProductRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/marketing-delivery-products/repositories"
|
marketingDeliveryProductRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/marketing-delivery-products/repositories"
|
||||||
productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
@@ -176,11 +175,6 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.MarketingRepo.DB()))
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.MarketingRepo.DB()))
|
||||||
|
|
||||||
latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, req.MarketingId, nil)
|
latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, req.MarketingId, nil)
|
||||||
@@ -196,6 +190,9 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
if latestApproval.StepNumber >= uint16(utils.MarketingDeliveryOrder) {
|
if latestApproval.StepNumber >= uint16(utils.MarketingDeliveryOrder) {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Delivery order already exists for this marketing")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Delivery order already exists for this marketing")
|
||||||
}
|
}
|
||||||
|
if latestApproval.Action == nil || *latestApproval.Action != entity.ApprovalActionApproved {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Marketing is not approved - current status: %v", *latestApproval.Action))
|
||||||
|
}
|
||||||
|
|
||||||
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
|
|
||||||
@@ -259,7 +256,7 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actorID := uint(1) // TODO: ambil dari auth context
|
||||||
approvalAction := entity.ApprovalActionApproved
|
approvalAction := entity.ApprovalActionApproved
|
||||||
if _, err := approvalSvcTx.CreateApproval(
|
if _, err := approvalSvcTx.CreateApproval(
|
||||||
c.Context(),
|
c.Context(),
|
||||||
|
|||||||
@@ -2,22 +2,16 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"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"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MarketingRepository interface {
|
type MarketingRepository interface {
|
||||||
repository.BaseRepository[entity.Marketing]
|
repository.BaseRepository[entity.Marketing]
|
||||||
IdExists(ctx context.Context, id uint) (bool, error)
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
GetNextSequence(ctx context.Context) (uint, error)
|
GetNextSequence(ctx context.Context) (uint, error)
|
||||||
NextSoNumber(ctx context.Context, tx *gorm.DB) (string, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type MarketingRepositoryImpl struct {
|
type MarketingRepositoryImpl struct {
|
||||||
@@ -41,82 +35,3 @@ func (r *MarketingRepositoryImpl) GetNextSequence(ctx context.Context) (uint, er
|
|||||||
}
|
}
|
||||||
return maxID + 1, nil
|
return maxID + 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *MarketingRepositoryImpl) NextSoNumber(ctx context.Context, tx *gorm.DB) (string, error) {
|
|
||||||
return r.generateSequentialNumber(ctx, tx, "so_number", utils.MarketingSoNumberPrefix, utils.MarketingNumberPadding)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseNumericSuffix(value, prefix string) (int, bool) {
|
|
||||||
if !strings.HasPrefix(value, prefix) {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
suffix := strings.TrimPrefix(value, prefix)
|
|
||||||
if suffix == "" {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
trimmed := strings.TrimLeft(suffix, "0")
|
|
||||||
if trimmed == "" {
|
|
||||||
trimmed = "0"
|
|
||||||
}
|
|
||||||
number, err := strconv.Atoi(trimmed)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return number, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *MarketingRepositoryImpl) numberExists(ctx context.Context, db *gorm.DB, column, value string) (bool, error) {
|
|
||||||
var count int64
|
|
||||||
if err := db.WithContext(ctx).
|
|
||||||
Model(&entity.Marketing{}).
|
|
||||||
Where(fmt.Sprintf("%s = ?", column), value).
|
|
||||||
Count(&count).Error; err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return count > 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *MarketingRepositoryImpl) generateSequentialNumber(ctx context.Context, tx *gorm.DB, column, prefix string, padding int) (string, error) {
|
|
||||||
|
|
||||||
db := tx
|
|
||||||
if db == nil {
|
|
||||||
db = r.DB()
|
|
||||||
}
|
|
||||||
|
|
||||||
var values []string
|
|
||||||
err := db.WithContext(ctx).
|
|
||||||
Model(&entity.Marketing{}).
|
|
||||||
Where(fmt.Sprintf("%s LIKE ?", column), prefix+"%").
|
|
||||||
Select(column).
|
|
||||||
Order(fmt.Sprintf("%s DESC", column)).
|
|
||||||
Limit(20).
|
|
||||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
|
||||||
Pluck(column, &values).Error
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
next := 1
|
|
||||||
for _, value := range values {
|
|
||||||
if number, ok := parseNumericSuffix(value, prefix); ok {
|
|
||||||
next = number + 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxAttempts = 20
|
|
||||||
for attempt := 0; attempt < maxAttempts; attempt++ {
|
|
||||||
candidate := fmt.Sprintf("%s%0*d", prefix, padding, next)
|
|
||||||
exists, err := r.numberExists(ctx, db, column, candidate)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
return candidate, nil
|
|
||||||
}
|
|
||||||
next++
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("unable to generate unique %s", column)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ 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"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
rInvMarketingDeliveryProduct "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/marketing-delivery-products/repositories"
|
rInvMarketingDeliveryProduct "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/marketing-delivery-products/repositories"
|
||||||
productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
||||||
@@ -91,11 +90,6 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Customer", ID: &req.CustomerId, Exists: s.CustomerRepo.IdExists},
|
commonSvc.RelationCheck{Name: "Customer", ID: &req.CustomerId, Exists: s.CustomerRepo.IdExists},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@@ -115,10 +109,11 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e
|
|||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid date format")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid date format")
|
||||||
}
|
}
|
||||||
|
|
||||||
soNumber, err := s.MarketingRepo.NextSoNumber(context.Background(), nil)
|
nextSeq, err := s.MarketingRepo.GetNextSequence(c.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate SO number")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate SO number")
|
||||||
}
|
}
|
||||||
|
soNumber := fmt.Sprintf("SO-%05d", nextSeq)
|
||||||
|
|
||||||
var marketing *entity.Marketing
|
var marketing *entity.Marketing
|
||||||
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
@@ -134,7 +129,7 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e
|
|||||||
SoDate: soDate,
|
SoDate: soDate,
|
||||||
SalesPersonId: req.SalesPersonId,
|
SalesPersonId: req.SalesPersonId,
|
||||||
Notes: req.Notes,
|
Notes: req.Notes,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
if err := marketingRepoTx.CreateOne(c.Context(), marketing, nil); err != nil {
|
if err := marketingRepoTx.CreateOne(c.Context(), marketing, nil); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create salesOrders")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create salesOrders")
|
||||||
@@ -148,6 +143,7 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actorID := uint(1) // TODO: ambil dari auth context
|
||||||
approvalAction := entity.ApprovalActionCreated
|
approvalAction := entity.ApprovalActionCreated
|
||||||
if _, err := approvalSvcTx.CreateApproval(
|
if _, err := approvalSvcTx.CreateApproval(
|
||||||
c.Context(),
|
c.Context(),
|
||||||
@@ -184,11 +180,6 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := commonSvc.EnsureRelations(c.Context(),
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
commonSvc.RelationCheck{Name: "Marketing", ID: &id, Exists: s.MarketingRepo.IdExists},
|
commonSvc.RelationCheck{Name: "Marketing", ID: &id, Exists: s.MarketingRepo.IdExists},
|
||||||
commonSvc.RelationCheck{Name: "Customer", ID: &req.CustomerId, Exists: s.CustomerRepo.IdExists},
|
commonSvc.RelationCheck{Name: "Customer", ID: &req.CustomerId, Exists: s.CustomerRepo.IdExists},
|
||||||
@@ -330,6 +321,7 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if latestApproval != nil {
|
if latestApproval != nil {
|
||||||
|
actorID := uint(1) // todo: ambil dari auth context
|
||||||
action := entity.ApprovalActionUpdated
|
action := entity.ApprovalActionUpdated
|
||||||
_, err := approvalSvcTx.CreateApproval(
|
_, err := approvalSvcTx.CreateApproval(
|
||||||
c.Context(),
|
c.Context(),
|
||||||
@@ -413,11 +405,6 @@ func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.MarketingRepo.DB()))
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.MarketingRepo.DB()))
|
||||||
|
|
||||||
var action entity.ApprovalAction
|
var action entity.ApprovalAction
|
||||||
@@ -461,7 +448,7 @@ func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err := s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
|
|
||||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
|
|
||||||
@@ -492,6 +479,7 @@ func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]e
|
|||||||
nextStep = approvalutils.ApprovalStep(currentStep)
|
nextStep = approvalutils.ApprovalStep(currentStep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actorID := uint(1) // todo ambil dari auth context
|
||||||
if _, err := approvalSvc.CreateApproval(
|
if _, err := approvalSvc.CreateApproval(
|
||||||
c.Context(),
|
c.Context(),
|
||||||
utils.ApprovalWorkflowMarketing,
|
utils.ApprovalWorkflowMarketing,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
@@ -88,14 +87,10 @@ func (s *areaService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.A
|
|||||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Area with name %s already exists", req.Name))
|
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Area with name %s already exists", req.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
//TODO: created by dummy
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
createBody := &entity.Area{
|
createBody := &entity.Area{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,max=50"`
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
Alias string `json:"alias" validate:"required_strict,max=5"`
|
Alias string `json:"alias" validate:"required_strict"`
|
||||||
Owner *string `json:"owner,omitempty" validate:"omitempty,max=50"`
|
Owner *string `json:"owner,omitempty" validate:"omitempty"`
|
||||||
AccountNumber string `json:"account_number" validate:"required_strict,max=50"`
|
AccountNumber string `json:"account_number" validate:"required_strict,max=50"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,max=50"`
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
Alias *string `json:"alias,omitempty" validate:"omitempty,max=5"`
|
Alias *string `json:"alias,omitempty" validate:"omitempty"`
|
||||||
Owner *string `json:"owner,omitempty" validate:"omitempty,max=50"`
|
Owner *string `json:"owner,omitempty" validate:"omitempty"`
|
||||||
AccountNumber *string `json:"account_number,omitempty" validate:"omitempty,max=50"`
|
AccountNumber *string `json:"account_number,omitempty" validate:"omitempty,max=50"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
common "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"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -81,10 +81,6 @@ func (s *customerService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti
|
|||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if exists, err := s.Repository.NameExists(c.Context(), req.Name, nil); err != nil {
|
if exists, err := s.Repository.NameExists(c.Context(), req.Name, nil); err != nil {
|
||||||
s.Log.Errorf("Failed to check customer name: %+v", err)
|
s.Log.Errorf("Failed to check customer name: %+v", err)
|
||||||
@@ -104,6 +100,7 @@ func (s *customerService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: created by dummy
|
||||||
createBody := &entity.Customer{
|
createBody := &entity.Customer{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
PicId: req.PicId,
|
PicId: req.PicId,
|
||||||
@@ -112,7 +109,7 @@ func (s *customerService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti
|
|||||||
Phone: req.Phone,
|
Phone: req.Phone,
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
AccountNumber: req.AccountNumber,
|
AccountNumber: req.AccountNumber,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"`
|
PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"`
|
||||||
Type string `json:"type" validate:"required_strict,max=50"`
|
Type string `json:"type" validate:"required_strict"`
|
||||||
Address string `json:"address" validate:"required_strict"`
|
Address string `json:"address" validate:"required_strict"`
|
||||||
Phone string `json:"phone" validate:"required_strict,max=20"`
|
Phone string `json:"phone" validate:"required_strict,max=20"`
|
||||||
Email string `json:"email" validate:"required_strict,email,max=50"`
|
Email string `json:"email" validate:"required_strict,email"`
|
||||||
AccountNumber string `json:"account_number" validate:"required_strict,max=50"`
|
AccountNumber string `json:"account_number" validate:"required_strict"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,max=50"`
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"`
|
PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
Type *string `json:"type,omitempty" validate:"omitempty,max=50"`
|
Type *string `json:"type,omitempty" validate:"omitempty"`
|
||||||
Address *string `json:"address,omitempty" validate:"omitempty"`
|
Address *string `json:"address,omitempty" validate:"omitempty"`
|
||||||
Phone *string `json:"phone,omitempty" validate:"omitempty,max=20"`
|
Phone *string `json:"phone,omitempty" validate:"omitempty"`
|
||||||
Email *string `json:"email,omitempty" validate:"omitempty,max=50"`
|
Email *string `json:"email,omitempty" validate:"omitempty"`
|
||||||
AccountNumber *string `json:"account_number,omitempty" validate:"omitempty,max=50"`
|
AccountNumber *string `json:"account_number,omitempty" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
|
|||||||
@@ -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,7 +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.Use(m.Auth(u))
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
common "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"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -130,18 +130,14 @@ func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
//TODO: created by dummy
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
createBody := &entity.Kandang{
|
createBody := &entity.Kandang{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
LocationId: req.LocationId,
|
LocationId: req.LocationId,
|
||||||
Capacity: req.Capacity,
|
Capacity: req.Capacity,
|
||||||
Status: status,
|
Status: status,
|
||||||
PicId: req.PicId,
|
PicId: req.PicId,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
Status string `json:"status,omitempty" validate:"omitempty,min=3,max=50"`
|
Status string `json:"status,omitempty" validate:"omitempty,min=3"`
|
||||||
Capacity float64 `json:"capacity" validate:"required_strict,gt=0"`
|
Capacity float64 `json:"capacity" validate:"required_strict,gt=0"`
|
||||||
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
|
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
|
||||||
PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"`
|
PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"`
|
||||||
@@ -10,8 +10,8 @@ type Create struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,max=50"`
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
Status *string `json:"status,omitempty" validate:"omitempty,min=3,max=50"`
|
Status *string `json:"status,omitempty" validate:"omitempty,min=3"`
|
||||||
Capacity *float64 `json:"capacity" validate:"omitempty,gt=0"`
|
Capacity *float64 `json:"capacity" validate:"omitempty,gt=0"`
|
||||||
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
|
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"`
|
PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
common "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"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,16 +97,12 @@ func (s *locationService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
//TODO: created by dummy
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
createBody := &entity.Location{
|
createBody := &entity.Location{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Address: req.Address,
|
Address: req.Address,
|
||||||
AreaId: req.AreaId,
|
AreaId: req.AreaId,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
Address string `json:"address" validate:"required_strict"`
|
Address string `json:"address" 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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,max=50"`
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
Address *string `json:"address,omitempty" validate:"omitempty"`
|
Address *string `json:"address,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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
UomID uint `json:"uom_id" validate:"required,gt=0"`
|
UomID uint `json:"uom_id" validate:"required,gt=0"`
|
||||||
SupplierIDs []uint `json:"supplier_ids,omitempty" validate:"omitempty,dive,gt=0"`
|
SupplierIDs []uint `json:"supplier_ids,omitempty" validate:"omitempty,dive,gt=0"`
|
||||||
Flags []string `json:"flags,omitempty" validate:"omitempty,dive,max=50"`
|
Flags []string `json:"flags,omitempty" validate:"omitempty,dive,max=50"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,min=3,max=50"`
|
Name *string `json:"name,omitempty" validate:"omitempty,min=3"`
|
||||||
UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"`
|
UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"`
|
||||||
SupplierIDs *[]uint `json:"supplier_ids,omitempty" validate:"omitempty,dive,gt=0"`
|
SupplierIDs *[]uint `json:"supplier_ids,omitempty" validate:"omitempty,dive,gt=0"`
|
||||||
Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive,max=50"`
|
Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive,max=50"`
|
||||||
|
|||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
Code string `json:"code" validate:"required_strict,max=10"`
|
Code string `json:"code" validate:"required_strict,max=10"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,max=50"`
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
Code *string `json:"code,omitempty" validate:"omitempty,max=10"`
|
Code *string `json:"code,omitempty" validate:"omitempty,max=10"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ type ProductRelationDTO struct {
|
|||||||
SellingPrice *float64 `gorm:"type:numeric(15,3)"`
|
SellingPrice *float64 `gorm:"type:numeric(15,3)"`
|
||||||
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||||
Flags *[]string `json:"flags,omitempty"`
|
Flags *[]string `json:"flags,omitempty"`
|
||||||
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductListDTO struct {
|
type ProductListDTO struct {
|
||||||
@@ -56,12 +55,6 @@ func ToProductRelationDTO(e entity.Product) ProductRelationDTO {
|
|||||||
uomRef = &mapped
|
uomRef = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var categoryRef *productCategoryDTO.ProductCategoryRelationDTO
|
|
||||||
if e.ProductCategory.Id != 0 {
|
|
||||||
mapped := productCategoryDTO.ToProductCategoryRelationDTO(e.ProductCategory)
|
|
||||||
categoryRef = &mapped
|
|
||||||
}
|
|
||||||
|
|
||||||
return ProductRelationDTO{
|
return ProductRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
@@ -69,7 +62,6 @@ func ToProductRelationDTO(e entity.Product) ProductRelationDTO {
|
|||||||
SellingPrice: e.SellingPrice,
|
SellingPrice: e.SellingPrice,
|
||||||
Flags: &flags,
|
Flags: &flags,
|
||||||
Uom: uomRef,
|
Uom: uomRef,
|
||||||
ProductCategory: categoryRef,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
Brand string `json:"brand" validate:"required_strict,min=2,max=50"`
|
Brand string `json:"brand" validate:"required_strict,min=2"`
|
||||||
Sku *string `json:"sku,omitempty" validate:"omitempty,max=100"`
|
Sku *string `json:"sku,omitempty" validate:"omitempty"`
|
||||||
UomID uint `json:"uom_id" validate:"required,gt=0"`
|
UomID uint `json:"uom_id" validate:"required,gt=0"`
|
||||||
ProductCategoryID uint `json:"product_category_id" validate:"required,gt=0"`
|
ProductCategoryID uint `json:"product_category_id" validate:"required,gt=0"`
|
||||||
ProductPrice float64 `json:"product_price" validate:"required"`
|
ProductPrice float64 `json:"product_price" validate:"required"`
|
||||||
|
|||||||
@@ -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,7 +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.Use(m.Auth(u))
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -124,10 +124,8 @@ func (s *supplierService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti
|
|||||||
}
|
}
|
||||||
|
|
||||||
alias := strings.TrimSpace(strings.ToUpper(req.Alias))
|
alias := strings.TrimSpace(strings.ToUpper(req.Alias))
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
|
||||||
if err != nil {
|
//TODO: created by dummy
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
createBody := &entity.Supplier{
|
createBody := &entity.Supplier{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Alias: alias,
|
Alias: alias,
|
||||||
@@ -141,7 +139,7 @@ func (s *supplierService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti
|
|||||||
Npwp: req.Npwp,
|
Npwp: req.Npwp,
|
||||||
AccountNumber: req.AccountNumber,
|
AccountNumber: req.AccountNumber,
|
||||||
DueDate: req.DueDate,
|
DueDate: req.DueDate,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
Alias string `json:"alias" validate:"required_strict,max=5"`
|
Alias string `json:"alias" validate:"required_strict,max=5"`
|
||||||
Pic string `json:"pic" validate:"required_strict,max=50"`
|
Pic string `json:"pic" validate:"required_strict"`
|
||||||
Type string `json:"type" validate:"required_strict,max=50"`
|
Type string `json:"type" validate:"required_strict"`
|
||||||
Category string `json:"category" validate:"required_strict,max=20"`
|
Category string `json:"category" validate:"required_strict"`
|
||||||
Hatchery *string `json:"hatchery,omitempty" validate:"omitempty,max=50"`
|
Hatchery *string `json:"hatchery,omitempty" validate:"omitempty"`
|
||||||
Phone string `json:"phone" validate:"required_strict,max=20"`
|
Phone string `json:"phone" validate:"required_strict,max=20"`
|
||||||
Email string `json:"email" validate:"required_strict,email,max=50"`
|
Email string `json:"email" validate:"required_strict,email"`
|
||||||
Address string `json:"address" validate:"required_strict"`
|
Address string `json:"address" validate:"required_strict"`
|
||||||
Npwp *string `json:"npwp,omitempty" validate:"omitempty,max=50"`
|
Npwp *string `json:"npwp,omitempty" validate:"omitempty,max=50"`
|
||||||
AccountNumber *string `json:"account_number,omitempty" validate:"omitempty,max=50"`
|
AccountNumber *string `json:"account_number,omitempty" validate:"omitempty,max=50"`
|
||||||
@@ -16,14 +16,14 @@ type Create struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,min=3,max=50"`
|
Name *string `json:"name,omitempty" validate:"omitempty,min=3"`
|
||||||
Alias *string `json:"alias,omitempty" validate:"omitempty,max=5"`
|
Alias *string `json:"alias,omitempty" validate:"omitempty,max=5"`
|
||||||
Pic *string `json:"pic,omitempty" validate:"omitempty,max=50"`
|
Pic *string `json:"pic,omitempty" validate:"omitempty"`
|
||||||
Type *string `json:"type,omitempty" validate:"omitempty,max=50"`
|
Type *string `json:"type,omitempty" validate:"omitempty"`
|
||||||
Category *string `json:"category,omitempty" validate:"omitempty,max=20"`
|
Category *string `json:"category,omitempty" validate:"omitempty"`
|
||||||
Hatchery *string `json:"hatchery,omitempty" validate:"omitempty,max=50"`
|
Hatchery *string `json:"hatchery,omitempty" validate:"omitempty"`
|
||||||
Phone *string `json:"phone,omitempty" validate:"omitempty,max=20"`
|
Phone *string `json:"phone,omitempty" validate:"omitempty,max=20"`
|
||||||
Email *string `json:"email,omitempty" validate:"omitempty,email,max=50"`
|
Email *string `json:"email,omitempty" validate:"omitempty,email"`
|
||||||
Address *string `json:"address,omitempty" validate:"omitempty"`
|
Address *string `json:"address,omitempty" validate:"omitempty"`
|
||||||
Npwp *string `json:"npwp,omitempty" validate:"omitempty,max=50"`
|
Npwp *string `json:"npwp,omitempty" validate:"omitempty,max=50"`
|
||||||
AccountNumber *string `json:"account_number,omitempty" validate:"omitempty,max=50"`
|
AccountNumber *string `json:"account_number,omitempty" validate:"omitempty,max=50"`
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -87,13 +87,10 @@ func (s *uomService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Uo
|
|||||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Uom with name %s already exists", req.Name))
|
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Uom with name %s already exists", req.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
//TODO: created by dummy
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
createBody := &entity.Uom{
|
createBody := &entity.Uom{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,max=50"`
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type WarehouseRepository interface {
|
|||||||
IdExists(ctx context.Context, id uint) (bool, error)
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
GetByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error)
|
GetByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error)
|
||||||
GetLatestByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error)
|
GetLatestByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error)
|
||||||
|
GetDetailByID(ctx context.Context, id uint) (*entity.Warehouse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type WarehouseRepositoryImpl struct {
|
type WarehouseRepositoryImpl struct {
|
||||||
@@ -62,6 +63,18 @@ func (r *WarehouseRepositoryImpl) GetByKandangID(ctx context.Context, kandangId
|
|||||||
return &warehouse, nil
|
return &warehouse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *WarehouseRepositoryImpl) GetDetailByID(ctx context.Context, id uint) (*entity.Warehouse, error) {
|
||||||
|
var warehouse entity.Warehouse
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Preload("Area").
|
||||||
|
Preload("Location").
|
||||||
|
First(&warehouse, id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &warehouse, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *WarehouseRepositoryImpl) GetLatestByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error) {
|
func (r *WarehouseRepositoryImpl) GetLatestByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error) {
|
||||||
var warehouse entity.Warehouse
|
var warehouse entity.Warehouse
|
||||||
err := r.db.WithContext(ctx).
|
err := r.db.WithContext(ctx).
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
common "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"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -105,15 +105,13 @@ func (s *warehouseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
|
||||||
if err != nil {
|
//TODO: created by dummy
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
createBody := &entity.Warehouse{
|
createBody := &entity.Warehouse{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Type: typ,
|
Type: typ,
|
||||||
AreaId: req.AreaId,
|
AreaId: req.AreaId,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
if req.LocationId != nil {
|
if req.LocationId != nil {
|
||||||
createBody.LocationId = req.LocationId
|
createBody.LocationId = req.LocationId
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3,max=50"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
Type string `json:"type" validate:"required_strict,max=50"`
|
Type string `json:"type" 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"`
|
||||||
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
|
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
KandangId *uint `json:"kandang_id,omitempty" validate:"omitempty,number,gt=0"`
|
KandangId *uint `json:"kandang_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty,max=50"`
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
Type *string `json:"type,omitempty" validate:"omitempty,max=50"`
|
Type *string `json:"type,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"`
|
||||||
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
|
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
KandangId *uint `json:"kandang_id,omitempty" validate:"omitempty,number,gt=0"`
|
KandangId *uint `json:"kandang_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ 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"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
KandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
KandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||||
@@ -126,10 +125,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
|||||||
|
|
||||||
category := strings.ToUpper(strings.TrimSpace(projectFlockKandang.ProjectFlock.Category))
|
category := strings.ToUpper(strings.TrimSpace(projectFlockKandang.ProjectFlock.Category))
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
actorID := uint(1) // todo nanti ambil dari auth context
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
newChikins := make([]*entity.ProjectChickin, 0)
|
newChikins := make([]*entity.ProjectChickin, 0)
|
||||||
|
|
||||||
for _, chickinReq := range req.ChickinRequests {
|
for _, chickinReq := range req.ChickinRequests {
|
||||||
@@ -360,11 +356,6 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.Repository.DB()))
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.Repository.DB()))
|
||||||
|
|
||||||
var action entity.ApprovalAction
|
var action entity.ApprovalAction
|
||||||
@@ -406,13 +397,14 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
|||||||
step = utils.ProjectFlockKandangStepDisetujui
|
step = utils.ProjectFlockKandangStepDisetujui
|
||||||
}
|
}
|
||||||
|
|
||||||
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))
|
||||||
chickinRepoTx := repository.NewChickinRepository(dbTransaction)
|
chickinRepoTx := repository.NewChickinRepository(dbTransaction)
|
||||||
productWarehouseTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
productWarehouseTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||||
|
|
||||||
for _, approvableID := range approvableIDs {
|
for _, approvableID := range approvableIDs {
|
||||||
|
actorID := uint(1) // todo nanti ambil dari auth context
|
||||||
if _, err := approvalSvc.CreateApproval(
|
if _, err := approvalSvc.CreateApproval(
|
||||||
c.Context(),
|
c.Context(),
|
||||||
utils.ApprovalWorkflowProjectFlockKandang,
|
utils.ApprovalWorkflowProjectFlockKandang,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package project_flock_kandangs
|
package project_flock_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/production/project-flock-kandangs/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/controllers"
|
||||||
projectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/services"
|
projectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-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,7 +13,7 @@ func ProjectFlockKandangRoutes(v1 fiber.Router, u user.UserService, s projectFlo
|
|||||||
ctrl := controller.NewProjectFlockKandangController(s)
|
ctrl := controller.NewProjectFlockKandangController(s)
|
||||||
|
|
||||||
route := v1.Group("/project-flock-kandangs")
|
route := v1.Group("/project-flock-kandangs")
|
||||||
route.Use(m.Auth(u))
|
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProjectBudgetRepository interface {
|
|
||||||
repository.BaseRepository[entity.ProjectBudget]
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProjectBudgetRepositoryImpl struct {
|
|
||||||
*repository.BaseRepositoryImpl[entity.ProjectBudget]
|
|
||||||
db *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProjectBudgetRepository(db *gorm.DB) ProjectBudgetRepository {
|
|
||||||
return &ProjectBudgetRepositoryImpl{
|
|
||||||
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectBudget](db),
|
|
||||||
db: db,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type ProjectflockRepository interface {
|
type ProjectflockRepository interface {
|
||||||
repository.BaseRepository[entity.ProjectFlock]
|
repository.BaseRepository[entity.ProjectFlock]
|
||||||
GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error)
|
GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error)
|
||||||
@@ -41,7 +42,7 @@ func NewProjectflockRepository(db *gorm.DB) ProjectflockRepository {
|
|||||||
|
|
||||||
func (r *ProjectflockRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
|
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 {
|
return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB {
|
||||||
return r.applyQueryFilters(r.WithDefaultRelations()(db), params)
|
return r.applyQueryFilters(db, params)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ func (r *ProjectflockRepositoryImpl) WithDefaultRelations() func(*gorm.DB) *gorm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (r *ProjectflockRepositoryImpl) applyQueryFilters(db *gorm.DB, params *validation.Query) *gorm.DB {
|
func (r *ProjectflockRepositoryImpl) applyQueryFilters(db *gorm.DB, params *validation.Query) *gorm.DB {
|
||||||
if params == nil {
|
if params == nil {
|
||||||
return db
|
return db
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -13,7 +13,7 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj
|
|||||||
ctrl := controller.NewProjectflockController(s)
|
ctrl := controller.NewProjectflockController(s)
|
||||||
|
|
||||||
route := v1.Group("/project-flocks")
|
route := v1.Group("/project-flocks")
|
||||||
route.Use(m.Auth(u))
|
// route.Use(m.Auth(u))
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Post("/", ctrl.CreateOne)
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ 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"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
|
// authmiddleware "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
productWarehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
productWarehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
||||||
flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
||||||
@@ -84,17 +85,18 @@ func NewProjectflockService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s projectflockService) approvalQueryModifier() func(*gorm.DB) *gorm.DB {
|
|
||||||
return func(db *gorm.DB) *gorm.DB {
|
|
||||||
return db.Preload("ActionUser")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, map[uint]*flockDTO.FlockRelationDTO, error) {
|
func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, map[uint]*flockDTO.FlockRelationDTO, error) {
|
||||||
if err := s.Validate.Struct(params); err != nil {
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
return nil, 0, nil, err
|
return nil, 0, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.Page <= 0 {
|
||||||
|
params.Page = 1
|
||||||
|
}
|
||||||
|
if params.Limit <= 0 {
|
||||||
|
params.Limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
offset := (params.Page - 1) * params.Limit
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
projectflocks, total, err := s.Repository.GetAllWithFilters(c.Context(), offset, params.Limit, params)
|
projectflocks, total, err := s.Repository.GetAllWithFilters(c.Context(), offset, params.Limit, params)
|
||||||
@@ -110,7 +112,7 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
|
|||||||
ids[i] = item.Id
|
ids[i] = item.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
latestMap, err := s.ApprovalSvc.LatestByTargets(c.Context(), s.approvalWorkflow, ids, s.approvalQueryModifier())
|
latestMap, err := s.ApprovalSvc.LatestByTargets(c.Context(), s.approvalWorkflow, ids, s.Repository.WithDefaultRelations())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Warnf("Unable to load latest approvals for projectflocks: %+v", err)
|
s.Log.Warnf("Unable to load latest approvals for projectflocks: %+v", err)
|
||||||
} else if len(latestMap) > 0 {
|
} else if len(latestMap) > 0 {
|
||||||
@@ -154,7 +156,7 @@ func (s projectflockService) getOneEntityOnly(c *fiber.Ctx, id uint) (*entity.Pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.ApprovalSvc != nil {
|
if s.ApprovalSvc != nil {
|
||||||
approvals, err := s.ApprovalSvc.ListByTarget(c.Context(), s.approvalWorkflow, id, s.approvalQueryModifier())
|
approvals, err := s.ApprovalSvc.ListByTarget(c.Context(), s.approvalWorkflow, id, s.Repository.WithDefaultRelations())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Warnf("Unable to load approvals for projectflock %d: %+v", id, err)
|
s.Log.Warnf("Unable to load approvals for projectflock %d: %+v", id, err)
|
||||||
} else if len(approvals) > 0 {
|
} else if len(approvals) > 0 {
|
||||||
@@ -181,7 +183,7 @@ func (s projectflockService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.ApprovalSvc != nil {
|
if s.ApprovalSvc != nil {
|
||||||
approvals, err := s.ApprovalSvc.ListByTarget(c.Context(), s.approvalWorkflow, id, s.approvalQueryModifier())
|
approvals, err := s.ApprovalSvc.ListByTarget(c.Context(), s.approvalWorkflow, id, s.Repository.WithDefaultRelations())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Warnf("Unable to load approvals for projectflock %d: %+v", id, err)
|
s.Log.Warnf("Unable to load approvals for projectflock %d: %+v", id, err)
|
||||||
} else if len(approvals) > 0 {
|
} else if len(approvals) > 0 {
|
||||||
@@ -219,7 +221,7 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
actorID, err := actorIDFromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -342,7 +344,7 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
actorID, err := actorIDFromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -600,7 +602,7 @@ func (s projectflockService) Approval(c *fiber.Ctx, req *validation.Approve) ([]
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
actorID, err := actorIDFromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -845,7 +847,7 @@ func (s projectflockService) GetPeriodSummary(c *fiber.Ctx, locationID uint) ([]
|
|||||||
|
|
||||||
summaries := make([]KandangPeriodSummary, 0, len(rows))
|
summaries := make([]KandangPeriodSummary, 0, len(rows))
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
nextPeriod := 1
|
nextPeriod := 0
|
||||||
if row.LatestPeriod > 0 {
|
if row.LatestPeriod > 0 {
|
||||||
nextPeriod = row.LatestPeriod + 1
|
nextPeriod = row.LatestPeriod + 1
|
||||||
}
|
}
|
||||||
@@ -1044,3 +1046,12 @@ func (s projectflockService) kandangRepoWithTx(tx *gorm.DB) kandangRepository.Ka
|
|||||||
}
|
}
|
||||||
return kandangRepository.NewKandangRepository(s.Repository.DB())
|
return kandangRepository.NewKandangRepository(s.Repository.DB())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func actorIDFromContext(_ *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
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package recordings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -15,7 +14,6 @@ import (
|
|||||||
rRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
rRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||||
sRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
|
sRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
|
||||||
|
|
||||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -28,25 +26,6 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
projectFlockKandangRepo := rProjectFlock.NewProjectFlockKandangRepository(db)
|
projectFlockKandangRepo := rProjectFlock.NewProjectFlockKandangRepository(db)
|
||||||
projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
|
projectFlockPopulationRepo := rProjectFlock.NewProjectFlockPopulationRepository(db)
|
||||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||||
stockAllocationRepo := commonRepo.NewStockAllocationRepository(db)
|
|
||||||
|
|
||||||
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
|
|
||||||
if err := fifoService.RegisterUsable(fifo.UsableConfig{
|
|
||||||
Key: fifo.UsableKeyRecordingStock,
|
|
||||||
Table: "recording_stocks",
|
|
||||||
Columns: fifo.UsableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
UsageQuantity: "usage_qty",
|
|
||||||
PendingQuantity: "pending_qty",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
if !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
|
||||||
panic(fmt.Sprintf("failed to register recording usable workflow: %v", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||||
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||||
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowRecording, utils.RecordingApprovalSteps); err != nil {
|
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowRecording, utils.RecordingApprovalSteps); err != nil {
|
||||||
@@ -62,7 +41,6 @@ func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
projectFlockPopulationRepo,
|
projectFlockPopulationRepo,
|
||||||
approvalRepo,
|
approvalRepo,
|
||||||
approvalService,
|
approvalService,
|
||||||
fifoService,
|
|
||||||
validate,
|
validate,
|
||||||
)
|
)
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ type RecordingRepository interface {
|
|||||||
CreateStocks(tx *gorm.DB, stocks []entity.RecordingStock) error
|
CreateStocks(tx *gorm.DB, stocks []entity.RecordingStock) error
|
||||||
DeleteStocks(tx *gorm.DB, recordingID uint) error
|
DeleteStocks(tx *gorm.DB, recordingID uint) error
|
||||||
ListStocks(tx *gorm.DB, recordingID uint) ([]entity.RecordingStock, error)
|
ListStocks(tx *gorm.DB, recordingID uint) ([]entity.RecordingStock, error)
|
||||||
UpdateStockUsage(tx *gorm.DB, stockID uint, usageQty, pendingQty float64) error
|
|
||||||
|
|
||||||
CreateDepletions(tx *gorm.DB, depletions []entity.RecordingDepletion) error
|
CreateDepletions(tx *gorm.DB, depletions []entity.RecordingDepletion) error
|
||||||
DeleteDepletions(tx *gorm.DB, recordingID uint) error
|
DeleteDepletions(tx *gorm.DB, recordingID uint) error
|
||||||
@@ -121,15 +120,6 @@ func (r *RecordingRepositoryImpl) ListStocks(tx *gorm.DB, recordingID uint) ([]e
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RecordingRepositoryImpl) UpdateStockUsage(tx *gorm.DB, stockID uint, usageQty, pendingQty float64) error {
|
|
||||||
return tx.Model(&entity.RecordingStock{}).
|
|
||||||
Where("id = ?", stockID).
|
|
||||||
Updates(map[string]any{
|
|
||||||
"usage_qty": usageQty,
|
|
||||||
"pending_qty": pendingQty,
|
|
||||||
}).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RecordingRepositoryImpl) CreateDepletions(tx *gorm.DB, depletions []entity.RecordingDepletion) error {
|
func (r *RecordingRepositoryImpl) CreateDepletions(tx *gorm.DB, depletions []entity.RecordingDepletion) error {
|
||||||
if len(depletions) == 0 {
|
if len(depletions) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -4,21 +4,20 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
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"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-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"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations"
|
||||||
"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"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
|
||||||
recordingutil "gitlab.com/mbugroup/lti-api.git/internal/utils/recording"
|
recordingutil "gitlab.com/mbugroup/lti-api.git/internal/utils/recording"
|
||||||
"math"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -37,13 +36,6 @@ type RecordingService interface {
|
|||||||
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.Recording, error)
|
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.Recording, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordingFIFOIntegrationService interface {
|
|
||||||
ConsumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error
|
|
||||||
ReleaseRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error
|
|
||||||
}
|
|
||||||
|
|
||||||
var recordingStockUsableKey = fifo.UsableKeyRecordingStock
|
|
||||||
|
|
||||||
type recordingService struct {
|
type recordingService struct {
|
||||||
Log *logrus.Logger
|
Log *logrus.Logger
|
||||||
Validate *validator.Validate
|
Validate *validator.Validate
|
||||||
@@ -53,7 +45,6 @@ type recordingService struct {
|
|||||||
ProjectFlockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository
|
ProjectFlockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository
|
||||||
ApprovalRepo commonRepo.ApprovalRepository
|
ApprovalRepo commonRepo.ApprovalRepository
|
||||||
ApprovalSvc commonSvc.ApprovalService
|
ApprovalSvc commonSvc.ApprovalService
|
||||||
FifoSvc commonSvc.FifoService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRecordingService(
|
func NewRecordingService(
|
||||||
@@ -63,7 +54,6 @@ func NewRecordingService(
|
|||||||
projectFlockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository,
|
projectFlockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository,
|
||||||
approvalRepo commonRepo.ApprovalRepository,
|
approvalRepo commonRepo.ApprovalRepository,
|
||||||
approvalSvc commonSvc.ApprovalService,
|
approvalSvc commonSvc.ApprovalService,
|
||||||
fifoSvc commonSvc.FifoService,
|
|
||||||
validate *validator.Validate,
|
validate *validator.Validate,
|
||||||
) RecordingService {
|
) RecordingService {
|
||||||
return &recordingService{
|
return &recordingService{
|
||||||
@@ -75,20 +65,6 @@ func NewRecordingService(
|
|||||||
ProjectFlockPopulationRepo: projectFlockPopulationRepo,
|
ProjectFlockPopulationRepo: projectFlockPopulationRepo,
|
||||||
ApprovalRepo: approvalRepo,
|
ApprovalRepo: approvalRepo,
|
||||||
ApprovalSvc: approvalSvc,
|
ApprovalSvc: approvalSvc,
|
||||||
FifoSvc: fifoSvc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRecordingFIFOIntegrationService(
|
|
||||||
repo repository.RecordingRepository,
|
|
||||||
productWarehouseRepo rProductWarehouse.ProductWarehouseRepository,
|
|
||||||
fifoSvc commonSvc.FifoService,
|
|
||||||
) RecordingFIFOIntegrationService {
|
|
||||||
return &recordingService{
|
|
||||||
Log: utils.Log,
|
|
||||||
Repository: repo,
|
|
||||||
ProductWarehouseRepo: productWarehouseRepo,
|
|
||||||
FifoSvc: fifoSvc,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,10 +169,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
if err := s.ensureProductWarehousesExist(c, req.Stocks, req.Depletions, req.Eggs); err != nil {
|
if err := s.ensureProductWarehousesExist(c, req.Stocks, req.Depletions, req.Eggs); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var createdRecording entity.Recording
|
var createdRecording entity.Recording
|
||||||
transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
nextDay, err := s.Repository.GenerateNextDay(tx, req.ProjectFlockKandangId)
|
nextDay, err := s.Repository.GenerateNextDay(tx, req.ProjectFlockKandangId)
|
||||||
@@ -220,7 +193,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
||||||
RecordDatetime: recordTime,
|
RecordDatetime: recordTime,
|
||||||
Day: &day,
|
Day: &day,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1, // TODO: replace with authenticated user
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Repository.CreateOne(ctx, &createdRecording, func(*gorm.DB) *gorm.DB { return tx }); err != nil {
|
if err := s.Repository.CreateOne(ctx, &createdRecording, func(*gorm.DB) *gorm.DB { return tx }); err != nil {
|
||||||
@@ -246,10 +219,6 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.consumeRecordingStocks(ctx, tx, mappedStocks); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mappedDepletions := recordingutil.MapDepletions(createdRecording.Id, req.Depletions)
|
mappedDepletions := recordingutil.MapDepletions(createdRecording.Id, req.Depletions)
|
||||||
if err := s.Repository.CreateDepletions(tx, mappedDepletions); err != nil {
|
if err := s.Repository.CreateDepletions(tx, mappedDepletions); err != nil {
|
||||||
s.Log.Errorf("Failed to persist depletions: %+v", err)
|
s.Log.Errorf("Failed to persist depletions: %+v", err)
|
||||||
@@ -262,7 +231,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, mappedDepletions, nil, nil, nil, mappedEggs)); err != nil {
|
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, mappedDepletions, nil, mappedStocks, nil, mappedEggs)); err != nil {
|
||||||
s.Log.Errorf("Failed to adjust product warehouses: %+v", err)
|
s.Log.Errorf("Failed to adjust product warehouses: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -375,10 +344,6 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.releaseRecordingStocks(ctx, tx, existingStocks); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.Repository.DeleteStocks(tx, recordingEntity.Id); err != nil {
|
if err := s.Repository.DeleteStocks(tx, recordingEntity.Id); err != nil {
|
||||||
s.Log.Errorf("Failed to clear stocks: %+v", err)
|
s.Log.Errorf("Failed to clear stocks: %+v", err)
|
||||||
return err
|
return err
|
||||||
@@ -390,7 +355,8 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.consumeRecordingStocks(ctx, tx, mappedStocks); err != nil {
|
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(nil, nil, existingStocks, mappedStocks, nil, nil)); err != nil {
|
||||||
|
s.Log.Errorf("Failed to adjust product warehouses for stocks: %+v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -456,7 +422,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
action := entity.ApprovalActionUpdated
|
action := entity.ApprovalActionUpdated
|
||||||
actorID := recordingEntity.CreatedBy
|
actorID := recordingEntity.CreatedBy
|
||||||
if actorID == 0 {
|
if actorID == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Actor Id tidak valid untuk approval")
|
actorID = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
var step approvalutils.ApprovalStep
|
var step approvalutils.ApprovalStep
|
||||||
@@ -647,10 +613,7 @@ func (s recordingService) Approval(c *fiber.Ctx, req *validation.Approve) ([]ent
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := c.Context()
|
ctx := c.Context()
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
actorID := uint(1) // TODO: replace with authenticated user once auth is integrated
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
repoTx := s.Repository.WithTx(tx)
|
repoTx := s.Repository.WithTx(tx)
|
||||||
@@ -722,11 +685,7 @@ func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.releaseRecordingStocks(ctx, tx, oldStocks); err != nil {
|
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(oldDepletions, nil, oldStocks, nil, oldEggs, nil)); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.adjustProductWarehouseQuantities(ctx, tx, buildWarehouseDeltas(oldDepletions, nil, nil, nil, oldEggs, nil)); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -781,77 +740,6 @@ func (s *recordingService) ensureProductWarehousesExist(c *fiber.Ctx, stocks []v
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) consumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error {
|
|
||||||
if len(stocks) == 0 || s.FifoSvc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, stock := range stocks {
|
|
||||||
if stock.Id == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var desired float64
|
|
||||||
if stock.UsageQty != nil {
|
|
||||||
desired = *stock.UsageQty
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := s.FifoSvc.Consume(ctx, commonSvc.StockConsumeRequest{
|
|
||||||
UsableKey: recordingStockUsableKey,
|
|
||||||
UsableID: stock.Id,
|
|
||||||
ProductWarehouseID: stock.ProductWarehouseId,
|
|
||||||
Quantity: desired,
|
|
||||||
AllowPending: true,
|
|
||||||
Tx: tx,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
s.Log.Errorf("Failed to consume FIFO stock for recording stock %d: %+v", stock.Id, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.Repository.UpdateStockUsage(tx, stock.Id, result.UsageQuantity, result.PendingQuantity); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *recordingService) ConsumeRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error {
|
|
||||||
return s.consumeRecordingStocks(ctx, tx, stocks)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *recordingService) releaseRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error {
|
|
||||||
if len(stocks) == 0 || s.FifoSvc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, stock := range stocks {
|
|
||||||
if stock.Id == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
|
||||||
UsableKey: recordingStockUsableKey,
|
|
||||||
UsableID: stock.Id,
|
|
||||||
Tx: tx,
|
|
||||||
}); err != nil {
|
|
||||||
s.Log.Errorf("Failed to release FIFO stock for recording stock %d: %+v", stock.Id, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.Repository.UpdateStockUsage(tx, stock.Id, 0, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *recordingService) ReleaseRecordingStocks(ctx context.Context, tx *gorm.DB, stocks []entity.RecordingStock) error {
|
|
||||||
return s.releaseRecordingStocks(ctx, tx, stocks)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildWarehouseDeltas(
|
func buildWarehouseDeltas(
|
||||||
oldDepletions, newDepletions []entity.RecordingDepletion,
|
oldDepletions, newDepletions []entity.RecordingDepletion,
|
||||||
oldStocks, newStocks []entity.RecordingStock,
|
oldStocks, newStocks []entity.RecordingStock,
|
||||||
@@ -864,6 +752,12 @@ func buildWarehouseDeltas(
|
|||||||
for _, item := range newDepletions {
|
for _, item := range newDepletions {
|
||||||
accumulateWarehouseDelta(deltas, item.ProductWarehouseId, item.Qty)
|
accumulateWarehouseDelta(deltas, item.ProductWarehouseId, item.Qty)
|
||||||
}
|
}
|
||||||
|
for _, item := range oldStocks {
|
||||||
|
accumulateWarehouseDelta(deltas, item.ProductWarehouseId, usageQtyValue(item.UsageQty))
|
||||||
|
}
|
||||||
|
for _, item := range newStocks {
|
||||||
|
accumulateWarehouseDelta(deltas, item.ProductWarehouseId, -usageQtyValue(item.UsageQty))
|
||||||
|
}
|
||||||
for _, item := range oldEggs {
|
for _, item := range oldEggs {
|
||||||
accumulateWarehouseDelta(deltas, item.ProductWarehouseId, -float64(item.Qty))
|
accumulateWarehouseDelta(deltas, item.ProductWarehouseId, -float64(item.Qty))
|
||||||
}
|
}
|
||||||
@@ -873,6 +767,13 @@ func buildWarehouseDeltas(
|
|||||||
return deltas
|
return deltas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func usageQtyValue(val *float64) float64 {
|
||||||
|
if val == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *val
|
||||||
|
}
|
||||||
|
|
||||||
func accumulateWarehouseDelta(deltas map[uint]float64, id uint, value float64) {
|
func accumulateWarehouseDelta(deltas map[uint]float64, id uint, value float64) {
|
||||||
if id == 0 || value == 0 {
|
if id == 0 || value == 0 {
|
||||||
return
|
return
|
||||||
@@ -1050,7 +951,7 @@ func (s *recordingService) createRecordingApproval(
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, "Recording tidak valid untuk approval")
|
return fiber.NewError(fiber.StatusBadRequest, "Recording tidak valid untuk approval")
|
||||||
}
|
}
|
||||||
if actorID == 0 {
|
if actorID == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Actor Id tidak valid untuk approval")
|
actorID = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
var svc commonSvc.ApprovalService
|
var svc commonSvc.ApprovalService
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package transfer_layings
|
package transfer_layings
|
||||||
|
|
||||||
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/transfer_layings/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/controllers"
|
||||||
transferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services"
|
transferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,7 +13,7 @@ func TransferLayingRoutes(v1 fiber.Router, u user.UserService, s transferLaying.
|
|||||||
ctrl := controller.NewTransferLayingController(s)
|
ctrl := controller.NewTransferLayingController(s)
|
||||||
|
|
||||||
route := v1.Group("/transfer_layings")
|
route := v1.Group("/transfer_layings")
|
||||||
route.Use(m.Auth(u))
|
|
||||||
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
||||||
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||||
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ 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"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
rInventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
rInventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||||
ProjectFlockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
ProjectFlockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
@@ -155,11 +154,6 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := s.ProjectFlockRepo.GetByID(c.Context(), req.SourceProjectFlockId, nil); err != nil {
|
if _, err := s.ProjectFlockRepo.GetByID(c.Context(), req.SourceProjectFlockId, nil); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Source Project Flock not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Source Project Flock not found")
|
||||||
@@ -265,7 +259,7 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
ToProjectFlockId: req.TargetProjectFlockId,
|
ToProjectFlockId: req.TargetProjectFlockId,
|
||||||
TransferDate: transferDate,
|
TransferDate: transferDate,
|
||||||
PendingUsageQty: &totalSourceQty,
|
PendingUsageQty: &totalSourceQty,
|
||||||
CreatedBy: actorID,
|
CreatedBy: 1, //todo : harus diambil dari auth
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -598,11 +592,7 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
actorID := uint(1) // TODO: change from auth context
|
||||||
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):
|
||||||
@@ -623,7 +613,7 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
step = utils.TransferToLayingStepDisetujui
|
step = utils.TransferToLayingStepDisetujui
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|
||||||
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
|
sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction)
|
||||||
|
|||||||
@@ -23,19 +23,21 @@ func NewPurchaseController(s service.PurchaseService) *PurchaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error {
|
func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error {
|
||||||
query := &validation.Query{
|
query := &validation.PurchaseQuery{
|
||||||
Page: c.QueryInt("page", 1),
|
Page: c.QueryInt("page", 1),
|
||||||
Limit: c.QueryInt("limit", 10),
|
Limit: c.QueryInt("limit", 10),
|
||||||
|
Search: strings.TrimSpace(c.Query("search")),
|
||||||
|
PrNumber: strings.TrimSpace(c.Query("pr_number")),
|
||||||
CreatedFrom: strings.TrimSpace(c.Query("created_from")),
|
CreatedFrom: strings.TrimSpace(c.Query("created_from")),
|
||||||
CreatedTo: strings.TrimSpace(c.Query("created_to")),
|
CreatedTo: strings.TrimSpace(c.Query("created_to")),
|
||||||
SupplierID: uint(c.QueryInt("supplier_id", 0)),
|
|
||||||
AreaID: uint(c.QueryInt("area_id", 0)),
|
|
||||||
LocationID: uint(c.QueryInt("location_id", 0)),
|
|
||||||
ProductCategoryID: uint(c.QueryInt("product_category_id", 0)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if query.Page < 1 || query.Limit < 1 {
|
if supplierID := c.QueryInt("supplier_id", 0); supplierID > 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
query.SupplierID = uint(supplierID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status := strings.TrimSpace(c.Query("status")); status != "" {
|
||||||
|
query.Status = strings.ToUpper(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
results, total, err := ctrl.service.GetAll(c, query)
|
results, total, err := ctrl.service.GetAll(c, query)
|
||||||
@@ -43,15 +45,24 @@ func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
limit := query.Limit
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
page := query.Page
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).
|
return c.Status(fiber.StatusOK).
|
||||||
JSON(response.SuccessWithPaginate[dto.PurchaseListDTO]{
|
JSON(response.SuccessWithPaginate[dto.PurchaseListItemDTO]{
|
||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Purchase fetched successfully",
|
Message: "Purchase fetched successfully",
|
||||||
Meta: response.Meta{
|
Meta: response.Meta{
|
||||||
Page: query.Page,
|
Page: page,
|
||||||
Limit: query.Limit,
|
Limit: limit,
|
||||||
TotalPages: int64(math.Ceil(float64(total) / float64(query.Limit))),
|
TotalPages: int64(math.Ceil(float64(total) / float64(limit))),
|
||||||
TotalResults: total,
|
TotalResults: total,
|
||||||
},
|
},
|
||||||
Data: dto.ToPurchaseListDTOs(results),
|
Data: dto.ToPurchaseListDTOs(results),
|
||||||
@@ -60,13 +71,12 @@ func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
func (ctrl *PurchaseController) GetOne(c *fiber.Ctx) error {
|
func (ctrl *PurchaseController) GetOne(c *fiber.Ctx) error {
|
||||||
param := c.Params("id")
|
param := c.Params("id")
|
||||||
|
id, err := strconv.ParseUint(param, 10, 64)
|
||||||
id, err := strconv.Atoi(param)
|
|
||||||
if err != nil || id == 0 {
|
if err != nil || id == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := ctrl.service.GetOne(c, uint(id))
|
result, err := ctrl.service.GetOne(c, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -103,7 +113,7 @@ func (ctrl *PurchaseController) CreateOne(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
func (ctrl *PurchaseController) ApproveStaffPurchase(c *fiber.Ctx) error {
|
func (ctrl *PurchaseController) ApproveStaffPurchase(c *fiber.Ctx) error {
|
||||||
param := c.Params("id")
|
param := c.Params("id")
|
||||||
id, err := strconv.Atoi(param)
|
id, err := strconv.ParseUint(param, 10, 64)
|
||||||
if err != nil || id == 0 {
|
if err != nil || id == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
|
||||||
}
|
}
|
||||||
@@ -113,7 +123,7 @@ func (ctrl *PurchaseController) ApproveStaffPurchase(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid request body: %v", err))
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid request body: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := ctrl.service.ApproveStaffPurchase(c, uint(id), req)
|
result, err := ctrl.service.ApproveStaffPurchase(c, id, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -127,9 +137,10 @@ func (ctrl *PurchaseController) ApproveStaffPurchase(c *fiber.Ctx) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (ctrl *PurchaseController) ApproveManagerPurchase(c *fiber.Ctx) error {
|
func (ctrl *PurchaseController) ApproveManagerPurchase(c *fiber.Ctx) error {
|
||||||
param := c.Params("id")
|
param := c.Params("id")
|
||||||
id, err := strconv.Atoi(param)
|
id, err := strconv.ParseUint(param, 10, 64)
|
||||||
if err != nil || id == 0 {
|
if err != nil || id == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
|
||||||
}
|
}
|
||||||
@@ -139,7 +150,7 @@ func (ctrl *PurchaseController) ApproveManagerPurchase(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := ctrl.service.ApproveManagerPurchase(c, uint(id), req)
|
result, err := ctrl.service.ApproveManagerPurchase(c, id, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -155,7 +166,7 @@ func (ctrl *PurchaseController) ApproveManagerPurchase(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
func (ctrl *PurchaseController) ReceiveProducts(c *fiber.Ctx) error {
|
func (ctrl *PurchaseController) ReceiveProducts(c *fiber.Ctx) error {
|
||||||
param := c.Params("id")
|
param := c.Params("id")
|
||||||
id, err := strconv.Atoi(param)
|
id, err := strconv.ParseUint(param, 10, 64)
|
||||||
if err != nil || id == 0 {
|
if err != nil || id == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
|
||||||
}
|
}
|
||||||
@@ -165,7 +176,7 @@ func (ctrl *PurchaseController) ReceiveProducts(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := ctrl.service.ReceiveProducts(c, uint(id), req)
|
result, err := ctrl.service.ReceiveProducts(c, id, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -181,7 +192,7 @@ func (ctrl *PurchaseController) ReceiveProducts(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error {
|
func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error {
|
||||||
param := c.Params("id")
|
param := c.Params("id")
|
||||||
id, err := strconv.Atoi(param)
|
id, err := strconv.ParseUint(param, 10, 64)
|
||||||
if err != nil || id == 0 {
|
if err != nil || id == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
|
||||||
}
|
}
|
||||||
@@ -191,7 +202,7 @@ func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := ctrl.service.DeleteItems(c, uint(id), req)
|
result, err := ctrl.service.DeleteItems(c, id, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -207,12 +218,12 @@ func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
func (ctrl *PurchaseController) DeletePurchase(c *fiber.Ctx) error {
|
func (ctrl *PurchaseController) DeletePurchase(c *fiber.Ctx) error {
|
||||||
param := c.Params("id")
|
param := c.Params("id")
|
||||||
id, err := strconv.Atoi(param)
|
id, err := strconv.ParseUint(param, 10, 64)
|
||||||
if err != nil || id == 0 {
|
if err != nil || id == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctrl.service.DeletePurchase(c, uint(id)); err != nil {
|
if err := ctrl.service.DeletePurchase(c, id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,51 +10,46 @@ import (
|
|||||||
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto"
|
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto"
|
||||||
supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
|
supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
|
||||||
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
||||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PurchaseRelationDTO struct {
|
type PurchaseListItemDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
PrNumber string `json:"pr_number"`
|
PrNumber string `json:"pr_number"`
|
||||||
PoNumber *string `json:"po_number"`
|
PoNumber *string `json:"po_number"`
|
||||||
PoDate *time.Time `json:"po_date"`
|
|
||||||
Notes *string `json:"notes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type PurchaseListDTO struct {
|
|
||||||
PurchaseRelationDTO
|
|
||||||
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
|
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
|
||||||
CreditTerm *int `json:"credit_term"`
|
CreditTerm *int `json:"credit_term"`
|
||||||
DueDate *time.Time `json:"due_date"`
|
DueDate *time.Time `json:"due_date"`
|
||||||
|
PoDate *time.Time `json:"po_date"`
|
||||||
GrandTotal float64 `json:"grand_total"`
|
GrandTotal float64 `json:"grand_total"`
|
||||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
Notes *string `json:"notes"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
|
Approval *approvalDTO.ApprovalRelationDTO `json:"approval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PurchaseDetailDTO struct {
|
type PurchaseDetailDTO struct {
|
||||||
PurchaseRelationDTO
|
Id uint64 `json:"id"`
|
||||||
|
PrNumber string `json:"pr_number"`
|
||||||
|
PoNumber *string `json:"po_number"`
|
||||||
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
|
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
|
||||||
CreditTerm *int `json:"credit_term"`
|
CreditTerm *int `json:"credit_term"`
|
||||||
DueDate *time.Time `json:"due_date"`
|
DueDate *time.Time `json:"due_date"`
|
||||||
|
PoDate *time.Time `json:"po_date"`
|
||||||
GrandTotal float64 `json:"grand_total"`
|
GrandTotal float64 `json:"grand_total"`
|
||||||
|
Notes *string `json:"notes"`
|
||||||
Items []PurchaseItemDTO `json:"items"`
|
Items []PurchaseItemDTO `json:"items"`
|
||||||
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
|
Approval *approvalDTO.ApprovalRelationDTO `json:"approval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type PurchaseItemDTO struct {
|
type PurchaseItemDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
ProductID uint `json:"product_id"`
|
ProductID uint64 `json:"product_id"`
|
||||||
Product *productDTO.ProductRelationDTO `json:"product"`
|
Product *productDTO.ProductRelationDTO `json:"product"`
|
||||||
WarehouseID uint `json:"warehouse_id"`
|
WarehouseID uint64 `json:"warehouse_id"`
|
||||||
Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse"`
|
Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse"`
|
||||||
ProductWarehouseID *uint `json:"product_warehouse_id"`
|
ProductWarehouseID *uint64 `json:"product_warehouse_id"`
|
||||||
SubQty float64 `json:"sub_qty"`
|
SubQty float64 `json:"sub_qty"`
|
||||||
TotalQty float64 `json:"total_qty"`
|
TotalQty float64 `json:"total_qty"`
|
||||||
TotalUsed float64 `json:"total_used"`
|
TotalUsed float64 `json:"total_used"`
|
||||||
@@ -66,17 +61,6 @@ type PurchaseItemDTO struct {
|
|||||||
VehicleNumber *string `json:"vehicle_number"`
|
VehicleNumber *string `json:"vehicle_number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func ToPurchaseRelationDTO(p *entity.Purchase) PurchaseRelationDTO {
|
|
||||||
return PurchaseRelationDTO{
|
|
||||||
Id: p.Id,
|
|
||||||
PrNumber: p.PrNumber,
|
|
||||||
PoNumber: p.PoNumber,
|
|
||||||
PoDate: p.PoDate,
|
|
||||||
Notes: p.Notes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO {
|
func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO {
|
||||||
dto := PurchaseItemDTO{
|
dto := PurchaseItemDTO{
|
||||||
Id: item.Id,
|
Id: item.Id,
|
||||||
@@ -93,12 +77,10 @@ func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO {
|
|||||||
TravelDocumentPath: item.TravelNumberDocs,
|
TravelDocumentPath: item.TravelNumberDocs,
|
||||||
VehicleNumber: item.VehicleNumber,
|
VehicleNumber: item.VehicleNumber,
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.Product != nil && item.Product.Id != 0 {
|
if item.Product != nil && item.Product.Id != 0 {
|
||||||
summary := productDTO.ToProductRelationDTO(*item.Product)
|
summary := productDTO.ToProductRelationDTO(*item.Product)
|
||||||
dto.Product = &summary
|
dto.Product = &summary
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.Warehouse != nil && item.Warehouse.Id != 0 {
|
if item.Warehouse != nil && item.Warehouse.Id != 0 {
|
||||||
summary := warehouseDTO.ToWarehouseRelationDTO(*item.Warehouse)
|
summary := warehouseDTO.ToWarehouseRelationDTO(*item.Warehouse)
|
||||||
if item.Warehouse.Area.Id != 0 {
|
if item.Warehouse.Area.Id != 0 {
|
||||||
@@ -111,7 +93,6 @@ func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO {
|
|||||||
}
|
}
|
||||||
dto.Warehouse = &summary
|
dto.Warehouse = &summary
|
||||||
}
|
}
|
||||||
|
|
||||||
return dto
|
return dto
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,78 +104,70 @@ func ToPurchaseItemDTOs(items []entity.PurchaseItem) []PurchaseItemDTO {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO {
|
func ToPurchaseDetailDTO(p entity.Purchase) PurchaseDetailDTO {
|
||||||
var supplier *supplierDTO.SupplierRelationDTO
|
dto := PurchaseDetailDTO{
|
||||||
if p.Supplier.Id != 0 {
|
Id: p.Id,
|
||||||
mapped := supplierDTO.ToSupplierRelationDTO(p.Supplier)
|
PrNumber: p.PrNumber,
|
||||||
supplier = &mapped
|
PoNumber: p.PoNumber,
|
||||||
}
|
Supplier: mapSupplier(p.Supplier),
|
||||||
|
|
||||||
var createdUser *userDTO.UserRelationDTO
|
|
||||||
if p.CreatedUser.Id != 0 {
|
|
||||||
mapped := userDTO.ToUserRelationDTO(p.CreatedUser)
|
|
||||||
createdUser = &mapped
|
|
||||||
}
|
|
||||||
|
|
||||||
var latestApproval *approvalDTO.ApprovalRelationDTO
|
|
||||||
if p.LatestApproval != nil && p.LatestApproval.Id != 0 {
|
|
||||||
mapped := approvalDTO.ToApprovalDTO(*p.LatestApproval)
|
|
||||||
latestApproval = &mapped
|
|
||||||
}
|
|
||||||
|
|
||||||
return PurchaseListDTO{
|
|
||||||
PurchaseRelationDTO: ToPurchaseRelationDTO(&p),
|
|
||||||
Supplier: supplier,
|
|
||||||
CreditTerm: p.CreditTerm,
|
CreditTerm: p.CreditTerm,
|
||||||
DueDate: p.DueDate,
|
DueDate: p.DueDate,
|
||||||
|
PoDate: p.PoDate,
|
||||||
GrandTotal: p.GrandTotal,
|
GrandTotal: p.GrandTotal,
|
||||||
CreatedUser: createdUser,
|
Notes: p.Notes,
|
||||||
|
Items: ToPurchaseItemDTOs(p.Items),
|
||||||
CreatedAt: p.CreatedAt,
|
CreatedAt: p.CreatedAt,
|
||||||
UpdatedAt: p.UpdatedAt,
|
UpdatedAt: p.UpdatedAt,
|
||||||
LatestApproval: latestApproval,
|
|
||||||
}
|
}
|
||||||
|
if approval := toPurchaseApprovalDTO(p); approval != nil {
|
||||||
|
dto.Approval = approval
|
||||||
|
}
|
||||||
|
return dto
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToPurchaseListDTOs(items []entity.Purchase) []PurchaseListDTO {
|
func ToPurchaseListDTO(p entity.Purchase) PurchaseListItemDTO {
|
||||||
if len(items) == 0 {
|
dto := PurchaseListItemDTO{
|
||||||
return make([]PurchaseListDTO, 0)
|
Id: p.Id,
|
||||||
|
PrNumber: p.PrNumber,
|
||||||
|
PoNumber: p.PoNumber,
|
||||||
|
Supplier: mapSupplier(p.Supplier),
|
||||||
|
CreditTerm: p.CreditTerm,
|
||||||
|
DueDate: p.DueDate,
|
||||||
|
PoDate: p.PoDate,
|
||||||
|
GrandTotal: p.GrandTotal,
|
||||||
|
Notes: p.Notes,
|
||||||
|
CreatedAt: p.CreatedAt,
|
||||||
|
UpdatedAt: p.UpdatedAt,
|
||||||
}
|
}
|
||||||
result := make([]PurchaseListDTO, len(items))
|
if approval := toPurchaseApprovalDTO(p); approval != nil {
|
||||||
|
dto.Approval = approval
|
||||||
|
}
|
||||||
|
return dto
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapSupplier(s entity.Supplier) *supplierDTO.SupplierRelationDTO {
|
||||||
|
if s.Id == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
summary := supplierDTO.ToSupplierRelationDTO(s)
|
||||||
|
return &summary
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToPurchaseListDTOs(items []entity.Purchase) []PurchaseListItemDTO {
|
||||||
|
if len(items) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := make([]PurchaseListItemDTO, len(items))
|
||||||
for i, item := range items {
|
for i, item := range items {
|
||||||
result[i] = ToPurchaseListDTO(item)
|
result[i] = ToPurchaseListDTO(item)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToPurchaseDetailDTO(p entity.Purchase) PurchaseDetailDTO {
|
func toPurchaseApprovalDTO(p entity.Purchase) *approvalDTO.ApprovalRelationDTO {
|
||||||
var supplier *supplierDTO.SupplierRelationDTO
|
if p.LatestApproval == nil || p.LatestApproval.Id == 0 {
|
||||||
if p.Supplier.Id != 0 {
|
return nil
|
||||||
mapped := supplierDTO.ToSupplierRelationDTO(p.Supplier)
|
|
||||||
supplier = &mapped
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var createdUser *userDTO.UserRelationDTO
|
|
||||||
if p.CreatedUser.Id != 0 {
|
|
||||||
mapped := userDTO.ToUserRelationDTO(p.CreatedUser)
|
|
||||||
createdUser = &mapped
|
|
||||||
}
|
|
||||||
|
|
||||||
var latestApproval *approvalDTO.ApprovalRelationDTO
|
|
||||||
if p.LatestApproval != nil && p.LatestApproval.Id != 0 {
|
|
||||||
mapped := approvalDTO.ToApprovalDTO(*p.LatestApproval)
|
mapped := approvalDTO.ToApprovalDTO(*p.LatestApproval)
|
||||||
latestApproval = &mapped
|
return &mapped
|
||||||
}
|
|
||||||
|
|
||||||
return PurchaseDetailDTO{
|
|
||||||
PurchaseRelationDTO: ToPurchaseRelationDTO(&p),
|
|
||||||
Supplier: supplier,
|
|
||||||
CreditTerm: p.CreditTerm,
|
|
||||||
DueDate: p.DueDate,
|
|
||||||
GrandTotal: p.GrandTotal,
|
|
||||||
Items: ToPurchaseItemDTOs(p.Items),
|
|
||||||
CreatedUser: createdUser,
|
|
||||||
CreatedAt: p.CreatedAt,
|
|
||||||
UpdatedAt: p.UpdatedAt,
|
|
||||||
LatestApproval: latestApproval,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -43,6 +43,7 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
|||||||
warehouseRepo,
|
warehouseRepo,
|
||||||
supplierRepo,
|
supplierRepo,
|
||||||
productWarehouseRepo,
|
productWarehouseRepo,
|
||||||
|
approvalRepo,
|
||||||
approvalService,
|
approvalService,
|
||||||
expenseBridge,
|
expenseBridge,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,11 +18,14 @@ import (
|
|||||||
type PurchaseRepository interface {
|
type PurchaseRepository interface {
|
||||||
repository.BaseRepository[entity.Purchase]
|
repository.BaseRepository[entity.Purchase]
|
||||||
CreateWithItems(ctx context.Context, purchase *entity.Purchase, items []*entity.PurchaseItem) error
|
CreateWithItems(ctx context.Context, purchase *entity.Purchase, items []*entity.PurchaseItem) error
|
||||||
CreateItems(ctx context.Context, purchaseID uint, items []*entity.PurchaseItem) error
|
CreateItems(ctx context.Context, purchaseID uint64, items []*entity.PurchaseItem) error
|
||||||
UpdatePricing(ctx context.Context, purchaseID uint, updates []PurchasePricingUpdate, grandTotal float64) error
|
GetByIDWithRelations(ctx context.Context, id uint64) (*entity.Purchase, error)
|
||||||
UpdateReceivingDetails(ctx context.Context, purchaseID uint, updates []PurchaseReceivingUpdate) error
|
GetAllWithFilters(ctx context.Context, offset, limit int, filter *PurchaseListFilter) ([]entity.Purchase, int64, error)
|
||||||
DeleteItems(ctx context.Context, purchaseID uint, itemIDs []uint) error
|
UpdatePricing(ctx context.Context, purchaseID uint64, updates []PurchasePricingUpdate, grandTotal float64) error
|
||||||
UpdateGrandTotal(ctx context.Context, purchaseID uint, grandTotal float64) error
|
UpdateReceivingDetails(ctx context.Context, purchaseID uint64, updates []PurchaseReceivingUpdate) error
|
||||||
|
DeleteItems(ctx context.Context, purchaseID uint64, itemIDs []uint64) error
|
||||||
|
WithListRelations() func(*gorm.DB) *gorm.DB
|
||||||
|
UpdateGrandTotal(ctx context.Context, purchaseID uint64, grandTotal float64) error
|
||||||
NextPrNumber(ctx context.Context, tx *gorm.DB) (string, error)
|
NextPrNumber(ctx context.Context, tx *gorm.DB) (string, error)
|
||||||
NextPoNumber(ctx context.Context, tx *gorm.DB) (string, error)
|
NextPoNumber(ctx context.Context, tx *gorm.DB) (string, error)
|
||||||
}
|
}
|
||||||
@@ -37,10 +40,19 @@ func NewPurchaseRepository(db *gorm.DB) PurchaseRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PurchaseListFilter struct {
|
||||||
|
SupplierID uint
|
||||||
|
Search string
|
||||||
|
PrNumber string
|
||||||
|
CreatedFrom *time.Time
|
||||||
|
CreatedTo *time.Time
|
||||||
|
Status *entity.ApprovalAction
|
||||||
|
CompletedOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
func (r *PurchaseRepositoryImpl) CreateWithItems(ctx context.Context, purchase *entity.Purchase, items []*entity.PurchaseItem) error {
|
func (r *PurchaseRepositoryImpl) CreateWithItems(ctx context.Context, purchase *entity.Purchase, items []*entity.PurchaseItem) error {
|
||||||
db := r.DB().WithContext(ctx)
|
db := r.DB().WithContext(ctx)
|
||||||
|
|
||||||
//ambil dari base repository
|
|
||||||
if err := db.Create(purchase).Error; err != nil {
|
if err := db.Create(purchase).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -59,7 +71,7 @@ func (r *PurchaseRepositoryImpl) CreateWithItems(ctx context.Context, purchase *
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *PurchaseRepositoryImpl) CreateItems(ctx context.Context, purchaseID uint, items []*entity.PurchaseItem) error {
|
func (r *PurchaseRepositoryImpl) CreateItems(ctx context.Context, purchaseID uint64, items []*entity.PurchaseItem) error {
|
||||||
if len(items) == 0 {
|
if len(items) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -74,9 +86,52 @@ func (r *PurchaseRepositoryImpl) CreateItems(ctx context.Context, purchaseID uin
|
|||||||
return r.DB().WithContext(ctx).Create(&items).Error
|
return r.DB().WithContext(ctx).Create(&items).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseRepositoryImpl) GetByIDWithRelations(ctx context.Context, id uint64) (*entity.Purchase, error) {
|
||||||
|
var purchase entity.Purchase
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Scopes(r.withDetailRelations).
|
||||||
|
First(&purchase, id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &purchase, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, filter *PurchaseListFilter) ([]entity.Purchase, int64, error) {
|
||||||
|
return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB {
|
||||||
|
db = r.withListRelations(db)
|
||||||
|
return r.applyListFilters(db, filter)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseRepositoryImpl) WithListRelations() func(*gorm.DB) *gorm.DB {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return r.withListRelations(db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseRepositoryImpl) withDetailRelations(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.
|
||||||
|
Preload("Supplier").
|
||||||
|
Preload("Items", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Order("id ASC")
|
||||||
|
}).
|
||||||
|
Preload("Items.Product").
|
||||||
|
Preload("Items.Warehouse").
|
||||||
|
Preload("Items.Warehouse.Area").
|
||||||
|
Preload("Items.Warehouse.Location").
|
||||||
|
Preload("Items.ProductWarehouse")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseRepositoryImpl) WithDetailRelations() func(*gorm.DB) *gorm.DB {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
return r.withDetailRelations(db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type PurchasePricingUpdate struct {
|
type PurchasePricingUpdate struct {
|
||||||
ItemID uint
|
ItemID uint64
|
||||||
ProductID *uint
|
ProductID *uint64
|
||||||
Price float64
|
Price float64
|
||||||
TotalPrice float64
|
TotalPrice float64
|
||||||
Quantity *float64
|
Quantity *float64
|
||||||
@@ -84,7 +139,7 @@ type PurchasePricingUpdate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PurchaseReceivingUpdate struct {
|
type PurchaseReceivingUpdate struct {
|
||||||
ItemID uint
|
ItemID uint64
|
||||||
ReceivedDate *time.Time
|
ReceivedDate *time.Time
|
||||||
TravelNumber *string
|
TravelNumber *string
|
||||||
TravelDocumentPath *string
|
TravelDocumentPath *string
|
||||||
@@ -97,7 +152,7 @@ type PurchaseReceivingUpdate struct {
|
|||||||
|
|
||||||
func (r *PurchaseRepositoryImpl) UpdatePricing(
|
func (r *PurchaseRepositoryImpl) UpdatePricing(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
purchaseID uint,
|
purchaseID uint64,
|
||||||
updates []PurchasePricingUpdate,
|
updates []PurchasePricingUpdate,
|
||||||
grandTotal float64,
|
grandTotal float64,
|
||||||
) error {
|
) error {
|
||||||
@@ -137,6 +192,7 @@ func (r *PurchaseRepositoryImpl) UpdatePricing(
|
|||||||
Where("id = ?", purchaseID).
|
Where("id = ?", purchaseID).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"grand_total": grandTotal,
|
"grand_total": grandTotal,
|
||||||
|
"updated_at": gorm.Expr("NOW()"),
|
||||||
}).Error; err != nil {
|
}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -146,7 +202,7 @@ func (r *PurchaseRepositoryImpl) UpdatePricing(
|
|||||||
|
|
||||||
func (r *PurchaseRepositoryImpl) UpdateReceivingDetails(
|
func (r *PurchaseRepositoryImpl) UpdateReceivingDetails(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
purchaseID uint,
|
purchaseID uint64,
|
||||||
updates []PurchaseReceivingUpdate,
|
updates []PurchaseReceivingUpdate,
|
||||||
) error {
|
) error {
|
||||||
if len(updates) == 0 {
|
if len(updates) == 0 {
|
||||||
@@ -203,7 +259,7 @@ func (r *PurchaseRepositoryImpl) UpdateReceivingDetails(
|
|||||||
|
|
||||||
func (r *PurchaseRepositoryImpl) UpdateGrandTotal(
|
func (r *PurchaseRepositoryImpl) UpdateGrandTotal(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
purchaseID uint,
|
purchaseID uint64,
|
||||||
grandTotal float64,
|
grandTotal float64,
|
||||||
) error {
|
) error {
|
||||||
return r.DB().WithContext(ctx).
|
return r.DB().WithContext(ctx).
|
||||||
@@ -215,7 +271,7 @@ func (r *PurchaseRepositoryImpl) UpdateGrandTotal(
|
|||||||
}).Error
|
}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *PurchaseRepositoryImpl) DeleteItems(ctx context.Context, purchaseID uint, itemIDs []uint) error {
|
func (r *PurchaseRepositoryImpl) DeleteItems(ctx context.Context, purchaseID uint64, itemIDs []uint64) error {
|
||||||
if len(itemIDs) == 0 {
|
if len(itemIDs) == 0 {
|
||||||
return errors.New("itemIDs cannot be empty")
|
return errors.New("itemIDs cannot be empty")
|
||||||
}
|
}
|
||||||
@@ -305,3 +361,63 @@ func parseNumericSuffix(value, prefix string) (int, bool) {
|
|||||||
}
|
}
|
||||||
return number, true
|
return number, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseRepositoryImpl) withListRelations(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Preload("Supplier")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseRepositoryImpl) applyListFilters(db *gorm.DB, filter *PurchaseListFilter) *gorm.DB {
|
||||||
|
if filter == nil {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.SupplierID > 0 {
|
||||||
|
db = db.Where("purchases.supplier_id = ?", filter.SupplierID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if search := strings.ToLower(strings.TrimSpace(filter.Search)); search != "" {
|
||||||
|
like := "%" + search + "%"
|
||||||
|
db = db.Where("(LOWER(purchases.pr_number) LIKE ? OR LOWER(COALESCE(purchases.notes, '')) LIKE ?)", like, like)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pr := strings.TrimSpace(filter.PrNumber); pr != "" {
|
||||||
|
db = db.Where("purchases.pr_number ILIKE ?", "%"+pr+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.CreatedFrom != nil {
|
||||||
|
db = db.Where("purchases.created_at >= ?", *filter.CreatedFrom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.CreatedTo != nil {
|
||||||
|
db = db.Where("purchases.created_at < ?", *filter.CreatedTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.CompletedOnly {
|
||||||
|
step := uint16(utils.PurchaseStepCompleted)
|
||||||
|
db = r.applyLatestApprovalFilter(db, entity.ApprovalActionApproved, &step)
|
||||||
|
} else if filter.Status != nil {
|
||||||
|
db = r.applyLatestApprovalFilter(db, *filter.Status, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Order("purchases.created_at DESC").Order("purchases.id DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseRepositoryImpl) applyLatestApprovalFilter(db *gorm.DB, action entity.ApprovalAction, minStep *uint16) *gorm.DB {
|
||||||
|
latestSub := r.DB().
|
||||||
|
Model(&entity.Approval{}).
|
||||||
|
Select("approvable_id, MAX(action_at) AS latest_action_at").
|
||||||
|
Where("approvable_type = ?", utils.ApprovalWorkflowPurchase.String()).
|
||||||
|
Group("approvable_id")
|
||||||
|
|
||||||
|
db = db.
|
||||||
|
Joins("LEFT JOIN (?) AS latest_purchase_approvals ON latest_purchase_approvals.approvable_id = purchases.id", latestSub).
|
||||||
|
Joins(
|
||||||
|
"LEFT JOIN approvals ON approvals.approvable_id = purchases.id AND approvals.approvable_type = ? AND approvals.action_at = latest_purchase_approvals.latest_action_at",
|
||||||
|
utils.ApprovalWorkflowPurchase.String(),
|
||||||
|
).
|
||||||
|
Where("approvals.action = ?", string(action))
|
||||||
|
if minStep != nil {
|
||||||
|
db = db.Where("approvals.step_number >= ?", *minStep)
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package purchases
|
|||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
middleware "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/controllers"
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/controllers"
|
||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/services"
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/services"
|
||||||
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -13,7 +13,7 @@ func Routes(router fiber.Router, purchaseService service.PurchaseService, userSe
|
|||||||
ctrl := controller.NewPurchaseController(purchaseService)
|
ctrl := controller.NewPurchaseController(purchaseService)
|
||||||
|
|
||||||
route := router.Group("/purchases")
|
route := router.Group("/purchases")
|
||||||
route.Use(m.Auth(userService))
|
route.Use(middleware.Auth(userService))
|
||||||
|
|
||||||
route.Get("/", ctrl.GetAll)
|
route.Get("/", ctrl.GetAll)
|
||||||
route.Get("/:id", ctrl.GetOne)
|
route.Get("/:id", ctrl.GetOne)
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ import (
|
|||||||
|
|
||||||
// PurchaseExpenseBridge defines hooks that allow purchase flows to stay in sync with expense data once it exists.
|
// PurchaseExpenseBridge defines hooks that allow purchase flows to stay in sync with expense data once it exists.
|
||||||
type PurchaseExpenseBridge interface {
|
type PurchaseExpenseBridge interface {
|
||||||
OnItemsCreated(ctx context.Context, purchaseID uint, items []entity.PurchaseItem) error
|
OnItemsCreated(ctx context.Context, purchaseID uint64, items []entity.PurchaseItem) error
|
||||||
OnItemsDeleted(ctx context.Context, purchaseID uint, itemIDs []uint) error
|
OnItemsDeleted(ctx context.Context, purchaseID uint64, itemIDs []uint64) error
|
||||||
OnItemsReceived(ctx context.Context, purchaseID uint, updates []ExpenseReceivingPayload) error
|
OnItemsReceived(ctx context.Context, purchaseID uint64, updates []ExpenseReceivingPayload) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpenseReceivingPayload captures the minimum data expense integration will need once available.
|
// ExpenseReceivingPayload captures the minimum data expense integration will need once available.
|
||||||
type ExpenseReceivingPayload struct {
|
type ExpenseReceivingPayload struct {
|
||||||
PurchaseItemID uint
|
PurchaseItemID uint64
|
||||||
ProductID uint
|
ProductID uint64
|
||||||
WarehouseID uint
|
WarehouseID uint64
|
||||||
ReceivedQty float64
|
ReceivedQty float64
|
||||||
ReceivedDate *time.Time
|
ReceivedDate *time.Time
|
||||||
}
|
}
|
||||||
@@ -30,14 +30,14 @@ func NewNoopPurchaseExpenseBridge() PurchaseExpenseBridge {
|
|||||||
return &noopPurchaseExpenseBridge{}
|
return &noopPurchaseExpenseBridge{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *noopPurchaseExpenseBridge) OnItemsCreated(_ context.Context, _ uint, _ []entity.PurchaseItem) error {
|
func (n *noopPurchaseExpenseBridge) OnItemsCreated(_ context.Context, _ uint64, _ []entity.PurchaseItem) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *noopPurchaseExpenseBridge) OnItemsDeleted(_ context.Context, _ uint, _ []uint) error {
|
func (n *noopPurchaseExpenseBridge) OnItemsDeleted(_ context.Context, _ uint64, _ []uint64) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *noopPurchaseExpenseBridge) OnItemsReceived(_ context.Context, _ uint, _ []ExpenseReceivingPayload) error {
|
func (n *noopPurchaseExpenseBridge) OnItemsReceived(_ context.Context, _ uint64, _ []ExpenseReceivingPayload) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -14,28 +14,26 @@ type CreatePurchaseRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type StaffPurchaseApprovalItem struct {
|
type StaffPurchaseApprovalItem struct {
|
||||||
PurchaseItemID uint `json:"purchase_item_id,omitempty" validate:"omitempty,gt=0"`
|
PurchaseItemID uint64 `json:"purchase_item_id,omitempty" validate:"omitempty,gt=0"`
|
||||||
// For new items (no purchase_item_id), product_id is required.
|
// For new items (no purchase_item_id), product_id is required.
|
||||||
ProductID uint `json:"product_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
|
ProductID uint64 `json:"product_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
|
||||||
WarehouseID uint `json:"warehouse_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
|
WarehouseID uint64 `json:"warehouse_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
|
||||||
Qty *float64 `json:"qty,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
|
Qty *float64 `json:"qty,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
|
||||||
Price float64 `json:"price" validate:"required,gt=0"`
|
Price float64 `json:"price" validate:"required,gt=0"`
|
||||||
TotalPrice float64 `json:"total_price" validate:"required,gt=0"`
|
TotalPrice float64 `json:"total_price" validate:"required,gt=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApproveStaffPurchaseRequest struct {
|
type ApproveStaffPurchaseRequest struct {
|
||||||
Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"`
|
Items []StaffPurchaseApprovalItem `json:"items" validate:"required,min=1,dive"`
|
||||||
Items []StaffPurchaseApprovalItem `json:"items,omitempty" validate:"omitempty,min=1,dive"`
|
|
||||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApproveManagerPurchaseRequest struct {
|
type ApproveManagerPurchaseRequest struct {
|
||||||
Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"`
|
|
||||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReceivePurchaseItemRequest struct {
|
type ReceivePurchaseItemRequest struct {
|
||||||
PurchaseItemID uint `json:"purchase_item_id" validate:"required,gt=0"`
|
PurchaseItemID uint64 `json:"purchase_item_id" validate:"required,gt=0"`
|
||||||
WarehouseID *uint `json:"warehouse_id" validate:"omitempty,gt=0"`
|
WarehouseID *uint `json:"warehouse_id" validate:"omitempty,gt=0"`
|
||||||
ReceivedDate string `json:"received_date" validate:"required,datetime=2006-01-02"`
|
ReceivedDate string `json:"received_date" validate:"required,datetime=2006-01-02"`
|
||||||
TravelNumber *string `json:"travel_number" validate:"omitempty,max=100"`
|
TravelNumber *string `json:"travel_number" validate:"omitempty,max=100"`
|
||||||
@@ -45,23 +43,21 @@ type ReceivePurchaseItemRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ReceivePurchaseRequest struct {
|
type ReceivePurchaseRequest struct {
|
||||||
Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"`
|
Items []ReceivePurchaseItemRequest `json:"items" validate:"required,min=1,dive"`
|
||||||
Items []ReceivePurchaseItemRequest `json:"items,omitempty" validate:"omitempty,min=1,dive"`
|
|
||||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeletePurchaseItemsRequest struct {
|
type DeletePurchaseItemsRequest struct {
|
||||||
ItemIDs []uint `json:"item_ids" validate:"required,min=1,dive,gt=0"`
|
ItemIDs []uint64 `json:"item_ids" validate:"required,min=1,dive,gt=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type PurchaseQuery 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"`
|
||||||
SupplierID uint `query:"supplier_id" validate:"omitempty,gt=0"`
|
SupplierID uint `query:"supplier_id" validate:"omitempty,gt=0"`
|
||||||
AreaID uint `query:"area_id" validate:"omitempty,gt=0"`
|
|
||||||
LocationID uint `query:"location_id" validate:"omitempty,gt=0"`
|
|
||||||
ProductCategoryID uint `query:"product_category_id" validate:"omitempty,gt=0"`
|
|
||||||
Search string `query:"search" validate:"omitempty,max=100"`
|
Search string `query:"search" validate:"omitempty,max=100"`
|
||||||
|
PrNumber string `query:"pr_number" validate:"omitempty,max=50"`
|
||||||
CreatedFrom string `query:"created_from" validate:"omitempty,datetime=2006-01-02"`
|
CreatedFrom string `query:"created_from" validate:"omitempty,datetime=2006-01-02"`
|
||||||
CreatedTo string `query:"created_to" validate:"omitempty,datetime=2006-01-02"`
|
CreatedTo string `query:"created_to" validate:"omitempty,datetime=2006-01-02"`
|
||||||
|
Status string `query:"status" validate:"omitempty,oneof=CREATED UPDATED APPROVED REJECTED COMPLETED"`
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user