mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-25 15:55:44 +00:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f003c512d | |||
| 3ca95750a7 | |||
| 5dd2dbfa98 | |||
| 00bfd5ca45 | |||
| 3e8bcb3f64 | |||
| cfc4846170 | |||
| c8f96c6086 | |||
| 930fe25589 | |||
| 4137008b92 | |||
| 477f65554e | |||
| 6fcbcfd554 | |||
| 99c34c5793 | |||
| ecc9a51ea7 | |||
| 75ade0d8b3 | |||
| b6a60d5009 | |||
| 91ad7ad5e0 | |||
| 2e2aed67b8 | |||
| a801081a99 | |||
| b0dfa717d5 | |||
| 16d562e024 | |||
| 8881be2a22 | |||
| 3fc330d8f7 | |||
| af147f4f2b | |||
| 6768092e3b | |||
| 53b226f243 | |||
| cd752f19f4 | |||
| 5a73ad0164 | |||
| b8d1268dfa | |||
| da10861fd2 | |||
| 228aedc215 | |||
| b4b860b9d4 | |||
| 3080a6f8ef | |||
| b502751b4e | |||
| 4c7e5b0731 | |||
| 105b20c333 | |||
| f5b7fd60ad | |||
| ced27e23a0 | |||
| 242ccc9230 | |||
| bf8519df3f | |||
| a57ef82ebb | |||
| 320f5e65c6 | |||
| 28c81aac25 | |||
| 1dac74e25b | |||
| 9ca9dfc2be | |||
| 5c25c84f7f | |||
| aaf129622b | |||
| 09d503f5be | |||
| d528096d56 | |||
| cb1df12b7e | |||
| 60757237c0 | |||
| 7905bdb0d7 | |||
| 903b114315 | |||
| 2f5fab9f80 | |||
| 74ec25db5b | |||
| 0a0c3f869b | |||
| 762dfa9fb9 | |||
| 6b5d27ae8e | |||
| fd0943dfaf | |||
| b2ed58c734 | |||
| 3785d52925 | |||
| 4c279baad7 | |||
| 6e69e97d26 | |||
| ba12320d12 | |||
| d21aaead7b | |||
| 954cccd564 | |||
| 663d5129bb | |||
| d587a793fe | |||
| a587584156 | |||
| 4b69afe4fa | |||
| 5cfa97dd03 | |||
| 028d5f6f91 | |||
| 60fe553f63 | |||
| 1c99093ff8 |
@@ -1,17 +0,0 @@
|
|||||||
# .air.toml
|
|
||||||
root = "."
|
|
||||||
tmp_dir = "tmp"
|
|
||||||
|
|
||||||
[build]
|
|
||||||
# Build binary utama
|
|
||||||
cmd = "go build -o /lti-api/tmp/main ./cmd/api"
|
|
||||||
# Lokasi binary hasil build
|
|
||||||
bin = "/lti-api/tmp/main"
|
|
||||||
# Jalankan binary langsung dengan environment dev
|
|
||||||
full_bin = "APP_ENV=dev /lti-api/tmp/main"
|
|
||||||
# File yang dipantau oleh Air
|
|
||||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
|
||||||
exclude_dir = ["vendor", "tmp"]
|
|
||||||
|
|
||||||
[log]
|
|
||||||
time = true
|
|
||||||
+55
-53
@@ -1,69 +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"'
|
||||||
|
needs:
|
||||||
|
- job: build:stg-ec2
|
||||||
|
|
||||||
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
|
|
||||||
- mkdir -p ~/.ssh
|
- mkdir -p ~/.ssh
|
||||||
- echo "$SSH_PRIVATE_KEY" > ~/.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
|
||||||
- eval $(ssh-agent -s)
|
|
||||||
|
- head -n 1 ~/.ssh/id_rsa
|
||||||
|
- tail -n 1 ~/.ssh/id_rsa
|
||||||
|
|
||||||
|
- eval "$(ssh-agent -s)"
|
||||||
- ssh-add ~/.ssh/id_rsa
|
- ssh-add ~/.ssh/id_rsa
|
||||||
- ssh-keyscan -H "$SERVER_IP" >> ~/.ssh/known_hosts
|
- ssh-keyscan -H "$SERVER_IP" >> ~/.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"
|
||||||
cd /home/devops/docker/deployment/development/lti-api &&
|
"export CI_REGISTRY_USER='$CI_REGISTRY_USER';
|
||||||
git fetch origin development &&
|
export CI_REGISTRY_PASSWORD='$CI_REGISTRY_PASSWORD';
|
||||||
git reset --hard origin/development &&
|
export CI_REGISTRY='$CI_REGISTRY';
|
||||||
docker compose restart dev-api-lti || docker compose up -d dev-api-lti
|
set -e;
|
||||||
"; then
|
cd /home/ubuntu/docker/deployment/staging/stg-lti-api;
|
||||||
STATUS='success';
|
echo \"\$CI_REGISTRY_PASSWORD\" | docker login -u \"\$CI_REGISTRY_USER\" --password-stdin \"\$CI_REGISTRY\";
|
||||||
else
|
docker compose pull;
|
||||||
STATUS='failed';
|
docker compose up -d;
|
||||||
fi;
|
docker image prune -f"
|
||||||
|
|
||||||
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,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:
|
|
||||||
@@ -5,7 +5,6 @@ go 1.23
|
|||||||
require (
|
require (
|
||||||
github.com/MicahParks/keyfunc/v2 v2.1.0
|
github.com/MicahParks/keyfunc/v2 v2.1.0
|
||||||
github.com/bytedance/sonic v1.12.1
|
github.com/bytedance/sonic v1.12.1
|
||||||
github.com/glebarez/sqlite v1.11.0
|
|
||||||
github.com/go-playground/validator/v10 v10.27.0
|
github.com/go-playground/validator/v10 v10.27.0
|
||||||
github.com/gofiber/contrib/jwt v1.0.10
|
github.com/gofiber/contrib/jwt v1.0.10
|
||||||
github.com/gofiber/fiber/v2 v2.52.5
|
github.com/gofiber/fiber/v2 v2.52.5
|
||||||
@@ -26,10 +25,8 @@ require (
|
|||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
@@ -54,7 +51,6 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/philhofer/fwd v1.1.2 // indirect
|
github.com/philhofer/fwd v1.1.2 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
@@ -79,8 +75,4 @@ require (
|
|||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
modernc.org/libc v1.22.5 // indirect
|
|
||||||
modernc.org/mathutil v1.5.0 // indirect
|
|
||||||
modernc.org/memory v1.5.0 // indirect
|
|
||||||
modernc.org/sqlite v1.23.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,18 +27,12 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
|||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
|
||||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
|
||||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
|
||||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
@@ -56,8 +50,6 @@ github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17w
|
|||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
@@ -154,9 +146,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE=
|
github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE=
|
||||||
github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
@@ -317,12 +306,4 @@ gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
|||||||
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
|
||||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
|
||||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
|
||||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
|
||||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
|
||||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
|
||||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
|
||||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
DROP TABLE IF EXISTS marketing_delivery_products CASCADE;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS marketing_products CASCADE;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS marketings CASCADE;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
CREATE TABLE marketings (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
so_number VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
customer_id BIGINT NOT NULL,
|
||||||
|
so_docs VARCHAR(20),
|
||||||
|
so_date DATE NOT NULL,
|
||||||
|
sales_person_id BIGINT NOT NULL,
|
||||||
|
notes TEXT,
|
||||||
|
created_by BIGINT NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'customers') THEN
|
||||||
|
ALTER TABLE marketings
|
||||||
|
ADD CONSTRAINT fk_marketings_customer_id
|
||||||
|
FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE RESTRICT;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN
|
||||||
|
ALTER TABLE marketings
|
||||||
|
ADD CONSTRAINT fk_marketings_sales_person_id
|
||||||
|
FOREIGN KEY (sales_person_id) REFERENCES users(id) ON DELETE RESTRICT;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN
|
||||||
|
ALTER TABLE marketings
|
||||||
|
ADD CONSTRAINT fk_marketings_created_by
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE RESTRICT;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
CREATE INDEX idx_marketings_customer_id ON marketings (customer_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_marketings_sales_person_id ON marketings (sales_person_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_marketings_created_by ON marketings (created_by);
|
||||||
|
|
||||||
|
CREATE INDEX idx_marketings_so_date ON marketings (so_date);
|
||||||
|
|
||||||
|
CREATE INDEX idx_marketings_deleted_at ON marketings (deleted_at);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS marketing_products CASCADE;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
CREATE TABLE marketing_products (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
marketing_id BIGINT NOT NULL,
|
||||||
|
product_warehouse_id BIGINT NOT NULL,
|
||||||
|
qty NUMERIC(15, 3) NOT NULL,
|
||||||
|
unit_price NUMERIC(15, 3) NOT NULL,
|
||||||
|
avg_weight NUMERIC(15, 3) NOT NULL,
|
||||||
|
total_weight NUMERIC(15, 3) NOT NULL,
|
||||||
|
total_price NUMERIC(15, 3) NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'marketings') THEN
|
||||||
|
ALTER TABLE marketing_products
|
||||||
|
ADD CONSTRAINT fk_marketing_products_marketing_id
|
||||||
|
FOREIGN KEY (marketing_id) REFERENCES marketings(id) ON DELETE CASCADE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'product_warehouses') THEN
|
||||||
|
ALTER TABLE marketing_products
|
||||||
|
ADD CONSTRAINT fk_marketing_products_product_warehouse_id
|
||||||
|
FOREIGN KEY (product_warehouse_id) REFERENCES product_warehouses(id) ON DELETE RESTRICT;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
CREATE INDEX idx_marketing_products_marketing_id ON marketing_products (marketing_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_marketing_products_product_warehouse_id ON marketing_products (product_warehouse_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_marketing_products_deleted_at ON marketing_products (deleted_at);
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
DROP TABLE IF EXISTS marketing_delivery_products CASCADE;
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
CREATE TABLE marketing_delivery_products (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
marketing_product_id BIGINT UNIQUE NOT NULL,
|
||||||
|
qty NUMERIC(15, 3) NOT NULL,
|
||||||
|
unit_price NUMERIC(15, 3) NOT NULL,
|
||||||
|
total_weight NUMERIC(15, 3) NOT NULL,
|
||||||
|
avg_weight NUMERIC(15, 3) NOT NULL,
|
||||||
|
total_price NUMERIC(15, 3) NOT NULL,
|
||||||
|
delivery_date DATE,
|
||||||
|
vehicle_number VARCHAR(50),
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'marketing_products') THEN
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
ADD CONSTRAINT fk_marketing_delivery_products_marketing_product_id
|
||||||
|
FOREIGN KEY (marketing_product_id) REFERENCES marketing_products(id) ON DELETE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
CREATE INDEX idx_marketing_delivery_products_marketing_product_id ON marketing_delivery_products (marketing_product_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_marketing_delivery_products_delivery_date ON marketing_delivery_products (delivery_date);
|
||||||
|
|
||||||
|
CREATE INDEX idx_marketing_delivery_products_deleted_at ON marketing_delivery_products (deleted_at);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS expenses;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
CREATE TABLE expenses (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
reference_number VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
supplier_id BIGINT NULL,
|
||||||
|
category VARCHAR(50) NOT NULL CHECK (
|
||||||
|
category IN ('BOP', 'NON-BOP')
|
||||||
|
),
|
||||||
|
po_number VARCHAR(50) NULL,
|
||||||
|
document_path JSON,
|
||||||
|
realization_document_path JSON,
|
||||||
|
expense_date DATE NOT NULL,
|
||||||
|
realization_date DATE,
|
||||||
|
grand_total NUMERIC(15, 3) DEFAULT 0,
|
||||||
|
note TEXT,
|
||||||
|
created_by BIGINT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT now(),
|
||||||
|
deleted_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE expenses_ref_seq INCREMENT BY 1 START WITH 1;
|
||||||
|
|
||||||
|
-- Tambahkan Foreign Key ke suppliers
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'suppliers') THEN
|
||||||
|
ALTER TABLE expenses
|
||||||
|
ADD CONSTRAINT fk_expenses_supplier_id
|
||||||
|
FOREIGN KEY (supplier_id) REFERENCES suppliers(id);
|
||||||
|
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Tambahkan Foreign Key ke users (created_by)
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN
|
||||||
|
ALTER TABLE expenses
|
||||||
|
ADD CONSTRAINT fk_expenses_created_by
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users(id);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Index
|
||||||
|
CREATE INDEX idx_expenses_supplier_id ON expenses (supplier_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_expenses_expense_date ON expenses (expense_date);
|
||||||
|
|
||||||
|
CREATE INDEX idx_expenses_deleted_at ON expenses (deleted_at);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
DROP TABLE IF EXISTS expense_nonstocks;
|
||||||
|
|
||||||
|
DROP SEQUENCE expenses_ref_seq;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
CREATE TABLE expense_nonstocks (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
expense_id BIGINT NOT NULL,
|
||||||
|
project_flock_kandang_id BIGINT NULL,
|
||||||
|
kandang_id BIGINT NULL,
|
||||||
|
nonstock_id BIGINT,
|
||||||
|
qty NUMERIC(15, 3) NOT NULL,
|
||||||
|
unit_price NUMERIC(15, 3) NOT NULL,
|
||||||
|
total_price NUMERIC(15, 3) NOT NULL,
|
||||||
|
note TEXT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Tambahkan Foreign Key ke expenses
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'expenses') THEN
|
||||||
|
ALTER TABLE expense_nonstocks
|
||||||
|
ADD CONSTRAINT fk_expense_nonstocks_expense_id
|
||||||
|
FOREIGN KEY (expense_id) REFERENCES expenses(id);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Tambahkan Foreign Key ke project_flock_kandangs
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_flock_kandangs') THEN
|
||||||
|
ALTER TABLE expense_nonstocks
|
||||||
|
ADD CONSTRAINT fk_expense_nonstocks_kandang_id
|
||||||
|
FOREIGN KEY (project_flock_kandang_id) REFERENCES project_flock_kandangs(id);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Tambahkan Foreign key ke kandang_id
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'kandangs') THEN
|
||||||
|
ALTER TABLE expense_nonstocks
|
||||||
|
ADD CONSTRAINT fk_expense_nonstocks_kandang_id_2
|
||||||
|
FOREIGN KEY (kandang_id) REFERENCES kandangs(id);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Tambahkan Foreign Key ke nonstocks
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'nonstocks') THEN
|
||||||
|
ALTER TABLE expense_nonstocks
|
||||||
|
ADD CONSTRAINT fk_expense_nonstocks_nonstock_id
|
||||||
|
FOREIGN KEY (nonstock_id) REFERENCES nonstocks(id);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Index
|
||||||
|
CREATE INDEX idx_expense_nonstocks_expense_id ON expense_nonstocks (expense_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_expense_nonstocks_nonstock_id ON expense_nonstocks (nonstock_id);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS expense_realizations;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
CREATE TABLE expense_realizations (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
expense_nonstock_id BIGINT UNIQUE,
|
||||||
|
realization_qty NUMERIC(15, 3) NOT NULL,
|
||||||
|
realization_unit_price NUMERIC(15, 3) NOT NULL,
|
||||||
|
realization_total_price NUMERIC(15, 3) NOT NULL,
|
||||||
|
realization_date DATE NOT NULL,
|
||||||
|
note TEXT,
|
||||||
|
created_by BIGINT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Tambahkan Foreign Key ke expense_nonstocks
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'expense_nonstocks') THEN
|
||||||
|
ALTER TABLE expense_realizations
|
||||||
|
ADD CONSTRAINT fk_expense_realizations_nonstock_id
|
||||||
|
FOREIGN KEY (expense_nonstock_id) REFERENCES expense_nonstocks(id);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Tambahkan Foreign Key ke users (created_by)
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN
|
||||||
|
ALTER TABLE expense_realizations
|
||||||
|
ADD CONSTRAINT fk_expense_realizations_created_by
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users(id);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Index
|
||||||
|
CREATE INDEX idx_expense_realizations_nonstock_id ON expense_realizations (expense_nonstock_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_expense_realizations_date ON expense_realizations (realization_date);
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
-- Add back timestamp columns to marketing_products table
|
||||||
|
ALTER TABLE marketing_products
|
||||||
|
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
-- Add back timestamp columns to marketing_delivery_products table
|
||||||
|
ALTER TABLE marketing_delivery_products
|
||||||
|
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
-- Drop timestamp columns from marketing_products table if it exists
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'marketing_products' AND column_name = 'created_at') THEN
|
||||||
|
ALTER TABLE marketing_products DROP COLUMN created_at;
|
||||||
|
END IF;
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'marketing_products' AND column_name = 'updated_at') THEN
|
||||||
|
ALTER TABLE marketing_products DROP COLUMN updated_at;
|
||||||
|
END IF;
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'marketing_products' AND column_name = 'deleted_at') THEN
|
||||||
|
ALTER TABLE marketing_products DROP COLUMN deleted_at;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Drop timestamp columns from marketing_delivery_products table if it exists
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'marketing_delivery_products' AND column_name = 'created_at') THEN
|
||||||
|
ALTER TABLE marketing_delivery_products DROP COLUMN created_at;
|
||||||
|
END IF;
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'marketing_delivery_products' AND column_name = 'updated_at') THEN
|
||||||
|
ALTER TABLE marketing_delivery_products DROP COLUMN updated_at;
|
||||||
|
END IF;
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'marketing_delivery_products' AND column_name = 'deleted_at') THEN
|
||||||
|
ALTER TABLE marketing_delivery_products DROP COLUMN deleted_at;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
@@ -82,7 +82,7 @@ func Run(db *gorm.DB) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := seedTransferStock(tx, adminID); err != nil {
|
if err := seedTransferStock(tx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("✅ Master data seeding completed")
|
fmt.Println("✅ Master data seeding completed")
|
||||||
@@ -589,6 +589,14 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories
|
|||||||
Category: "Day Old Chick",
|
Category: "Day Old Chick",
|
||||||
Price: 1,
|
Price: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "Ayam Mati",
|
||||||
|
Brand: "-",
|
||||||
|
Sku: "2",
|
||||||
|
Uom: "Ekor",
|
||||||
|
Category: "Day Old Chick",
|
||||||
|
Price: 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "Ayam Culling",
|
Name: "Ayam Culling",
|
||||||
Brand: "-",
|
Brand: "-",
|
||||||
@@ -684,7 +692,7 @@ func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories
|
|||||||
var existing entity.ProductSupplier
|
var existing entity.ProductSupplier
|
||||||
err := tx.Where("product_id = ? AND supplier_id = ?", product.Id, supplierID).First(&existing).Error
|
err := tx.Where("product_id = ? AND supplier_id = ?", product.Id, supplierID).First(&existing).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
link := entity.ProductSupplier{ProductID: product.Id, SupplierID: supplierID}
|
link := entity.ProductSupplier{ProductId: product.Id, SupplierId: supplierID}
|
||||||
if err := tx.Create(&link).Error; err != nil {
|
if err := tx.Create(&link).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -757,7 +765,7 @@ func seedNonstocks(tx *gorm.DB, createdBy uint, uoms map[string]uint, suppliers
|
|||||||
var existing entity.NonstockSupplier
|
var existing entity.NonstockSupplier
|
||||||
err := tx.Where("nonstock_id = ? AND supplier_id = ?", nonstock.Id, supplierID).First(&existing).Error
|
err := tx.Where("nonstock_id = ? AND supplier_id = ?", nonstock.Id, supplierID).First(&existing).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
link := entity.NonstockSupplier{NonstockID: nonstock.Id, SupplierID: supplierID}
|
link := entity.NonstockSupplier{NonstockId: nonstock.Id, SupplierId: supplierID}
|
||||||
if err := tx.Create(&link).Error; err != nil {
|
if err := tx.Create(&link).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -921,7 +929,7 @@ func seedProductWarehouse(tx *gorm.DB, createdBy uint) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func seedTransferStock(tx *gorm.DB, createdBy uint) error {
|
func seedTransferStock(tx *gorm.DB) error {
|
||||||
|
|
||||||
transfer := entity.StockTransfer{
|
transfer := entity.StockTransfer{
|
||||||
FromWarehouseId: 1,
|
FromWarehouseId: 1,
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Expense struct {
|
||||||
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
|
ReferenceNumber string `gorm:"type:varchar(50);uniqueIndex"`
|
||||||
|
SupplierId uint64 `gorm:""`
|
||||||
|
Category string `gorm:"type:varchar(50);not null"`
|
||||||
|
PoNumber string `gorm:"type:varchar(50)"`
|
||||||
|
DocumentPath sql.NullString `gorm:"type:json"` // Dokumen pengajuan
|
||||||
|
RealizationDocumentPath sql.NullString `gorm:"type:json;column:realization_document_path"` // Dokumen realisasi
|
||||||
|
RealizationDate time.Time `gorm:"type:date;column:realization_date"` // Tanggal realisasi
|
||||||
|
ExpenseDate time.Time `gorm:"type:date;not null"`
|
||||||
|
GrandTotal float64 `gorm:"type:numeric(15,3);default:0"`
|
||||||
|
Note string `gorm:"type:text"`
|
||||||
|
CreatedBy uint64 `gorm:""`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Supplier *Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
||||||
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
|
Nonstocks []ExpenseNonstock `gorm:"foreignKey:ExpenseId;references:Id"`
|
||||||
|
LatestApproval *Approval `gorm:"-" json:"latest_approval,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
type ExpenseNonstock struct {
|
||||||
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
|
ExpenseId *uint64 `gorm:""`
|
||||||
|
ProjectFlockKandangId *uint64 `gorm:""`
|
||||||
|
KandangId *uint64 `gorm:""`
|
||||||
|
NonstockId *uint64 `gorm:""`
|
||||||
|
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
UnitPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
TotalPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
Note string `gorm:"type:text"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Expense *Expense `gorm:"foreignKey:ExpenseId;references:Id"`
|
||||||
|
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
||||||
|
Kandang *Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
||||||
|
Nonstock *Nonstock `gorm:"foreignKey:NonstockId;references:Id"`
|
||||||
|
Realization *ExpenseRealization `gorm:"foreignKey:ExpenseNonstockId;references:Id"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExpenseRealization struct {
|
||||||
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
|
ExpenseNonstockId *uint64 `gorm:""`
|
||||||
|
RealizationQty float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
RealizationUnitPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
RealizationTotalPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
RealizationDate time.Time `gorm:"type:date;not null"`
|
||||||
|
Note *string `gorm:"type:text"`
|
||||||
|
CreatedBy *uint64 `gorm:""`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
ExpenseNonstock *ExpenseNonstock `gorm:"foreignKey:ExpenseNonstockId;references:Id"`
|
||||||
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Marketing struct {
|
||||||
|
Id uint `gorm:"primaryKey;autoIncrement"`
|
||||||
|
SoNumber string `gorm:"uniqueIndex;not null"`
|
||||||
|
CustomerId uint `gorm:"not null"`
|
||||||
|
SoDocs string `gorm:"type:varchar(20)"`
|
||||||
|
SoDate time.Time `gorm:"type:date;not null"`
|
||||||
|
SalesPersonId uint `gorm:"not null"`
|
||||||
|
Notes string `gorm:"type:text"`
|
||||||
|
CreatedBy uint `gorm:"not null"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
|
Customer Customer `gorm:"foreignKey:CustomerId;references:Id"`
|
||||||
|
SalesPerson User `gorm:"foreignKey:SalesPersonId;references:Id"`
|
||||||
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
|
Products []MarketingProduct `gorm:"foreignKey:MarketingId;references:Id"`
|
||||||
|
LatestApproval *Approval `gorm:"-" json:"latest_approval,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MarketingDeliveryProduct struct {
|
||||||
|
Id uint `gorm:"primaryKey;autoIncrement"`
|
||||||
|
MarketingProductId uint `gorm:"uniqueIndex;not null"`
|
||||||
|
Qty float64 `gorm:"type:numeric(15,3)"`
|
||||||
|
UnitPrice float64 `gorm:"type:numeric(15,3)"`
|
||||||
|
TotalWeight float64 `gorm:"type:numeric(15,3)"`
|
||||||
|
AvgWeight float64 `gorm:"type:numeric(15,3)"`
|
||||||
|
TotalPrice float64 `gorm:"type:numeric(15,3)"`
|
||||||
|
DeliveryDate *time.Time `gorm:"type:timestamptz"`
|
||||||
|
VehicleNumber string `gorm:"type:varchar(50)"`
|
||||||
|
|
||||||
|
MarketingProduct MarketingProduct `gorm:"foreignKey:MarketingProductId;references:Id"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
type MarketingProduct struct {
|
||||||
|
Id uint `gorm:"primaryKey;autoIncrement"`
|
||||||
|
MarketingId uint `gorm:"not null"`
|
||||||
|
ProductWarehouseId uint `gorm:"not null"`
|
||||||
|
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
UnitPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
AvgWeight float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
TotalWeight float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
TotalPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
|
||||||
|
Marketing Marketing `gorm:"foreignKey:MarketingId;references:Id"`
|
||||||
|
ProductWarehouse ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||||
|
DeliveryProduct *MarketingDeliveryProduct `gorm:"foreignKey:MarketingProductId;references:Id"`
|
||||||
|
}
|
||||||
@@ -15,8 +15,8 @@ type Nonstock struct {
|
|||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
Uom Uom `gorm:"foreignKey:UomId;references:Id"`
|
Uom Uom `gorm:"foreignKey:UomId;references:Id"`
|
||||||
Suppliers []Supplier `gorm:"many2many:nonstock_suppliers;joinForeignKey:NonstockID;joinReferences:SupplierID"`
|
NonstockSuppliers []NonstockSupplier `gorm:"foreignKey:NonstockId;references:Id"`
|
||||||
Flags []Flag `gorm:"polymorphic:Flagable;polymorphicValue:nonstocks"`
|
Flags []Flag `gorm:"polymorphic:Flagable;polymorphicValue:nonstocks"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ package entities
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type NonstockSupplier struct {
|
type NonstockSupplier struct {
|
||||||
NonstockID uint `gorm:"primaryKey"`
|
NonstockId uint `gorm:"not null"`
|
||||||
SupplierID uint `gorm:"primaryKey"`
|
SupplierId uint `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|
||||||
|
Nonstock Nonstock `gorm:"foreignKey:NonstockId;references:Id"`
|
||||||
|
Supplier Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ type Product struct {
|
|||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
Uom Uom `gorm:"foreignKey:UomId;references:Id"`
|
Uom Uom `gorm:"foreignKey:UomId;references:Id"`
|
||||||
ProductCategory ProductCategory `gorm:"foreignKey:ProductCategoryId;references:Id"`
|
ProductCategory ProductCategory `gorm:"foreignKey:ProductCategoryId;references:Id"`
|
||||||
Suppliers []Supplier `gorm:"many2many:product_suppliers;joinForeignKey:ProductID;joinReferences:SupplierID"`
|
ProductSuppliers []ProductSupplier `gorm:"foreignKey:ProductId;references:Id"`
|
||||||
Flags []Flag `gorm:"polymorphic:Flagable;polymorphicValue:products"`
|
Flags []Flag `gorm:"polymorphic:Flagable;polymorphicValue:products"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ package entities
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type ProductSupplier struct {
|
type ProductSupplier struct {
|
||||||
ProductID uint `gorm:"primaryKey"`
|
ProductId uint `gorm:"not null"`
|
||||||
SupplierID uint `gorm:"primaryKey"`
|
SupplierId uint `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|
||||||
|
Product Product `gorm:"foreignKey:ProductId;references:Id"`
|
||||||
|
Supplier Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,5 +26,7 @@ type Supplier struct {
|
|||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
|
ProductSuppliers []ProductSupplier `gorm:"foreignKey:SupplierId;references:Id"`
|
||||||
|
NonstockSuppliers []NonstockSupplier `gorm:"foreignKey:SupplierId;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func (u *ApprovalController) GetAll(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
flat := dto.ToApprovalDTOs(records)
|
flat := dto.ToApprovalDTOs(records)
|
||||||
return c.Status(fiber.StatusOK).
|
return c.Status(fiber.StatusOK).
|
||||||
JSON(response.SuccessWithPaginate[dto.ApprovalBaseDTO]{
|
JSON(response.SuccessWithPaginate[dto.ApprovalRelationDTO]{
|
||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Get All approvals successfully",
|
Message: "Get All approvals successfully",
|
||||||
|
|||||||
@@ -10,24 +10,24 @@ import (
|
|||||||
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ApprovalBaseDTO struct {
|
type ApprovalRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
StepNumber uint16 `json:"step_number"`
|
StepNumber uint16 `json:"step_number"`
|
||||||
StepName string `json:"step_name"`
|
StepName string `json:"step_name"`
|
||||||
Action *string `json:"action"`
|
Action *string `json:"action"`
|
||||||
Notes *string `json:"notes"`
|
Notes *string `json:"notes"`
|
||||||
ActionBy userDTO.UserBaseDTO `json:"action_by"`
|
ActionBy userDTO.UserRelationDTO `json:"action_by"`
|
||||||
ActionAt time.Time `json:"action_at"`
|
ActionAt time.Time `json:"action_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApprovalGroupDTO struct {
|
type ApprovalGroupDTO struct {
|
||||||
StepNumber uint16 `json:"step_number"`
|
StepNumber uint16 `json:"step_number"`
|
||||||
StepName string `json:"step_name"`
|
StepName string `json:"step_name"`
|
||||||
Approvals []ApprovalBaseDTO `json:"approvals"`
|
Approvals []ApprovalRelationDTO `json:"approvals"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToApprovalDTO(e entity.Approval) ApprovalBaseDTO {
|
func ToApprovalDTO(e entity.Approval) ApprovalRelationDTO {
|
||||||
dto := ApprovalBaseDTO{
|
dto := ApprovalRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Notes: e.Notes,
|
Notes: e.Notes,
|
||||||
}
|
}
|
||||||
@@ -54,10 +54,10 @@ func ToApprovalDTO(e entity.Approval) ApprovalBaseDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if e.ActionUser != nil && e.ActionUser.Id != 0 {
|
if e.ActionUser != nil && e.ActionUser.Id != 0 {
|
||||||
user := userDTO.ToUserBaseDTO(*e.ActionUser)
|
user := userDTO.ToUserRelationDTO(*e.ActionUser)
|
||||||
dto.ActionBy = user
|
dto.ActionBy = user
|
||||||
} else if e.ActionBy != nil && *e.ActionBy != 0 {
|
} else if e.ActionBy != nil && *e.ActionBy != 0 {
|
||||||
dto.ActionBy = userDTO.UserBaseDTO{
|
dto.ActionBy = userDTO.UserRelationDTO{
|
||||||
Id: *e.ActionBy,
|
Id: *e.ActionBy,
|
||||||
IdUser: int64(*e.ActionBy),
|
IdUser: int64(*e.ActionBy),
|
||||||
}
|
}
|
||||||
@@ -71,8 +71,8 @@ func ToApprovalDTO(e entity.Approval) ApprovalBaseDTO {
|
|||||||
return dto
|
return dto
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToApprovalDTOs(items []entity.Approval) []ApprovalBaseDTO {
|
func ToApprovalDTOs(items []entity.Approval) []ApprovalRelationDTO {
|
||||||
result := make([]ApprovalBaseDTO, len(items))
|
result := make([]ApprovalRelationDTO, len(items))
|
||||||
for i, item := range items {
|
for i, item := range items {
|
||||||
result[i] = ToApprovalDTO(item)
|
result[i] = ToApprovalDTO(item)
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ func ToApprovalGroupDTOs(items []entity.Approval) []ApprovalGroupDTO {
|
|||||||
|
|
||||||
type groupAccumulator struct {
|
type groupAccumulator struct {
|
||||||
StepName string
|
StepName string
|
||||||
Approvals []ApprovalBaseDTO
|
Approvals []ApprovalRelationDTO
|
||||||
}
|
}
|
||||||
|
|
||||||
groups := make(map[uint16]*groupAccumulator)
|
groups := make(map[uint16]*groupAccumulator)
|
||||||
|
|||||||
@@ -0,0 +1,391 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/dto"
|
||||||
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/services"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExpenseController struct {
|
||||||
|
ExpenseService service.ExpenseService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExpenseController(expenseService service.ExpenseService) *ExpenseController {
|
||||||
|
return &ExpenseController{
|
||||||
|
ExpenseService: expenseService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ExpenseController) 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.ExpenseService.GetAll(c, query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.SuccessWithPaginate[dto.ExpenseListDTO]{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get all expenses successfully",
|
||||||
|
Meta: response.Meta{
|
||||||
|
Page: query.Page,
|
||||||
|
Limit: query.Limit,
|
||||||
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||||
|
TotalResults: totalResults,
|
||||||
|
},
|
||||||
|
Data: result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ExpenseController) 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.ExpenseService.GetOne(c, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get expense successfully",
|
||||||
|
Data: result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ExpenseController) CreateOne(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Create)
|
||||||
|
|
||||||
|
req.TransactionDate = c.FormValue("transaction_date")
|
||||||
|
req.Category = c.FormValue("category")
|
||||||
|
|
||||||
|
supplierID, err := strconv.ParseUint(c.FormValue("supplier_id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid supplier_id format")
|
||||||
|
}
|
||||||
|
req.SupplierID = supplierID
|
||||||
|
|
||||||
|
form, err := c.MultipartForm()
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
|
||||||
|
}
|
||||||
|
req.Documents = form.File["documents"]
|
||||||
|
|
||||||
|
costPerKandangJSON := c.FormValue("cost_per_kandangs")
|
||||||
|
if costPerKandangJSON != "" {
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(costPerKandangJSON), &req.CostPerKandangs); err != nil {
|
||||||
|
|
||||||
|
var singleCostPerKandang validation.CostPerKandang
|
||||||
|
if err := json.Unmarshal([]byte(costPerKandangJSON), &singleCostPerKandang); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid cost_per_kandangs JSON: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if singleCostPerKandang.KandangID == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Field KandangID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
req.CostPerKandangs = []validation.CostPerKandang{singleCostPerKandang}
|
||||||
|
} else {
|
||||||
|
for i, costPerKandang := range req.CostPerKandangs {
|
||||||
|
if costPerKandang.KandangID == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Field KandangID is required for cost_per_kandangs[%d]", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Field cost_per_kandangs is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.ExpenseService.CreateOne(c, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusCreated,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Create expense successfully",
|
||||||
|
Data: result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ExpenseController) UpdateOne(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Update)
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
form, err := c.MultipartForm()
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Documents = form.File["documents"]
|
||||||
|
if transactionDate := c.FormValue("transaction_date"); transactionDate != "" {
|
||||||
|
req.TransactionDate = &transactionDate
|
||||||
|
}
|
||||||
|
|
||||||
|
costPerKandangJSON := c.FormValue("cost_per_kandang")
|
||||||
|
if costPerKandangJSON != "" {
|
||||||
|
var costPerKandang []validation.CostPerKandang
|
||||||
|
if err := json.Unmarshal([]byte(costPerKandangJSON), &costPerKandang); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid cost_per_kandang JSON: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, costPerKandang := range costPerKandang {
|
||||||
|
if costPerKandang.KandangID == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Field KandangID is required for cost_per_kandang[%d]", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.CostPerKandang = &costPerKandang
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.ExpenseService.UpdateOne(c, req, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Update expense successfully",
|
||||||
|
Data: result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ExpenseController) DeleteOne(c *fiber.Ctx) error {
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.ExpenseService.DeleteOne(c, uint(id)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Common{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Delete expense successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ExpenseController) Approval(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.ApprovalRequest)
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
path := c.Path()
|
||||||
|
approvalType := ""
|
||||||
|
if strings.Contains(path, "/approvals/manager") {
|
||||||
|
approvalType = "manager"
|
||||||
|
} else if strings.Contains(path, "/approvals/finance") {
|
||||||
|
approvalType = "finance"
|
||||||
|
} else {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid approval path")
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := u.ExpenseService.Approval(c, req, approvalType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
data interface{}
|
||||||
|
message = "Submit expense approval successfully"
|
||||||
|
)
|
||||||
|
if len(results) == 1 {
|
||||||
|
data = results[0]
|
||||||
|
} else {
|
||||||
|
message = "Submit expense approvals successfully"
|
||||||
|
data = results
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: message,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ExpenseController) CreateRealization(c *fiber.Ctx) error {
|
||||||
|
expenseID := c.Params("id")
|
||||||
|
id, err := strconv.Atoi(expenseID)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(validation.CreateRealization)
|
||||||
|
|
||||||
|
form, err := c.MultipartForm()
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
|
||||||
|
}
|
||||||
|
req.Documents = form.File["documents"]
|
||||||
|
req.RealizationDate = c.FormValue("realization_date")
|
||||||
|
|
||||||
|
realizationsJSON := c.FormValue("realizations")
|
||||||
|
if realizationsJSON != "" {
|
||||||
|
if err := json.Unmarshal([]byte(realizationsJSON), &req.Realizations); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid realizations JSON: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expense, err := u.ExpenseService.CreateRealization(c, uint(id), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusCreated,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Create realization successfully",
|
||||||
|
Data: expense,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ExpenseController) UpdateRealization(c *fiber.Ctx) error {
|
||||||
|
expenseID := c.Params("id")
|
||||||
|
id, err := strconv.Atoi(expenseID)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
var req validation.UpdateRealization
|
||||||
|
|
||||||
|
form, err := c.MultipartForm()
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Documents = form.File["documents"]
|
||||||
|
|
||||||
|
req.RealizationDate = c.FormValue("realization_date")
|
||||||
|
|
||||||
|
realizationsJSON := c.FormValue("realizations")
|
||||||
|
if realizationsJSON != "" {
|
||||||
|
if err := json.Unmarshal([]byte(realizationsJSON), &req.Realizations); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid realizations JSON: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expense, err := u.ExpenseService.UpdateRealization(c, uint(id), &req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Update realization successfully",
|
||||||
|
Data: expense,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ExpenseController) CompleteExpense(c *fiber.Ctx) error {
|
||||||
|
expenseID := c.Params("id")
|
||||||
|
id, err := strconv.Atoi(expenseID)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
expense, err := u.ExpenseService.CompleteExpense(c, uint(id), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Complete expense successfully",
|
||||||
|
Data: expense,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ExpenseController) DeleteDocument(c *fiber.Ctx) error {
|
||||||
|
expenseID, err := strconv.Atoi(c.Params("id"))
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
documentID, err := strconv.ParseUint(c.Params("documentId"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid document ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.ExpenseService.DeleteDocument(c, uint(expenseID), documentID, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Common{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Delete document successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ExpenseController) DeleteRealizationDocument(c *fiber.Ctx) error {
|
||||||
|
expenseID, err := strconv.Atoi(c.Params("id"))
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
documentID, err := strconv.ParseUint(c.Params("documentId"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid document ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.ExpenseService.DeleteDocument(c, uint(expenseID), documentID, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Common{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Delete realization document successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,321 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
||||||
|
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||||
|
nonstockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks/dto"
|
||||||
|
supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
|
||||||
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// === DTO Structs ===
|
||||||
|
|
||||||
|
type ExpenseRelationDTO struct {
|
||||||
|
Id uint64 `json:"id"`
|
||||||
|
PoNumber string `json:"po_number"`
|
||||||
|
ExpenseDate time.Time `json:"expense_date"`
|
||||||
|
GrandTotal float64 `json:"grand_total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpenseBaseDTO struct {
|
||||||
|
Id uint64 `json:"id"`
|
||||||
|
ReferenceNumber string `json:"reference_number"`
|
||||||
|
PoNumber string `json:"po_number"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier,omitempty"`
|
||||||
|
RealizationDate *time.Time `json:"realization_date,omitempty"`
|
||||||
|
ExpenseDate time.Time `json:"expense_date"`
|
||||||
|
GrandTotal float64 `json:"grand_total"`
|
||||||
|
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpenseListDTO struct {
|
||||||
|
ExpenseBaseDTO
|
||||||
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpenseDetailDTO struct {
|
||||||
|
ExpenseBaseDTO
|
||||||
|
Documents []DocumentDTO `json:"documents,omitempty"`
|
||||||
|
RealizationDocs []DocumentDTO `json:"realization_docs,omitempty"`
|
||||||
|
Kandangs []KandangGroupDTO `json:"kandangs,omitempty"`
|
||||||
|
TotalPengajuan float64 `json:"total_pengajuan"`
|
||||||
|
TotalRealisasi float64 `json:"total_realisasi"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
||||||
|
LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpenseNonstockDTO struct {
|
||||||
|
Id uint64 `json:"id"`
|
||||||
|
Qty float64 `json:"qty"`
|
||||||
|
UnitPrice float64 `json:"unit_price"`
|
||||||
|
TotalPrice float64 `json:"total_price"`
|
||||||
|
Note *string `json:"note,omitempty"`
|
||||||
|
Nonstock *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpenseRealizationDTO struct {
|
||||||
|
Id uint64 `json:"id"`
|
||||||
|
Qty float64 `json:"qty"`
|
||||||
|
UnitPrice float64 `json:"unit_price"`
|
||||||
|
TotalPrice float64 `json:"total_price"`
|
||||||
|
Note *string `json:"note,omitempty"`
|
||||||
|
Nonstock *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KandangGroupDTO struct {
|
||||||
|
Id uint64 `json:"id"`
|
||||||
|
KandangId uint64 `json:"kandang_id"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Pengajuans []ExpenseNonstockDTO `json:"pengajuans,omitempty"`
|
||||||
|
Realisasi []ExpenseRealizationDTO `json:"realisasi,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocumentDTO struct {
|
||||||
|
ID uint64 `json:"id"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// === MAPPERS ===
|
||||||
|
|
||||||
|
func ToExpenseRelationDTO(e entity.Expense) ExpenseRelationDTO {
|
||||||
|
return ExpenseRelationDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
PoNumber: e.PoNumber,
|
||||||
|
ExpenseDate: e.ExpenseDate,
|
||||||
|
GrandTotal: e.GrandTotal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToExpenseBaseDTO(e *entity.Expense) ExpenseBaseDTO {
|
||||||
|
var location *locationDTO.LocationRelationDTO
|
||||||
|
var supplier *supplierDTO.SupplierRelationDTO
|
||||||
|
|
||||||
|
var realizationDate *time.Time
|
||||||
|
if !e.RealizationDate.IsZero() {
|
||||||
|
realizationDate = &e.RealizationDate
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(e.Nonstocks) > 0 && e.Nonstocks[0].Kandang != nil {
|
||||||
|
if e.Nonstocks[0].Kandang.Location.Id != 0 {
|
||||||
|
mapped := locationDTO.ToLocationRelationDTO(e.Nonstocks[0].Kandang.Location)
|
||||||
|
location = &mapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Supplier != nil && e.Supplier.Id != 0 {
|
||||||
|
mapped := supplierDTO.ToSupplierRelationDTO(*e.Supplier)
|
||||||
|
supplier = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpenseBaseDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
ReferenceNumber: e.ReferenceNumber,
|
||||||
|
PoNumber: e.PoNumber,
|
||||||
|
Category: e.Category,
|
||||||
|
Supplier: supplier,
|
||||||
|
RealizationDate: realizationDate,
|
||||||
|
ExpenseDate: e.ExpenseDate,
|
||||||
|
GrandTotal: e.GrandTotal,
|
||||||
|
Location: location,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToExpenseListDTO(e entity.Expense) ExpenseListDTO {
|
||||||
|
var createdUser *userDTO.UserRelationDTO
|
||||||
|
if e.CreatedUser.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserRelationDTO(*e.CreatedUser)
|
||||||
|
createdUser = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var latestApproval *approvalDTO.ApprovalRelationDTO
|
||||||
|
if e.LatestApproval != nil {
|
||||||
|
mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval)
|
||||||
|
latestApproval = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpenseListDTO{
|
||||||
|
ExpenseBaseDTO: ToExpenseBaseDTO(&e),
|
||||||
|
CreatedUser: createdUser,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
LatestApproval: latestApproval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToExpenseListDTOs(expenses []entity.Expense) []ExpenseListDTO {
|
||||||
|
result := make([]ExpenseListDTO, len(expenses))
|
||||||
|
for i, expense := range expenses {
|
||||||
|
result[i] = ToExpenseListDTO(expense)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
|
||||||
|
var documents []DocumentDTO
|
||||||
|
var realizationDocs []DocumentDTO
|
||||||
|
var createdUser *userDTO.UserRelationDTO
|
||||||
|
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserRelationDTO(*e.CreatedUser)
|
||||||
|
createdUser = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var latestApproval *approvalDTO.ApprovalRelationDTO
|
||||||
|
if e.LatestApproval != nil {
|
||||||
|
mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval)
|
||||||
|
latestApproval = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var pengajuans []ExpenseNonstockDTO
|
||||||
|
var realisasi []ExpenseRealizationDTO
|
||||||
|
|
||||||
|
if e.DocumentPath.Valid && e.DocumentPath.String != "" {
|
||||||
|
json.Unmarshal([]byte(e.DocumentPath.String), &documents)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.RealizationDocumentPath.Valid && e.RealizationDocumentPath.String != "" {
|
||||||
|
json.Unmarshal([]byte(e.RealizationDocumentPath.String), &realizationDocs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(e.Nonstocks) > 0 {
|
||||||
|
pengajuans = make([]ExpenseNonstockDTO, 0)
|
||||||
|
realisasi = make([]ExpenseRealizationDTO, 0)
|
||||||
|
|
||||||
|
for _, ns := range e.Nonstocks {
|
||||||
|
pengajuanDTO := ToExpenseNonstockDTO(ns)
|
||||||
|
|
||||||
|
pengajuans = append(pengajuans, pengajuanDTO)
|
||||||
|
|
||||||
|
if ns.Realization != nil && ns.Realization.Id != 0 {
|
||||||
|
var nonstock *nonstockDTO.NonstockRelationDTO
|
||||||
|
if ns.Nonstock != nil && ns.Nonstock.Id != 0 {
|
||||||
|
mapped := nonstockDTO.ToNonstockRelationDTO(*ns.Nonstock)
|
||||||
|
nonstock = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
realisasiDTO := ExpenseRealizationDTO{
|
||||||
|
Id: ns.Realization.Id,
|
||||||
|
Qty: ns.Realization.RealizationQty,
|
||||||
|
UnitPrice: ns.Realization.RealizationUnitPrice,
|
||||||
|
TotalPrice: ns.Realization.RealizationTotalPrice,
|
||||||
|
Note: ns.Realization.Note,
|
||||||
|
Nonstock: nonstock,
|
||||||
|
}
|
||||||
|
realisasi = append(realisasi, realisasiDTO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalPengajuan float64
|
||||||
|
for _, p := range pengajuans {
|
||||||
|
totalPengajuan += p.TotalPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalRealisasi float64
|
||||||
|
for _, r := range realisasi {
|
||||||
|
totalRealisasi += r.TotalPrice
|
||||||
|
}
|
||||||
|
kandangs := ToKandangGroupDTO(pengajuans, realisasi, e.Nonstocks)
|
||||||
|
|
||||||
|
return ExpenseDetailDTO{
|
||||||
|
ExpenseBaseDTO: ToExpenseBaseDTO(e),
|
||||||
|
Documents: documents,
|
||||||
|
RealizationDocs: realizationDocs,
|
||||||
|
CreatedUser: createdUser,
|
||||||
|
Kandangs: kandangs,
|
||||||
|
TotalPengajuan: totalPengajuan,
|
||||||
|
TotalRealisasi: totalRealisasi,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
LatestApproval: latestApproval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToExpenseNonstockDTO(ns entity.ExpenseNonstock) ExpenseNonstockDTO {
|
||||||
|
var nonstock *nonstockDTO.NonstockRelationDTO
|
||||||
|
if ns.Nonstock != nil && ns.Nonstock.Id != 0 {
|
||||||
|
mapped := nonstockDTO.ToNonstockRelationDTO(*ns.Nonstock)
|
||||||
|
nonstock = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpenseNonstockDTO{
|
||||||
|
Id: ns.Id,
|
||||||
|
Qty: ns.Qty,
|
||||||
|
UnitPrice: ns.UnitPrice,
|
||||||
|
TotalPrice: ns.TotalPrice,
|
||||||
|
Note: &ns.Note,
|
||||||
|
Nonstock: nonstock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToKandangGroupDTO(pengajuans []ExpenseNonstockDTO, realisasi []ExpenseRealizationDTO, nonstocks []entity.ExpenseNonstock) []KandangGroupDTO {
|
||||||
|
kandangMap := make(map[uint64]*KandangGroupDTO)
|
||||||
|
|
||||||
|
for _, p := range pengajuans {
|
||||||
|
var kandangId uint64
|
||||||
|
var kandangName string
|
||||||
|
|
||||||
|
for _, ns := range nonstocks {
|
||||||
|
if ns.Id == p.Id && ns.Kandang != nil {
|
||||||
|
kandangId = uint64(ns.Kandang.Id)
|
||||||
|
kandangName = ns.Kandang.Name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if kandangId > 0 {
|
||||||
|
if kandangMap[kandangId] == nil {
|
||||||
|
kandangMap[kandangId] = &KandangGroupDTO{
|
||||||
|
Id: kandangId,
|
||||||
|
KandangId: kandangId,
|
||||||
|
Name: kandangName,
|
||||||
|
Pengajuans: []ExpenseNonstockDTO{},
|
||||||
|
Realisasi: []ExpenseRealizationDTO{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kandangMap[kandangId].Pengajuans = append(kandangMap[kandangId].Pengajuans, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range realisasi {
|
||||||
|
var kandangId uint64
|
||||||
|
var kandangName string
|
||||||
|
|
||||||
|
for _, ns := range nonstocks {
|
||||||
|
if ns.Realization != nil && ns.Realization.Id == r.Id && ns.Kandang != nil {
|
||||||
|
kandangId = uint64(ns.Kandang.Id)
|
||||||
|
kandangName = ns.Kandang.Name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if kandangId > 0 {
|
||||||
|
if kandangMap[kandangId] == nil {
|
||||||
|
kandangMap[kandangId] = &KandangGroupDTO{
|
||||||
|
Id: kandangId,
|
||||||
|
KandangId: kandangId,
|
||||||
|
Name: kandangName,
|
||||||
|
Pengajuans: []ExpenseNonstockDTO{},
|
||||||
|
Realisasi: []ExpenseRealizationDTO{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kandangMap[kandangId].Realisasi = append(kandangMap[kandangId].Realisasi, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var kandangs []KandangGroupDTO
|
||||||
|
for _, k := range kandangMap {
|
||||||
|
kandangs = append(kandangs, *k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return kandangs
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package expenses
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
|
rExpense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
|
||||||
|
sExpense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/services"
|
||||||
|
|
||||||
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
|
||||||
|
rNonstock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks/repositories"
|
||||||
|
rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories"
|
||||||
|
rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExpenseModule struct{}
|
||||||
|
|
||||||
|
func (ExpenseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
expenseRepo := rExpense.NewExpenseRepository(db)
|
||||||
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
supplierRepo := rSupplier.NewSupplierRepository(db)
|
||||||
|
nonstockRepo := rNonstock.NewNonstockRepository(db)
|
||||||
|
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||||
|
realizationRepo := rExpense.NewExpenseRealizationRepository(db)
|
||||||
|
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
||||||
|
|
||||||
|
approvalSvc := commonSvc.NewApprovalService(approvalRepo)
|
||||||
|
|
||||||
|
// Register workflow steps for EXPENSES approval
|
||||||
|
if err := approvalSvc.RegisterWorkflowSteps(utils.ApprovalWorkflowExpense, utils.ExpenseApprovalSteps); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to register expense approval workflow: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
expenseService := sExpense.NewExpenseService(expenseRepo, supplierRepo, nonstockRepo, approvalSvc, realizationRepo, projectFlockKandangRepo, validate)
|
||||||
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
ExpenseRoutes(router, userService, expenseService)
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExpenseRepository interface {
|
||||||
|
repository.BaseRepository[entity.Expense]
|
||||||
|
IdExists(ctx context.Context, id uint64) (bool, error)
|
||||||
|
GetNextSequence(ctx context.Context) (int, error)
|
||||||
|
GetWithSupplier(ctx context.Context, id uint64) (*entity.Expense, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpenseRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.Expense]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExpenseRepository(db *gorm.DB) ExpenseRepository {
|
||||||
|
return &ExpenseRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.Expense](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ExpenseRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) {
|
||||||
|
return repository.Exists[entity.Expense](ctx, r.DB(), uint(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ExpenseRepositoryImpl) GetNextSequence(ctx context.Context) (int, error) {
|
||||||
|
var sequence int
|
||||||
|
err := r.DB().Raw("SELECT nextval('expenses_ref_seq')").Scan(&sequence).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return sequence, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ExpenseRepositoryImpl) GetWithSupplier(ctx context.Context, id uint64) (*entity.Expense, error) {
|
||||||
|
var expense entity.Expense
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Preload("Supplier").
|
||||||
|
First(&expense).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &expense, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExpenseNonstockRepository interface {
|
||||||
|
repository.BaseRepository[entity.ExpenseNonstock]
|
||||||
|
IdExists(ctx context.Context, id uint64) (bool, error)
|
||||||
|
GetByExpenseID(ctx context.Context, expenseID uint64, id uint64) (bool, error)
|
||||||
|
GetWithRelations(ctx context.Context, id uint64) (*entity.ExpenseNonstock, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpenseNonstockRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.ExpenseNonstock]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExpenseNonstockRepository(db *gorm.DB) ExpenseNonstockRepository {
|
||||||
|
return &ExpenseNonstockRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.ExpenseNonstock](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ExpenseNonstockRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) {
|
||||||
|
return repository.Exists[entity.ExpenseNonstock](ctx, r.DB(), uint(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ExpenseNonstockRepositoryImpl) GetByExpenseID(ctx context.Context, expenseID uint64, id uint64) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := r.DB().WithContext(ctx).Model(&entity.ExpenseNonstock{}).
|
||||||
|
Where("id = ? AND expense_id = ?", id, expenseID).
|
||||||
|
Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ExpenseNonstockRepositoryImpl) GetWithRelations(ctx context.Context, id uint64) (*entity.ExpenseNonstock, error) {
|
||||||
|
var expenseNonstock entity.ExpenseNonstock
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Preload("Nonstock", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Preload("Suppliers")
|
||||||
|
}).
|
||||||
|
First(&expenseNonstock).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &expenseNonstock, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExpenseRealizationRepository interface {
|
||||||
|
repository.BaseRepository[entity.ExpenseRealization]
|
||||||
|
IdExists(ctx context.Context, id uint64) (bool, error)
|
||||||
|
GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpenseRealizationRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.ExpenseRealization]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExpenseRealizationRepository(db *gorm.DB) ExpenseRealizationRepository {
|
||||||
|
return &ExpenseRealizationRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.ExpenseRealization](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ExpenseRealizationRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) {
|
||||||
|
return repository.Exists[entity.ExpenseRealization](ctx, r.DB(), uint(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ExpenseRealizationRepositoryImpl) GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error) {
|
||||||
|
var realization entity.ExpenseRealization
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Where("expense_nonstock_id = ?", expenseNonstockID).
|
||||||
|
First(&realization).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &realization, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package expenses
|
||||||
|
|
||||||
|
import (
|
||||||
|
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/controllers"
|
||||||
|
expense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/services"
|
||||||
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExpenseRoutes(v1 fiber.Router, u user.UserService, s expense.ExpenseService) {
|
||||||
|
ctrl := controller.NewExpenseController(s)
|
||||||
|
|
||||||
|
route := v1.Group("/expenses")
|
||||||
|
|
||||||
|
// 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.Post("/", ctrl.CreateOne)
|
||||||
|
route.Get("/:id", ctrl.GetOne)
|
||||||
|
route.Patch("/:id", ctrl.UpdateOne)
|
||||||
|
route.Delete("/:id", ctrl.DeleteOne)
|
||||||
|
route.Post("/approvals/manager", ctrl.Approval)
|
||||||
|
route.Post("/approvals/finance", ctrl.Approval)
|
||||||
|
route.Post("/:id/realizations", ctrl.CreateRealization)
|
||||||
|
route.Patch("/:id/realizations", ctrl.UpdateRealization)
|
||||||
|
route.Post("/:id/complete", ctrl.CompleteExpense)
|
||||||
|
route.Delete("/:id/documents/:documentId", ctrl.DeleteDocument)
|
||||||
|
route.Delete("/:id/realization-documents/:documentId", ctrl.DeleteRealizationDocument)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mime/multipart"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Create struct {
|
||||||
|
PoNumber string `form:"po_number" json:"po_number" validate:"omitempty,max=50"`
|
||||||
|
TransactionDate string `form:"transaction_date" json:"transaction_date" validate:"required,datetime=2006-01-02"`
|
||||||
|
Category string `form:"category" json:"category" validate:"required,oneof=BOP NON-BOP"`
|
||||||
|
SupplierID uint64 `form:"supplier_id" json:"supplier_id" validate:"required,gt=0"`
|
||||||
|
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
|
||||||
|
CostPerKandangs []CostPerKandang `form:"cost_per_kandangs" json:"cost_per_kandangs" validate:"required,min=1,dive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CostPerKandang struct {
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CostItem struct {
|
||||||
|
NonstockID uint64 `form:"nonstock_id" json:"nonstock_id" validate:"required,gt=0"`
|
||||||
|
Quantity float64 `form:"quantity" json:"quantity" 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Update struct {
|
||||||
|
TransactionDate *string `form:"transaction_date" json:"transaction_date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
|
CostPerKandang *[]CostPerKandang `form:"cost_per_kandang" json:"cost_per_kandang" validate:"omitempty,min=1,dive"`
|
||||||
|
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateRealization struct {
|
||||||
|
RealizationDate string `form:"realization_date" json:"realization_date" validate:"required,datetime=2006-01-02"`
|
||||||
|
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
|
||||||
|
Realizations []RealizationItem `form:"realizations" json:"realizations" validate:"required,min=1,dive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateRealization struct {
|
||||||
|
RealizationDate string `form:"realization_date" json:"realization_date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
|
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
|
||||||
|
Realizations []RealizationItem `form:"realizations" json:"realizations" validate:"required,min=1,dive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RealizationItem struct {
|
||||||
|
ExpenseNonstockID uint64 `form:"expense_nonstock_id" json:"expense_nonstock_id" validate:"required,gt=0"`
|
||||||
|
Qty float64 `form:"qty" json:"qty" 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApprovalRequest struct {
|
||||||
|
Action string `json:"action" form:"action" validate:"required,oneof=APPROVED REJECTED"`
|
||||||
|
ApprovableIds []uint `json:"approvable_ids" validate:"required,min=1,dive,gt=0"`
|
||||||
|
Notes *string `json:"notes" form:"notes"`
|
||||||
|
}
|
||||||
@@ -10,28 +10,28 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type ProductBaseDTO struct {
|
type ProductRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
SKU string `json:"sku"`
|
SKU string `json:"sku"`
|
||||||
ProductCategory *productCategoryDTO.ProductCategoryBaseDTO `json:"product_category,omitempty"`
|
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WarehouseBaseDTO struct {
|
type WarehouseRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductWarehouseDTO struct {
|
type ProductWarehouseDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
ProductId uint `json:"product_id"`
|
ProductId uint `json:"product_id"`
|
||||||
WarehouseId uint `json:"warehouse_id"`
|
WarehouseId uint `json:"warehouse_id"`
|
||||||
Quantity float64 `json:"quantity"`
|
Quantity float64 `json:"quantity"`
|
||||||
Product *ProductBaseDTO `json:"product,omitempty"`
|
Product *ProductRelationDTO `json:"product,omitempty"`
|
||||||
Warehouse *WarehouseBaseDTO `json:"warehouse,omitempty"`
|
Warehouse *WarehouseRelationDTO `json:"warehouse,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdjustmentBaseDTO struct {
|
type AdjustmentRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
TransactionType string `json:"transaction_type"`
|
TransactionType string `json:"transaction_type"`
|
||||||
Quantity float64 `json:"quantity"`
|
Quantity float64 `json:"quantity"`
|
||||||
@@ -43,9 +43,9 @@ type AdjustmentBaseDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AdjustmentListDTO struct {
|
type AdjustmentListDTO struct {
|
||||||
AdjustmentBaseDTO
|
AdjustmentRelationDTO
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdjustmentDetailDTO struct {
|
type AdjustmentDetailDTO struct {
|
||||||
@@ -55,7 +55,7 @@ type AdjustmentDetailDTO struct {
|
|||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToProductBaseDTO(e *entity.Product) *ProductBaseDTO {
|
func ToProductRelationDTO(e *entity.Product) *ProductRelationDTO {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -64,13 +64,13 @@ func ToProductBaseDTO(e *entity.Product) *ProductBaseDTO {
|
|||||||
sku = *e.Sku
|
sku = *e.Sku
|
||||||
}
|
}
|
||||||
|
|
||||||
var category *productCategoryDTO.ProductCategoryBaseDTO
|
var category *productCategoryDTO.ProductCategoryRelationDTO
|
||||||
if e.ProductCategory.Id != 0 {
|
if e.ProductCategory.Id != 0 {
|
||||||
mapped := productCategoryDTO.ToProductCategoryBaseDTO(e.ProductCategory)
|
mapped := productCategoryDTO.ToProductCategoryRelationDTO(e.ProductCategory)
|
||||||
category = &mapped
|
category = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ProductBaseDTO{
|
return &ProductRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
SKU: sku,
|
SKU: sku,
|
||||||
@@ -78,11 +78,11 @@ func ToProductBaseDTO(e *entity.Product) *ProductBaseDTO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToWarehouseBaseDTO(e *entity.Warehouse) *WarehouseBaseDTO {
|
func ToWarehouseRelationDTO(e *entity.Warehouse) *WarehouseRelationDTO {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &WarehouseBaseDTO{
|
return &WarehouseRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
}
|
}
|
||||||
@@ -97,13 +97,13 @@ func ToProductWarehouseDTO(e *entity.ProductWarehouse) *ProductWarehouseDTO {
|
|||||||
ProductId: e.ProductId,
|
ProductId: e.ProductId,
|
||||||
WarehouseId: e.WarehouseId,
|
WarehouseId: e.WarehouseId,
|
||||||
Quantity: e.Quantity,
|
Quantity: e.Quantity,
|
||||||
Product: ToProductBaseDTO(&e.Product),
|
Product: ToProductRelationDTO(&e.Product),
|
||||||
Warehouse: ToWarehouseBaseDTO(&e.Warehouse),
|
Warehouse: ToWarehouseRelationDTO(&e.Warehouse),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToAdjustmentBaseDTO(e *entity.StockLog) AdjustmentBaseDTO {
|
func ToAdjustmentRelationDTO(e *entity.StockLog) AdjustmentRelationDTO {
|
||||||
return AdjustmentBaseDTO{
|
return AdjustmentRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
TransactionType: e.TransactionType,
|
TransactionType: e.TransactionType,
|
||||||
Quantity: e.Quantity,
|
Quantity: e.Quantity,
|
||||||
@@ -116,9 +116,9 @@ func ToAdjustmentBaseDTO(e *entity.StockLog) AdjustmentBaseDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToAdjustmentListDTO(e *entity.StockLog) AdjustmentListDTO {
|
func ToAdjustmentListDTO(e *entity.StockLog) AdjustmentListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser != nil {
|
if e.CreatedUser != nil {
|
||||||
createdUser = &userDTO.UserBaseDTO{
|
createdUser = &userDTO.UserRelationDTO{
|
||||||
Id: e.CreatedUser.Id,
|
Id: e.CreatedUser.Id,
|
||||||
IdUser: e.CreatedUser.IdUser,
|
IdUser: e.CreatedUser.IdUser,
|
||||||
Email: e.CreatedUser.Email,
|
Email: e.CreatedUser.Email,
|
||||||
@@ -127,9 +127,9 @@ func ToAdjustmentListDTO(e *entity.StockLog) AdjustmentListDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return AdjustmentListDTO{
|
return AdjustmentListDTO{
|
||||||
AdjustmentBaseDTO: ToAdjustmentBaseDTO(e),
|
AdjustmentRelationDTO: ToAdjustmentRelationDTO(e),
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MarketingDeliveryProductRepository interface {
|
||||||
|
repository.BaseRepository[entity.MarketingDeliveryProduct]
|
||||||
|
GetByMarketingProductID(ctx context.Context, marketingProductID uint) (*entity.MarketingDeliveryProduct, error)
|
||||||
|
GetByMarketingId(ctx context.Context, marketingId uint) ([]entity.MarketingDeliveryProduct, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarketingDeliveryProductRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.MarketingDeliveryProduct]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMarketingDeliveryProductRepository(db *gorm.DB) MarketingDeliveryProductRepository {
|
||||||
|
return &MarketingDeliveryProductRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.MarketingDeliveryProduct](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MarketingDeliveryProductRepositoryImpl) GetByMarketingProductID(ctx context.Context, marketingProductID uint) (*entity.MarketingDeliveryProduct, error) {
|
||||||
|
var deliveryProduct entity.MarketingDeliveryProduct
|
||||||
|
if err := r.DB().WithContext(ctx).Where("marketing_product_id = ?", marketingProductID).First(&deliveryProduct).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &deliveryProduct, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MarketingDeliveryProductRepositoryImpl) GetByMarketingId(ctx context.Context, marketingId uint) ([]entity.MarketingDeliveryProduct, error) {
|
||||||
|
var deliveryProducts []entity.MarketingDeliveryProduct
|
||||||
|
|
||||||
|
// Raw query untuk mengambil delivery products berdasarkan marketing ID dengan preload MarketingProduct
|
||||||
|
// Filter: hanya ambil yang sudah memiliki delivery_date (delivery date tidak null)
|
||||||
|
if err := r.DB().WithContext(ctx).
|
||||||
|
Preload("MarketingProduct").
|
||||||
|
Joins("INNER JOIN marketing_products mp ON marketing_delivery_products.marketing_product_id = mp.id").
|
||||||
|
Where("mp.marketing_id = ?", marketingId).
|
||||||
|
Where("marketing_delivery_products.delivery_date IS NOT NULL").
|
||||||
|
Order("marketing_delivery_products.id ASC").
|
||||||
|
Find(&deliveryProducts).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return deliveryProducts, nil
|
||||||
|
}
|
||||||
+1
@@ -29,6 +29,7 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error {
|
|||||||
ProductId: uint(c.QueryInt("product_id", 0)),
|
ProductId: uint(c.QueryInt("product_id", 0)),
|
||||||
WarehouseId: uint(c.QueryInt("warehouse_id", 0)),
|
WarehouseId: uint(c.QueryInt("warehouse_id", 0)),
|
||||||
Flags: c.Query("flags", ""),
|
Flags: c.Query("flags", ""),
|
||||||
|
KandangId: uint(c.QueryInt("kandang_id", 0)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if query.Page < 1 || query.Limit < 1 {
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
|||||||
@@ -4,27 +4,34 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type ProductWarehouseBaseDTO struct {
|
type ProductWarehouseRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
ProductId uint `json:"product_id"`
|
ProductId uint `json:"product_id"`
|
||||||
WarehouseId uint `json:"warehouse_id"`
|
WarehouseId uint `json:"warehouse_id"`
|
||||||
Quantity float64 `json:"quantity"`
|
Quantity float64 `json:"quantity"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductWarehouseListDTO struct {
|
type ProductWarehousNestedDTO struct {
|
||||||
ProductWarehouseBaseDTO
|
Id uint `json:"id"`
|
||||||
Product *ProductBaseDTO `json:"product,omitempty"`
|
Product *productDTO.ProductRelationDTO `json:"product,omitempty"`
|
||||||
Warehouse *WarehouseBaseDTO `json:"warehouse,omitempty"`
|
Warehouse *WarehouseRelationDTO `json:"warehouse,omitempty"`
|
||||||
CreatedUser *UserBaseDTO `json:"created_user,omitempty"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserBaseDTO struct {
|
type ProductWarehouseListDTO struct {
|
||||||
|
ProductWarehouseRelationDTO
|
||||||
|
Product *productDTO.ProductRelationDTO `json:"product,omitempty"`
|
||||||
|
Warehouse *WarehouseRelationDTO `json:"warehouse,omitempty"`
|
||||||
|
CreatedUser *UserRelationDTO `json:"created_user,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
}
|
}
|
||||||
@@ -34,40 +41,40 @@ type ProductWarehouseDetailDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Nested DTOs for relations
|
// Nested DTOs for relations
|
||||||
type ProductBaseDTO struct {
|
type ProductRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Sku string `json:"sku"`
|
Sku string `json:"sku"`
|
||||||
Flags []string `json:"flags"`
|
Flags []string `json:"flags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WarehouseBaseDTO struct {
|
type WarehouseRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Kandang *KandangBaseDTO `json:"kandang,omitempty"`
|
Kandang *KandangRelationDTO `json:"kandang,omitempty"`
|
||||||
Location *LocationBaseDTO `json:"location,omitempty"`
|
Location *LocationRelationDTO `json:"location,omitempty"`
|
||||||
Area *AreaBaseDTO `json:"area,omitempty"`
|
Area *AreaRelationDTO `json:"area,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type KandangBaseDTO struct {
|
type KandangRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocationBaseDTO struct {
|
type LocationRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AreaBaseDTO struct {
|
type AreaRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToProductWarehouseBaseDTO(e entity.ProductWarehouse) ProductWarehouseBaseDTO {
|
func ToProductWarehouseRelationDTO(e entity.ProductWarehouse) ProductWarehouseRelationDTO {
|
||||||
return ProductWarehouseBaseDTO{
|
return ProductWarehouseRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
ProductId: e.ProductId, // Field yang benar dari entity
|
ProductId: e.ProductId, // Field yang benar dari entity
|
||||||
WarehouseId: e.WarehouseId, // Field yang benar dari entity
|
WarehouseId: e.WarehouseId, // Field yang benar dari entity
|
||||||
@@ -75,53 +82,55 @@ func ToProductWarehouseBaseDTO(e entity.ProductWarehouse) ProductWarehouseBaseDT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToProductWarehouseNestedDTO(e entity.ProductWarehouse) ProductWarehousNestedDTO {
|
||||||
|
product := productDTO.ToProductRelationDTO(e.Product)
|
||||||
|
|
||||||
|
return ProductWarehousNestedDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
Product: &product,
|
||||||
|
Warehouse: &WarehouseRelationDTO{
|
||||||
|
Id: e.Warehouse.Id,
|
||||||
|
Name: e.Warehouse.Name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ToProductWarehouseListDTO(e entity.ProductWarehouse) ProductWarehouseListDTO {
|
func ToProductWarehouseListDTO(e entity.ProductWarehouse) ProductWarehouseListDTO {
|
||||||
dto := ProductWarehouseListDTO{
|
dto := ProductWarehouseListDTO{
|
||||||
ProductWarehouseBaseDTO: ToProductWarehouseBaseDTO(e),
|
ProductWarehouseRelationDTO: ToProductWarehouseRelationDTO(e),
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map Product relation jika ada
|
// Map Product relation jika ada
|
||||||
if e.Product.Id != 0 {
|
if e.Product.Id != 0 {
|
||||||
product := ProductBaseDTO{
|
product := productDTO.ToProductRelationDTO(e.Product)
|
||||||
Id: e.Product.Id,
|
|
||||||
Name: e.Product.Name,
|
|
||||||
}
|
|
||||||
if e.Product.Sku != nil {
|
|
||||||
product.Sku = *e.Product.Sku
|
|
||||||
}
|
|
||||||
if len(e.Product.Flags) > 0 {
|
|
||||||
for _, f := range e.Product.Flags {
|
|
||||||
product.Flags = append(product.Flags, f.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dto.Product = &product
|
dto.Product = &product
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map Warehouse relation jika ada
|
// Map Warehouse relation jika ada
|
||||||
if e.Warehouse.Id != 0 {
|
if e.Warehouse.Id != 0 {
|
||||||
warehouse := WarehouseBaseDTO{
|
warehouse := WarehouseRelationDTO{
|
||||||
Id: e.Warehouse.Id,
|
Id: e.Warehouse.Id,
|
||||||
Name: e.Warehouse.Name,
|
Name: e.Warehouse.Name,
|
||||||
}
|
}
|
||||||
// Map Kandang jika ada
|
// Map Kandang jika ada
|
||||||
if e.Warehouse.Kandang != nil && e.Warehouse.Kandang.Id != 0 {
|
if e.Warehouse.Kandang != nil && e.Warehouse.Kandang.Id != 0 {
|
||||||
warehouse.Kandang = &KandangBaseDTO{
|
warehouse.Kandang = &KandangRelationDTO{
|
||||||
Id: e.Warehouse.Kandang.Id,
|
Id: e.Warehouse.Kandang.Id,
|
||||||
Name: e.Warehouse.Kandang.Name,
|
Name: e.Warehouse.Kandang.Name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Map Location jika ada
|
// Map Location jika ada
|
||||||
if e.Warehouse.Location != nil && e.Warehouse.Location.Id != 0 {
|
if e.Warehouse.Location != nil && e.Warehouse.Location.Id != 0 {
|
||||||
warehouse.Location = &LocationBaseDTO{
|
warehouse.Location = &LocationRelationDTO{
|
||||||
Id: e.Warehouse.Location.Id,
|
Id: e.Warehouse.Location.Id,
|
||||||
Name: e.Warehouse.Location.Name,
|
Name: e.Warehouse.Location.Name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if &e.Warehouse.Area != nil && e.Warehouse.Area.Id != 0 {
|
if e.Warehouse.Area.Id != 0 {
|
||||||
warehouse.Area = &AreaBaseDTO{
|
warehouse.Area = &AreaRelationDTO{
|
||||||
Id: e.Warehouse.Area.Id,
|
Id: e.Warehouse.Area.Id,
|
||||||
Name: e.Warehouse.Area.Name,
|
Name: e.Warehouse.Area.Name,
|
||||||
}
|
}
|
||||||
@@ -132,7 +141,7 @@ func ToProductWarehouseListDTO(e entity.ProductWarehouse) ProductWarehouseListDT
|
|||||||
|
|
||||||
// Map CreatedUser relation jika ada
|
// Map CreatedUser relation jika ada
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
user := UserBaseDTO{
|
user := UserRelationDTO{
|
||||||
Id: e.CreatedUser.Id,
|
Id: e.CreatedUser.Id,
|
||||||
Username: e.CreatedUser.Name,
|
Username: e.CreatedUser.Name,
|
||||||
}
|
}
|
||||||
@@ -156,22 +165,22 @@ func ToProductWarehouseDetailDTO(e entity.ProductWarehouse) ProductWarehouseDeta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToKandangBaseDTO(e entity.Kandang) KandangBaseDTO {
|
func ToKandangRelationDTO(e entity.Kandang) KandangRelationDTO {
|
||||||
return KandangBaseDTO{
|
return KandangRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToLocationBaseDTO(e entity.Location) LocationBaseDTO {
|
func ToLocationRelationDTO(e entity.Location) LocationRelationDTO {
|
||||||
return LocationBaseDTO{
|
return LocationRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToAreaBaseDTO(e entity.Area) AreaBaseDTO {
|
func ToAreaRelationDTO(e entity.Area) AreaRelationDTO {
|
||||||
return AreaBaseDTO{
|
return AreaRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
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"
|
||||||
sProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/services"
|
sProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/services"
|
||||||
|
rKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||||
|
|
||||||
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"
|
||||||
@@ -17,8 +18,9 @@ type ProductWarehouseModule struct{}
|
|||||||
func (ProductWarehouseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
func (ProductWarehouseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||||
userRepo := rUser.NewUserRepository(db)
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
kandangRepo := rKandang.NewKandangRepository(db)
|
||||||
|
|
||||||
productWarehouseService := sProductWarehouse.NewProductWarehouseService(productWarehouseRepo, validate)
|
productWarehouseService := sProductWarehouse.NewProductWarehouseService(productWarehouseRepo, validate, kandangRepo)
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
ProductWarehouseRoutes(router, userService, productWarehouseService)
|
ProductWarehouseRoutes(router, userService, productWarehouseService)
|
||||||
|
|||||||
+7
-2
@@ -25,6 +25,7 @@ type ProductWarehouseRepository interface {
|
|||||||
ApplyFlagsFilter(db *gorm.DB, flags []string) *gorm.DB
|
ApplyFlagsFilter(db *gorm.DB, flags []string) *gorm.DB
|
||||||
AdjustQuantities(ctx context.Context, deltas map[uint]float64, modifier func(*gorm.DB) *gorm.DB) error
|
AdjustQuantities(ctx context.Context, deltas map[uint]float64, modifier func(*gorm.DB) *gorm.DB) error
|
||||||
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)
|
||||||
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 uint64) (uint, error)
|
EnsureProductWarehouse(ctx context.Context, productID, warehouseID uint, createdBy uint64) (uint, error)
|
||||||
}
|
}
|
||||||
@@ -50,6 +51,10 @@ func (r *ProductWarehouseRepositoryImpl) ExistsByID(ctx context.Context, id uint
|
|||||||
return repository.Exists[entity.ProductWarehouse](ctx, r.DB(), id)
|
return repository.Exists[entity.ProductWarehouse](ctx, r.DB(), id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ProductWarehouseRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.ProductWarehouse](ctx, r.DB(), id)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExists(ctx context.Context, productId, warehouseId uint, excludeID *uint) (bool, error) {
|
func (r *ProductWarehouseRepositoryImpl) ProductWarehouseExists(ctx context.Context, productId, warehouseId uint, excludeID *uint) (bool, error) {
|
||||||
var count int64
|
var count int64
|
||||||
query := r.DB().WithContext(ctx).Model(&entity.ProductWarehouse{}).
|
query := r.DB().WithContext(ctx).Model(&entity.ProductWarehouse{}).
|
||||||
@@ -250,7 +255,7 @@ func (r *ProductWarehouseRepositoryImpl) GetByFlagAndWarehouseID(ctx context.Con
|
|||||||
var productWarehouses []entity.ProductWarehouse
|
var productWarehouses []entity.ProductWarehouse
|
||||||
err := r.DB().WithContext(ctx).Model(&entity.ProductWarehouse{}).
|
err := r.DB().WithContext(ctx).Model(&entity.ProductWarehouse{}).
|
||||||
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
||||||
Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = ?", "products").
|
Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = 'products'").
|
||||||
Where("flags.name = ? AND product_warehouses.warehouse_id = ?", flagName, warehouseId).
|
Where("flags.name = ? AND product_warehouses.warehouse_id = ?", flagName, warehouseId).
|
||||||
Order("product_warehouses.created_at DESC").
|
Order("product_warehouses.created_at DESC").
|
||||||
Preload("Product").Preload("Warehouse").
|
Preload("Product").Preload("Warehouse").
|
||||||
@@ -264,7 +269,7 @@ func (r *ProductWarehouseRepositoryImpl) GetByFlagAndWarehouseID(ctx context.Con
|
|||||||
func (r *ProductWarehouseRepositoryImpl) GetFirstProductByFlag(ctx context.Context, flagName string) (*entity.Product, error) {
|
func (r *ProductWarehouseRepositoryImpl) GetFirstProductByFlag(ctx context.Context, flagName string) (*entity.Product, error) {
|
||||||
var product entity.Product
|
var product entity.Product
|
||||||
err := r.DB().WithContext(ctx).
|
err := r.DB().WithContext(ctx).
|
||||||
Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = ?", "products").
|
Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = 'products'").
|
||||||
Where("flags.name = ?", flagName).
|
Where("flags.name = ?", flagName).
|
||||||
First(&product).Error
|
First(&product).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+25
-7
@@ -6,6 +6,7 @@ import (
|
|||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/validations"
|
||||||
|
kandangrepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
@@ -20,16 +21,18 @@ type ProductWarehouseService interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type productWarehouseService struct {
|
type productWarehouseService struct {
|
||||||
Log *logrus.Logger
|
Log *logrus.Logger
|
||||||
Validate *validator.Validate
|
Validate *validator.Validate
|
||||||
Repository repository.ProductWarehouseRepository
|
Repository repository.ProductWarehouseRepository
|
||||||
|
KandangRepo kandangrepo.KandangRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProductWarehouseService(repo repository.ProductWarehouseRepository, validate *validator.Validate) ProductWarehouseService {
|
func NewProductWarehouseService(repo repository.ProductWarehouseRepository, validate *validator.Validate, kandangRepo kandangrepo.KandangRepository) ProductWarehouseService {
|
||||||
return &productWarehouseService{
|
return &productWarehouseService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
Repository: repo,
|
Repository: repo,
|
||||||
|
KandangRepo: kandangRepo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +72,16 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.KandangId > 0 {
|
||||||
|
isKandangExist, err := s.KandangRepo.IdExists(c.Context(), params.KandangId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if !isKandangExist {
|
||||||
|
return nil, 0, fiber.NewError(fiber.StatusNotFound, "Kandang not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
offset := (params.Page - 1) * params.Limit
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
cleanFlags := utils.ParseFlags(params.Flags)
|
cleanFlags := utils.ParseFlags(params.Flags)
|
||||||
@@ -80,6 +93,11 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
|
|||||||
db = db.Where("product_id = ?", params.ProductId)
|
db = db.Where("product_id = ?", params.ProductId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.KandangId != 0 {
|
||||||
|
db = db.Joins("JOIN warehouses ON product_warehouses.warehouse_id = warehouses.id").
|
||||||
|
Where("warehouses.kandang_id = ?", params.KandangId)
|
||||||
|
}
|
||||||
|
|
||||||
if params.WarehouseId != 0 {
|
if params.WarehouseId != 0 {
|
||||||
db = db.Where("warehouse_id = ?", params.WarehouseId)
|
db = db.Where("warehouse_id = ?", params.WarehouseId)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -18,4 +18,5 @@ type Query struct {
|
|||||||
ProductId uint `query:"product_id" validate:"omitempty,number,min=1"`
|
ProductId uint `query:"product_id" validate:"omitempty,number,min=1"`
|
||||||
WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"`
|
WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"`
|
||||||
Flags string `query:"flags" validate:"omitempty"`
|
Flags string `query:"flags" validate:"omitempty"`
|
||||||
|
KandangId uint `query:"kandang_id" validate:"omitempty,number,min=1"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type TransferBaseDTO struct {
|
type TransferRelationDTO struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
TransferReason string `json:"transfer_reason"`
|
TransferReason string `json:"transfer_reason"`
|
||||||
TransferDate string `json:"transfer_date"`
|
TransferDate string `json:"transfer_date"`
|
||||||
@@ -51,12 +51,12 @@ type WarehouseDetailDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TransferListDTO struct {
|
type TransferListDTO struct {
|
||||||
TransferBaseDTO
|
TransferRelationDTO
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
Details []TransferDetailItemDTO `json:"details"`
|
Details []TransferDetailItemDTO `json:"details"`
|
||||||
Deliveries []TransferDeliveryDTO `json:"deliveries"`
|
Deliveries []TransferDeliveryDTO `json:"deliveries"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TransferDetailDTO struct {
|
type TransferDetailDTO struct {
|
||||||
@@ -93,7 +93,7 @@ type TransferDeliveryItemDTO struct {
|
|||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToTransferBaseDTO(e entity.StockTransfer) TransferBaseDTO {
|
func ToTransferRelationDTO(e entity.StockTransfer) TransferRelationDTO {
|
||||||
|
|
||||||
var sourceWarehouse *WarehouseDetailDTO
|
var sourceWarehouse *WarehouseDetailDTO
|
||||||
if e.FromWarehouse != nil && e.FromWarehouse.Id != 0 {
|
if e.FromWarehouse != nil && e.FromWarehouse.Id != 0 {
|
||||||
@@ -103,7 +103,7 @@ func ToTransferBaseDTO(e entity.StockTransfer) TransferBaseDTO {
|
|||||||
if e.ToWarehouse != nil && e.ToWarehouse.Id != 0 {
|
if e.ToWarehouse != nil && e.ToWarehouse.Id != 0 {
|
||||||
destinationWarehouse = toWarehouseDetailDTO(e.ToWarehouse)
|
destinationWarehouse = toWarehouseDetailDTO(e.ToWarehouse)
|
||||||
}
|
}
|
||||||
return TransferBaseDTO{
|
return TransferRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
TransferReason: e.Reason,
|
TransferReason: e.Reason,
|
||||||
TransferDate: e.CreatedAt.Format("2006-01-02"),
|
TransferDate: e.CreatedAt.Format("2006-01-02"),
|
||||||
@@ -145,9 +145,9 @@ func toWarehouseDetailDTO(w *entity.Warehouse) *WarehouseDetailDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToTransferListDTO(e entity.StockTransfer) TransferListDTO {
|
func ToTransferListDTO(e entity.StockTransfer) TransferListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser != nil {
|
if e.CreatedUser != nil {
|
||||||
mapped := userDTO.ToUserBaseDTO(*e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(*e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
// Map details
|
// Map details
|
||||||
@@ -190,12 +190,12 @@ func ToTransferListDTO(e entity.StockTransfer) TransferListDTO {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return TransferListDTO{
|
return TransferListDTO{
|
||||||
TransferBaseDTO: ToTransferBaseDTO(e),
|
TransferRelationDTO: ToTransferRelationDTO(e),
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
Details: details,
|
Details: details,
|
||||||
Deliveries: deliveries,
|
Deliveries: deliveries,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/dto"
|
||||||
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/services"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeliveryOrdersController struct {
|
||||||
|
DeliveryOrdersService service.DeliveryOrdersService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeliveryOrdersController(deliveryOrdersService service.DeliveryOrdersService) *DeliveryOrdersController {
|
||||||
|
return &DeliveryOrdersController{
|
||||||
|
DeliveryOrdersService: deliveryOrdersService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DeliveryOrdersController) GetAll(c *fiber.Ctx) error {
|
||||||
|
query := &validation.Query{
|
||||||
|
Page: c.QueryInt("page", 1),
|
||||||
|
Limit: c.QueryInt("limit", 10),
|
||||||
|
MarketingId: uint(c.QueryInt("marketing_id", 0)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, totalResults, err := u.DeliveryOrdersService.GetAll(c, query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.SuccessWithPaginate[dto.MarketingListDTO]{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get all deliveryOrderss successfully",
|
||||||
|
Meta: response.Meta{
|
||||||
|
Page: query.Page,
|
||||||
|
Limit: query.Limit,
|
||||||
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||||
|
TotalResults: totalResults,
|
||||||
|
},
|
||||||
|
Data: result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DeliveryOrdersController) 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.DeliveryOrdersService.GetOne(c, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get deliveryOrders successfully",
|
||||||
|
Data: *result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DeliveryOrdersController) CreateOne(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Create)
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.DeliveryOrdersService.CreateOne(c, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusCreated,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Create delivery products successfully",
|
||||||
|
Data: result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *DeliveryOrdersController) UpdateOne(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Update)
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.DeliveryOrdersService.UpdateOne(c, req, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Update deliveryOrders successfully",
|
||||||
|
Data: result,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
|
||||||
|
productwarehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/dto"
|
||||||
|
customerDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/dto"
|
||||||
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MarketingRelationDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
SoNumber string `json:"so_number"`
|
||||||
|
SoDate time.Time `json:"so_date"`
|
||||||
|
Notes string `json:"notes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarketingListDTO struct {
|
||||||
|
MarketingRelationDTO
|
||||||
|
Customer customerDTO.CustomerRelationDTO `json:"customer"`
|
||||||
|
SalesPerson userDTO.UserRelationDTO `json:"sales_person"`
|
||||||
|
SoDocs string `json:"so_docs"`
|
||||||
|
SalesOrder []MarketingProductDTO `json:"sales_order"`
|
||||||
|
CreatedUser userDTO.UserRelationDTO `json:"created_user"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
LatestApproval approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarketingDetailDTO struct {
|
||||||
|
MarketingRelationDTO
|
||||||
|
Customer customerDTO.CustomerRelationDTO `json:"customer"`
|
||||||
|
SalesPerson userDTO.UserRelationDTO `json:"sales_person"`
|
||||||
|
SoDocs string `json:"so_docs"`
|
||||||
|
SalesOrder []MarketingProductDTO `json:"sales_order"`
|
||||||
|
DeliveryOrder []DeliveryGroupDTO `json:"delivery_order"`
|
||||||
|
CreatedUser userDTO.UserRelationDTO `json:"created_user"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
LatestApproval approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
|
||||||
|
}
|
||||||
|
type MarketingDeliveryProductDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
MarketingProductId uint `json:"marketing_product_id"`
|
||||||
|
Qty float64 `json:"qty"`
|
||||||
|
UnitPrice float64 `json:"unit_price"`
|
||||||
|
TotalWeight float64 `json:"total_weight"`
|
||||||
|
AvgWeight float64 `json:"avg_weight"`
|
||||||
|
TotalPrice float64 `json:"total_price"`
|
||||||
|
DeliveryDate *time.Time `json:"delivery_date"`
|
||||||
|
VehicleNumber string `json:"vehicle_number"`
|
||||||
|
ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeliveryItemDTO struct {
|
||||||
|
ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse"`
|
||||||
|
Qty float64 `json:"qty"`
|
||||||
|
UnitPrice float64 `json:"unit_price"`
|
||||||
|
TotalWeight float64 `json:"total_weight"`
|
||||||
|
AvgWeight float64 `json:"avg_weight"`
|
||||||
|
TotalPrice float64 `json:"total_price"`
|
||||||
|
VehicleNumber string `json:"vehicle_number"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeliveryGroupDTO struct {
|
||||||
|
DoNumber string `json:"do_number"`
|
||||||
|
DeliveryDate *time.Time `json:"delivery_date"`
|
||||||
|
Warehouse *productwarehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
|
||||||
|
Deliveries []DeliveryItemDTO `json:"deliveries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarketingProductDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
MarketingId uint `json:"marketing_id"`
|
||||||
|
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||||
|
Qty float64 `json:"qty"`
|
||||||
|
UnitPrice float64 `json:"unit_price"`
|
||||||
|
AvgWeight float64 `json:"avg_weight"`
|
||||||
|
TotalWeight float64 `json:"total_weight"`
|
||||||
|
TotalPrice float64 `json:"total_price"`
|
||||||
|
ProductWarehouse *productwarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"`
|
||||||
|
VehicleNumber string `json:"vehicle_number,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToMarketingRelationDTO(marketing *entity.Marketing) MarketingRelationDTO {
|
||||||
|
return MarketingRelationDTO{
|
||||||
|
Id: marketing.Id,
|
||||||
|
SoNumber: marketing.SoNumber,
|
||||||
|
SoDate: marketing.SoDate,
|
||||||
|
Notes: marketing.Notes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToMarketingProductDTO(e entity.MarketingProduct) MarketingProductDTO {
|
||||||
|
var productWarehouse *productwarehouseDTO.ProductWarehousNestedDTO
|
||||||
|
if e.ProductWarehouse.Id != 0 {
|
||||||
|
mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(e.ProductWarehouse)
|
||||||
|
productWarehouse = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
return MarketingProductDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
MarketingId: e.MarketingId,
|
||||||
|
ProductWarehouseId: e.ProductWarehouseId,
|
||||||
|
Qty: e.Qty,
|
||||||
|
UnitPrice: e.UnitPrice,
|
||||||
|
AvgWeight: e.AvgWeight,
|
||||||
|
TotalWeight: e.TotalWeight,
|
||||||
|
TotalPrice: e.TotalPrice,
|
||||||
|
ProductWarehouse: productWarehouse,
|
||||||
|
VehicleNumber: getVehicleNumber(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToMarketingDeliveryProductDTO(e entity.MarketingDeliveryProduct) MarketingDeliveryProductDTO {
|
||||||
|
return MarketingDeliveryProductDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
MarketingProductId: e.MarketingProductId,
|
||||||
|
Qty: e.Qty,
|
||||||
|
UnitPrice: e.UnitPrice,
|
||||||
|
TotalWeight: e.TotalWeight,
|
||||||
|
AvgWeight: e.AvgWeight,
|
||||||
|
TotalPrice: e.TotalPrice,
|
||||||
|
DeliveryDate: e.DeliveryDate,
|
||||||
|
VehicleNumber: e.VehicleNumber,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToMarketingListDTO(marketing *entity.Marketing, deliveryProducts []entity.MarketingDeliveryProduct) MarketingListDTO {
|
||||||
|
var createdUser userDTO.UserRelationDTO
|
||||||
|
if marketing.CreatedUser.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserRelationDTO(marketing.CreatedUser)
|
||||||
|
createdUser = mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var customer customerDTO.CustomerRelationDTO
|
||||||
|
if marketing.Customer.Id != 0 {
|
||||||
|
mapped := customerDTO.ToCustomerRelationDTO(marketing.Customer)
|
||||||
|
customer = mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var salesPerson userDTO.UserRelationDTO
|
||||||
|
if marketing.SalesPerson.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserRelationDTO(marketing.SalesPerson)
|
||||||
|
salesPerson = mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var latestApproval approvalDTO.ApprovalRelationDTO
|
||||||
|
if marketing.LatestApproval != nil {
|
||||||
|
mapped := approvalDTO.ToApprovalDTO(*marketing.LatestApproval)
|
||||||
|
latestApproval = mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var salesOrderProducts []MarketingProductDTO
|
||||||
|
if len(marketing.Products) > 0 {
|
||||||
|
salesOrderProducts = make([]MarketingProductDTO, len(marketing.Products))
|
||||||
|
for i, product := range marketing.Products {
|
||||||
|
salesOrderProducts[i] = ToMarketingProductDTO(product)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MarketingListDTO{
|
||||||
|
MarketingRelationDTO: ToMarketingRelationDTO(marketing),
|
||||||
|
Customer: customer,
|
||||||
|
SalesPerson: salesPerson,
|
||||||
|
SoDocs: marketing.SoDocs,
|
||||||
|
SalesOrder: salesOrderProducts,
|
||||||
|
CreatedUser: createdUser,
|
||||||
|
CreatedAt: marketing.CreatedAt,
|
||||||
|
UpdatedAt: marketing.UpdatedAt,
|
||||||
|
LatestApproval: latestApproval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToMarketingDetailDTO(marketing *entity.Marketing, deliveryProducts []entity.MarketingDeliveryProduct) MarketingDetailDTO {
|
||||||
|
var createdUser userDTO.UserRelationDTO
|
||||||
|
if marketing.CreatedUser.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserRelationDTO(marketing.CreatedUser)
|
||||||
|
createdUser = mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var customer customerDTO.CustomerRelationDTO
|
||||||
|
if marketing.Customer.Id != 0 {
|
||||||
|
mapped := customerDTO.ToCustomerRelationDTO(marketing.Customer)
|
||||||
|
customer = mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var salesPerson userDTO.UserRelationDTO
|
||||||
|
if marketing.SalesPerson.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserRelationDTO(marketing.SalesPerson)
|
||||||
|
salesPerson = mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var salesOrderProducts []MarketingProductDTO
|
||||||
|
if len(marketing.Products) > 0 {
|
||||||
|
salesOrderProducts = make([]MarketingProductDTO, len(marketing.Products))
|
||||||
|
for i, product := range marketing.Products {
|
||||||
|
salesOrderProducts[i] = ToMarketingProductDTO(product)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var deliveryProductsDTOs []MarketingDeliveryProductDTO
|
||||||
|
if len(deliveryProducts) > 0 {
|
||||||
|
deliveryProductsDTOs = make([]MarketingDeliveryProductDTO, len(deliveryProducts))
|
||||||
|
for i, dp := range deliveryProducts {
|
||||||
|
deliveryProductsDTOs[i] = ToMarketingDeliveryProductDTO(dp)
|
||||||
|
}
|
||||||
|
deliveryProductsDTOs = enrichDeliveryProductDTOsWithWarehouse(deliveryProductsDTOs, marketing)
|
||||||
|
}
|
||||||
|
|
||||||
|
deliveryGroups := groupDeliveryProducts(deliveryProductsDTOs, marketing.SoNumber)
|
||||||
|
|
||||||
|
var latestApproval approvalDTO.ApprovalRelationDTO
|
||||||
|
if marketing.LatestApproval != nil {
|
||||||
|
mapped := approvalDTO.ToApprovalDTO(*marketing.LatestApproval)
|
||||||
|
latestApproval = mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
return MarketingDetailDTO{
|
||||||
|
MarketingRelationDTO: ToMarketingRelationDTO(marketing),
|
||||||
|
SoDocs: marketing.SoDocs,
|
||||||
|
Customer: customer,
|
||||||
|
SalesPerson: salesPerson,
|
||||||
|
SalesOrder: salesOrderProducts,
|
||||||
|
DeliveryOrder: deliveryGroups,
|
||||||
|
CreatedUser: createdUser,
|
||||||
|
CreatedAt: marketing.CreatedAt,
|
||||||
|
UpdatedAt: marketing.UpdatedAt,
|
||||||
|
LatestApproval: latestApproval,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToMarketingListDTOs(marketings []entity.Marketing) []MarketingListDTO {
|
||||||
|
result := make([]MarketingListDTO, len(marketings))
|
||||||
|
for i, m := range marketings {
|
||||||
|
result[i] = ToMarketingListDTO(&m, []entity.MarketingDeliveryProduct{})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func enrichDeliveryProductDTOsWithWarehouse(deliveryProductDTOs []MarketingDeliveryProductDTO, marketing *entity.Marketing) []MarketingDeliveryProductDTO {
|
||||||
|
if len(deliveryProductDTOs) == 0 || marketing == nil || len(marketing.Products) == 0 {
|
||||||
|
return deliveryProductDTOs
|
||||||
|
}
|
||||||
|
|
||||||
|
productMap := make(map[uint]*entity.MarketingProduct)
|
||||||
|
for i := range marketing.Products {
|
||||||
|
productMap[marketing.Products[i].Id] = &marketing.Products[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range deliveryProductDTOs {
|
||||||
|
if product, exists := productMap[deliveryProductDTOs[i].MarketingProductId]; exists {
|
||||||
|
if product.ProductWarehouse.Id != 0 {
|
||||||
|
mapped := productwarehouseDTO.ToProductWarehouseNestedDTO(product.ProductWarehouse)
|
||||||
|
deliveryProductDTOs[i].ProductWarehouse = &mapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deliveryProductDTOs
|
||||||
|
}
|
||||||
|
|
||||||
|
func groupDeliveryProducts(products []MarketingDeliveryProductDTO, soNumber string) []DeliveryGroupDTO {
|
||||||
|
groupMap := make(map[string]*DeliveryGroupDTO)
|
||||||
|
|
||||||
|
for _, product := range products {
|
||||||
|
if product.DeliveryDate == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var warehouseId uint
|
||||||
|
var warehouseName string
|
||||||
|
if product.ProductWarehouse != nil {
|
||||||
|
warehouseId = product.ProductWarehouse.Warehouse.Id
|
||||||
|
warehouseName = product.ProductWarehouse.Warehouse.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
key := fmt.Sprintf("%d_%s", warehouseId, product.DeliveryDate.Format("2006-01-02"))
|
||||||
|
|
||||||
|
group, exists := groupMap[key]
|
||||||
|
if !exists {
|
||||||
|
group = &DeliveryGroupDTO{
|
||||||
|
DeliveryDate: product.DeliveryDate,
|
||||||
|
Warehouse: &productwarehouseDTO.WarehouseRelationDTO{
|
||||||
|
Id: warehouseId,
|
||||||
|
Name: warehouseName,
|
||||||
|
},
|
||||||
|
Deliveries: []DeliveryItemDTO{},
|
||||||
|
}
|
||||||
|
groupMap[key] = group
|
||||||
|
}
|
||||||
|
|
||||||
|
deliveryItem := DeliveryItemDTO{
|
||||||
|
ProductWarehouse: product.ProductWarehouse,
|
||||||
|
Qty: product.Qty,
|
||||||
|
UnitPrice: product.UnitPrice,
|
||||||
|
TotalWeight: product.TotalWeight,
|
||||||
|
AvgWeight: product.AvgWeight,
|
||||||
|
TotalPrice: product.TotalPrice,
|
||||||
|
VehicleNumber: product.VehicleNumber,
|
||||||
|
}
|
||||||
|
group.Deliveries = append(group.Deliveries, deliveryItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
var groups []DeliveryGroupDTO
|
||||||
|
for _, group := range groupMap {
|
||||||
|
groups = append(groups, *group)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(groups, func(i, j int) bool {
|
||||||
|
if groups[i].DeliveryDate == nil || groups[j].DeliveryDate == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return groups[i].DeliveryDate.Before(*groups[j].DeliveryDate)
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := range groups {
|
||||||
|
if groups[i].DeliveryDate != nil {
|
||||||
|
dateStr := groups[i].DeliveryDate.Format("20060102")
|
||||||
|
groups[i].DoNumber = fmt.Sprintf("%s-%s-%d", soNumber, dateStr, groups[i].Warehouse.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVehicleNumber(e entity.MarketingProduct) string {
|
||||||
|
if e.DeliveryProduct != nil && e.DeliveryProduct.VehicleNumber != "" {
|
||||||
|
return e.DeliveryProduct.VehicleNumber
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package delivery_orderss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
|
rMarketingDeliveryProduct "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/marketing-delivery-products/repositories"
|
||||||
|
sDeliveryOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/services"
|
||||||
|
rMarketing "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
||||||
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeliveryOrdersModule struct{}
|
||||||
|
|
||||||
|
func (DeliveryOrdersModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
marketingRepo := rMarketing.NewMarketingRepository(db)
|
||||||
|
marketingProductRepo := rMarketing.NewMarketingProductRepository(db)
|
||||||
|
marketingDeliveryProductRepo := rMarketingDeliveryProduct.NewMarketingDeliveryProductRepository(db)
|
||||||
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||||
|
approvalSvc := commonSvc.NewApprovalService(approvalRepo)
|
||||||
|
|
||||||
|
// Register workflow steps for MARKETINGS approval
|
||||||
|
if err := approvalSvc.RegisterWorkflowSteps(utils.ApprovalWorkflowMarketing, utils.MarketingApprovalSteps); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to register marketing approval workflow: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
deliveryOrdersService := sDeliveryOrders.NewDeliveryOrdersService(marketingRepo, marketingProductRepo, marketingDeliveryProductRepo, approvalSvc, validate)
|
||||||
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
DeliveryOrdersRoutes(router, userService, deliveryOrdersService)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package delivery_orderss
|
||||||
|
|
||||||
|
import (
|
||||||
|
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/controllers"
|
||||||
|
deliveryOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/services"
|
||||||
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeliveryOrdersRoutes(v1 fiber.Router, u user.UserService, s deliveryOrders.DeliveryOrdersService) {
|
||||||
|
ctrl := controller.NewDeliveryOrdersController(s)
|
||||||
|
|
||||||
|
v1.Get("/", ctrl.GetAll)
|
||||||
|
v1.Get("/:id", ctrl.GetOne)
|
||||||
|
|
||||||
|
// Sisanya di group /delivery-orders
|
||||||
|
route := v1.Group("/delivery-orders")
|
||||||
|
|
||||||
|
// 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.Post("/", ctrl.CreateOne)
|
||||||
|
route.Patch("/:id", ctrl.UpdateOne)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,427 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
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"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/dto"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss/validations"
|
||||||
|
marketingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
||||||
|
"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 DeliveryOrdersService interface {
|
||||||
|
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]dto.MarketingListDTO, int64, error)
|
||||||
|
GetOne(ctx *fiber.Ctx, id uint) (*dto.MarketingDetailDTO, error)
|
||||||
|
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*dto.MarketingDetailDTO, error)
|
||||||
|
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*dto.MarketingDetailDTO, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type deliveryOrdersService struct {
|
||||||
|
Log *logrus.Logger
|
||||||
|
Validate *validator.Validate
|
||||||
|
MarketingRepo marketingRepo.MarketingRepository
|
||||||
|
MarketingProductRepo marketingRepo.MarketingProductRepository
|
||||||
|
MarketingDeliveryProductRepo marketingDeliveryProductRepo.MarketingDeliveryProductRepository
|
||||||
|
ApprovalSvc commonSvc.ApprovalService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeliveryOrdersService(
|
||||||
|
marketingRepo marketingRepo.MarketingRepository,
|
||||||
|
marketingProductRepo marketingRepo.MarketingProductRepository,
|
||||||
|
marketingDeliveryProductRepo marketingDeliveryProductRepo.MarketingDeliveryProductRepository,
|
||||||
|
approvalSvc commonSvc.ApprovalService,
|
||||||
|
validate *validator.Validate,
|
||||||
|
) DeliveryOrdersService {
|
||||||
|
return &deliveryOrdersService{
|
||||||
|
Log: utils.Log,
|
||||||
|
Validate: validate,
|
||||||
|
MarketingRepo: marketingRepo,
|
||||||
|
MarketingProductRepo: marketingProductRepo,
|
||||||
|
MarketingDeliveryProductRepo: marketingDeliveryProductRepo,
|
||||||
|
ApprovalSvc: approvalSvc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s deliveryOrdersService) withRelations(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.
|
||||||
|
Preload("CreatedUser").
|
||||||
|
Preload("Customer").
|
||||||
|
Preload("SalesPerson").
|
||||||
|
Preload("Products.ProductWarehouse.Product").
|
||||||
|
Preload("Products.ProductWarehouse.Warehouse").
|
||||||
|
Preload("Products.DeliveryProduct")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s deliveryOrdersService) getMarketingWithDeliveries(c *fiber.Ctx, marketingId uint) (*dto.MarketingDetailDTO, error) {
|
||||||
|
marketing, err := s.MarketingRepo.GetByID(c.Context(), marketingId, s.withRelations)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch marketing")
|
||||||
|
}
|
||||||
|
|
||||||
|
latestApproval, err := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, marketingId, nil)
|
||||||
|
if err != nil {
|
||||||
|
}
|
||||||
|
marketing.LatestApproval = latestApproval
|
||||||
|
|
||||||
|
allDeliveryProducts, err := s.MarketingDeliveryProductRepo.GetByMarketingId(c.Context(), marketingId)
|
||||||
|
if err != nil {
|
||||||
|
allDeliveryProducts = []entity.MarketingDeliveryProduct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
responseDTO := dto.ToMarketingDetailDTO(marketing, allDeliveryProducts)
|
||||||
|
return &responseDTO, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.MarketingListDTO, int64, error) {
|
||||||
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
|
marketings, total, err := s.MarketingRepo.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
|
db = db.
|
||||||
|
Preload("CreatedUser").
|
||||||
|
Preload("Customer").
|
||||||
|
Preload("SalesPerson").
|
||||||
|
Preload("Products.ProductWarehouse.Product").
|
||||||
|
Preload("Products.ProductWarehouse.Warehouse").
|
||||||
|
Preload("Products.DeliveryProduct")
|
||||||
|
|
||||||
|
if params.MarketingId != 0 {
|
||||||
|
return db.Where("id = ?", params.MarketingId)
|
||||||
|
}
|
||||||
|
return db.Order("created_at DESC").Order("updated_at DESC")
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get marketings: %+v", err)
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
for i := range marketings {
|
||||||
|
latestApproval, err := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, marketings[i].Id, func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Preload("ActionUser")
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Warnf("Failed to load approval for marketing %d: %+v", marketings[i].Id, err)
|
||||||
|
}
|
||||||
|
marketings[i].LatestApproval = latestApproval
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]dto.MarketingListDTO, len(marketings))
|
||||||
|
for i, marketing := range marketings {
|
||||||
|
result[i] = dto.ToMarketingListDTO(&marketing, []entity.MarketingDeliveryProduct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s deliveryOrdersService) GetOne(c *fiber.Ctx, id uint) (*dto.MarketingDetailDTO, error) {
|
||||||
|
|
||||||
|
marketing, err := s.MarketingRepo.GetByID(c.Context(), id, s.withRelations)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Marketing not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allDeliveryProducts, err := s.MarketingDeliveryProductRepo.GetByMarketingId(c.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
allDeliveryProducts = []entity.MarketingDeliveryProduct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.ApprovalSvc != nil {
|
||||||
|
approvals, err := s.ApprovalSvc.ListByTarget(c.Context(), utils.ApprovalWorkflowMarketing, marketing.Id, func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Preload("ActionUser")
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
} else if len(approvals) > 0 {
|
||||||
|
if marketing.LatestApproval == nil {
|
||||||
|
latest := approvals[len(approvals)-1]
|
||||||
|
marketing.LatestApproval = &latest
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
marketing.LatestApproval = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responseDTO := dto.ToMarketingDetailDTO(marketing, allDeliveryProducts)
|
||||||
|
return &responseDTO, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*dto.MarketingDetailDTO, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
|
commonSvc.RelationCheck{Name: "Marketing", ID: &req.MarketingId, Exists: s.MarketingRepo.IdExists},
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.MarketingRepo.DB()))
|
||||||
|
|
||||||
|
latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, req.MarketingId, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
||||||
|
}
|
||||||
|
if latestApproval == nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Marketing has not been submitted for approval")
|
||||||
|
}
|
||||||
|
if latestApproval.StepNumber < uint16(utils.MarketingStepSalesOrder) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Marketing must be approved to Sales Order step before creating delivery order")
|
||||||
|
}
|
||||||
|
if latestApproval.StepNumber >= uint16(utils.MarketingDeliveryOrder) {
|
||||||
|
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 {
|
||||||
|
|
||||||
|
marketingProductRepositoryTx := marketingRepo.NewMarketingProductRepository(dbTransaction)
|
||||||
|
marketingDeliveryProductRepositoryTx := marketingDeliveryProductRepo.NewMarketingDeliveryProductRepository(dbTransaction)
|
||||||
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
|
|
||||||
|
allMarketingProducts, err := marketingProductRepositoryTx.GetByMarketingID(c.Context(), req.MarketingId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("No marketing products found for marketing %d", req.MarketingId))
|
||||||
|
}
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch marketing products")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, requestedProduct := range req.DeliveryProducts {
|
||||||
|
var foundMarketingProduct *entity.MarketingProduct
|
||||||
|
for i := range allMarketingProducts {
|
||||||
|
if allMarketingProducts[i].Id == requestedProduct.MarketingProductId {
|
||||||
|
foundMarketingProduct = &allMarketingProducts[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundMarketingProduct == nil {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Marketing product %d not found for this marketing", requestedProduct.MarketingProductId))
|
||||||
|
}
|
||||||
|
|
||||||
|
deliveryProduct, err := marketingDeliveryProductRepositoryTx.GetByMarketingProductID(c.Context(), foundMarketingProduct.Id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Delivery product for marketing product %d not found", requestedProduct.MarketingProductId))
|
||||||
|
}
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch delivery product")
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemDeliveryDate *time.Time
|
||||||
|
if requestedProduct.DeliveryDate != "" {
|
||||||
|
parsedDate, err := utils.ParseDateString(requestedProduct.DeliveryDate)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid delivery date format for product %d: %v", requestedProduct.MarketingProductId, err))
|
||||||
|
}
|
||||||
|
itemDeliveryDate = &parsedDate
|
||||||
|
}
|
||||||
|
|
||||||
|
deliveryProduct.Qty = requestedProduct.Qty
|
||||||
|
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
|
||||||
|
deliveryProduct.AvgWeight = requestedProduct.AvgWeight
|
||||||
|
deliveryProduct.TotalWeight = requestedProduct.TotalWeight
|
||||||
|
deliveryProduct.TotalPrice = requestedProduct.TotalPrice
|
||||||
|
deliveryProduct.DeliveryDate = itemDeliveryDate
|
||||||
|
deliveryProduct.VehicleNumber = requestedProduct.VehicleNumber
|
||||||
|
|
||||||
|
if requestedProduct.Qty > 0 {
|
||||||
|
if err := s.validateAndReduceProductWarehouse(c.Context(), dbTransaction, foundMarketingProduct, requestedProduct.Qty); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := marketingDeliveryProductRepositoryTx.UpdateOne(c.Context(), deliveryProduct.Id, deliveryProduct, nil); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
actorID := uint(1) // TODO: ambil dari auth context
|
||||||
|
approvalAction := entity.ApprovalActionApproved
|
||||||
|
if _, err := approvalSvcTx.CreateApproval(
|
||||||
|
c.Context(),
|
||||||
|
utils.ApprovalWorkflowMarketing,
|
||||||
|
req.MarketingId,
|
||||||
|
utils.MarketingDeliveryOrder,
|
||||||
|
&approvalAction,
|
||||||
|
actorID,
|
||||||
|
nil); err != nil {
|
||||||
|
if !errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create delivery order approval")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||||
|
return nil, fiberErr
|
||||||
|
}
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create delivery order")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.getMarketingWithDeliveries(c, req.MarketingId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*dto.MarketingDetailDTO, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
|
commonSvc.RelationCheck{Name: "Marketing", ID: &id, Exists: s.MarketingRepo.IdExists},
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
|
|
||||||
|
marketingProductRepositoryTx := marketingRepo.NewMarketingProductRepository(dbTransaction)
|
||||||
|
marketingDeliveryProductRepositoryTx := marketingDeliveryProductRepo.NewMarketingDeliveryProductRepository(dbTransaction)
|
||||||
|
|
||||||
|
allMarketingProducts, err := marketingProductRepositoryTx.GetByMarketingID(c.Context(), id)
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch marketing products")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.DeliveryProducts) > 0 {
|
||||||
|
for _, requestedProduct := range req.DeliveryProducts {
|
||||||
|
|
||||||
|
var foundMarketingProduct *entity.MarketingProduct
|
||||||
|
for i := range allMarketingProducts {
|
||||||
|
if allMarketingProducts[i].Id == requestedProduct.MarketingProductId {
|
||||||
|
foundMarketingProduct = &allMarketingProducts[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundMarketingProduct == nil {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Marketing product %d not found for this marketing", requestedProduct.MarketingProductId))
|
||||||
|
}
|
||||||
|
|
||||||
|
deliveryProduct, err := marketingDeliveryProductRepositoryTx.GetByMarketingProductID(c.Context(), foundMarketingProduct.Id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Delivery product for marketing product %d not found", requestedProduct.MarketingProductId))
|
||||||
|
}
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch delivery product")
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemDeliveryDate *time.Time
|
||||||
|
if requestedProduct.DeliveryDate != "" {
|
||||||
|
parsedDate, err := utils.ParseDateString(requestedProduct.DeliveryDate)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid delivery date format for product %d: %v", requestedProduct.MarketingProductId, err))
|
||||||
|
}
|
||||||
|
itemDeliveryDate = &parsedDate
|
||||||
|
} else if deliveryProduct.DeliveryDate != nil {
|
||||||
|
itemDeliveryDate = deliveryProduct.DeliveryDate
|
||||||
|
}
|
||||||
|
|
||||||
|
oldQty := deliveryProduct.Qty
|
||||||
|
deliveryProduct.Qty = requestedProduct.Qty
|
||||||
|
deliveryProduct.UnitPrice = requestedProduct.UnitPrice
|
||||||
|
deliveryProduct.AvgWeight = requestedProduct.AvgWeight
|
||||||
|
deliveryProduct.TotalWeight = requestedProduct.TotalWeight
|
||||||
|
deliveryProduct.TotalPrice = requestedProduct.TotalPrice
|
||||||
|
deliveryProduct.DeliveryDate = itemDeliveryDate
|
||||||
|
deliveryProduct.VehicleNumber = requestedProduct.VehicleNumber
|
||||||
|
|
||||||
|
qtyChange := requestedProduct.Qty - oldQty
|
||||||
|
if qtyChange > 0 {
|
||||||
|
if err := s.validateAndReduceProductWarehouse(c.Context(), dbTransaction, foundMarketingProduct, qtyChange); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if qtyChange < 0 {
|
||||||
|
if err := s.restoreProductWarehouseStock(c.Context(), dbTransaction, foundMarketingProduct, -qtyChange); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := marketingDeliveryProductRepositoryTx.UpdateOne(c.Context(), deliveryProduct.Id, deliveryProduct, nil); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery product")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||||
|
return nil, fiberErr
|
||||||
|
}
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update delivery order")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.getMarketingWithDeliveries(c, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s deliveryOrdersService) validateAndReduceProductWarehouse(ctx context.Context, tx *gorm.DB, marketingProduct *entity.MarketingProduct, qtyDeliver float64) error {
|
||||||
|
if marketingProduct == nil || marketingProduct.ProductWarehouseId == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Product warehouse not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
pwRepo := productWarehouseRepo.NewProductWarehouseRepository(tx)
|
||||||
|
|
||||||
|
pw, err := pwRepo.GetByID(ctx, marketingProduct.ProductWarehouseId, nil)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Product warehouse not found")
|
||||||
|
}
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check stock")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pw.Quantity < qtyDeliver {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Insufficient stock for warehouse - available: %.2f, requested: %.2f", pw.Quantity, qtyDeliver))
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.Quantity = pw.Quantity - qtyDeliver
|
||||||
|
if err := pwRepo.UpdateOne(ctx, pw.Id, pw, nil); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update stock")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s deliveryOrdersService) restoreProductWarehouseStock(ctx context.Context, tx *gorm.DB, marketingProduct *entity.MarketingProduct, qtyRestore float64) error {
|
||||||
|
if marketingProduct == nil || marketingProduct.ProductWarehouseId == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Product warehouse not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
pwRepo := productWarehouseRepo.NewProductWarehouseRepository(tx)
|
||||||
|
pw, err := pwRepo.GetByID(ctx, marketingProduct.ProductWarehouseId, nil)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Product warehouse not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check stock")
|
||||||
|
}
|
||||||
|
|
||||||
|
pw.Quantity = pw.Quantity + qtyRestore
|
||||||
|
if err := pwRepo.UpdateOne(ctx, pw.Id, pw, nil); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update stock")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
type DeliveryProduct struct {
|
||||||
|
MarketingProductId uint `json:"marketing_product_id" validate:"required,gt=0"`
|
||||||
|
Qty float64 `json:"qty" validate:"omitempty,gte=0"`
|
||||||
|
UnitPrice float64 `json:"unit_price" validate:"omitempty,gte=0"`
|
||||||
|
AvgWeight float64 `json:"avg_weight" validate:"omitempty,gte=0"`
|
||||||
|
TotalWeight float64 `json:"total_weight" validate:"omitempty,gte=0"`
|
||||||
|
TotalPrice float64 `json:"total_price" validate:"omitempty,gte=0"`
|
||||||
|
DeliveryDate string `json:"delivery_date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
|
VehicleNumber string `json:"vehicle_number" validate:"omitempty,max=50"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Create struct {
|
||||||
|
MarketingId uint `json:"marketing_id" validate:"required,gt=0"`
|
||||||
|
DeliveryProducts []DeliveryProduct `json:"delivery_products" validate:"required,min=1,dive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Update struct {
|
||||||
|
DeliveryProducts []DeliveryProduct `json:"delivery_products" validate:"omitempty,min=1,dive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
MarketingId uint `query:"marketing_id" validate:"omitempty,gt=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Approve struct {
|
||||||
|
Action string `json:"action" validate:"required_strict"`
|
||||||
|
ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
|
||||||
|
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package marketing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MarketingModule struct{}
|
||||||
|
|
||||||
|
func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
RegisterRoutes(router, db, validate)
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package marketing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
salesOrderss "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders"
|
||||||
|
deliveryOrderss "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/delivery-orderss"
|
||||||
|
// MODULE IMPORTS
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
group := router.Group("/marketing")
|
||||||
|
|
||||||
|
allModules := []modules.Module{
|
||||||
|
salesOrderss.SalesOrdersModule{},
|
||||||
|
deliveryOrderss.DeliveryOrdersModule{},
|
||||||
|
// MODULE REGISTRY
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range allModules {
|
||||||
|
m.RegisterRoutes(group, db, validate)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/dto"
|
||||||
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SalesOrdersController struct {
|
||||||
|
SalesOrdersService service.SalesOrdersService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSalesOrdersController(salesOrdersService service.SalesOrdersService) *SalesOrdersController {
|
||||||
|
return &SalesOrdersController{
|
||||||
|
SalesOrdersService: salesOrdersService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *SalesOrdersController) CreateOne(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Create)
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.SalesOrdersService.CreateOne(c, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusCreated,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Create salesOrders successfully",
|
||||||
|
Data: dto.ToSalesOrdersListDTOFromMarketing(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *SalesOrdersController) UpdateOne(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Update)
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.SalesOrdersService.UpdateOne(c, req, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Update salesOrders successfully",
|
||||||
|
Data: dto.ToSalesOrdersListDTOFromMarketing(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *SalesOrdersController) DeleteOne(c *fiber.Ctx) error {
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.SalesOrdersService.DeleteOne(c, uint(id)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Common{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Delete salesOrders successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *SalesOrdersController) Approval(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Approve)
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
results, err := u.SalesOrdersService.Approval(c, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
data interface{}
|
||||||
|
message = "Submit sales order approval successfully"
|
||||||
|
)
|
||||||
|
if len(results) == 1 {
|
||||||
|
data = dto.ToSalesOrdersListDTOFromMarketing(results[0])
|
||||||
|
} else {
|
||||||
|
message = "Submit sales order approvals successfully"
|
||||||
|
data = dto.ToSalesOrdersListDTOsFromMarketing(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: message,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
productWarehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/dto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// === DTO Structs ===
|
||||||
|
|
||||||
|
type MarketingProductDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Qty float64 `json:"qty"`
|
||||||
|
UnitPrice float64 `json:"unit_price"`
|
||||||
|
AvgWeight float64 `json:"avg_weight"`
|
||||||
|
TotalWeight float64 `json:"total_weight"`
|
||||||
|
TotalPrice float64 `json:"total_price"`
|
||||||
|
ProductWarehouse *productWarehouseDTO.ProductWarehousNestedDTO `json:"product_warehouse,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SalesOrdersListDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
SoNumber string `json:"so_number"`
|
||||||
|
SoDate time.Time `json:"so_date"`
|
||||||
|
Notes string `json:"notes,omitempty"`
|
||||||
|
SalesOrder []MarketingProductDTO `json:"sales_order,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Mapper Functions ===
|
||||||
|
|
||||||
|
func ToMarketingProductDTO(e entity.MarketingProduct) MarketingProductDTO {
|
||||||
|
var productWarehouse *productWarehouseDTO.ProductWarehousNestedDTO
|
||||||
|
|
||||||
|
if e.ProductWarehouse.Id != 0 {
|
||||||
|
mapped := productWarehouseDTO.ToProductWarehouseNestedDTO(e.ProductWarehouse)
|
||||||
|
productWarehouse = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
return MarketingProductDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
Qty: e.Qty,
|
||||||
|
UnitPrice: e.UnitPrice,
|
||||||
|
AvgWeight: e.AvgWeight,
|
||||||
|
TotalWeight: e.TotalWeight,
|
||||||
|
TotalPrice: e.TotalPrice,
|
||||||
|
ProductWarehouse: productWarehouse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToSalesOrdersListDTO(e entity.Marketing) SalesOrdersListDTO {
|
||||||
|
products := make([]MarketingProductDTO, len(e.Products))
|
||||||
|
for i, p := range e.Products {
|
||||||
|
products[i] = ToMarketingProductDTO(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return SalesOrdersListDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
SoNumber: e.SoNumber,
|
||||||
|
SoDate: e.SoDate,
|
||||||
|
Notes: e.Notes,
|
||||||
|
SalesOrder: products,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToSalesOrdersListDTOFromMarketing(e entity.Marketing) SalesOrdersListDTO {
|
||||||
|
var salesOrder []MarketingProductDTO
|
||||||
|
if len(e.Products) > 0 {
|
||||||
|
salesOrder = make([]MarketingProductDTO, len(e.Products))
|
||||||
|
for i, product := range e.Products {
|
||||||
|
salesOrder[i] = ToMarketingProductDTO(product)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SalesOrdersListDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
SoNumber: e.SoNumber,
|
||||||
|
SoDate: e.SoDate,
|
||||||
|
Notes: e.Notes,
|
||||||
|
SalesOrder: salesOrder,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToSalesOrdersListDTOsFromMarketing(e []entity.Marketing) []SalesOrdersListDTO {
|
||||||
|
result := make([]SalesOrdersListDTO, len(e))
|
||||||
|
for i, r := range e {
|
||||||
|
result[i] = ToSalesOrdersListDTOFromMarketing(r)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package sales_orders
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
|
rSalesOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
||||||
|
sSalesOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services"
|
||||||
|
rCustomer "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
||||||
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SalesOrdersModule struct{}
|
||||||
|
|
||||||
|
func (SalesOrdersModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
marketingRepo := rSalesOrders.NewMarketingRepository(db)
|
||||||
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
customerRepo := rCustomer.NewCustomerRepository(db)
|
||||||
|
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
||||||
|
|
||||||
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(db))
|
||||||
|
|
||||||
|
if err := approvalSvc.RegisterWorkflowSteps(utils.ApprovalWorkflowMarketing, utils.MarketingApprovalSteps); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to register marketing approval workflow: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
salesOrdersService := sSalesOrders.NewSalesOrdersService(marketingRepo, customerRepo, productWarehouseRepo, userRepo, approvalSvc, validate)
|
||||||
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
SalesOrdersRoutes(router, userService, salesOrdersService)
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
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 MarketingDeliveryProductRepository interface {
|
||||||
|
repository.BaseRepository[entity.MarketingDeliveryProduct]
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarketingDeliveryProductRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.MarketingDeliveryProduct]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMarketingDeliveryProductRepository(db *gorm.DB) MarketingDeliveryProductRepository {
|
||||||
|
return &MarketingDeliveryProductRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.MarketingDeliveryProduct](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MarketingProductRepository interface {
|
||||||
|
repository.BaseRepository[entity.MarketingProduct]
|
||||||
|
GetByMarketingID(ctx context.Context, marketingID uint) ([]entity.MarketingProduct, error)
|
||||||
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarketingProductRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.MarketingProduct]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMarketingProductRepository(db *gorm.DB) MarketingProductRepository {
|
||||||
|
return &MarketingProductRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.MarketingProduct](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MarketingProductRepositoryImpl) GetByMarketingID(ctx context.Context, marketingID uint) ([]entity.MarketingProduct, error) {
|
||||||
|
var products []entity.MarketingProduct
|
||||||
|
if err := r.DB().WithContext(ctx).Where("marketing_id = ?", marketingID).Find(&products).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(products) == 0 {
|
||||||
|
return products, gorm.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return products, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MarketingProductRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.MarketingProduct](ctx, r.DB(), id)
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MarketingRepository interface {
|
||||||
|
repository.BaseRepository[entity.Marketing]
|
||||||
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
|
GetNextSequence(ctx context.Context) (uint, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarketingRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.Marketing]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMarketingRepository(db *gorm.DB) MarketingRepository {
|
||||||
|
return &MarketingRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.Marketing](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MarketingRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.Marketing](ctx, r.DB(), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MarketingRepositoryImpl) GetNextSequence(ctx context.Context) (uint, error) {
|
||||||
|
var maxID uint
|
||||||
|
if err := r.DB().WithContext(ctx).Model(&entity.Marketing{}).Select("COALESCE(MAX(id), 0)").Scan(&maxID).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return maxID + 1, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package sales_orders
|
||||||
|
|
||||||
|
import (
|
||||||
|
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/controllers"
|
||||||
|
salesOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services"
|
||||||
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SalesOrdersRoutes(v1 fiber.Router, u user.UserService, s salesOrders.SalesOrdersService) {
|
||||||
|
ctrl := controller.NewSalesOrdersController(s)
|
||||||
|
|
||||||
|
v1.Delete("/:id", ctrl.DeleteOne)
|
||||||
|
route := v1.Group("/sales-orders")
|
||||||
|
|
||||||
|
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||||
|
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
||||||
|
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
||||||
|
|
||||||
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
route.Patch("/:id", ctrl.UpdateOne)
|
||||||
|
|
||||||
|
route.Post("/approvals", ctrl.Approval)
|
||||||
|
}
|
||||||
@@ -0,0 +1,548 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
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"
|
||||||
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/validations"
|
||||||
|
customerRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories"
|
||||||
|
userRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SalesOrdersService interface {
|
||||||
|
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Marketing, error)
|
||||||
|
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Marketing, error)
|
||||||
|
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||||
|
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.Marketing, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type salesOrdersService struct {
|
||||||
|
Log *logrus.Logger
|
||||||
|
Validate *validator.Validate
|
||||||
|
MarketingRepo repository.MarketingRepository
|
||||||
|
CustomerRepo customerRepo.CustomerRepository
|
||||||
|
ProductWarehouseRepo productWarehouseRepo.ProductWarehouseRepository
|
||||||
|
UserRepo userRepo.UserRepository
|
||||||
|
ApprovalSvc commonSvc.ApprovalService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSalesOrdersService(marketingRepo repository.MarketingRepository, customerRepo customerRepo.CustomerRepository, productWarehouseRepo productWarehouseRepo.ProductWarehouseRepository, userRepo userRepo.UserRepository, approvalSvc commonSvc.ApprovalService, validate *validator.Validate) SalesOrdersService {
|
||||||
|
return &salesOrdersService{
|
||||||
|
Log: utils.Log,
|
||||||
|
Validate: validate,
|
||||||
|
MarketingRepo: marketingRepo,
|
||||||
|
CustomerRepo: customerRepo,
|
||||||
|
ProductWarehouseRepo: productWarehouseRepo,
|
||||||
|
UserRepo: userRepo,
|
||||||
|
ApprovalSvc: approvalSvc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s salesOrdersService) withRelations(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.
|
||||||
|
Preload("CreatedUser").
|
||||||
|
Preload("Customer").
|
||||||
|
Preload("SalesPerson").
|
||||||
|
Preload("Products.ProductWarehouse.Product.Flags").
|
||||||
|
Preload("Products.ProductWarehouse.Warehouse")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s salesOrdersService) getOne(c *fiber.Ctx, id uint) (*entity.Marketing, error) {
|
||||||
|
marketing, err := s.MarketingRepo.GetByID(c.Context(), id, s.withRelations)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "SalesOrders not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed get marketing by id: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sales order")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.ApprovalSvc != nil {
|
||||||
|
approvals, err := s.ApprovalSvc.ListByTarget(c.Context(), utils.ApprovalWorkflowMarketing, id, func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Preload("ActionUser")
|
||||||
|
})
|
||||||
|
if err == nil && len(approvals) > 0 {
|
||||||
|
latest := approvals[len(approvals)-1]
|
||||||
|
marketing.LatestApproval = &latest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return marketing, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Marketing, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
|
commonSvc.RelationCheck{Name: "Customer", ID: &req.CustomerId, Exists: s.CustomerRepo.IdExists},
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range req.MarketingProducts {
|
||||||
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
|
commonSvc.RelationCheck{Name: "ProductWarehouse", ID: &item.ProductWarehouseId, Exists: s.ProductWarehouseRepo.IdExists},
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
soDate, err := utils.ParseDateString(req.Date)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid date format")
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSeq, err := s.MarketingRepo.GetNextSequence(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to generate SO number")
|
||||||
|
}
|
||||||
|
soNumber := fmt.Sprintf("SO-%05d", nextSeq)
|
||||||
|
|
||||||
|
var marketing *entity.Marketing
|
||||||
|
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
|
|
||||||
|
marketingRepoTx := repository.NewMarketingRepository(dbTransaction)
|
||||||
|
marketingProductRepoTx := repository.NewMarketingProductRepository(dbTransaction)
|
||||||
|
invDeliveryRepoTx := rInvMarketingDeliveryProduct.NewMarketingDeliveryProductRepository(dbTransaction)
|
||||||
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
|
|
||||||
|
marketing = &entity.Marketing{
|
||||||
|
CustomerId: req.CustomerId,
|
||||||
|
SoNumber: soNumber,
|
||||||
|
SoDate: soDate,
|
||||||
|
SalesPersonId: req.SalesPersonId,
|
||||||
|
Notes: req.Notes,
|
||||||
|
CreatedBy: 1,
|
||||||
|
}
|
||||||
|
if err := marketingRepoTx.CreateOne(c.Context(), marketing, nil); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create salesOrders")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.MarketingProducts) > 0 {
|
||||||
|
for _, product := range req.MarketingProducts {
|
||||||
|
if err := s.createMarketingProductWithDelivery(c.Context(), marketing.Id, product, marketingProductRepoTx, invDeliveryRepoTx); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create marketing product")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actorID := uint(1) // TODO: ambil dari auth context
|
||||||
|
approvalAction := entity.ApprovalActionCreated
|
||||||
|
if _, err := approvalSvcTx.CreateApproval(
|
||||||
|
c.Context(),
|
||||||
|
utils.ApprovalWorkflowMarketing,
|
||||||
|
marketing.Id,
|
||||||
|
utils.MarketingStepPengajuan,
|
||||||
|
&approvalAction,
|
||||||
|
actorID,
|
||||||
|
nil); err != nil {
|
||||||
|
if !errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
fiber.NewError(fiber.StatusInternalServerError, "Failed to create approval")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||||
|
return nil, fiberErr
|
||||||
|
}
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create salesOrders")
|
||||||
|
}
|
||||||
|
|
||||||
|
marketing, err = s.MarketingRepo.GetByID(c.Context(), marketing.Id, s.withRelations)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch created sales order")
|
||||||
|
}
|
||||||
|
|
||||||
|
return marketing, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Marketing, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
|
commonSvc.RelationCheck{Name: "Marketing", ID: &id, Exists: s.MarketingRepo.IdExists},
|
||||||
|
commonSvc.RelationCheck{Name: "Customer", ID: &req.CustomerId, Exists: s.CustomerRepo.IdExists},
|
||||||
|
commonSvc.RelationCheck{Name: "SalesPerson", ID: &req.SalesPersonId, Exists: s.UserRepo.IdExists},
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
latestApproval, err := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
||||||
|
}
|
||||||
|
if latestApproval != nil && latestApproval.StepNumber >= 3 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Cannot update sales order after delivery order approval")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.MarketingProducts) > 0 {
|
||||||
|
for _, item := range req.MarketingProducts {
|
||||||
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
|
commonSvc.RelationCheck{Name: "ProductWarehouse", ID: &item.ProductWarehouseId, Exists: s.ProductWarehouseRepo.IdExists},
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
|
|
||||||
|
marketingRepoTx := repository.NewMarketingRepository(dbTransaction)
|
||||||
|
marketingProductRepoTx := repository.NewMarketingProductRepository(dbTransaction)
|
||||||
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
|
invDeliveryRepoTx := rInvMarketingDeliveryProduct.NewMarketingDeliveryProductRepository(dbTransaction)
|
||||||
|
|
||||||
|
updateBody := make(map[string]any)
|
||||||
|
if req.CustomerId != 0 {
|
||||||
|
updateBody["customer_id"] = req.CustomerId
|
||||||
|
}
|
||||||
|
if req.SalesPersonId != 0 {
|
||||||
|
updateBody["sales_person_id"] = req.SalesPersonId
|
||||||
|
}
|
||||||
|
if req.Date != "" {
|
||||||
|
soDate, err := utils.ParseDateString(req.Date)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid date format")
|
||||||
|
}
|
||||||
|
updateBody["so_date"] = soDate
|
||||||
|
}
|
||||||
|
if req.Notes != "" {
|
||||||
|
updateBody["notes"] = req.Notes
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(updateBody) > 0 {
|
||||||
|
if err := marketingRepoTx.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update sales order")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.MarketingProducts) > 0 {
|
||||||
|
|
||||||
|
oldProducts, err := marketingProductRepoTx.GetByMarketingID(c.Context(), id)
|
||||||
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch existing products")
|
||||||
|
}
|
||||||
|
|
||||||
|
oldByPW := make(map[uint]*entity.MarketingProduct)
|
||||||
|
for i := range oldProducts {
|
||||||
|
p := oldProducts[i]
|
||||||
|
oldByPW[p.ProductWarehouseId] = &p
|
||||||
|
}
|
||||||
|
|
||||||
|
reqByPW := make(map[uint]validation.CreateMarketingProduct)
|
||||||
|
for _, rp := range req.MarketingProducts {
|
||||||
|
reqByPW[rp.ProductWarehouseId] = rp
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rp := range req.MarketingProducts {
|
||||||
|
if old, ok := oldByPW[rp.ProductWarehouseId]; ok {
|
||||||
|
|
||||||
|
updateBody := map[string]any{
|
||||||
|
"product_warehouse_id": rp.ProductWarehouseId,
|
||||||
|
"qty": rp.Qty,
|
||||||
|
"unit_price": rp.UnitPrice,
|
||||||
|
"avg_weight": rp.AvgWeight,
|
||||||
|
"total_weight": rp.TotalWeight,
|
||||||
|
"total_price": rp.TotalPrice,
|
||||||
|
}
|
||||||
|
if err := marketingProductRepoTx.PatchOne(c.Context(), old.Id, updateBody, nil); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update marketing product")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := invDeliveryRepoTx.GetByMarketingProductID(c.Context(), old.Id); err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
mdp := &entity.MarketingDeliveryProduct{
|
||||||
|
MarketingProductId: old.Id,
|
||||||
|
Qty: 0,
|
||||||
|
UnitPrice: 0,
|
||||||
|
TotalWeight: 0,
|
||||||
|
AvgWeight: 0,
|
||||||
|
TotalPrice: 0,
|
||||||
|
DeliveryDate: nil,
|
||||||
|
VehicleNumber: rp.VehicleNumber,
|
||||||
|
}
|
||||||
|
if err := invDeliveryRepoTx.CreateOne(c.Context(), mdp, nil); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create marketing delivery product")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check delivery product")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := s.createMarketingProductWithDelivery(c.Context(), id, rp, marketingProductRepoTx, invDeliveryRepoTx); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create marketing product")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, old := range oldProducts {
|
||||||
|
if _, ok := reqByPW[old.ProductWarehouseId]; !ok {
|
||||||
|
|
||||||
|
deliveryProduct, err := invDeliveryRepoTx.GetByMarketingProductID(c.Context(), old.Id)
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check existing delivery product")
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
if deliveryProduct.DeliveryDate != nil || deliveryProduct.Qty > 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Cannot delete marketing product %d because it has delivery records", old.Id))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := invDeliveryRepoTx.DeleteOne(c.Context(), deliveryProduct.Id); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete marketing delivery product")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := marketingProductRepoTx.DeleteOne(c.Context(), old.Id); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete marketing product")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if latestApproval != nil {
|
||||||
|
actorID := uint(1) // todo: ambil dari auth context
|
||||||
|
action := entity.ApprovalActionUpdated
|
||||||
|
_, err := approvalSvcTx.CreateApproval(
|
||||||
|
c.Context(),
|
||||||
|
utils.ApprovalWorkflowMarketing,
|
||||||
|
id,
|
||||||
|
approvalutils.ApprovalStep(latestApproval.StepNumber),
|
||||||
|
&action,
|
||||||
|
actorID,
|
||||||
|
nil)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create update approval")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||||
|
return nil, fiberErr
|
||||||
|
}
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update sales order")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.getOne(c, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s salesOrdersService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||||
|
marketing, err := s.MarketingRepo.GetByID(c.Context(), id, s.withRelations)
|
||||||
|
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "SalesOrders not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch sales order")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
|
|
||||||
|
marketingProductRepoTx := repository.NewMarketingProductRepository(dbTransaction)
|
||||||
|
marketingDeliveryProductRepoTx := repository.NewMarketingDeliveryProductRepository(dbTransaction)
|
||||||
|
marketingRepoTx := repository.NewMarketingRepository(dbTransaction)
|
||||||
|
|
||||||
|
if len(marketing.Products) > 0 {
|
||||||
|
for _, product := range marketing.Products {
|
||||||
|
if err := marketingDeliveryProductRepoTx.DeleteMany(c.Context(), func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("marketing_product_id = ?", product.Id).Unscoped()
|
||||||
|
}); err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete sales order products")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := marketingProductRepoTx.DeleteMany(c.Context(), func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Where("marketing_id = ?", id).Unscoped()
|
||||||
|
}); err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete sales order products")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := marketingRepoTx.DeleteOne(c.Context(), id); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete sales order")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||||
|
return fiberErr
|
||||||
|
}
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete sales order")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.Marketing, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.MarketingRepo.DB()))
|
||||||
|
|
||||||
|
var action entity.ApprovalAction
|
||||||
|
switch strings.ToUpper(strings.TrimSpace(req.Action)) {
|
||||||
|
case string(entity.ApprovalActionRejected):
|
||||||
|
action = entity.ApprovalActionRejected
|
||||||
|
case string(entity.ApprovalActionApproved):
|
||||||
|
action = entity.ApprovalActionApproved
|
||||||
|
default:
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "action must be APPROVED or REJECTED")
|
||||||
|
}
|
||||||
|
|
||||||
|
approvableIDs := utils.UniqueUintSlice(req.ApprovableIds)
|
||||||
|
if len(approvableIDs) == 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range approvableIDs {
|
||||||
|
if err := commonSvc.EnsureRelations(c.Context(),
|
||||||
|
commonSvc.RelationCheck{Name: "Marketing", ID: &id, Exists: s.MarketingRepo.IdExists},
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check approval status")
|
||||||
|
}
|
||||||
|
if latestApproval == nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No approval found for Marketing %d - sales orders must be created first", id))
|
||||||
|
}
|
||||||
|
|
||||||
|
if action == entity.ApprovalActionApproved {
|
||||||
|
switch latestApproval.StepNumber {
|
||||||
|
case uint16(utils.MarketingStepPengajuan):
|
||||||
|
case uint16(utils.MarketingStepSalesOrder):
|
||||||
|
default:
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest,
|
||||||
|
fmt.Sprintf("Marketing %d cannot be approved - current step is %d", id, latestApproval.StepNumber))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
|
|
||||||
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
|
|
||||||
|
for _, approvableID := range approvableIDs {
|
||||||
|
latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, approvableID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check current approval step")
|
||||||
|
}
|
||||||
|
|
||||||
|
if latestApproval == nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No approval found for Marketing %d", approvableID))
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextStep approvalutils.ApprovalStep
|
||||||
|
currentStep := latestApproval.StepNumber
|
||||||
|
|
||||||
|
if action == entity.ApprovalActionApproved {
|
||||||
|
|
||||||
|
if currentStep == uint16(utils.MarketingStepPengajuan) {
|
||||||
|
nextStep = utils.MarketingStepSalesOrder
|
||||||
|
} else if currentStep == uint16(utils.MarketingStepSalesOrder) {
|
||||||
|
nextStep = utils.MarketingDeliveryOrder
|
||||||
|
} else {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Marketing %d already completed all approval steps", approvableID))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
nextStep = approvalutils.ApprovalStep(currentStep)
|
||||||
|
}
|
||||||
|
|
||||||
|
actorID := uint(1) // todo ambil dari auth context
|
||||||
|
if _, err := approvalSvc.CreateApproval(
|
||||||
|
c.Context(),
|
||||||
|
utils.ApprovalWorkflowMarketing,
|
||||||
|
approvableID,
|
||||||
|
nextStep,
|
||||||
|
&action,
|
||||||
|
actorID,
|
||||||
|
req.Notes,
|
||||||
|
); err != nil {
|
||||||
|
s.Log.Errorf("Failed to create approval for %d: %+v", approvableID, err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create approval")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||||
|
return nil, fiberErr
|
||||||
|
}
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval")
|
||||||
|
}
|
||||||
|
|
||||||
|
updated := make([]entity.Marketing, 0, len(approvableIDs))
|
||||||
|
for _, id := range approvableIDs {
|
||||||
|
marketing, err := s.getOne(c, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
updated = append(updated, *marketing)
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *salesOrdersService) createMarketingProductWithDelivery(ctx context.Context, marketingId uint, rp validation.CreateMarketingProduct, marketingProductRepo repository.MarketingProductRepository, invDeliveryRepo rInvMarketingDeliveryProduct.MarketingDeliveryProductRepository) error {
|
||||||
|
|
||||||
|
marketingProduct := &entity.MarketingProduct{
|
||||||
|
MarketingId: marketingId,
|
||||||
|
ProductWarehouseId: rp.ProductWarehouseId,
|
||||||
|
Qty: rp.Qty,
|
||||||
|
UnitPrice: rp.UnitPrice,
|
||||||
|
AvgWeight: rp.AvgWeight,
|
||||||
|
TotalWeight: rp.TotalWeight,
|
||||||
|
TotalPrice: rp.TotalPrice,
|
||||||
|
}
|
||||||
|
if err := marketingProductRepo.CreateOne(ctx, marketingProduct, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
marketingDeliveryProduct := &entity.MarketingDeliveryProduct{
|
||||||
|
MarketingProductId: marketingProduct.Id,
|
||||||
|
Qty: 0,
|
||||||
|
UnitPrice: 0,
|
||||||
|
TotalWeight: 0,
|
||||||
|
AvgWeight: 0,
|
||||||
|
TotalPrice: 0,
|
||||||
|
DeliveryDate: nil,
|
||||||
|
VehicleNumber: rp.VehicleNumber,
|
||||||
|
}
|
||||||
|
if err := invDeliveryRepo.CreateOne(ctx, marketingDeliveryProduct, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
type Create struct {
|
||||||
|
CustomerId uint `json:"customer_id" validate:"required,gt=0"`
|
||||||
|
SalesPersonId uint `json:"sales_person_id" validate:"required,gt=0"`
|
||||||
|
Date string `json:"date" validate:"required,datetime=2006-01-02"`
|
||||||
|
Notes string `json:"notes" validate:"omitempty,max=500"`
|
||||||
|
MarketingProducts []CreateMarketingProduct `json:"marketing_products" validate:"required,min=1,dive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateMarketingProduct struct {
|
||||||
|
VehicleNumber string `json:"vehicle_number" validate:"required,min=1,max=50"`
|
||||||
|
ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,gt=0"`
|
||||||
|
UnitPrice float64 `json:"unit_price" validate:"required,gt=0"`
|
||||||
|
TotalWeight float64 `json:"total_weight" validate:"required,gt=0"`
|
||||||
|
Qty float64 `json:"qty" validate:"required,gt=0"`
|
||||||
|
AvgWeight float64 `json:"avg_weight" validate:"required,gt=0"`
|
||||||
|
TotalPrice float64 `json:"total_price" validate:"required,gt=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Update struct {
|
||||||
|
CustomerId uint `json:"customer_id" validate:"omitempty,gt=0"`
|
||||||
|
SalesPersonId uint `json:"sales_person_id" validate:"omitempty,gt=0"`
|
||||||
|
Date string `json:"date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
|
Notes string `json:"notes" validate:"omitempty,max=500"`
|
||||||
|
MarketingProducts []CreateMarketingProduct `json:"marketing_products" validate:"omitempty,min=1,dive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Approve struct {
|
||||||
|
Action string `json:"action" validate:"required_strict"`
|
||||||
|
ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
|
||||||
|
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||||
|
}
|
||||||
@@ -9,16 +9,16 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type AreaBaseDTO struct {
|
type AreaRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AreaListDTO struct {
|
type AreaListDTO struct {
|
||||||
AreaBaseDTO
|
AreaRelationDTO
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AreaDetailDTO struct {
|
type AreaDetailDTO struct {
|
||||||
@@ -27,25 +27,25 @@ type AreaDetailDTO struct {
|
|||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToAreaBaseDTO(e entity.Area) AreaBaseDTO {
|
func ToAreaRelationDTO(e entity.Area) AreaRelationDTO {
|
||||||
return AreaBaseDTO{
|
return AreaRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToAreaListDTO(e entity.Area) AreaListDTO {
|
func ToAreaListDTO(e entity.Area) AreaListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return AreaListDTO{
|
return AreaListDTO{
|
||||||
AreaBaseDTO: ToAreaBaseDTO(e),
|
AreaRelationDTO: ToAreaRelationDTO(e),
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type BankBaseDTO struct {
|
type BankRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Alias string `json:"alias"`
|
Alias string `json:"alias"`
|
||||||
@@ -18,10 +18,10 @@ type BankBaseDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BankListDTO struct {
|
type BankListDTO struct {
|
||||||
BankBaseDTO
|
BankRelationDTO
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BankDetailDTO struct {
|
type BankDetailDTO struct {
|
||||||
@@ -30,8 +30,8 @@ type BankDetailDTO struct {
|
|||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToBankBaseDTO(e entity.Bank) BankBaseDTO {
|
func ToBankRelationDTO(e entity.Bank) BankRelationDTO {
|
||||||
return BankBaseDTO{
|
return BankRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
Alias: e.Alias,
|
Alias: e.Alias,
|
||||||
@@ -41,17 +41,17 @@ func ToBankBaseDTO(e entity.Bank) BankBaseDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToBankListDTO(e entity.Bank) BankListDTO {
|
func ToBankListDTO(e entity.Bank) BankListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return BankListDTO{
|
return BankListDTO{
|
||||||
BankBaseDTO: ToBankBaseDTO(e),
|
BankRelationDTO: ToBankRelationDTO(e),
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,25 +9,29 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type CustomerBaseDTO struct {
|
type CustomerRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
PicId uint `json:"pic_id"`
|
Type string `json:"type"`
|
||||||
Type string `json:"type"`
|
AccountNumber string `json:"account_number"`
|
||||||
Address string `json:"address"`
|
Balance float64 `json:"balance"`
|
||||||
Phone string `json:"phone"`
|
Pic *userDTO.UserRelationDTO `json:"pic,omitempty"`
|
||||||
Email string `json:"email"`
|
|
||||||
AccountNumber string `json:"account_number"`
|
|
||||||
Balance float64 `json:"balance"`
|
|
||||||
|
|
||||||
Pic *userDTO.UserBaseDTO `json:"pic,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomerListDTO struct {
|
type CustomerListDTO struct {
|
||||||
CustomerBaseDTO
|
Id uint `json:"id"`
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
Name string `json:"name"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
PicId uint `json:"pic_id"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
Type string `json:"type"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
AccountNumber string `json:"account_number"`
|
||||||
|
Balance float64 `json:"balance"`
|
||||||
|
Pic userDTO.UserRelationDTO `json:"pic"`
|
||||||
|
CreatedUser userDTO.UserRelationDTO `json:"created_user"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomerDetailDTO struct {
|
type CustomerDetailDTO struct {
|
||||||
@@ -36,14 +40,36 @@ type CustomerDetailDTO struct {
|
|||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToCustomerBaseDTO(e entity.Customer) CustomerBaseDTO {
|
func ToCustomerRelationDTO(e entity.Customer) CustomerRelationDTO {
|
||||||
var pic *userDTO.UserBaseDTO
|
var pic *userDTO.UserRelationDTO
|
||||||
if e.Pic.Id != 0 {
|
if e.Pic.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.Pic)
|
mapped := userDTO.ToUserRelationDTO(e.Pic)
|
||||||
pic = &mapped
|
pic = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return CustomerBaseDTO{
|
return CustomerRelationDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
Name: e.Name,
|
||||||
|
Type: e.Type,
|
||||||
|
AccountNumber: e.AccountNumber,
|
||||||
|
Pic: pic,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToCustomerListDTO(e entity.Customer) CustomerListDTO {
|
||||||
|
var createdUser userDTO.UserRelationDTO
|
||||||
|
if e.CreatedUser.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
|
createdUser = mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var pic userDTO.UserRelationDTO
|
||||||
|
if e.Pic.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserRelationDTO(e.Pic)
|
||||||
|
pic = mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
return CustomerListDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
PicId: e.PicId,
|
PicId: e.PicId,
|
||||||
@@ -53,21 +79,9 @@ func ToCustomerBaseDTO(e entity.Customer) CustomerBaseDTO {
|
|||||||
Email: e.Email,
|
Email: e.Email,
|
||||||
AccountNumber: e.AccountNumber,
|
AccountNumber: e.AccountNumber,
|
||||||
Pic: pic,
|
Pic: pic,
|
||||||
}
|
CreatedAt: e.CreatedAt,
|
||||||
}
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
CreatedUser: createdUser,
|
||||||
func ToCustomerListDTO(e entity.Customer) CustomerListDTO {
|
|
||||||
var createdUser *userDTO.UserBaseDTO
|
|
||||||
if e.CreatedUser.Id != 0 {
|
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
|
||||||
createdUser = &mapped
|
|
||||||
}
|
|
||||||
|
|
||||||
return CustomerListDTO{
|
|
||||||
CustomerBaseDTO: ToCustomerBaseDTO(e),
|
|
||||||
CreatedAt: e.CreatedAt,
|
|
||||||
UpdatedAt: e.UpdatedAt,
|
|
||||||
CreatedUser: createdUser,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type CustomerRepository interface {
|
|||||||
repository.BaseRepository[entity.Customer]
|
repository.BaseRepository[entity.Customer]
|
||||||
PicExists(ctx context.Context, areaId uint) (bool, error)
|
PicExists(ctx context.Context, areaId uint) (bool, error)
|
||||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||||
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomerRepositoryImpl struct {
|
type CustomerRepositoryImpl struct {
|
||||||
@@ -33,3 +34,7 @@ func (r *CustomerRepositoryImpl) PicExists(ctx context.Context, picId uint) (boo
|
|||||||
func (r *CustomerRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
func (r *CustomerRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
||||||
return repository.ExistsByName[entity.Customer](ctx, r.db, name, excludeID)
|
return repository.ExistsByName[entity.Customer](ctx, r.db, name, excludeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *CustomerRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.Customer](ctx, r.db, id)
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type FcrBaseDTO struct {
|
type FcrRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
@@ -22,10 +22,10 @@ type FcrStandardDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FcrListDTO struct {
|
type FcrListDTO struct {
|
||||||
FcrBaseDTO
|
FcrRelationDTO
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FcrDetailDTO struct {
|
type FcrDetailDTO struct {
|
||||||
@@ -35,25 +35,25 @@ type FcrDetailDTO struct {
|
|||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToFcrBaseDTO(e entity.Fcr) FcrBaseDTO {
|
func ToFcrRelationDTO(e entity.Fcr) FcrRelationDTO {
|
||||||
return FcrBaseDTO{
|
return FcrRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToFcrListDTO(e entity.Fcr) FcrListDTO {
|
func ToFcrListDTO(e entity.Fcr) FcrListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return FcrListDTO{
|
return FcrListDTO{
|
||||||
FcrBaseDTO: ToFcrBaseDTO(e),
|
FcrRelationDTO: ToFcrRelationDTO(e),
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type FlockBaseDTO struct {
|
type FlockRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlockListDTO struct {
|
type FlockListDTO struct {
|
||||||
FlockBaseDTO
|
FlockRelationDTO
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlockDetailDTO struct {
|
type FlockDetailDTO struct {
|
||||||
@@ -27,25 +27,25 @@ type FlockDetailDTO struct {
|
|||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToFlockBaseDTO(e entity.Flock) FlockBaseDTO {
|
func ToFlockRelationDTO(e entity.Flock) FlockRelationDTO {
|
||||||
return FlockBaseDTO{
|
return FlockRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToFlockListDTO(e entity.Flock) FlockListDTO {
|
func ToFlockListDTO(e entity.Flock) FlockListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return FlockListDTO{
|
return FlockListDTO{
|
||||||
FlockBaseDTO: ToFlockBaseDTO(e),
|
FlockRelationDTO: ToFlockRelationDTO(e),
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,25 +10,25 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type KandangBaseDTO struct {
|
type KandangRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Capacity float64 `json:"capacity"`
|
Capacity float64 `json:"capacity"`
|
||||||
Location locationDTO.LocationBaseDTO `json:"location,omitempty"`
|
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
||||||
Pic userDTO.UserBaseDTO `json:"pic,omitempty"`
|
Pic *userDTO.UserRelationDTO `json:"pic,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type KandangListDTO struct {
|
type KandangListDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Capacity float64 `json:"capacity"`
|
Capacity float64 `json:"capacity"`
|
||||||
Location locationDTO.LocationBaseDTO `json:"location"`
|
Location locationDTO.LocationRelationDTO `json:"location"`
|
||||||
Pic userDTO.UserBaseDTO `json:"pic"`
|
Pic userDTO.UserRelationDTO `json:"pic"`
|
||||||
CreatedUser userDTO.UserBaseDTO `json:"created_user"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type KandangDetailDTO struct {
|
type KandangDetailDTO struct {
|
||||||
@@ -37,20 +37,20 @@ type KandangDetailDTO struct {
|
|||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToKandangBaseDTO(e entity.Kandang) KandangBaseDTO {
|
func ToKandangRelationDTO(e entity.Kandang) KandangRelationDTO {
|
||||||
var location locationDTO.LocationBaseDTO
|
var location *locationDTO.LocationRelationDTO
|
||||||
if e.Location.Id != 0 {
|
if e.Location.Id != 0 {
|
||||||
mapped := locationDTO.ToLocationBaseDTO(e.Location)
|
mapped := locationDTO.ToLocationRelationDTO(e.Location)
|
||||||
location = mapped
|
location = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var pic userDTO.UserBaseDTO
|
var pic *userDTO.UserRelationDTO
|
||||||
if e.Pic.Id != 0 {
|
if e.Pic.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.Pic)
|
mapped := userDTO.ToUserRelationDTO(e.Pic)
|
||||||
pic = mapped
|
pic = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return KandangBaseDTO{
|
return KandangRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
Status: e.Status,
|
Status: e.Status,
|
||||||
@@ -61,21 +61,21 @@ func ToKandangBaseDTO(e entity.Kandang) KandangBaseDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToKandangListDTO(e entity.Kandang) KandangListDTO {
|
func ToKandangListDTO(e entity.Kandang) KandangListDTO {
|
||||||
var location locationDTO.LocationBaseDTO
|
var location locationDTO.LocationRelationDTO
|
||||||
if e.Location.Id != 0 {
|
if e.Location.Id != 0 {
|
||||||
mapped := locationDTO.ToLocationBaseDTO(e.Location)
|
mapped := locationDTO.ToLocationRelationDTO(e.Location)
|
||||||
location = mapped
|
location = mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var pic userDTO.UserBaseDTO
|
var pic userDTO.UserRelationDTO
|
||||||
if e.Pic.Id != 0 {
|
if e.Pic.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.Pic)
|
mapped := userDTO.ToUserRelationDTO(e.Pic)
|
||||||
pic = mapped
|
pic = mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var createdUser userDTO.UserBaseDTO
|
var createdUser userDTO.UserRelationDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
createdUser = mapped
|
createdUser = mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type KandangRepository interface {
|
|||||||
UpdateStatusByProjectFlockID(ctx context.Context, projectFlockID uint, status utils.KandangStatus) error
|
UpdateStatusByProjectFlockID(ctx context.Context, projectFlockID uint, status utils.KandangStatus) error
|
||||||
UpsertProjectFlockKandang(ctx context.Context, projectFlockID, kandangID uint) error
|
UpsertProjectFlockKandang(ctx context.Context, projectFlockID, kandangID uint) error
|
||||||
UpdateStatusByIDs(ctx context.Context, kandangIDs []uint, status utils.KandangStatus) error
|
UpdateStatusByIDs(ctx context.Context, kandangIDs []uint, status utils.KandangStatus) error
|
||||||
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type KandangRepositoryImpl struct {
|
type KandangRepositoryImpl struct {
|
||||||
@@ -60,6 +61,10 @@ func (r *KandangRepositoryImpl) ProjectFlockExists(ctx context.Context, projectF
|
|||||||
return count > 0, nil
|
return count > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *KandangRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.Kandang](ctx, r.db, id)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *KandangRepositoryImpl) HasActiveKandangForProjectFlock(ctx context.Context, projectFlockID uint, excludeID *uint) (bool, error) {
|
func (r *KandangRepositoryImpl) HasActiveKandangForProjectFlock(ctx context.Context, projectFlockID uint, excludeID *uint) (bool, error) {
|
||||||
var count int64
|
var count int64
|
||||||
q := r.db.WithContext(ctx).
|
q := r.db.WithContext(ctx).
|
||||||
|
|||||||
@@ -10,21 +10,21 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type LocationBaseDTO struct {
|
type LocationRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Area *areaDTO.AreaBaseDTO `json:"area,omitempty"`
|
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocationListDTO struct {
|
type LocationListDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Area *areaDTO.AreaBaseDTO `json:"area"`
|
Area *areaDTO.AreaRelationDTO `json:"area"`
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocationDetailDTO struct {
|
type LocationDetailDTO struct {
|
||||||
@@ -33,14 +33,14 @@ type LocationDetailDTO struct {
|
|||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToLocationBaseDTO(e entity.Location) LocationBaseDTO {
|
func ToLocationRelationDTO(e entity.Location) LocationRelationDTO {
|
||||||
var area *areaDTO.AreaBaseDTO
|
var area *areaDTO.AreaRelationDTO
|
||||||
if e.Area.Id != 0 {
|
if e.Area.Id != 0 {
|
||||||
mapped := areaDTO.ToAreaBaseDTO(e.Area)
|
mapped := areaDTO.ToAreaRelationDTO(e.Area)
|
||||||
area = &mapped
|
area = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocationBaseDTO{
|
return LocationRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
Address: e.Address,
|
Address: e.Address,
|
||||||
@@ -49,15 +49,15 @@ func ToLocationBaseDTO(e entity.Location) LocationBaseDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToLocationListDTO(e entity.Location) LocationListDTO {
|
func ToLocationListDTO(e entity.Location) LocationListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var area *areaDTO.AreaBaseDTO
|
var area *areaDTO.AreaRelationDTO
|
||||||
if e.Area.Id != 0 {
|
if e.Area.Id != 0 {
|
||||||
mapped := areaDTO.ToAreaBaseDTO(e.Area)
|
mapped := areaDTO.ToAreaRelationDTO(e.Area)
|
||||||
area = &mapped
|
area = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,41 +4,39 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
|
|
||||||
uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto"
|
uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto"
|
||||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type NonstockBaseDTO struct {
|
type NonstockRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Uom *uomDTO.UomBaseDTO `json:"uom,omitempty"`
|
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||||
Flags []string `json:"flags"`
|
Flags []string `json:"flags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NonstockListDTO struct {
|
type NonstockListDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Uom *uomDTO.UomBaseDTO `json:"uom"`
|
Flags []string `json:"flags"`
|
||||||
Suppliers []supplierDTO.SupplierBaseDTO `json:"suppliers"`
|
Uom *uomDTO.UomRelationDTO `json:"uom"`
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NonstockDetailDTO struct {
|
type NonstockDetailDTO struct {
|
||||||
NonstockListDTO
|
NonstockListDTO
|
||||||
Flags []string `json:"flags"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToNonstockBaseDTO(e entity.Nonstock) NonstockBaseDTO {
|
func ToNonstockRelationDTO(e entity.Nonstock) NonstockRelationDTO {
|
||||||
var uomRef *uomDTO.UomBaseDTO
|
var uomRef *uomDTO.UomRelationDTO
|
||||||
if e.Uom.Id != 0 {
|
if e.Uom.Id != 0 {
|
||||||
mapped := uomDTO.ToUomBaseDTO(e.Uom)
|
mapped := uomDTO.ToUomRelationDTO(e.Uom)
|
||||||
uomRef = &mapped
|
uomRef = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +45,7 @@ func ToNonstockBaseDTO(e entity.Nonstock) NonstockBaseDTO {
|
|||||||
flags[i] = f.Name
|
flags[i] = f.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
return NonstockBaseDTO{
|
return NonstockRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
Uom: uomRef,
|
Uom: uomRef,
|
||||||
@@ -56,23 +54,18 @@ func ToNonstockBaseDTO(e entity.Nonstock) NonstockBaseDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToNonstockListDTO(e entity.Nonstock) NonstockListDTO {
|
func ToNonstockListDTO(e entity.Nonstock) NonstockListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var uomRef *uomDTO.UomBaseDTO
|
var uomRef *uomDTO.UomRelationDTO
|
||||||
if e.Uom.Id != 0 {
|
if e.Uom.Id != 0 {
|
||||||
mapped := uomDTO.ToUomBaseDTO(e.Uom)
|
mapped := uomDTO.ToUomRelationDTO(e.Uom)
|
||||||
uomRef = &mapped
|
uomRef = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
suppliers := make([]supplierDTO.SupplierBaseDTO, len(e.Suppliers))
|
|
||||||
for i, s := range e.Suppliers {
|
|
||||||
suppliers[i] = supplierDTO.ToSupplierBaseDTO(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := make([]string, len(e.Flags))
|
flags := make([]string, len(e.Flags))
|
||||||
for i, f := range e.Flags {
|
for i, f := range e.Flags {
|
||||||
flags[i] = f.Name
|
flags[i] = f.Name
|
||||||
@@ -81,11 +74,11 @@ func ToNonstockListDTO(e entity.Nonstock) NonstockListDTO {
|
|||||||
return NonstockListDTO{
|
return NonstockListDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
|
Flags: flags,
|
||||||
Uom: uomRef,
|
Uom: uomRef,
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
Suppliers: suppliers,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,13 +91,7 @@ func ToNonstockListDTOs(e []entity.Nonstock) []NonstockListDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToNonstockDetailDTO(e entity.Nonstock) NonstockDetailDTO {
|
func ToNonstockDetailDTO(e entity.Nonstock) NonstockDetailDTO {
|
||||||
flags := make([]string, len(e.Flags))
|
|
||||||
for i, f := range e.Flags {
|
|
||||||
flags[i] = f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
return NonstockDetailDTO{
|
return NonstockDetailDTO{
|
||||||
NonstockListDTO: ToNonstockListDTO(e),
|
NonstockListDTO: ToNonstockListDTO(e),
|
||||||
Flags: flags,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ type NonstockRepository interface {
|
|||||||
SyncFlags(ctx context.Context, tx *gorm.DB, nonstockID uint, flags []string) error
|
SyncFlags(ctx context.Context, tx *gorm.DB, nonstockID uint, flags []string) error
|
||||||
DeleteFlags(ctx context.Context, tx *gorm.DB, nonstockID uint) error
|
DeleteFlags(ctx context.Context, tx *gorm.DB, nonstockID uint) error
|
||||||
GetFlags(ctx context.Context, nonstockID uint) ([]entity.Flag, error)
|
GetFlags(ctx context.Context, nonstockID uint) ([]entity.Flag, error)
|
||||||
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
|
IsNonstockAssociatedWithSupplier(ctx context.Context, nonstockID uint, supplierID uint64) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type NonstockRepositoryImpl struct {
|
type NonstockRepositoryImpl struct {
|
||||||
@@ -34,6 +36,10 @@ func (r *NonstockRepositoryImpl) NameExists(ctx context.Context, name string, ex
|
|||||||
return repository.ExistsByName[entity.Nonstock](ctx, r.DB(), name, excludeID)
|
return repository.ExistsByName[entity.Nonstock](ctx, r.DB(), name, excludeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *NonstockRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.Nonstock](ctx, r.DB(), id)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *NonstockRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, nonstockID uint, supplierIDs []uint) error {
|
func (r *NonstockRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, nonstockID uint, supplierIDs []uint) error {
|
||||||
db := tx
|
db := tx
|
||||||
if db == nil {
|
if db == nil {
|
||||||
@@ -57,7 +63,7 @@ func (r *NonstockRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm
|
|||||||
|
|
||||||
existingMap := make(map[uint]struct{}, len(existing))
|
existingMap := make(map[uint]struct{}, len(existing))
|
||||||
for _, rel := range existing {
|
for _, rel := range existing {
|
||||||
existingMap[rel.SupplierID] = struct{}{}
|
existingMap[rel.SupplierId] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
incomingMap := make(map[uint]struct{}, len(supplierIDs))
|
incomingMap := make(map[uint]struct{}, len(supplierIDs))
|
||||||
@@ -66,16 +72,16 @@ func (r *NonstockRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm
|
|||||||
if _, exists := existingMap[id]; exists {
|
if _, exists := existingMap[id]; exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
record := entity.NonstockSupplier{NonstockID: nonstockID, SupplierID: id}
|
record := entity.NonstockSupplier{NonstockId: nonstockID, SupplierId: id}
|
||||||
if err := db.WithContext(ctx).Create(&record).Error; err != nil {
|
if err := db.WithContext(ctx).Create(&record).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rel := range existing {
|
for _, rel := range existing {
|
||||||
if _, keep := incomingMap[rel.SupplierID]; !keep {
|
if _, keep := incomingMap[rel.SupplierId]; !keep {
|
||||||
if err := db.WithContext(ctx).
|
if err := db.WithContext(ctx).
|
||||||
Where("nonstock_id = ? AND supplier_id = ?", nonstockID, rel.SupplierID).
|
Where("nonstock_id = ? AND supplier_id = ?", nonstockID, rel.SupplierId).
|
||||||
Delete(&entity.NonstockSupplier{}).
|
Delete(&entity.NonstockSupplier{}).
|
||||||
Error; err != nil {
|
Error; err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -170,3 +176,15 @@ func (r *NonstockRepositoryImpl) GetFlags(ctx context.Context, nonstockID uint)
|
|||||||
}
|
}
|
||||||
return flags, nil
|
return flags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *NonstockRepositoryImpl) IsNonstockAssociatedWithSupplier(ctx context.Context, nonstockID uint, supplierID uint64) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
if err := r.DB().WithContext(ctx).
|
||||||
|
Model(&entity.NonstockSupplier{}).
|
||||||
|
Where("nonstock_id = ? AND supplier_id = ?", nonstockID, supplierID).
|
||||||
|
Count(&count).
|
||||||
|
Error; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ func (s nonstockService) withRelations(db *gorm.DB) *gorm.DB {
|
|||||||
Preload("CreatedUser").
|
Preload("CreatedUser").
|
||||||
Preload("Uom").
|
Preload("Uom").
|
||||||
Preload("Flags").
|
Preload("Flags").
|
||||||
Preload("Suppliers", func(db *gorm.DB) *gorm.DB {
|
Preload("NonstockSuppliers").
|
||||||
|
Preload("NonstockSuppliers.Supplier", func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Order("suppliers.name ASC")
|
return db.Order("suppliers.name ASC")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,17 +9,17 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type ProductCategoryBaseDTO struct {
|
type ProductCategoryRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductCategoryListDTO struct {
|
type ProductCategoryListDTO struct {
|
||||||
ProductCategoryBaseDTO
|
ProductCategoryRelationDTO
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductCategoryDetailDTO struct {
|
type ProductCategoryDetailDTO struct {
|
||||||
@@ -28,8 +28,8 @@ type ProductCategoryDetailDTO struct {
|
|||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToProductCategoryBaseDTO(e entity.ProductCategory) ProductCategoryBaseDTO {
|
func ToProductCategoryRelationDTO(e entity.ProductCategory) ProductCategoryRelationDTO {
|
||||||
return ProductCategoryBaseDTO{
|
return ProductCategoryRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
Code: e.Code,
|
Code: e.Code,
|
||||||
@@ -37,17 +37,17 @@ func ToProductCategoryBaseDTO(e entity.ProductCategory) ProductCategoryBaseDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToProductCategoryListDTO(e entity.ProductCategory) ProductCategoryListDTO {
|
func ToProductCategoryListDTO(e entity.ProductCategory) ProductCategoryListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return ProductCategoryListDTO{
|
return ProductCategoryListDTO{
|
||||||
ProductCategoryBaseDTO: ToProductCategoryBaseDTO(e),
|
ProductCategoryRelationDTO: ToProductCategoryRelationDTO(e),
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,93 +5,105 @@ import (
|
|||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
productCategoryDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/dto"
|
productCategoryDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/dto"
|
||||||
supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
|
|
||||||
uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto"
|
uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto"
|
||||||
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type ProductBaseDTO struct {
|
type ProductRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Uom *uomDTO.UomBaseDTO `json:"uom,omitempty"`
|
ProductPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
Flags []string `json:"flags"`
|
SellingPrice *float64 `gorm:"type:numeric(15,3)"`
|
||||||
|
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||||
|
Flags *[]string `json:"flags,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductListDTO struct {
|
type ProductListDTO struct {
|
||||||
ProductBaseDTO
|
Id uint `json:"id"`
|
||||||
Brand string `json:"brand"`
|
Name string `json:"name"`
|
||||||
Sku *string `json:"sku,omitempty"`
|
Brand string `json:"brand"`
|
||||||
ProductPrice float64 `json:"product_price"`
|
Sku *string `json:"sku,omitempty"`
|
||||||
SellingPrice *float64 `json:"selling_price,omitempty"`
|
ProductPrice float64 `json:"product_price"`
|
||||||
Tax *float64 `json:"tax,omitempty"`
|
SellingPrice *float64 `json:"selling_price,omitempty"`
|
||||||
ExpiryPeriod *int `json:"expiry_period,omitempty"`
|
Tax *float64 `json:"tax,omitempty"`
|
||||||
ProductCategory *productCategoryDTO.ProductCategoryBaseDTO `json:"product_category,omitempty"`
|
ExpiryPeriod *int `json:"expiry_period,omitempty"`
|
||||||
Suppliers []supplierDTO.SupplierBaseDTO `json:"suppliers"`
|
Flags []string `json:"flags"`
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductDetailDTO struct {
|
type ProductDetailDTO struct {
|
||||||
ProductListDTO
|
ProductListDTO
|
||||||
Flags []string `json:"flags"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToProductBaseDTO(e entity.Product) ProductBaseDTO {
|
func ToProductRelationDTO(e entity.Product) ProductRelationDTO {
|
||||||
flags := make([]string, len(e.Flags))
|
flags := make([]string, len(e.Flags))
|
||||||
for i, f := range e.Flags {
|
for i, f := range e.Flags {
|
||||||
flags[i] = f.Name
|
flags[i] = f.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
var uomRef *uomDTO.UomBaseDTO
|
var uomRef *uomDTO.UomRelationDTO
|
||||||
if e.Uom.Id != 0 {
|
if e.Uom.Id != 0 {
|
||||||
mapped := uomDTO.ToUomBaseDTO(e.Uom)
|
mapped := uomDTO.ToUomRelationDTO(e.Uom)
|
||||||
uomRef = &mapped
|
uomRef = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return ProductBaseDTO{
|
return ProductRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
Flags: flags,
|
ProductPrice: e.ProductPrice,
|
||||||
Uom: uomRef,
|
SellingPrice: e.SellingPrice,
|
||||||
|
Flags: &flags,
|
||||||
|
Uom: uomRef,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToProductListDTO(e entity.Product) ProductListDTO {
|
func ToProductListDTO(e entity.Product) ProductListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var categoryRef *productCategoryDTO.ProductCategoryBaseDTO
|
var categoryRef *productCategoryDTO.ProductCategoryRelationDTO
|
||||||
if e.ProductCategory.Id != 0 {
|
if e.ProductCategory.Id != 0 {
|
||||||
mapped := productCategoryDTO.ToProductCategoryBaseDTO(e.ProductCategory)
|
mapped := productCategoryDTO.ToProductCategoryRelationDTO(e.ProductCategory)
|
||||||
categoryRef = &mapped
|
categoryRef = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
suppliers := make([]supplierDTO.SupplierBaseDTO, len(e.Suppliers))
|
flags := make([]string, len(e.Flags))
|
||||||
for i, s := range e.Suppliers {
|
for i, f := range e.Flags {
|
||||||
suppliers[i] = supplierDTO.ToSupplierBaseDTO(s)
|
flags[i] = f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
var uomRef *uomDTO.UomRelationDTO
|
||||||
|
if e.Uom.Id != 0 {
|
||||||
|
mapped := uomDTO.ToUomRelationDTO(e.Uom)
|
||||||
|
uomRef = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return ProductListDTO{
|
return ProductListDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
Name: e.Name,
|
||||||
|
Flags: flags,
|
||||||
|
Uom: uomRef,
|
||||||
Brand: e.Brand,
|
Brand: e.Brand,
|
||||||
Sku: e.Sku,
|
Sku: e.Sku,
|
||||||
ProductPrice: e.ProductPrice,
|
ProductPrice: e.ProductPrice,
|
||||||
SellingPrice: e.SellingPrice,
|
SellingPrice: e.SellingPrice,
|
||||||
Tax: e.Tax,
|
Tax: e.Tax,
|
||||||
ExpiryPeriod: e.ExpiryPeriod,
|
ExpiryPeriod: e.ExpiryPeriod,
|
||||||
ProductBaseDTO: ToProductBaseDTO(e),
|
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
ProductCategory: categoryRef,
|
ProductCategory: categoryRef,
|
||||||
Suppliers: suppliers,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,13 +116,7 @@ func ToProductListDTOs(e []entity.Product) []ProductListDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToProductDetailDTO(e entity.Product) ProductDetailDTO {
|
func ToProductDetailDTO(e entity.Product) ProductDetailDTO {
|
||||||
flags := make([]string, len(e.Flags))
|
|
||||||
for i, f := range e.Flags {
|
|
||||||
flags[i] = f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
return ProductDetailDTO{
|
return ProductDetailDTO{
|
||||||
ProductListDTO: ToProductListDTO(e),
|
ProductListDTO: ToProductListDTO(e),
|
||||||
Flags: flags,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,13 +102,13 @@ func (r *ProductRepositoryImpl) IsLinkedToSupplier(ctx context.Context, productI
|
|||||||
return count > 0, nil
|
return count > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProductRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, productID uint, supplierIDs []uint) error {
|
func (r *ProductRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm.DB, productID uint, supplierIds []uint) error {
|
||||||
db := tx
|
db := tx
|
||||||
if db == nil {
|
if db == nil {
|
||||||
db = r.DB()
|
db = r.DB()
|
||||||
}
|
}
|
||||||
|
|
||||||
if supplierIDs == nil {
|
if supplierIds == nil {
|
||||||
return db.WithContext(ctx).
|
return db.WithContext(ctx).
|
||||||
Where("product_id = ?", productID).
|
Where("product_id = ?", productID).
|
||||||
Delete(&entity.ProductSupplier{}).
|
Delete(&entity.ProductSupplier{}).
|
||||||
@@ -125,25 +125,25 @@ func (r *ProductRepositoryImpl) SyncSuppliersDiff(ctx context.Context, tx *gorm.
|
|||||||
|
|
||||||
existingMap := make(map[uint]struct{}, len(existing))
|
existingMap := make(map[uint]struct{}, len(existing))
|
||||||
for _, rel := range existing {
|
for _, rel := range existing {
|
||||||
existingMap[rel.SupplierID] = struct{}{}
|
existingMap[rel.SupplierId] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
incomingMap := make(map[uint]struct{}, len(supplierIDs))
|
incomingMap := make(map[uint]struct{}, len(supplierIds))
|
||||||
for _, id := range supplierIDs {
|
for _, id := range supplierIds {
|
||||||
incomingMap[id] = struct{}{}
|
incomingMap[id] = struct{}{}
|
||||||
if _, exists := existingMap[id]; exists {
|
if _, exists := existingMap[id]; exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
record := entity.ProductSupplier{ProductID: productID, SupplierID: id}
|
record := entity.ProductSupplier{ProductId: productID, SupplierId: id}
|
||||||
if err := db.WithContext(ctx).Create(&record).Error; err != nil {
|
if err := db.WithContext(ctx).Create(&record).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rel := range existing {
|
for _, rel := range existing {
|
||||||
if _, keep := incomingMap[rel.SupplierID]; !keep {
|
if _, keep := incomingMap[rel.SupplierId]; !keep {
|
||||||
if err := db.WithContext(ctx).
|
if err := db.WithContext(ctx).
|
||||||
Where("product_id = ? AND supplier_id = ?", productID, rel.SupplierID).
|
Where("product_id = ? AND supplier_id = ?", productID, rel.SupplierId).
|
||||||
Delete(&entity.ProductSupplier{}).
|
Delete(&entity.ProductSupplier{}).
|
||||||
Error; err != nil {
|
Error; err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ func (s productService) withRelations(db *gorm.DB) *gorm.DB {
|
|||||||
Preload("Uom").
|
Preload("Uom").
|
||||||
Preload("ProductCategory").
|
Preload("ProductCategory").
|
||||||
Preload("Flags").
|
Preload("Flags").
|
||||||
Preload("Suppliers", func(db *gorm.DB) *gorm.DB {
|
Preload("ProductSuppliers").
|
||||||
|
Preload("ProductSuppliers.Supplier", func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Order("suppliers.name ASC")
|
return db.Order("suppliers.name ASC")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ func NewSupplierController(supplierService service.SupplierService) *SupplierCon
|
|||||||
|
|
||||||
func (u *SupplierController) GetAll(c *fiber.Ctx) error {
|
func (u *SupplierController) GetAll(c *fiber.Ctx) error {
|
||||||
query := &validation.Query{
|
query := &validation.Query{
|
||||||
Page: c.QueryInt("page", 1),
|
Page: c.QueryInt("page", 1),
|
||||||
Limit: c.QueryInt("limit", 10),
|
Limit: c.QueryInt("limit", 10),
|
||||||
Search: c.Query("search", ""),
|
Search: c.Query("search", ""),
|
||||||
|
Category: c.Query("category", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
if query.Page < 1 || query.Limit < 1 {
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
@@ -71,7 +72,7 @@ func (u *SupplierController) GetOne(c *fiber.Ctx) error {
|
|||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Get supplier successfully",
|
Message: "Get supplier successfully",
|
||||||
Data: dto.ToSupplierListDTO(*result),
|
Data: dto.ToSupplierDetailDTO(*result),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type SupplierBaseDTO struct {
|
type SupplierRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Alias string `json:"alias"`
|
Alias string `json:"alias"`
|
||||||
@@ -17,30 +17,32 @@ type SupplierBaseDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SupplierListDTO struct {
|
type SupplierListDTO struct {
|
||||||
SupplierBaseDTO
|
SupplierRelationDTO
|
||||||
Pic string `json:"pic"`
|
Pic string `json:"pic"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Hatchery *string `json:"hatchery,omitempty"`
|
Hatchery *string `json:"hatchery,omitempty"`
|
||||||
Phone string `json:"phone"`
|
Phone string `json:"phone"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Npwp *string `json:"npwp,omitempty"`
|
Npwp *string `json:"npwp,omitempty"`
|
||||||
AccountNumber *string `json:"account_number,omitempty"`
|
AccountNumber *string `json:"account_number,omitempty"`
|
||||||
Balance float64 `json:"balance"`
|
Balance float64 `json:"balance"`
|
||||||
DueDate int `json:"due_date"`
|
DueDate int `json:"due_date"`
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SupplierDetailDTO struct {
|
type SupplierDetailDTO struct {
|
||||||
SupplierListDTO
|
SupplierListDTO
|
||||||
|
Products []SupplierProductDTO `json:"products"`
|
||||||
|
Nonstocks []SupplierNonstockDTO `json:"nonstocks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToSupplierBaseDTO(e entity.Supplier) SupplierBaseDTO {
|
func ToSupplierRelationDTO(e entity.Supplier) SupplierRelationDTO {
|
||||||
return SupplierBaseDTO{
|
return SupplierRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
Alias: e.Alias,
|
Alias: e.Alias,
|
||||||
@@ -49,27 +51,27 @@ func ToSupplierBaseDTO(e entity.Supplier) SupplierBaseDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToSupplierListDTO(e entity.Supplier) SupplierListDTO {
|
func ToSupplierListDTO(e entity.Supplier) SupplierListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return SupplierListDTO{
|
return SupplierListDTO{
|
||||||
Pic: e.Pic,
|
Pic: e.Pic,
|
||||||
Type: e.Type,
|
Type: e.Type,
|
||||||
Hatchery: e.Hatchery,
|
Hatchery: e.Hatchery,
|
||||||
Phone: e.Phone,
|
Phone: e.Phone,
|
||||||
Email: e.Email,
|
Email: e.Email,
|
||||||
Address: e.Address,
|
Address: e.Address,
|
||||||
Npwp: e.Npwp,
|
Npwp: e.Npwp,
|
||||||
AccountNumber: e.AccountNumber,
|
AccountNumber: e.AccountNumber,
|
||||||
Balance: e.Balance,
|
Balance: e.Balance,
|
||||||
DueDate: e.DueDate,
|
DueDate: e.DueDate,
|
||||||
SupplierBaseDTO: ToSupplierBaseDTO(e),
|
SupplierRelationDTO: ToSupplierRelationDTO(e),
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,5 +86,7 @@ func ToSupplierListDTOs(e []entity.Supplier) []SupplierListDTO {
|
|||||||
func ToSupplierDetailDTO(e entity.Supplier) SupplierDetailDTO {
|
func ToSupplierDetailDTO(e entity.Supplier) SupplierDetailDTO {
|
||||||
return SupplierDetailDTO{
|
return SupplierDetailDTO{
|
||||||
SupplierListDTO: ToSupplierListDTO(e),
|
SupplierListDTO: ToSupplierListDTO(e),
|
||||||
|
Products: toSupplierProductDTOs(e.ProductSuppliers),
|
||||||
|
Nonstocks: toSupplierNonstockDTOs(e.NonstockSuppliers),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// === DTO Structs ===
|
||||||
|
|
||||||
|
type SupplierNonstockDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||||
|
Flags []string `json:"flags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Mapper Functions ===
|
||||||
|
|
||||||
|
func toSupplierNonstockDTOs(relations []entity.NonstockSupplier) []SupplierNonstockDTO {
|
||||||
|
if len(relations) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]SupplierNonstockDTO, 0, len(relations))
|
||||||
|
for _, relation := range relations {
|
||||||
|
Nonstock := relation.Nonstock
|
||||||
|
if Nonstock.Id == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := make([]string, len(Nonstock.Flags))
|
||||||
|
for i, f := range Nonstock.Flags {
|
||||||
|
flags[i] = f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
var uomRef *uomDTO.UomRelationDTO
|
||||||
|
if Nonstock.Uom.Id != 0 {
|
||||||
|
mapped := uomDTO.ToUomRelationDTO(Nonstock.Uom)
|
||||||
|
uomRef = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, SupplierNonstockDTO{
|
||||||
|
Id: Nonstock.Id,
|
||||||
|
Name: Nonstock.Name,
|
||||||
|
Uom: uomRef,
|
||||||
|
Flags: flags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
uomDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/dto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// === DTO Structs ===
|
||||||
|
|
||||||
|
type SupplierProductDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ProductPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
SellingPrice *float64 `gorm:"type:numeric(15,3)"`
|
||||||
|
Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"`
|
||||||
|
Flags []string `json:"flags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Mapper Functions ===
|
||||||
|
|
||||||
|
func toSupplierProductDTOs(relations []entity.ProductSupplier) []SupplierProductDTO {
|
||||||
|
if len(relations) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]SupplierProductDTO, 0, len(relations))
|
||||||
|
for _, relation := range relations {
|
||||||
|
product := relation.Product
|
||||||
|
if product.Id == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := make([]string, len(product.Flags))
|
||||||
|
for i, f := range product.Flags {
|
||||||
|
flags[i] = f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
var uomRef *uomDTO.UomRelationDTO
|
||||||
|
if product.Uom.Id != 0 {
|
||||||
|
mapped := uomDTO.ToUomRelationDTO(product.Uom)
|
||||||
|
uomRef = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, SupplierProductDTO{
|
||||||
|
Id: product.Id,
|
||||||
|
Name: product.Name,
|
||||||
|
ProductPrice: product.ProductPrice,
|
||||||
|
SellingPrice: product.SellingPrice,
|
||||||
|
Uom: uomRef,
|
||||||
|
Flags: flags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -39,7 +39,12 @@ func NewSupplierService(repo repository.SupplierRepository, validate *validator.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s supplierService) withRelations(db *gorm.DB) *gorm.DB {
|
func (s supplierService) withRelations(db *gorm.DB) *gorm.DB {
|
||||||
return db.Preload("CreatedUser")
|
return db.
|
||||||
|
Preload("CreatedUser").
|
||||||
|
Preload("ProductSuppliers.Product.Uom").
|
||||||
|
Preload("ProductSuppliers.Product.Flags").
|
||||||
|
Preload("NonstockSuppliers.Nonstock.Uom").
|
||||||
|
Preload("NonstockSuppliers.Nonstock.Flags")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s supplierService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Supplier, int64, error) {
|
func (s supplierService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Supplier, int64, error) {
|
||||||
@@ -47,6 +52,14 @@ func (s supplierService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entit
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.Category != "" {
|
||||||
|
category := strings.ToUpper(params.Category)
|
||||||
|
if category != "BOP" && category != "SAPRONAK" {
|
||||||
|
return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid supplier category")
|
||||||
|
}
|
||||||
|
params.Category = category
|
||||||
|
}
|
||||||
|
|
||||||
offset := (params.Page - 1) * params.Limit
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
suppliers, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
suppliers, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
@@ -54,6 +67,11 @@ func (s supplierService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entit
|
|||||||
if params.Search != "" {
|
if params.Search != "" {
|
||||||
return db.Where("name LIKE ?", "%"+params.Search+"%")
|
return db.Where("name LIKE ?", "%"+params.Search+"%")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.Category != "" {
|
||||||
|
db = db.Where("category LIKE ?", "%"+params.Category+"%")
|
||||||
|
}
|
||||||
|
|
||||||
return db.Order("created_at DESC").Order("updated_at DESC")
|
return db.Order("created_at DESC").Order("updated_at DESC")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ type Update struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
Page int `query:"page" validate:"omitempty,number,min=1"`
|
Page int `query:"page" validate:"omitempty,number,min=1"`
|
||||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
||||||
Search string `query:"search" validate:"omitempty,max=50"`
|
Search string `query:"search" validate:"omitempty,max=50"`
|
||||||
|
Category string `query:"category" validate:"omitempty,max=50"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,17 +9,17 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type UomBaseDTO struct {
|
type UomRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UomListDTO struct {
|
type UomListDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UomDetailDTO struct {
|
type UomDetailDTO struct {
|
||||||
@@ -28,17 +28,17 @@ type UomDetailDTO struct {
|
|||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToUomBaseDTO(e entity.Uom) UomBaseDTO {
|
func ToUomRelationDTO(e entity.Uom) UomRelationDTO {
|
||||||
return UomBaseDTO{
|
return UomRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToUomListDTO(e entity.Uom) UomListDTO {
|
func ToUomListDTO(e entity.Uom) UomListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,25 +12,25 @@ import (
|
|||||||
|
|
||||||
// === DTO Structs ===
|
// === DTO Structs ===
|
||||||
|
|
||||||
type WarehouseBaseDTO struct {
|
type WarehouseRelationDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Area *areaDTO.AreaBaseDTO `json:"area,omitempty"`
|
Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
|
||||||
Location *locationDTO.LocationBaseDTO `json:"location,omitempty"`
|
Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
|
||||||
Kandang *kandangDTO.KandangBaseDTO `json:"kandang,omitempty"`
|
Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WarehouseListDTO struct {
|
type WarehouseListDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Area *areaDTO.AreaBaseDTO `json:"area"`
|
Area *areaDTO.AreaRelationDTO `json:"area"`
|
||||||
Location *locationDTO.LocationBaseDTO `json:"location"`
|
Location *locationDTO.LocationRelationDTO `json:"location"`
|
||||||
Kandang *kandangDTO.KandangBaseDTO `json:"kandang"`
|
Kandang *kandangDTO.KandangRelationDTO `json:"kandang"`
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WarehouseDetailDTO struct {
|
type WarehouseDetailDTO struct {
|
||||||
@@ -39,26 +39,26 @@ type WarehouseDetailDTO struct {
|
|||||||
|
|
||||||
// === Mapper Functions ===
|
// === Mapper Functions ===
|
||||||
|
|
||||||
func ToWarehouseBaseDTO(e entity.Warehouse) WarehouseBaseDTO {
|
func ToWarehouseRelationDTO(e entity.Warehouse) WarehouseRelationDTO {
|
||||||
var area *areaDTO.AreaBaseDTO
|
var area *areaDTO.AreaRelationDTO
|
||||||
if e.Area.Id != 0 {
|
if e.Area.Id != 0 {
|
||||||
mapped := areaDTO.ToAreaBaseDTO(e.Area)
|
mapped := areaDTO.ToAreaRelationDTO(e.Area)
|
||||||
area = &mapped
|
area = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var location *locationDTO.LocationBaseDTO
|
var location *locationDTO.LocationRelationDTO
|
||||||
if e.Location != nil && e.Location.Id != 0 {
|
if e.Location != nil && e.Location.Id != 0 {
|
||||||
mapped := locationDTO.ToLocationBaseDTO(*e.Location)
|
mapped := locationDTO.ToLocationRelationDTO(*e.Location)
|
||||||
location = &mapped
|
location = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var kandang *kandangDTO.KandangBaseDTO
|
var kandang *kandangDTO.KandangRelationDTO
|
||||||
if e.Kandang != nil && e.Kandang.Id != 0 {
|
if e.Kandang != nil && e.Kandang.Id != 0 {
|
||||||
mapped := kandangDTO.ToKandangBaseDTO(*e.Kandang)
|
mapped := kandangDTO.ToKandangRelationDTO(*e.Kandang)
|
||||||
kandang = &mapped
|
kandang = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
return WarehouseBaseDTO{
|
return WarehouseRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Name: e.Name,
|
Name: e.Name,
|
||||||
Type: e.Type,
|
Type: e.Type,
|
||||||
@@ -69,27 +69,27 @@ func ToWarehouseBaseDTO(e entity.Warehouse) WarehouseBaseDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToWarehouseListDTO(e entity.Warehouse) WarehouseListDTO {
|
func ToWarehouseListDTO(e entity.Warehouse) WarehouseListDTO {
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserRelationDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userDTO.ToUserRelationDTO(e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var area *areaDTO.AreaBaseDTO
|
var area *areaDTO.AreaRelationDTO
|
||||||
if e.Area.Id != 0 {
|
if e.Area.Id != 0 {
|
||||||
mapped := areaDTO.ToAreaBaseDTO(e.Area)
|
mapped := areaDTO.ToAreaRelationDTO(e.Area)
|
||||||
area = &mapped
|
area = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var location *locationDTO.LocationBaseDTO
|
var location *locationDTO.LocationRelationDTO
|
||||||
if e.Location != nil && e.Location.Id != 0 {
|
if e.Location != nil && e.Location.Id != 0 {
|
||||||
mapped := locationDTO.ToLocationBaseDTO(*e.Location)
|
mapped := locationDTO.ToLocationRelationDTO(*e.Location)
|
||||||
location = &mapped
|
location = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var kandang *kandangDTO.KandangBaseDTO
|
var kandang *kandangDTO.KandangRelationDTO
|
||||||
if e.Kandang != nil && e.Kandang.Id != 0 {
|
if e.Kandang != nil && e.Kandang.Id != 0 {
|
||||||
mapped := kandangDTO.ToKandangBaseDTO(*e.Kandang)
|
mapped := kandangDTO.ToKandangRelationDTO(*e.Kandang)
|
||||||
kandang = &mapped
|
kandang = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,41 +4,50 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
areaBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
areaRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
|
||||||
fcrBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto"
|
fcrRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto"
|
||||||
flockBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
flockRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
||||||
kandangBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
kandangRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
|
||||||
locationBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
locationRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
|
||||||
|
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto"
|
||||||
|
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
||||||
pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
|
pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
|
||||||
userBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
userRelationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// === DTO Structs (ordered) ===
|
// === DTO Structs (ordered) ===
|
||||||
|
|
||||||
type ChickinBaseDTO struct {
|
type ProductWarehouseDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
Product *productDTO.ProductRelationDTO `json:"product,omitempty"`
|
||||||
ChickInDate time.Time `json:"chick_in_date"`
|
Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
|
||||||
ProductWarehouseId uint `json:"product_warehouse_id"`
|
}
|
||||||
UsageQty float64 `json:"usage_qty"`
|
|
||||||
PendingUsageQty float64 `json:"pending_usage_qty"`
|
type ChickinRelationDTO struct {
|
||||||
Notes string `json:"notes"`
|
Id uint `json:"id"`
|
||||||
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||||
|
ChickInDate time.Time `json:"chick_in_date"`
|
||||||
|
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||||
|
ProductWarehouse *ProductWarehouseDTO `json:"product_warehouse,omitempty"`
|
||||||
|
UsageQty float64 `json:"usage_qty"`
|
||||||
|
PendingUsageQty float64 `json:"pending_usage_qty"`
|
||||||
|
Notes string `json:"notes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectFlockDTO struct {
|
type ProjectFlockDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Period int `json:"period"`
|
Period int `json:"period"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
Flock *flockBaseDTO.FlockBaseDTO `json:"flock"`
|
Flock *flockRelationDTO.FlockRelationDTO `json:"flock"`
|
||||||
Area *areaBaseDTO.AreaBaseDTO `json:"area"`
|
Area *areaRelationDTO.AreaRelationDTO `json:"area"`
|
||||||
Fcr *fcrBaseDTO.FcrBaseDTO `json:"fcr"`
|
Fcr *fcrRelationDTO.FcrRelationDTO `json:"fcr"`
|
||||||
Location *locationBaseDTO.LocationBaseDTO `json:"location"`
|
Location *locationRelationDTO.LocationRelationDTO `json:"location"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectFlockKandangDTO struct {
|
type ProjectFlockKandangDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
ProjectFlock *ProjectFlockDTO `json:"project_flock"`
|
ProjectFlock *ProjectFlockDTO `json:"project_flock"`
|
||||||
Kandang *kandangBaseDTO.KandangBaseDTO `json:"kandang"`
|
Kandang *kandangRelationDTO.KandangRelationDTO `json:"kandang"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// gunakan base DTO dari package users
|
// gunakan base DTO dari package users
|
||||||
@@ -55,71 +64,71 @@ type ChickinSimpleDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ChickinListDTO struct {
|
type ChickinListDTO struct {
|
||||||
ChickinBaseDTO
|
ChickinRelationDTO
|
||||||
CreatedUser *userBaseDTO.UserBaseDTO `json:"created_user"`
|
CreatedUser *userRelationDTO.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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChickinDetailDTO struct {
|
type ChickinDetailDTO struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||||
ChickInDate time.Time `json:"chick_in_date"`
|
ChickInDate time.Time `json:"chick_in_date"`
|
||||||
ProductWarehouseId uint `json:"product_warehouse_id"`
|
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||||
UsageQty float64 `json:"usage_qty"`
|
UsageQty float64 `json:"usage_qty"`
|
||||||
PendingUsageQty float64 `json:"pending_usage_qty"`
|
PendingUsageQty float64 `json:"pending_usage_qty"`
|
||||||
Notes string `json:"notes"`
|
Notes string `json:"notes"`
|
||||||
CreatedBy uint `json:"created_by"`
|
CreatedBy uint `json:"created_by"`
|
||||||
CreatedUser *userBaseDTO.UserBaseDTO `json:"created_user"`
|
CreatedUser *userRelationDTO.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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Mapper Functions (ordered) ===
|
// === Mapper Functions (ordered) ===
|
||||||
|
|
||||||
func ToFlockDTO(e entity.Flock) flockBaseDTO.FlockBaseDTO {
|
func ToFlockDTO(e entity.Flock) flockRelationDTO.FlockRelationDTO {
|
||||||
return flockBaseDTO.ToFlockBaseDTO(e)
|
return flockRelationDTO.ToFlockRelationDTO(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToKandangDTO(e entity.Kandang) kandangBaseDTO.KandangBaseDTO {
|
func ToKandangDTO(e entity.Kandang) kandangRelationDTO.KandangRelationDTO {
|
||||||
return kandangBaseDTO.ToKandangBaseDTO(e)
|
return kandangRelationDTO.ToKandangRelationDTO(e)
|
||||||
}
|
}
|
||||||
func ToAreaDTO(e entity.Area) areaBaseDTO.AreaBaseDTO {
|
func ToAreaDTO(e entity.Area) areaRelationDTO.AreaRelationDTO {
|
||||||
return areaBaseDTO.ToAreaBaseDTO(e)
|
return areaRelationDTO.ToAreaRelationDTO(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToFcrDTO(e entity.Fcr) fcrBaseDTO.FcrBaseDTO {
|
func ToFcrDTO(e entity.Fcr) fcrRelationDTO.FcrRelationDTO {
|
||||||
return fcrBaseDTO.ToFcrBaseDTO(e)
|
return fcrRelationDTO.ToFcrRelationDTO(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToLocationDTO(e entity.Location) locationBaseDTO.LocationBaseDTO {
|
func ToLocationDTO(e entity.Location) locationRelationDTO.LocationRelationDTO {
|
||||||
return locationBaseDTO.ToLocationBaseDTO(e)
|
return locationRelationDTO.ToLocationRelationDTO(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToUserBaseDTO(e entity.User) userBaseDTO.UserBaseDTO {
|
func ToUserRelationDTO(e entity.User) userRelationDTO.UserRelationDTO {
|
||||||
return userBaseDTO.ToUserBaseDTO(e)
|
return userRelationDTO.ToUserRelationDTO(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToProjectFlockDTO(pfk entity.ProjectFlockKandang) ProjectFlockDTO {
|
func ToProjectFlockDTO(pfk entity.ProjectFlockKandang) ProjectFlockDTO {
|
||||||
e := pfk.ProjectFlock
|
e := pfk.ProjectFlock
|
||||||
var flock *flockBaseDTO.FlockBaseDTO
|
var flock *flockRelationDTO.FlockRelationDTO
|
||||||
if base := pfutils.DeriveBaseName(e.FlockName); base != "" {
|
if base := pfutils.DeriveBaseName(e.FlockName); base != "" {
|
||||||
summary := flockBaseDTO.FlockBaseDTO{Id: 0, Name: base}
|
summary := flockRelationDTO.FlockRelationDTO{Id: 0, Name: base}
|
||||||
flock = &summary
|
flock = &summary
|
||||||
}
|
}
|
||||||
var area *areaBaseDTO.AreaBaseDTO
|
var area *areaRelationDTO.AreaRelationDTO
|
||||||
if e.Area.Id != 0 {
|
if e.Area.Id != 0 {
|
||||||
mapped := areaBaseDTO.ToAreaBaseDTO(e.Area)
|
mapped := areaRelationDTO.ToAreaRelationDTO(e.Area)
|
||||||
area = &mapped
|
area = &mapped
|
||||||
}
|
}
|
||||||
var fcr *fcrBaseDTO.FcrBaseDTO
|
var fcr *fcrRelationDTO.FcrRelationDTO
|
||||||
if e.Fcr.Id != 0 {
|
if e.Fcr.Id != 0 {
|
||||||
mapped := fcrBaseDTO.ToFcrBaseDTO(e.Fcr)
|
mapped := fcrRelationDTO.ToFcrRelationDTO(e.Fcr)
|
||||||
fcr = &mapped
|
fcr = &mapped
|
||||||
}
|
}
|
||||||
var location *locationBaseDTO.LocationBaseDTO
|
var location *locationRelationDTO.LocationRelationDTO
|
||||||
if e.Location.Id != 0 {
|
if e.Location.Id != 0 {
|
||||||
mapped := locationBaseDTO.ToLocationBaseDTO(e.Location)
|
mapped := locationRelationDTO.ToLocationRelationDTO(e.Location)
|
||||||
location = &mapped
|
location = &mapped
|
||||||
}
|
}
|
||||||
return ProjectFlockDTO{
|
return ProjectFlockDTO{
|
||||||
@@ -139,9 +148,9 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
|||||||
mapped := ToProjectFlockDTO(e)
|
mapped := ToProjectFlockDTO(e)
|
||||||
pf = &mapped
|
pf = &mapped
|
||||||
}
|
}
|
||||||
var kandang *kandangBaseDTO.KandangBaseDTO
|
var kandang *kandangRelationDTO.KandangRelationDTO
|
||||||
if e.Kandang.Id != 0 {
|
if e.Kandang.Id != 0 {
|
||||||
mapped := kandangBaseDTO.ToKandangBaseDTO(e.Kandang)
|
mapped := kandangRelationDTO.ToKandangRelationDTO(e.Kandang)
|
||||||
kandang = &mapped
|
kandang = &mapped
|
||||||
}
|
}
|
||||||
return ProjectFlockKandangDTO{
|
return ProjectFlockKandangDTO{
|
||||||
@@ -151,7 +160,7 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO {
|
func ToChickinRelationDTO(e entity.ProjectChickin) ChickinRelationDTO {
|
||||||
var projectFlockKandangId uint
|
var projectFlockKandangId uint
|
||||||
// Check if ProjectFlockKandang relation is loaded
|
// Check if ProjectFlockKandang relation is loaded
|
||||||
if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.Id != 0 {
|
if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.Id != 0 {
|
||||||
@@ -160,11 +169,18 @@ func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO {
|
|||||||
// If relation is not loaded but ID is available, use the ID
|
// If relation is not loaded but ID is available, use the ID
|
||||||
projectFlockKandangId = e.ProjectFlockKandangId
|
projectFlockKandangId = e.ProjectFlockKandangId
|
||||||
}
|
}
|
||||||
return ChickinBaseDTO{
|
|
||||||
|
var productWarehouse *ProductWarehouseDTO
|
||||||
|
if e.ProductWarehouse != nil && e.ProductWarehouse.Id != 0 {
|
||||||
|
productWarehouse = toProductWarehouseDTO(e.ProductWarehouse)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ChickinRelationDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
ProjectFlockKandangId: projectFlockKandangId,
|
ProjectFlockKandangId: projectFlockKandangId,
|
||||||
ChickInDate: e.ChickInDate,
|
ChickInDate: e.ChickInDate,
|
||||||
ProductWarehouseId: e.ProductWarehouseId,
|
ProductWarehouseId: e.ProductWarehouseId,
|
||||||
|
ProductWarehouse: productWarehouse,
|
||||||
UsageQty: e.UsageQty,
|
UsageQty: e.UsageQty,
|
||||||
PendingUsageQty: e.PendingUsageQty,
|
PendingUsageQty: e.PendingUsageQty,
|
||||||
Notes: e.Notes,
|
Notes: e.Notes,
|
||||||
@@ -185,16 +201,16 @@ func ToChickinSimpleDTO(e entity.ProjectChickin) ChickinSimpleDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToChickinListDTO(e entity.ProjectChickin) ChickinListDTO {
|
func ToChickinListDTO(e entity.ProjectChickin) ChickinListDTO {
|
||||||
var createdUser *userBaseDTO.UserBaseDTO
|
var createdUser *userRelationDTO.UserRelationDTO
|
||||||
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
|
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
|
||||||
mapped := userBaseDTO.ToUserBaseDTO(*e.CreatedUser)
|
mapped := userRelationDTO.ToUserRelationDTO(*e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
return ChickinListDTO{
|
return ChickinListDTO{
|
||||||
ChickinBaseDTO: ToChickinBaseDTO(e),
|
ChickinRelationDTO: ToChickinRelationDTO(e),
|
||||||
CreatedUser: createdUser,
|
CreatedUser: createdUser,
|
||||||
CreatedAt: e.CreatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
UpdatedAt: e.UpdatedAt,
|
UpdatedAt: e.UpdatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,9 +231,9 @@ func ToChickinSimpleDTOs(e []entity.ProjectChickin) []ChickinSimpleDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToChickinDetailDTO(e entity.ProjectChickin) ChickinDetailDTO {
|
func ToChickinDetailDTO(e entity.ProjectChickin) ChickinDetailDTO {
|
||||||
var createdUser *userBaseDTO.UserBaseDTO
|
var createdUser *userRelationDTO.UserRelationDTO
|
||||||
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
|
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
|
||||||
mapped := userBaseDTO.ToUserBaseDTO(*e.CreatedUser)
|
mapped := userRelationDTO.ToUserRelationDTO(*e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,3 +259,25 @@ func ToChickinDetailDTOs(e []entity.ProjectChickin) []ChickinDetailDTO {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Helper Functions ===
|
||||||
|
|
||||||
|
// ToProductWarehouseDTO adalah exported helper untuk mapping ProductWarehouse (shared logic)
|
||||||
|
func ToProductWarehouseDTO(pw *entity.ProductWarehouse) *ProductWarehouseDTO {
|
||||||
|
if pw == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
product := productDTO.ToProductRelationDTO(pw.Product)
|
||||||
|
warehouse := warehouseDTO.ToWarehouseRelationDTO(pw.Warehouse)
|
||||||
|
|
||||||
|
return &ProductWarehouseDTO{
|
||||||
|
Id: pw.Id,
|
||||||
|
Product: &product,
|
||||||
|
Warehouse: &warehouse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toProductWarehouseDTO(pw *entity.ProductWarehouse) *ProductWarehouseDTO {
|
||||||
|
return ToProductWarehouseDTO(pw)
|
||||||
|
}
|
||||||
|
|||||||
@@ -123,39 +123,34 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
|||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Warehouse for Kandang not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Warehouse for Kandang not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
var productWarehouses []entity.ProductWarehouse
|
|
||||||
category := strings.ToUpper(strings.TrimSpace(projectFlockKandang.ProjectFlock.Category))
|
category := strings.ToUpper(strings.TrimSpace(projectFlockKandang.ProjectFlock.Category))
|
||||||
|
|
||||||
var productCategoryCode string
|
|
||||||
switch category {
|
|
||||||
case string(utils.ProjectFlockCategoryGrowing):
|
|
||||||
productCategoryCode = "DOC"
|
|
||||||
case string(utils.ProjectFlockCategoryLaying):
|
|
||||||
productCategoryCode = "PULLET"
|
|
||||||
default:
|
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Unknown category: %s", category))
|
|
||||||
}
|
|
||||||
|
|
||||||
productWarehouses, err = s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(c.Context(), productCategoryCode, warehouse.Id)
|
|
||||||
if err != nil || len(productWarehouses) == 0 {
|
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product for %s category in the Kandang's warehouse not found", strings.ToLower(category)))
|
|
||||||
}
|
|
||||||
|
|
||||||
chickinDate, err := utils.ParseDateString(req.ChickInDate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid ChickInDate format")
|
|
||||||
}
|
|
||||||
|
|
||||||
actorID := uint(1) // todo nanti ambil dari auth context
|
actorID := uint(1) // todo nanti ambil dari auth context
|
||||||
newChikins := make([]*entity.ProjectChickin, 0)
|
newChikins := make([]*entity.ProjectChickin, 0)
|
||||||
for _, productWarehouse := range productWarehouses {
|
|
||||||
availableQty, err := s.calculateAvailableQuantity(c, req.ProjectFlockKandangId, &productWarehouse, category)
|
for _, chickinReq := range req.ChickinRequests {
|
||||||
|
|
||||||
|
productWarehouse, err := s.ProductWarehouseRepo.GetByID(c.Context(), chickinReq.ProductWarehouseId, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to calculate available quantity for product warehouse %d", productWarehouse.Id))
|
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Product warehouse %d not found", chickinReq.ProductWarehouseId))
|
||||||
|
}
|
||||||
|
|
||||||
|
if productWarehouse.WarehouseId != warehouse.Id {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Product warehouse %d is not bound to kandang's warehouse", chickinReq.ProductWarehouseId))
|
||||||
|
}
|
||||||
|
|
||||||
|
chickinDate, err := utils.ParseDateString(chickinReq.ChickInDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid ChickInDate format for product warehouse %d", chickinReq.ProductWarehouseId))
|
||||||
|
}
|
||||||
|
|
||||||
|
availableQty, err := s.calculateAvailableQuantity(c, req.ProjectFlockKandangId, productWarehouse, category)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to calculate available quantity for product warehouse %d", chickinReq.ProductWarehouseId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if availableQty <= 0 {
|
if availableQty <= 0 {
|
||||||
continue
|
return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("No available quantity for product warehouse %d", chickinReq.ProductWarehouseId))
|
||||||
}
|
}
|
||||||
|
|
||||||
newChickin := &entity.ProjectChickin{
|
newChickin := &entity.ProjectChickin{
|
||||||
@@ -163,8 +158,8 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
|||||||
ChickInDate: chickinDate,
|
ChickInDate: chickinDate,
|
||||||
UsageQty: 0,
|
UsageQty: 0,
|
||||||
PendingUsageQty: availableQty,
|
PendingUsageQty: availableQty,
|
||||||
ProductWarehouseId: productWarehouse.Id,
|
ProductWarehouseId: chickinReq.ProductWarehouseId,
|
||||||
Notes: req.Note,
|
Notes: chickinReq.Note,
|
||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required,number,min=1"`
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required,number,min=1"`
|
||||||
ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"`
|
ChickinRequests []ChickinRequestItem `json:"chickin_requests" validate:"required,min=1,dive"`
|
||||||
Note string `json:"note" validate:"omitempty"`
|
}
|
||||||
|
|
||||||
|
type ChickinRequestItem struct {
|
||||||
|
ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"`
|
||||||
|
ProductWarehouseId uint `json:"product_warehouse_id" validate:"required,number,min=1"`
|
||||||
|
Note string `json:"note" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user