Compare commits

...

30 Commits

Author SHA1 Message Date
kris 7a6b3121f6 Update .gitlab-ci.yml file 2025-12-01 04:37:25 +00:00
kris 92901bc60c Update .gitlab-ci.yml file 2025-12-01 04:35:16 +00:00
GitLab Deploy Bot 2ea2e1ddf3 Merge remote-tracking branch 'origin/development' into devops-ec2 2025-12-01 11:26:27 +07:00
Adnan Zahir 2e2aed67b8 Merge branch 'feat/BE/Sprint-5' into 'development'
[FEAT/BE][US#159,160,161,162,163,164,255,256] : Purchase Request, Purchase Order, Sales Order, Delivery Order, Expense Submission, Expense Realization

See merge request mbugroup/lti-api!71
2025-11-24 09:47:23 +07:00
Hafizh A. Y. a801081a99 Merge branch 'dev/teguh' into 'feat/BE/Sprint-5'
[FIX/BE][US#116,164/TASK#260,261,262,263,264,265,266,267,268] : Creating BOP Migration And All API Needed on bop an bop realization module

See merge request mbugroup/lti-api!70
2025-11-21 04:30:51 +00:00
aguhh18 b0dfa717d5 FIX[BE-261]: expense list dto ganti dari hardcoded ke ambil dari expensebasedto 2025-11-21 11:16:34 +07:00
aguhh18 16d562e024 Merge branch 'feat/BE/Sprint-5' of https://gitlab.com/mbugroup/lti-api into dev/teguh 2025-11-21 11:05:33 +07:00
Hafizh A. Y. 8881be2a22 Merge branch 'fix/merge-request-project-flock' into 'feat/BE/Sprint-5'
fix merging

See merge request mbugroup/lti-api!69
2025-11-21 03:55:34 +00:00
ragilap 3fc330d8f7 fix merging 2025-11-21 10:50:30 +07:00
aguhh18 af147f4f2b Merge branch 'feat/BE/Sprint-5' of https://gitlab.com/mbugroup/lti-api into dev/teguh 2025-11-21 10:46:50 +07:00
aguhh18 6768092e3b Fix[BE-261]: fixing location not preloaded on get one non-bop API 2025-11-21 10:20:07 +07:00
Hafizh A. Y 53b226f243 fix(BE): adjust dto and project flock, master data, and marketing 2025-11-21 09:53:33 +07:00
Hafizh A. Y. cd752f19f4 Merge branch 'fix/merge-request-project-flock' into 'feat/BE/Sprint-5'
fix merging

See merge request mbugroup/lti-api!68
2025-11-21 02:04:30 +00:00
aguhh18 5a73ad0164 Fix[BE-261]: delete timestampz on expense nonstock anda expense reaalizations table 2025-11-21 01:06:48 +07:00
aguhh18 b8d1268dfa Feat[BE-261]: creating multiple Approval API, Update API, Delete API and some logic Adjustment 2025-11-21 00:55:02 +07:00
ragilap da10861fd2 fix merging 2025-11-20 21:17:04 +07:00
Hafizh A. Y 228aedc215 fix(BE-273): add object nonstock and supplier in response get one and fix name base to relation in dto 2025-11-20 14:59:50 +07:00
Hafizh A. Y. b4b860b9d4 Merge branch 'feat/BE/US-159,160/marketing' into 'feat/BE/Sprint-5'
Feat/be/us 159,160/marketing

See merge request mbugroup/lti-api!66
2025-11-20 02:16:25 +00:00
aguhh18 b502751b4e Fix[BE-261]: change realization json to not using prefix Realization 2025-11-20 08:50:47 +07:00
aguhh18 4c7e5b0731 Feat[BE-261,265]: add category request body on create on 2025-11-20 08:27:02 +07:00
aguhh18 105b20c333 Feat[BE-261,265]: createing BOP and BOP realization(Unfinished) 2025-11-19 16:05:11 +07:00
kris 8f4548971e Update .gitlab-ci.yml file 2025-11-11 09:23:32 +00:00
kris d940580152 Update .gitlab-ci.yml file 2025-11-11 09:18:02 +00:00
kris c8052f4cb5 Update .gitlab-ci.yml file 2025-11-11 09:09:50 +00:00
kris b8dca3c25e Update .gitlab-ci.yml file 2025-11-11 09:07:45 +00:00
kris c885fba4ef Update .gitlab-ci.yml file 2025-11-11 08:53:59 +00:00
kris 4837ed4255 Update .gitlab-ci.yml file 2025-11-11 08:45:25 +00:00
kris d740a3e26e Update .gitlab-ci.yml file 2025-11-11 08:36:36 +00:00
kris 0766cfeeb2 Update .gitlab-ci.yml file 2025-11-11 08:28:54 +00:00
kris 8e89f9fad0 Update .gitlab-ci.yml file 2025-11-11 08:26:37 +00:00
87 changed files with 2973 additions and 4014 deletions
+28 -6
View File
@@ -6,24 +6,45 @@ deploy-dev:
image: alpine:3.20 image: alpine:3.20
variables: variables:
DEPLOY_APP: "LTI-MBUGROUP" DEPLOY_APP: "LTI-MBUGROUP"
# Opsional: kalau pakai submodule, ini bikin clone submodule pakai SSH juga
GIT_SUBMODULE_STRATEGY: recursive
GIT_DEPTH: "1"
before_script: before_script:
- echo "🧰 Installing dependencies..." - echo "🧰 Installing dependencies..."
- apk update && apk add --no-cache openssh git curl - apk update && apk add --no-cache openssh git curl bash
# Setup SSH di runner
- mkdir -p ~/.ssh - mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa
- eval $(ssh-agent -s) - eval "$(ssh-agent -s)"
- ssh-add ~/.ssh/id_rsa - ssh-add ~/.ssh/id_rsa
# Trust host keys (server + gitlab) biar SSH gak nanya interaktif
- ssh-keyscan -H "$SERVER_IP" >> ~/.ssh/known_hosts - ssh-keyscan -H "$SERVER_IP" >> ~/.ssh/known_hosts
- ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
script: script:
- echo "🚀 Deploying latest code to $SERVER_USER@$SERVER_IP" - echo "🚀 Deploying latest code to $SERVER_USER@$SERVER_IP"
- > - >
if ssh -o StrictHostKeyChecking=no "$SERVER_USER@$SERVER_IP" " if ssh -o StrictHostKeyChecking=no "$SERVER_USER@$SERVER_IP" "
cd /home/devops/docker/deployment/development/lti-api && set -e
git fetch origin development &&
git reset --hard origin/development && cd /home/devops/docker/deployment/development/lti-api
# Pastikan remote origin SSH (antisipasi kalau pernah ke-set HTTPS)
git remote set-url origin git@gitlab.com:mbugroup/lti-api.git
# Pastikan server percaya gitlab.com juga (untuk git fetch via SSH)
mkdir -p ~/.ssh
ssh-keyscan -H gitlab.com >> ~/.ssh/known_hosts
# Fetch/reset pakai SSH
GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' git fetch origin development
git reset --hard origin/development
docker compose restart dev-api-lti || docker compose up -d dev-api-lti docker compose restart dev-api-lti || docker compose up -d dev-api-lti
"; then "; then
STATUS='success'; STATUS='success';
@@ -64,6 +85,7 @@ deploy-dev:
only: only:
- development - development
- devops-ec2
environment: environment:
name: development name: development
+1 -9
View File
@@ -5,12 +5,10 @@ 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
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/jackc/pgconn v1.14.1 github.com/jackc/pgconn v1.14.1
github.com/redis/go-redis/v9 v9.14.0 github.com/redis/go-redis/v9 v9.14.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
@@ -27,13 +25,12 @@ 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
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgio v1.0.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
) )
-19
View File
@@ -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=
@@ -1,13 +1,15 @@
CREATE TABLE expenses ( CREATE TABLE expenses (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
reference_number VARCHAR, -- format => BOP-LTI-0001 = 0001 is increment reference_number VARCHAR(50) UNIQUE NOT NULL,
supplier_id BIGINT NULL, supplier_id BIGINT NULL,
category VARCHAR(50) NOT NULL CHECK ( category VARCHAR(50) NOT NULL CHECK (
category IN ('BOP', 'NON-BOP') category IN ('BOP', 'NON-BOP')
), ),
po_number VARCHAR(50) UNIQUE NOT NULL, po_number VARCHAR(50) NULL,
document_path JSON, document_path JSON,
realization_document_path JSON,
expense_date DATE NOT NULL, expense_date DATE NOT NULL,
realization_date DATE,
grand_total NUMERIC(15, 3) DEFAULT 0, grand_total NUMERIC(15, 3) DEFAULT 0,
note TEXT, note TEXT,
created_by BIGINT, created_by BIGINT,
@@ -16,6 +18,8 @@ CREATE TABLE expenses (
deleted_at TIMESTAMPTZ deleted_at TIMESTAMPTZ
); );
CREATE SEQUENCE expenses_ref_seq INCREMENT BY 1 START WITH 1;
-- Tambahkan Foreign Key ke suppliers -- Tambahkan Foreign Key ke suppliers
DO $$ DO $$
BEGIN BEGIN
@@ -23,7 +27,9 @@ BEGIN
ALTER TABLE expenses ALTER TABLE expenses
ADD CONSTRAINT fk_expenses_supplier_id ADD CONSTRAINT fk_expenses_supplier_id
FOREIGN KEY (supplier_id) REFERENCES suppliers(id); FOREIGN KEY (supplier_id) REFERENCES suppliers(id);
END IF;
END IF;
END $$; END $$;
-- Tambahkan Foreign Key ke users (created_by) -- Tambahkan Foreign Key ke users (created_by)
@@ -1 +1,3 @@
DROP TABLE IF EXISTS expense_nonstocks; DROP TABLE IF EXISTS expense_nonstocks;
DROP SEQUENCE expenses_ref_seq;
@@ -1,15 +1,13 @@
CREATE TABLE expense_nonstocks ( CREATE TABLE expense_nonstocks (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
expense_id BIGINT, expense_id BIGINT NOT NULL,
project_flock_kandang_id BIGINT, project_flock_kandang_id BIGINT NULL,
kandang_id BIGINT NULL,
nonstock_id BIGINT, nonstock_id BIGINT,
qty NUMERIC(15, 3) NOT NULL, qty NUMERIC(15, 3) NOT NULL,
unit_price NUMERIC(15, 3) NOT NULL, unit_price NUMERIC(15, 3) NOT NULL,
total_price NUMERIC(15, 3) NOT NULL, total_price NUMERIC(15, 3) NOT NULL,
note TEXT NULL, note TEXT NULL
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
deleted_at TIMESTAMPTZ
); );
-- Tambahkan Foreign Key ke expenses -- Tambahkan Foreign Key ke expenses
@@ -32,6 +30,16 @@ BEGIN
END IF; END IF;
END $$; 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 -- Tambahkan Foreign Key ke nonstocks
DO $$ DO $$
BEGIN BEGIN
@@ -46,5 +54,3 @@ END $$;
CREATE INDEX idx_expense_nonstocks_expense_id ON expense_nonstocks (expense_id); CREATE INDEX idx_expense_nonstocks_expense_id ON expense_nonstocks (expense_id);
CREATE INDEX idx_expense_nonstocks_nonstock_id ON expense_nonstocks (nonstock_id); CREATE INDEX idx_expense_nonstocks_nonstock_id ON expense_nonstocks (nonstock_id);
CREATE INDEX idx_expense_nonstocks_deleted_at ON expense_nonstocks (deleted_at);
@@ -1,15 +1,12 @@
CREATE TABLE expense_realizations ( CREATE TABLE expense_realizations (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
expense_nonstock_id BIGINT, expense_nonstock_id BIGINT UNIQUE,
realization_qty NUMERIC(15, 3) NOT NULL, realization_qty NUMERIC(15, 3) NOT NULL,
realization_unit_price NUMERIC(15, 3) NOT NULL, realization_unit_price NUMERIC(15, 3) NOT NULL,
realization_total_price NUMERIC(15, 3) NOT NULL, realization_total_price NUMERIC(15, 3) NOT NULL,
realization_date DATE NOT NULL, realization_date DATE NOT NULL,
note TEXT, note TEXT,
created_by BIGINT, created_by BIGINT
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
deleted_at TIMESTAMPTZ
); );
-- Tambahkan Foreign Key ke expense_nonstocks -- Tambahkan Foreign Key ke expense_nonstocks
@@ -36,5 +33,3 @@ END $$;
CREATE INDEX idx_expense_realizations_nonstock_id ON expense_realizations (expense_nonstock_id); CREATE INDEX idx_expense_realizations_nonstock_id ON expense_realizations (expense_nonstock_id);
CREATE INDEX idx_expense_realizations_date ON expense_realizations (realization_date); CREATE INDEX idx_expense_realizations_date ON expense_realizations (realization_date);
CREATE INDEX idx_expense_realizations_deleted_at ON expense_realizations (deleted_at);
+4 -4
View File
@@ -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")
@@ -692,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
} }
@@ -765,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
} }
@@ -929,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,
+15 -13
View File
@@ -8,19 +8,21 @@ import (
) )
type Expense struct { type Expense struct {
Id uint64 `gorm:"primaryKey;autoIncrement"` Id uint64 `gorm:"primaryKey;autoIncrement"`
ReferenceNumber *string `gorm:"type:varchar(50)"` ReferenceNumber string `gorm:"type:varchar(50);uniqueIndex"`
SupplierId *uint64 `gorm:""` SupplierId uint64 `gorm:""`
Category string `gorm:"type:varchar(50);not null"` Category string `gorm:"type:varchar(50);not null"`
PoNumber string `gorm:"uniqueIndex;not null;type:varchar(50)"` PoNumber string `gorm:"type:varchar(50)"`
DocumentPath sql.NullString `gorm:"type:json"` DocumentPath sql.NullString `gorm:"type:json"` // Dokumen pengajuan
ExpenseDate time.Time `gorm:"type:date;not null"` RealizationDocumentPath sql.NullString `gorm:"type:json;column:realization_document_path"` // Dokumen realisasi
GrandTotal float64 `gorm:"type:numeric(15,3);default:0"` RealizationDate time.Time `gorm:"type:date;column:realization_date"` // Tanggal realisasi
Note *string `gorm:"type:text"` ExpenseDate time.Time `gorm:"type:date;not null"`
CreatedBy *uint64 `gorm:""` GrandTotal float64 `gorm:"type:numeric(15,3);default:0"`
CreatedAt time.Time `gorm:"autoCreateTime"` Note string `gorm:"type:text"`
UpdatedAt time.Time `gorm:"autoUpdateTime"` CreatedBy uint64 `gorm:""`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// Relations // Relations
Supplier *Supplier `gorm:"foreignKey:SupplierId;references:Id"` Supplier *Supplier `gorm:"foreignKey:SupplierId;references:Id"`
+11 -18
View File
@@ -1,27 +1,20 @@
package entities package entities
import (
"time"
"gorm.io/gorm"
)
type ExpenseNonstock struct { type ExpenseNonstock struct {
Id uint64 `gorm:"primaryKey;autoIncrement"` Id uint64 `gorm:"primaryKey;autoIncrement"`
ExpenseId *uint64 `gorm:""` ExpenseId *uint64 `gorm:""`
ProjectFlockKandangId *uint64 `gorm:""` ProjectFlockKandangId *uint64 `gorm:""`
NonstockId *uint64 `gorm:""` KandangId *uint64 `gorm:""`
Qty float64 `gorm:"type:numeric(15,3);not null"` NonstockId *uint64 `gorm:""`
UnitPrice float64 `gorm:"type:numeric(15,3);not null"` Qty float64 `gorm:"type:numeric(15,3);not null"`
TotalPrice float64 `gorm:"type:numeric(15,3);not null"` UnitPrice float64 `gorm:"type:numeric(15,3);not null"`
Note *string `gorm:"type:text"` TotalPrice float64 `gorm:"type:numeric(15,3);not null"`
CreatedAt time.Time `gorm:"autoCreateTime"` Note string `gorm:"type:text"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// Relations // Relations
Expense *Expense `gorm:"foreignKey:ExpenseId;references:Id"` Expense *Expense `gorm:"foreignKey:ExpenseId;references:Id"`
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"` ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
Kandang *Kandang `gorm:"foreignKey:KandangId;references:Id"`
Nonstock *Nonstock `gorm:"foreignKey:NonstockId;references:Id"` Nonstock *Nonstock `gorm:"foreignKey:NonstockId;references:Id"`
Realizations []ExpenseRealization `gorm:"foreignKey:ExpenseNonstockId;references:Id"` Realization *ExpenseRealization `gorm:"foreignKey:ExpenseNonstockId;references:Id"`
} }
+8 -13
View File
@@ -2,22 +2,17 @@ package entities
import ( import (
"time" "time"
"gorm.io/gorm"
) )
type ExpenseRealization struct { type ExpenseRealization struct {
Id uint64 `gorm:"primaryKey;autoIncrement"` Id uint64 `gorm:"primaryKey;autoIncrement"`
ExpenseNonstockId *uint64 `gorm:""` ExpenseNonstockId *uint64 `gorm:""`
RealizationQty float64 `gorm:"type:numeric(15,3);not null"` RealizationQty float64 `gorm:"type:numeric(15,3);not null"`
RealizationUnitPrice 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"` RealizationTotalPrice float64 `gorm:"type:numeric(15,3);not null"`
RealizationDate time.Time `gorm:"type:date;not null"` RealizationDate time.Time `gorm:"type:date;not null"`
Note *string `gorm:"type:text"` Note *string `gorm:"type:text"`
CreatedBy *uint64 `gorm:""` CreatedBy *uint64 `gorm:""`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// Relations // Relations
ExpenseNonstock *ExpenseNonstock `gorm:"foreignKey:ExpenseNonstockId;references:Id"` ExpenseNonstock *ExpenseNonstock `gorm:"foreignKey:ExpenseNonstockId;references:Id"`
+4 -4
View File
@@ -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"`
} }
+5 -2
View File
@@ -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"`
} }
+5 -5
View File
@@ -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"`
} }
+5 -2
View File
@@ -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"`
} }
+3 -1
View File
@@ -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",
+18 -18
View File
@@ -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)
@@ -1,8 +1,11 @@
package controller package controller
import ( import (
"encoding/json"
"fmt"
"math" "math"
"strconv" "strconv"
"strings"
"gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/dto" "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/dto"
service "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/services" service "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/services"
@@ -29,7 +32,7 @@ func (u *ExpenseController) GetAll(c *fiber.Ctx) error {
Search: c.Query("search", ""), Search: c.Query("search", ""),
} }
if query.Page < 1 || query.Limit < 1 { if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
} }
@@ -49,7 +52,7 @@ func (u *ExpenseController) GetAll(c *fiber.Ctx) error {
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
TotalResults: totalResults, TotalResults: totalResults,
}, },
Data: dto.ToExpenseListDTOs(result), Data: result,
}) })
} }
@@ -71,15 +74,52 @@ func (u *ExpenseController) GetOne(c *fiber.Ctx) error {
Code: fiber.StatusOK, Code: fiber.StatusOK,
Status: "success", Status: "success",
Message: "Get expense successfully", Message: "Get expense successfully",
Data: dto.ToExpenseListDTO(*result), Data: result,
}) })
} }
func (u *ExpenseController) CreateOne(c *fiber.Ctx) error { func (u *ExpenseController) CreateOne(c *fiber.Ctx) error {
req := new(validation.Create) req := new(validation.Create)
if err := c.BodyParser(req); err != nil { req.TransactionDate = c.FormValue("transaction_date")
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") 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) result, err := u.ExpenseService.CreateOne(c, req)
@@ -92,7 +132,7 @@ func (u *ExpenseController) CreateOne(c *fiber.Ctx) error {
Code: fiber.StatusCreated, Code: fiber.StatusCreated,
Status: "success", Status: "success",
Message: "Create expense successfully", Message: "Create expense successfully",
Data: dto.ToExpenseListDTO(*result), Data: result,
}) })
} }
@@ -105,8 +145,30 @@ func (u *ExpenseController) UpdateOne(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
} }
if err := c.BodyParser(req); err != nil { form, err := c.MultipartForm()
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") 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)) result, err := u.ExpenseService.UpdateOne(c, req, uint(id))
@@ -119,7 +181,7 @@ func (u *ExpenseController) UpdateOne(c *fiber.Ctx) error {
Code: fiber.StatusOK, Code: fiber.StatusOK,
Status: "success", Status: "success",
Message: "Update expense successfully", Message: "Update expense successfully",
Data: dto.ToExpenseListDTO(*result), Data: result,
}) })
} }
@@ -142,3 +204,188 @@ func (u *ExpenseController) DeleteOne(c *fiber.Ctx) error {
Message: "Delete expense successfully", 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",
})
}
+282 -29
View File
@@ -1,37 +1,94 @@
package dto package dto
import ( import (
"encoding/json"
"time" "time"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities" 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" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
) )
// === DTO Structs === // === DTO Structs ===
type ExpenseBaseDTO struct { type ExpenseRelationDTO struct {
Id uint64 `json:"id"` Id uint64 `json:"id"`
PoNumber string `json:"po_number"` PoNumber string `json:"po_number"`
ExpenseDate time.Time `json:"expense_date"` ExpenseDate time.Time `json:"expense_date"`
GrandTotal float64 `json:"grand_total"` GrandTotal float64 `json:"grand_total"`
} }
type ExpenseListDTO struct { type ExpenseBaseDTO struct {
Id uint64 `json:"id"` Id uint64 `json:"id"`
ReferenceNumber string `json:"reference_number"` ReferenceNumber string `json:"reference_number"`
PoNumber string `json:"po_number"` PoNumber string `json:"po_number"`
Category string `json:"category"` Category string `json:"category"`
ExpenseDate time.Time `json:"expense_date"` Supplier *supplierDTO.SupplierRelationDTO `json:"supplier,omitempty"`
GrandTotal float64 `json:"grand_total"` RealizationDate *time.Time `json:"realization_date,omitempty"`
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"` ExpenseDate time.Time `json:"expense_date"`
CreatedAt time.Time `json:"created_at"` GrandTotal float64 `json:"grand_total"`
UpdatedAt time.Time `json:"updated_at"` Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
} }
// === Mapper Functions === 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"`
}
func ToExpenseBaseDTO(e entity.Expense) ExpenseBaseDTO { type ExpenseDetailDTO struct {
return ExpenseBaseDTO{ 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, Id: e.Id,
PoNumber: e.PoNumber, PoNumber: e.PoNumber,
ExpenseDate: e.ExpenseDate, ExpenseDate: e.ExpenseDate,
@@ -39,30 +96,226 @@ func ToExpenseBaseDTO(e entity.Expense) ExpenseBaseDTO {
} }
} }
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 { func ToExpenseListDTO(e entity.Expense) ExpenseListDTO {
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 latestApproval *approvalDTO.ApprovalRelationDTO
if e.LatestApproval != nil {
mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval)
latestApproval = &mapped
}
return ExpenseListDTO{ return ExpenseListDTO{
Id: e.Id, ExpenseBaseDTO: ToExpenseBaseDTO(&e),
ReferenceNumber: *e.ReferenceNumber, CreatedUser: createdUser,
PoNumber: e.PoNumber, CreatedAt: e.CreatedAt,
Category: e.Category, UpdatedAt: e.UpdatedAt,
ExpenseDate: e.ExpenseDate, LatestApproval: latestApproval,
GrandTotal: e.GrandTotal,
CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt,
CreatedUser: createdUser,
} }
} }
func ToExpenseListDTOs(e []entity.Expense) []ExpenseListDTO { func ToExpenseListDTOs(expenses []entity.Expense) []ExpenseListDTO {
result := make([]ExpenseListDTO, len(e)) result := make([]ExpenseListDTO, len(expenses))
for i, r := range e { for i, expense := range expenses {
result[i] = ToExpenseListDTO(r) result[i] = ToExpenseListDTO(expense)
} }
return result 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
}
+23 -2
View File
@@ -1,15 +1,25 @@
package expenses package expenses
import ( import (
"fmt"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"gorm.io/gorm" "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" rExpense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
sExpense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/services" sExpense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/services"
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"
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{} type ExpenseModule struct{}
@@ -17,10 +27,21 @@ type ExpenseModule struct{}
func (ExpenseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { func (ExpenseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
expenseRepo := rExpense.NewExpenseRepository(db) expenseRepo := rExpense.NewExpenseRepository(db)
userRepo := rUser.NewUserRepository(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)
expenseService := sExpense.NewExpenseService(expenseRepo, validate) 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) userService := sUser.NewUserService(userRepo, validate)
ExpenseRoutes(router, userService, expenseService) ExpenseRoutes(router, userService, expenseService)
} }
@@ -11,6 +11,8 @@ import (
type ExpenseRepository interface { type ExpenseRepository interface {
repository.BaseRepository[entity.Expense] repository.BaseRepository[entity.Expense]
IdExists(ctx context.Context, id uint64) (bool, error) 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 { type ExpenseRepositoryImpl struct {
@@ -26,3 +28,24 @@ func NewExpenseRepository(db *gorm.DB) ExpenseRepository {
func (r *ExpenseRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) { func (r *ExpenseRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) {
return repository.Exists[entity.Expense](ctx, r.DB(), uint(id)) 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
}
@@ -11,6 +11,8 @@ import (
type ExpenseNonstockRepository interface { type ExpenseNonstockRepository interface {
repository.BaseRepository[entity.ExpenseNonstock] repository.BaseRepository[entity.ExpenseNonstock]
IdExists(ctx context.Context, id uint64) (bool, error) 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 { type ExpenseNonstockRepositoryImpl struct {
@@ -26,3 +28,28 @@ func NewExpenseNonstockRepository(db *gorm.DB) ExpenseNonstockRepository {
func (r *ExpenseNonstockRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) { func (r *ExpenseNonstockRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) {
return repository.Exists[entity.ExpenseNonstock](ctx, r.DB(), uint(id)) 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
}
@@ -11,6 +11,7 @@ import (
type ExpenseRealizationRepository interface { type ExpenseRealizationRepository interface {
repository.BaseRepository[entity.ExpenseRealization] repository.BaseRepository[entity.ExpenseRealization]
IdExists(ctx context.Context, id uint64) (bool, error) IdExists(ctx context.Context, id uint64) (bool, error)
GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error)
} }
type ExpenseRealizationRepositoryImpl struct { type ExpenseRealizationRepositoryImpl struct {
@@ -26,3 +27,14 @@ func NewExpenseRealizationRepository(db *gorm.DB) ExpenseRealizationRepository {
func (r *ExpenseRealizationRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) { func (r *ExpenseRealizationRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) {
return repository.Exists[entity.ExpenseRealization](ctx, r.DB(), uint(id)) 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
}
+7
View File
@@ -25,4 +25,11 @@ func ExpenseRoutes(v1 fiber.Router, u user.UserService, s expense.ExpenseService
route.Get("/:id", ctrl.GetOne) route.Get("/:id", ctrl.GetOne)
route.Patch("/:id", ctrl.UpdateOne) route.Patch("/:id", ctrl.UpdateOne)
route.Delete("/:id", ctrl.DeleteOne) 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
@@ -1,13 +1,34 @@
package validation package validation
import (
"mime/multipart"
)
type Create struct { type Create struct {
PoNumber string `json:"po_number" validate:"required,max=50"` PoNumber string `form:"po_number" json:"po_number" validate:"omitempty,max=50"`
Category string `json:"category" validate:"required,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 { type Update struct {
PoNumber *string `json:"po_number,omitempty" validate:"omitempty,max=50"` TransactionDate *string `form:"transaction_date" json:"transaction_date" validate:"omitempty,datetime=2006-01-02"`
Category *string `json:"category,omitempty" validate:"omitempty,max=50"` 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 { type Query struct {
@@ -15,3 +36,29 @@ type Query struct {
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
Search string `query:"search" validate:"omitempty,max=50"` 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,
} }
} }
@@ -9,7 +9,7 @@ import (
// === 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"`
@@ -17,21 +17,21 @@ type ProductWarehouseBaseDTO struct {
} }
type ProductWarehousNestedDTO struct { type ProductWarehousNestedDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
Product *productDTO.ProductBaseDTO `json:"product,omitempty"` Product *productDTO.ProductRelationDTO `json:"product,omitempty"`
Warehouse *WarehouseBaseDTO `json:"warehouse,omitempty"` Warehouse *WarehouseRelationDTO `json:"warehouse,omitempty"`
} }
type ProductWarehouseListDTO struct { type ProductWarehouseListDTO struct {
ProductWarehouseBaseDTO ProductWarehouseRelationDTO
Product *productDTO.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"` CreatedUser *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"`
} }
type UserBaseDTO struct { type UserRelationDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
Username string `json:"username"` Username string `json:"username"`
} }
@@ -41,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
@@ -83,12 +83,12 @@ func ToProductWarehouseBaseDTO(e entity.ProductWarehouse) ProductWarehouseBaseDT
} }
func ToProductWarehouseNestedDTO(e entity.ProductWarehouse) ProductWarehousNestedDTO { func ToProductWarehouseNestedDTO(e entity.ProductWarehouse) ProductWarehousNestedDTO {
product := productDTO.ToProductBaseDTO(e.Product) product := productDTO.ToProductRelationDTO(e.Product)
return ProductWarehousNestedDTO{ return ProductWarehousNestedDTO{
Id: e.Id, Id: e.Id,
Product: &product, Product: &product,
Warehouse: &WarehouseBaseDTO{ Warehouse: &WarehouseRelationDTO{
Id: e.Warehouse.Id, Id: e.Warehouse.Id,
Name: e.Warehouse.Name, Name: e.Warehouse.Name,
}, },
@@ -97,40 +97,40 @@ func ToProductWarehouseNestedDTO(e entity.ProductWarehouse) ProductWarehousNeste
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 := productDTO.ToProductBaseDTO(e.Product) product := productDTO.ToProductRelationDTO(e.Product)
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.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,
} }
@@ -141,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,
} }
@@ -165,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,
} }
@@ -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,
} }
} }
@@ -12,7 +12,7 @@ import (
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
) )
type MarketingBaseDTO struct { type MarketingRelationDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
SoNumber string `json:"so_number"` SoNumber string `json:"so_number"`
SoDate time.Time `json:"so_date"` SoDate time.Time `json:"so_date"`
@@ -20,28 +20,28 @@ type MarketingBaseDTO struct {
} }
type MarketingListDTO struct { type MarketingListDTO struct {
MarketingBaseDTO MarketingRelationDTO
Customer *customerDTO.CustomerBaseDTO `json:"customer,omitempty"` Customer customerDTO.CustomerRelationDTO `json:"customer"`
SalesPerson *userDTO.UserBaseDTO `json:"sales_person,omitempty"` SalesPerson userDTO.UserRelationDTO `json:"sales_person"`
SoDocs string `json:"so_docs,omitempty"` SoDocs string `json:"so_docs"`
SalesOrder []MarketingProductDTO `json:"sales_order,omitempty"` SalesOrder []MarketingProductDTO `json:"sales_order"`
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"`
LatestApproval *approvalDTO.ApprovalBaseDTO `json:"latest_approval,omitempty"` LatestApproval approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
} }
type MarketingDetailDTO struct { type MarketingDetailDTO struct {
MarketingBaseDTO MarketingRelationDTO
Customer *customerDTO.CustomerBaseDTO `json:"customer,omitempty"` Customer customerDTO.CustomerRelationDTO `json:"customer"`
SalesPerson *userDTO.UserBaseDTO `json:"sales_person,omitempty"` SalesPerson userDTO.UserRelationDTO `json:"sales_person"`
SoDocs string `json:"so_docs,omitempty"` SoDocs string `json:"so_docs"`
SalesOrder []MarketingProductDTO `json:"sales_order,omitempty"` SalesOrder []MarketingProductDTO `json:"sales_order"`
DeliveryOrder []DeliveryGroupDTO `json:"delivery_order,omitempty"` DeliveryOrder []DeliveryGroupDTO `json:"delivery_order"`
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"`
LatestApproval *approvalDTO.ApprovalBaseDTO `json:"latest_approval,omitempty"` LatestApproval approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
} }
type MarketingDeliveryProductDTO struct { type MarketingDeliveryProductDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
@@ -67,10 +67,10 @@ type DeliveryItemDTO struct {
} }
type DeliveryGroupDTO struct { type DeliveryGroupDTO struct {
DoNumber string `json:"do_number"` DoNumber string `json:"do_number"`
DeliveryDate *time.Time `json:"delivery_date"` DeliveryDate *time.Time `json:"delivery_date"`
Warehouse *productwarehouseDTO.WarehouseBaseDTO `json:"warehouse,omitempty"` Warehouse *productwarehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
Deliveries []DeliveryItemDTO `json:"deliveries"` Deliveries []DeliveryItemDTO `json:"deliveries"`
} }
type MarketingProductDTO struct { type MarketingProductDTO struct {
@@ -86,8 +86,8 @@ type MarketingProductDTO struct {
VehicleNumber string `json:"vehicle_number,omitempty"` VehicleNumber string `json:"vehicle_number,omitempty"`
} }
func ToMarketingBaseDTO(marketing *entity.Marketing) MarketingBaseDTO { func ToMarketingRelationDTO(marketing *entity.Marketing) MarketingRelationDTO {
return MarketingBaseDTO{ return MarketingRelationDTO{
Id: marketing.Id, Id: marketing.Id,
SoNumber: marketing.SoNumber, SoNumber: marketing.SoNumber,
SoDate: marketing.SoDate, SoDate: marketing.SoDate,
@@ -131,28 +131,28 @@ func ToMarketingDeliveryProductDTO(e entity.MarketingDeliveryProduct) MarketingD
} }
func ToMarketingListDTO(marketing *entity.Marketing, deliveryProducts []entity.MarketingDeliveryProduct) MarketingListDTO { func ToMarketingListDTO(marketing *entity.Marketing, deliveryProducts []entity.MarketingDeliveryProduct) MarketingListDTO {
var createdUser *userDTO.UserBaseDTO var createdUser userDTO.UserRelationDTO
if marketing.CreatedUser.Id != 0 { if marketing.CreatedUser.Id != 0 {
mapped := userDTO.ToUserBaseDTO(marketing.CreatedUser) mapped := userDTO.ToUserRelationDTO(marketing.CreatedUser)
createdUser = &mapped createdUser = mapped
} }
var customer *customerDTO.CustomerBaseDTO var customer customerDTO.CustomerRelationDTO
if marketing.Customer.Id != 0 { if marketing.Customer.Id != 0 {
mapped := customerDTO.ToCustomerBaseDTO(marketing.Customer) mapped := customerDTO.ToCustomerRelationDTO(marketing.Customer)
customer = &mapped customer = mapped
} }
var salesPerson *userDTO.UserBaseDTO var salesPerson userDTO.UserRelationDTO
if marketing.SalesPerson.Id != 0 { if marketing.SalesPerson.Id != 0 {
mapped := userDTO.ToUserBaseDTO(marketing.SalesPerson) mapped := userDTO.ToUserRelationDTO(marketing.SalesPerson)
salesPerson = &mapped salesPerson = mapped
} }
var latestApproval *approvalDTO.ApprovalBaseDTO var latestApproval approvalDTO.ApprovalRelationDTO
if marketing.LatestApproval != nil { if marketing.LatestApproval != nil {
mapped := approvalDTO.ToApprovalDTO(*marketing.LatestApproval) mapped := approvalDTO.ToApprovalDTO(*marketing.LatestApproval)
latestApproval = &mapped latestApproval = mapped
} }
var salesOrderProducts []MarketingProductDTO var salesOrderProducts []MarketingProductDTO
@@ -164,35 +164,35 @@ func ToMarketingListDTO(marketing *entity.Marketing, deliveryProducts []entity.M
} }
return MarketingListDTO{ return MarketingListDTO{
MarketingBaseDTO: ToMarketingBaseDTO(marketing), MarketingRelationDTO: ToMarketingRelationDTO(marketing),
Customer: customer, Customer: customer,
SalesPerson: salesPerson, SalesPerson: salesPerson,
SoDocs: marketing.SoDocs, SoDocs: marketing.SoDocs,
SalesOrder: salesOrderProducts, SalesOrder: salesOrderProducts,
CreatedUser: createdUser, CreatedUser: createdUser,
CreatedAt: marketing.CreatedAt, CreatedAt: marketing.CreatedAt,
UpdatedAt: marketing.UpdatedAt, UpdatedAt: marketing.UpdatedAt,
LatestApproval: latestApproval, LatestApproval: latestApproval,
} }
} }
func ToMarketingDetailDTO(marketing *entity.Marketing, deliveryProducts []entity.MarketingDeliveryProduct) MarketingDetailDTO { func ToMarketingDetailDTO(marketing *entity.Marketing, deliveryProducts []entity.MarketingDeliveryProduct) MarketingDetailDTO {
var createdUser *userDTO.UserBaseDTO var createdUser userDTO.UserRelationDTO
if marketing.CreatedUser.Id != 0 { if marketing.CreatedUser.Id != 0 {
mapped := userDTO.ToUserBaseDTO(marketing.CreatedUser) mapped := userDTO.ToUserRelationDTO(marketing.CreatedUser)
createdUser = &mapped createdUser = mapped
} }
var customer *customerDTO.CustomerBaseDTO var customer customerDTO.CustomerRelationDTO
if marketing.Customer.Id != 0 { if marketing.Customer.Id != 0 {
mapped := customerDTO.ToCustomerBaseDTO(marketing.Customer) mapped := customerDTO.ToCustomerRelationDTO(marketing.Customer)
customer = &mapped customer = mapped
} }
var salesPerson *userDTO.UserBaseDTO var salesPerson userDTO.UserRelationDTO
if marketing.SalesPerson.Id != 0 { if marketing.SalesPerson.Id != 0 {
mapped := userDTO.ToUserBaseDTO(marketing.SalesPerson) mapped := userDTO.ToUserRelationDTO(marketing.SalesPerson)
salesPerson = &mapped salesPerson = mapped
} }
var salesOrderProducts []MarketingProductDTO var salesOrderProducts []MarketingProductDTO
@@ -214,23 +214,23 @@ func ToMarketingDetailDTO(marketing *entity.Marketing, deliveryProducts []entity
deliveryGroups := groupDeliveryProducts(deliveryProductsDTOs, marketing.SoNumber) deliveryGroups := groupDeliveryProducts(deliveryProductsDTOs, marketing.SoNumber)
var latestApproval *approvalDTO.ApprovalBaseDTO var latestApproval approvalDTO.ApprovalRelationDTO
if marketing.LatestApproval != nil { if marketing.LatestApproval != nil {
mapped := approvalDTO.ToApprovalDTO(*marketing.LatestApproval) mapped := approvalDTO.ToApprovalDTO(*marketing.LatestApproval)
latestApproval = &mapped latestApproval = mapped
} }
return MarketingDetailDTO{ return MarketingDetailDTO{
MarketingBaseDTO: ToMarketingBaseDTO(marketing), MarketingRelationDTO: ToMarketingRelationDTO(marketing),
SoDocs: marketing.SoDocs, SoDocs: marketing.SoDocs,
Customer: customer, Customer: customer,
SalesPerson: salesPerson, SalesPerson: salesPerson,
SalesOrder: salesOrderProducts, SalesOrder: salesOrderProducts,
DeliveryOrder: deliveryGroups, DeliveryOrder: deliveryGroups,
CreatedUser: createdUser, CreatedUser: createdUser,
CreatedAt: marketing.CreatedAt, CreatedAt: marketing.CreatedAt,
UpdatedAt: marketing.UpdatedAt, UpdatedAt: marketing.UpdatedAt,
LatestApproval: latestApproval, LatestApproval: latestApproval,
} }
} }
@@ -285,7 +285,7 @@ func groupDeliveryProducts(products []MarketingDeliveryProductDTO, soNumber stri
if !exists { if !exists {
group = &DeliveryGroupDTO{ group = &DeliveryGroupDTO{
DeliveryDate: product.DeliveryDate, DeliveryDate: product.DeliveryDate,
Warehouse: &productwarehouseDTO.WarehouseBaseDTO{ Warehouse: &productwarehouseDTO.WarehouseRelationDTO{
Id: warehouseId, Id: warehouseId,
Name: warehouseName, Name: warehouseName,
}, },
@@ -58,7 +58,7 @@ func (s salesOrdersService) withRelations(db *gorm.DB) *gorm.DB {
Preload("CreatedUser"). Preload("CreatedUser").
Preload("Customer"). Preload("Customer").
Preload("SalesPerson"). Preload("SalesPerson").
Preload("Products.ProductWarehouse.Product"). Preload("Products.ProductWarehouse.Product.Flags").
Preload("Products.ProductWarehouse.Warehouse") Preload("Products.ProductWarehouse.Warehouse")
} }
+13 -13
View File
@@ -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,
} }
} }
+13 -13
View File
@@ -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,
} }
} }
+13 -13
View File
@@ -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,
} }
} }
+13 -13
View File
@@ -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
} }
@@ -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
}
+2 -2
View File
@@ -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"`
} }
+10 -10
View File
@@ -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,26 +4,26 @@ 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" productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto"
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto" warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
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 ProductWarehouseDTO struct { type ProductWarehouseDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
Product *productDTO.ProductBaseDTO `json:"product,omitempty"` Product *productDTO.ProductRelationDTO `json:"product,omitempty"`
Warehouse *warehouseDTO.WarehouseBaseDTO `json:"warehouse,omitempty"` Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
} }
type ChickinBaseDTO struct { type ChickinRelationDTO 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"`
@@ -35,19 +35,19 @@ type ChickinBaseDTO struct {
} }
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
@@ -64,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{
@@ -148,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{
@@ -160,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 {
@@ -175,7 +175,7 @@ func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO {
productWarehouse = toProductWarehouseDTO(e.ProductWarehouse) productWarehouse = toProductWarehouseDTO(e.ProductWarehouse)
} }
return ChickinBaseDTO{ return ChickinRelationDTO{
Id: e.Id, Id: e.Id,
ProjectFlockKandangId: projectFlockKandangId, ProjectFlockKandangId: projectFlockKandangId,
ChickInDate: e.ChickInDate, ChickInDate: e.ChickInDate,
@@ -201,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,
} }
} }
@@ -231,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
} }
@@ -268,8 +268,8 @@ func ToProductWarehouseDTO(pw *entity.ProductWarehouse) *ProductWarehouseDTO {
return nil return nil
} }
product := productDTO.ToProductBaseDTO(pw.Product) product := productDTO.ToProductRelationDTO(pw.Product)
warehouse := warehouseDTO.ToWarehouseBaseDTO(pw.Warehouse) warehouse := warehouseDTO.ToWarehouseRelationDTO(pw.Warehouse)
return &ProductWarehouseDTO{ return &ProductWarehouseDTO{
Id: pw.Id, Id: pw.Id,
@@ -4,7 +4,6 @@ import (
"math" "math"
"strconv" "strconv"
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/dto" "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/dto"
service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/services" service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/services"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/validations" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/validations"
@@ -41,18 +40,14 @@ func (u *ProjectFlockKandangController) GetAll(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
} }
results, totalResults, flockMap, err := u.ProjectFlockKandangService.GetAll(c, query) results, totalResults, err := u.ProjectFlockKandangService.GetAll(c, query)
if err != nil { if err != nil {
return err return err
} }
data := make([]dto.ProjectFlockKandangListDTO, 0) data := make([]dto.ProjectFlockKandangListDTO, 0, len(results))
for _, result := range results { for _, result := range results {
var flock *flockDTO.FlockBaseDTO data = append(data, dto.ToProjectFlockKandangListDTO(result))
if flockMap != nil {
flock = flockMap[result.ProjectFlock.Id]
}
data = append(data, dto.ToProjectFlockKandangListDTOWithFlock(result, flock))
} }
return c.Status(fiber.StatusOK). return c.Status(fiber.StatusOK).
@@ -71,14 +66,12 @@ func (u *ProjectFlockKandangController) GetAll(c *fiber.Ctx) error {
} }
func (u *ProjectFlockKandangController) GetOne(c *fiber.Ctx) error { func (u *ProjectFlockKandangController) GetOne(c *fiber.Ctx) error {
param := c.Params("id") id, err := strconv.Atoi(c.Params("id"))
id, err := strconv.Atoi(param)
if err != nil { if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
} }
result, availableQtys, productWarehouses, flock, err := u.ProjectFlockKandangService.GetOne(c, uint(id)) result, availableQtys, productWarehouses, err := u.ProjectFlockKandangService.GetOne(c, uint(id))
if err != nil { if err != nil {
return err return err
} }
@@ -88,6 +81,6 @@ func (u *ProjectFlockKandangController) GetOne(c *fiber.Ctx) error {
Code: fiber.StatusOK, Code: fiber.StatusOK,
Status: "success", Status: "success",
Message: "Get projectFlockKandang successfully", Message: "Get projectFlockKandang successfully",
Data: dto.ToProjectFlockKandangDetailDTOWithAvailableQtyAndFlock(*result, availableQtys, productWarehouses, flock), Data: dto.ToProjectFlockKandangDetailDTOWithAvailableQty(*result, availableQtys, productWarehouses),
}) })
} }
@@ -5,9 +5,10 @@ import (
entity "gitlab.com/mbugroup/lti-api.git/internal/entities" entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto" approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
productWarehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/dto"
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto" areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto" fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto"
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto" kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto" locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/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" warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
@@ -18,36 +19,27 @@ import (
// === DTO Structs (ordered) === // === DTO Structs (ordered) ===
type ProjectFlockKandangBaseDTO struct { type ProjectFlockKandangRelationDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
KandangId uint `json:"kandang_id"` KandangId uint `json:"kandang_id"`
ProjectFlockId uint `json:"project_flock_id"` ProjectFlockId uint `json:"project_flock_id"`
} }
type ProjectFlockDTO struct { type ProjectFlockDTO struct {
Id uint `json:"id"` projectFlockDTO.ProjectFlockRelationDTO
Name string `json:"flock_name,omitempty"` Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
Period int `json:"period"` Category string `json:"category"`
Flock *flockDTO.FlockBaseDTO `json:"flock,omitempty"` Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"`
Area *areaDTO.AreaBaseDTO `json:"area,omitempty"` Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
Category string `json:"category"` CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
Fcr *fcrDTO.FcrBaseDTO `json:"fcr,omitempty"` CreatedAt time.Time `json:"created_at"`
Location *locationDTO.LocationBaseDTO `json:"location,omitempty"` UpdatedAt time.Time `json:"updated_at"`
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type KandangDTO struct {
Id uint `json:"id"`
Name string `json:"name"`
Status string `json:"status"`
} }
type ProductWarehouseDTO struct { type ProductWarehouseDTO struct {
Id uint `json:"id"` productWarehouseDTO.ProductWarehouseRelationDTO
Product *productDTO.ProductBaseDTO `json:"product,omitempty"` Product *productDTO.ProductRelationDTO `json:"product,omitempty"`
Warehouse *warehouseDTO.WarehouseBaseDTO `json:"warehouse,omitempty"` Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse,omitempty"`
} }
type AvailableQtyDTO struct { type AvailableQtyDTO struct {
@@ -56,24 +48,24 @@ type AvailableQtyDTO struct {
} }
type ProjectFlockKandangListDTO struct { type ProjectFlockKandangListDTO struct {
ProjectFlockKandangBaseDTO ProjectFlockKandangRelationDTO
ProjectFlock *ProjectFlockDTO `json:"project_flock,omitempty"` ProjectFlock *ProjectFlockDTO `json:"project_flock,omitempty"`
Kandang *KandangDTO `json:"kandang,omitempty"` Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"`
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"`
Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"` Approval *approvalDTO.ApprovalRelationDTO `json:"approval,omitempty"`
} }
type ProjectFlockKandangDetailDTO struct { type ProjectFlockKandangDetailDTO struct {
ProjectFlockKandangListDTO ProjectFlockKandangListDTO
Chickins []chickinDTO.ChickinBaseDTO `json:"chickins,omitempty"` Chickins []chickinDTO.ChickinRelationDTO `json:"chickins,omitempty"`
AvailableQtys []AvailableQtyDTO `json:"available_qtys,omitempty"` AvailableQtys []AvailableQtyDTO `json:"available_qtys,omitempty"`
} }
// === Mapper Functions (ordered) === // === Mapper Functions (ordered) ===
func ToProjectFlockKandangBaseDTO(e entity.ProjectFlockKandang) ProjectFlockKandangBaseDTO { func ToProjectFlockKandangRelationDTO(e entity.ProjectFlockKandang) ProjectFlockKandangRelationDTO {
return ProjectFlockKandangBaseDTO{ return ProjectFlockKandangRelationDTO{
Id: e.Id, Id: e.Id,
KandangId: e.KandangId, KandangId: e.KandangId,
ProjectFlockId: e.ProjectFlockId, ProjectFlockId: e.ProjectFlockId,
@@ -86,38 +78,31 @@ func toProjectFlockDTO(pf *projectFlockDTO.ProjectFlockListDTO) *ProjectFlockDTO
} }
return &ProjectFlockDTO{ return &ProjectFlockDTO{
Id: pf.Id, ProjectFlockRelationDTO: pf.ProjectFlockRelationDTO,
Name: pf.FlockName, Area: pf.Area,
Period: pf.Period, Category: pf.Category,
Flock: pf.Flock, Fcr: pf.Fcr,
Area: pf.Area, Location: pf.Location,
Category: pf.Category, CreatedUser: pf.CreatedUser,
Fcr: pf.Fcr, CreatedAt: pf.CreatedAt,
Location: pf.Location, UpdatedAt: pf.UpdatedAt,
CreatedUser: pf.CreatedUser,
CreatedAt: pf.CreatedAt,
UpdatedAt: pf.UpdatedAt,
} }
} }
func ToProjectFlockKandangDetailDTOWithAvailableQty(e entity.ProjectFlockKandang, availableQtyMap map[uint]float64, productWarehouses []entity.ProductWarehouse) ProjectFlockKandangDetailDTO { func ToProjectFlockKandangDetailDTOWithAvailableQty(e entity.ProjectFlockKandang, availableQtyMap map[uint]float64, productWarehouses []entity.ProductWarehouse) ProjectFlockKandangDetailDTO {
return ToProjectFlockKandangDetailDTOWithAvailableQtyAndFlock(e, availableQtyMap, productWarehouses, nil)
}
func ToProjectFlockKandangDetailDTOWithAvailableQtyAndFlock(e entity.ProjectFlockKandang, availableQtyMap map[uint]float64, productWarehouses []entity.ProductWarehouse, flock *flockDTO.FlockBaseDTO) ProjectFlockKandangDetailDTO {
var projectFlockSummary *projectFlockDTO.ProjectFlockListDTO var projectFlockSummary *projectFlockDTO.ProjectFlockListDTO
if e.ProjectFlock.Id != 0 { if e.ProjectFlock.Id != 0 {
mapped := projectFlockDTO.ToProjectFlockListDTO(e.ProjectFlock, flock) mapped := projectFlockDTO.ToProjectFlockListDTO(e.ProjectFlock)
projectFlockSummary = &mapped projectFlockSummary = &mapped
} }
listDTO := ProjectFlockKandangListDTO{ listDTO := ProjectFlockKandangListDTO{
ProjectFlockKandangBaseDTO: ToProjectFlockKandangBaseDTO(e), ProjectFlockKandangRelationDTO: ToProjectFlockKandangRelationDTO(e),
ProjectFlock: toProjectFlockDTO(projectFlockSummary), ProjectFlock: toProjectFlockDTO(projectFlockSummary),
Kandang: toKandangDTO(e.Kandang), Kandang: toKandangRelation(e.Kandang),
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
CreatedUser: toCreatedUserDTO(e.ProjectFlock), CreatedUser: toCreatedUserDTO(e.ProjectFlock),
Approval: toApprovalDTO(e), Approval: toApprovalDTO(e),
} }
return ProjectFlockKandangDetailDTO{ return ProjectFlockKandangDetailDTO{
@@ -127,30 +112,16 @@ func ToProjectFlockKandangDetailDTOWithAvailableQtyAndFlock(e entity.ProjectFloc
} }
} }
func toKandangDTO(kandang entity.Kandang) *KandangDTO { func toKandangRelation(kandang entity.Kandang) *kandangDTO.KandangRelationDTO {
if kandang.Id == 0 { if kandang.Id == 0 {
return nil return nil
} }
return &KandangDTO{ mapped := kandangDTO.ToKandangRelationDTO(kandang)
Id: kandang.Id, return &mapped
Name: kandang.Name,
Status: kandang.Status,
}
} }
func toFlockDTO(flock *entity.Flock) *flockDTO.FlockBaseDTO { func toApprovalDTO(e entity.ProjectFlockKandang) *approvalDTO.ApprovalRelationDTO {
if flock == nil || flock.Id == 0 {
return nil
}
return &flockDTO.FlockBaseDTO{
Id: flock.Id,
Name: flock.Name,
}
}
func toApprovalDTO(e entity.ProjectFlockKandang) *approvalDTO.ApprovalBaseDTO {
if e.LatestApproval != nil { if e.LatestApproval != nil {
mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval) mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval)
return &mapped return &mapped
@@ -159,23 +130,19 @@ func toApprovalDTO(e entity.ProjectFlockKandang) *approvalDTO.ApprovalBaseDTO {
} }
func ToProjectFlockKandangListDTO(e entity.ProjectFlockKandang) ProjectFlockKandangListDTO { func ToProjectFlockKandangListDTO(e entity.ProjectFlockKandang) ProjectFlockKandangListDTO {
return ToProjectFlockKandangListDTOWithFlock(e, nil)
}
func ToProjectFlockKandangListDTOWithFlock(e entity.ProjectFlockKandang, flock *flockDTO.FlockBaseDTO) ProjectFlockKandangListDTO {
var projectFlockSummary *projectFlockDTO.ProjectFlockListDTO var projectFlockSummary *projectFlockDTO.ProjectFlockListDTO
if e.ProjectFlock.Id != 0 { if e.ProjectFlock.Id != 0 {
mapped := projectFlockDTO.ToProjectFlockListDTOWithFlock(e.ProjectFlock, flock) mapped := projectFlockDTO.ToProjectFlockListDTO(e.ProjectFlock)
projectFlockSummary = &mapped projectFlockSummary = &mapped
} }
return ProjectFlockKandangListDTO{ return ProjectFlockKandangListDTO{
ProjectFlockKandangBaseDTO: ToProjectFlockKandangBaseDTO(e), ProjectFlockKandangRelationDTO: ToProjectFlockKandangRelationDTO(e),
ProjectFlock: toProjectFlockDTO(projectFlockSummary), ProjectFlock: toProjectFlockDTO(projectFlockSummary),
Kandang: toKandangDTO(e.Kandang), Kandang: toKandangRelation(e.Kandang),
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
CreatedUser: toCreatedUserDTO(e.ProjectFlock), CreatedUser: toCreatedUserDTO(e.ProjectFlock),
Approval: toApprovalDTO(e), Approval: toApprovalDTO(e),
} }
} }
@@ -187,12 +154,12 @@ func ToProjectFlockKandangListDTOs(e []entity.ProjectFlockKandang) []ProjectFloc
return result return result
} }
func toCreatedUserDTO(pf entity.ProjectFlock) *userDTO.UserBaseDTO { func toCreatedUserDTO(pf entity.ProjectFlock) *userDTO.UserRelationDTO {
if pf.CreatedUser.Id != 0 { if pf.CreatedUser.Id != 0 {
mapped := userDTO.ToUserBaseDTO(pf.CreatedUser) mapped := userDTO.ToUserRelationDTO(pf.CreatedUser)
return &mapped return &mapped
} else if pf.CreatedBy != 0 { } else if pf.CreatedBy != 0 {
return &userDTO.UserBaseDTO{ return &userDTO.UserRelationDTO{
Id: pf.CreatedBy, Id: pf.CreatedBy,
IdUser: int64(pf.CreatedBy), IdUser: int64(pf.CreatedBy),
} }
@@ -200,14 +167,14 @@ func toCreatedUserDTO(pf entity.ProjectFlock) *userDTO.UserBaseDTO {
return nil return nil
} }
func toChickinDTOs(chickins []entity.ProjectChickin) []chickinDTO.ChickinBaseDTO { func toChickinDTOs(chickins []entity.ProjectChickin) []chickinDTO.ChickinRelationDTO {
if len(chickins) == 0 { if len(chickins) == 0 {
return nil return nil
} }
result := make([]chickinDTO.ChickinBaseDTO, len(chickins)) result := make([]chickinDTO.ChickinRelationDTO, len(chickins))
for i, ch := range chickins { for i, ch := range chickins {
result[i] = chickinDTO.ToChickinBaseDTO(ch) result[i] = chickinDTO.ToChickinRelationDTO(ch)
} }
return result return result
} }
@@ -255,14 +222,23 @@ func ToProductWarehouseDTO(pw *entity.ProductWarehouse) *ProductWarehouseDTO {
return nil return nil
} }
chickinPwDTO := chickinDTO.ToProductWarehouseDTO(pw) base := productWarehouseDTO.ToProductWarehouseRelationDTO(*pw)
if chickinPwDTO == nil {
return nil var product *productDTO.ProductRelationDTO
if pw.Product.Id != 0 {
mapped := productDTO.ToProductRelationDTO(pw.Product)
product = &mapped
}
var warehouse *warehouseDTO.WarehouseRelationDTO
if pw.Warehouse.Id != 0 {
mapped := warehouseDTO.ToWarehouseRelationDTO(pw.Warehouse)
warehouse = &mapped
} }
return &ProductWarehouseDTO{ return &ProductWarehouseDTO{
Id: chickinPwDTO.Id, ProductWarehouseRelationDTO: base,
Product: chickinPwDTO.Product, Product: product,
Warehouse: chickinPwDTO.Warehouse, Warehouse: warehouse,
} }
} }
@@ -13,7 +13,6 @@ import (
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
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"
rFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
"gitlab.com/mbugroup/lti-api.git/internal/utils" "gitlab.com/mbugroup/lti-api.git/internal/utils"
@@ -29,7 +28,6 @@ func (ProjectFlockKandangModule) RegisterRoutes(router fiber.Router, db *gorm.DB
userRepo := rUser.NewUserRepository(db) userRepo := rUser.NewUserRepository(db)
warehouseRepo := rWarehouse.NewWarehouseRepository(db) warehouseRepo := rWarehouse.NewWarehouseRepository(db)
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
flockRepo := rFlock.NewFlockRepository(db)
approvalRepo := commonRepo.NewApprovalRepository(db) approvalRepo := commonRepo.NewApprovalRepository(db)
approvalService := commonSvc.NewApprovalService(approvalRepo) approvalService := commonSvc.NewApprovalService(approvalRepo)
@@ -38,7 +36,7 @@ func (ProjectFlockKandangModule) RegisterRoutes(router fiber.Router, db *gorm.DB
panic(fmt.Sprintf("failed to register project flock kandang approval workflow: %v", err)) panic(fmt.Sprintf("failed to register project flock kandang approval workflow: %v", err))
} }
projectFlockKandangService := sProjectFlockKandang.NewProjectFlockKandangService(projectFlockKandangRepo, approvalService, warehouseRepo, productWarehouseRepo, projectFlockPopulationRepo, flockRepo, validate) projectFlockKandangService := sProjectFlockKandang.NewProjectFlockKandangService(projectFlockKandangRepo, approvalService, warehouseRepo, productWarehouseRepo, projectFlockPopulationRepo, validate)
userService := sUser.NewUserService(userRepo, validate) userService := sUser.NewUserService(userRepo, validate)
ProjectFlockKandangRoutes(router, userService, projectFlockKandangService) ProjectFlockKandangRoutes(router, userService, projectFlockKandangService)
@@ -6,12 +6,9 @@ import (
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities" entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
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"
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/validations" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/validations"
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
"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"
@@ -21,8 +18,8 @@ import (
) )
type ProjectFlockKandangService interface { type ProjectFlockKandangService interface {
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlockKandang, int64, map[uint]*flockDTO.FlockBaseDTO, error) GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlockKandang, int64, error)
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, map[uint]float64, []entity.ProductWarehouse, *flockDTO.FlockBaseDTO, error) GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, map[uint]float64, []entity.ProductWarehouse, error)
} }
// Note: map[uint]float64 adalah mapping dari ProductWarehouse ID ke calculated available quantity // Note: map[uint]float64 adalah mapping dari ProductWarehouse ID ke calculated available quantity
@@ -35,10 +32,9 @@ type projectFlockKandangService struct {
WarehouseRepo rWarehouse.WarehouseRepository WarehouseRepo rWarehouse.WarehouseRepository
ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository
PopulationRepo repository.ProjectFlockPopulationRepository PopulationRepo repository.ProjectFlockPopulationRepository
FlockRepo flockRepository.FlockRepository
} }
func NewProjectFlockKandangService(repo repository.ProjectFlockKandangRepository, approvalSvc commonSvc.ApprovalService, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, populationRepo repository.ProjectFlockPopulationRepository, flockRepo flockRepository.FlockRepository, validate *validator.Validate) ProjectFlockKandangService { func NewProjectFlockKandangService(repo repository.ProjectFlockKandangRepository, approvalSvc commonSvc.ApprovalService, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, populationRepo repository.ProjectFlockPopulationRepository, validate *validator.Validate) ProjectFlockKandangService {
return &projectFlockKandangService{ return &projectFlockKandangService{
Log: utils.Log, Log: utils.Log,
Validate: validate, Validate: validate,
@@ -47,13 +43,12 @@ func NewProjectFlockKandangService(repo repository.ProjectFlockKandangRepository
WarehouseRepo: warehouseRepo, WarehouseRepo: warehouseRepo,
ProductWarehouseRepo: productWarehouseRepo, ProductWarehouseRepo: productWarehouseRepo,
PopulationRepo: populationRepo, PopulationRepo: populationRepo,
FlockRepo: flockRepo,
} }
} }
func (s projectFlockKandangService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlockKandang, int64, map[uint]*flockDTO.FlockBaseDTO, error) { func (s projectFlockKandangService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlockKandang, int64, error) {
if err := s.Validate.Struct(params); err != nil { if err := s.Validate.Struct(params); err != nil {
return nil, 0, nil, err return nil, 0, err
} }
offset := (params.Page - 1) * params.Limit offset := (params.Page - 1) * params.Limit
@@ -62,7 +57,7 @@ func (s projectFlockKandangService) GetAll(c *fiber.Ctx, params *validation.Quer
if err != nil { if err != nil {
s.Log.Errorf("Failed to get projectFlockKandangs: %+v", err) s.Log.Errorf("Failed to get projectFlockKandangs: %+v", err)
return nil, 0, nil, err return nil, 0, err
} }
if s.ApprovalSvc != nil { if s.ApprovalSvc != nil {
@@ -85,35 +80,16 @@ func (s projectFlockKandangService) GetAll(c *fiber.Ctx, params *validation.Quer
} }
} }
flockMap := make(map[uint]*flockDTO.FlockBaseDTO) return projectFlockKandangs, total, nil
for i := range projectFlockKandangs {
if projectFlockKandangs[i].ProjectFlock.Id != 0 {
baseName := pfutils.DeriveBaseName(projectFlockKandangs[i].ProjectFlock.FlockName)
if baseName != "" {
flock, err := s.FlockRepo.GetByName(c.Context(), baseName)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Warnf("Failed to fetch flock %q: %+v", baseName, err)
} else if flock != nil {
flockMap[projectFlockKandangs[i].ProjectFlock.Id] = &flockDTO.FlockBaseDTO{
Id: flock.Id,
Name: flock.Name,
}
}
}
}
}
return projectFlockKandangs, total, flockMap, nil
} }
func (s projectFlockKandangService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, map[uint]float64, []entity.ProductWarehouse, *flockDTO.FlockBaseDTO, error) { func (s projectFlockKandangService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlockKandang, map[uint]float64, []entity.ProductWarehouse, error) {
projectFlockKandang, err := s.Repository.GetByID(c.Context(), id) projectFlockKandang, err := s.Repository.GetByID(c.Context(), id)
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil, nil, nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found") return nil, nil, nil, fiber.NewError(fiber.StatusNotFound, "ProjectFlockKandang not found")
} }
if err != nil { if err != nil {
return nil, nil, nil, nil, err return nil, nil, nil, err
} }
if len(projectFlockKandang.Chickins) > 0 && s.ApprovalSvc != nil { if len(projectFlockKandang.Chickins) > 0 && s.ApprovalSvc != nil {
@@ -147,23 +123,8 @@ func (s projectFlockKandangService) GetOne(c *fiber.Ctx, id uint) (*entity.Proje
productWarehouses = []entity.ProductWarehouse{} productWarehouses = []entity.ProductWarehouse{}
} }
} }
var flockResult *flockDTO.FlockBaseDTO
if projectFlockKandang.ProjectFlock.Id != 0 {
baseName := pfutils.DeriveBaseName(projectFlockKandang.ProjectFlock.FlockName)
if baseName != "" {
flock, err := s.FlockRepo.GetByName(c.Context(), baseName)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Warnf("Failed to fetch flock %q: %+v", baseName, err)
} else if flock != nil {
flockResult = &flockDTO.FlockBaseDTO{
Id: flock.Id,
Name: flock.Name,
}
}
}
}
return projectFlockKandang, availableQtyMap, productWarehouses, flockResult, nil return projectFlockKandang, availableQtyMap, productWarehouses, nil
} }
func (s projectFlockKandangService) getAvailableQuantities(c *fiber.Ctx, projectFlockKandang *entity.ProjectFlockKandang) (map[uint]float64, error) { func (s projectFlockKandangService) getAvailableQuantities(c *fiber.Ctx, projectFlockKandang *entity.ProjectFlockKandang) (map[uint]float64, error) {
@@ -7,7 +7,6 @@ import (
"strconv" "strconv"
"strings" "strings"
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/dto" "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/dto"
service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services" service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
@@ -85,7 +84,7 @@ func (u *ProjectflockController) GetAll(c *fiber.Ctx) error {
query.KandangIds = ids query.KandangIds = ids
} }
result, totalResults, flockMap, err := u.ProjectflockService.GetAll(c, query) result, totalResults, _, err := u.ProjectflockService.GetAll(c, query)
if err != nil { if err != nil {
return err return err
} }
@@ -124,7 +123,7 @@ func (u *ProjectflockController) GetOne(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
} }
result, flock, err := u.ProjectflockService.GetOne(c, uint(id)) result, _, err := u.ProjectflockService.GetOne(c, uint(id))
if err != nil { if err != nil {
return err return err
} }
@@ -162,7 +161,7 @@ func (u *ProjectflockController) CreateOne(c *fiber.Ctx) error {
Code: fiber.StatusCreated, Code: fiber.StatusCreated,
Status: "success", Status: "success",
Message: "Create projectflock successfully", Message: "Create projectflock successfully",
Data: dto.ToProjectFlockListDTO(*result, nil), Data: dto.ToProjectFlockListDTO(*result),
}) })
} }
@@ -189,7 +188,7 @@ func (u *ProjectflockController) UpdateOne(c *fiber.Ctx) error {
Code: fiber.StatusOK, Code: fiber.StatusOK,
Status: "success", Status: "success",
Message: "Update projectflock successfully", Message: "Update projectflock successfully",
Data: dto.ToProjectFlockListDTO(*result, nil), Data: dto.ToProjectFlockListDTO(*result),
}) })
} }
@@ -262,7 +261,7 @@ func (u *ProjectflockController) Approval(c *fiber.Ctx) error {
}) })
} }
func (u *ProjectflockController) GetFlockPeriodSummary(c *fiber.Ctx) error { func (u *ProjectflockController) GetPeriodSummary(c *fiber.Ctx) error {
param := c.Params("location_id") param := c.Params("location_id")
id, err := strconv.Atoi(param) id, err := strconv.Atoi(param)
@@ -270,7 +269,7 @@ func (u *ProjectflockController) GetFlockPeriodSummary(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Invalid location_id") return fiber.NewError(fiber.StatusBadRequest, "Invalid location_id")
} }
summaries, err := u.ProjectflockService.GetFlockPeriodSummary(c, uint(id)) summaries, err := u.ProjectflockService.GetPeriodSummary(c, uint(id))
if err != nil { if err != nil {
return err return err
} }
@@ -7,7 +7,6 @@ import (
approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto" approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto"
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto" areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto" fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto"
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto" kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto" locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
@@ -17,40 +16,35 @@ import (
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals" approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
) )
type ProjectFlockBaseDTO struct { type ProjectFlockRelationDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
Period int `json:"period"` Period int `json:"period"`
FlockName string `json:"flock_name"` FlockName string `json:"flock_name"`
} }
type ProjectFlockListDTO struct { type ProjectFlockListDTO struct {
ProjectFlockBaseDTO ProjectFlockRelationDTO
Flock *flockDTO.FlockBaseDTO `json:"flock,omitempty"` Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
Area *areaDTO.AreaBaseDTO `json:"area,omitempty"` Category string `json:"category"`
Category string `json:"category"` Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"`
Fcr *fcrDTO.FcrBaseDTO `json:"fcr,omitempty"` Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
Location *locationDTO.LocationBaseDTO `json:"location,omitempty"` Kandangs []KandangWithProjectFlockIdDTO `json:"kandangs,omitempty"`
Kandangs []KandangWithProjectFlockIdDTO `json:"kandangs,omitempty"` CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
CreatedUser *userDTO.UserBaseDTO `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"` Approval approvalDTO.ApprovalRelationDTO `json:"approval"`
Approval approvalDTO.ApprovalBaseDTO `json:"approval"`
} }
type KandangWithProjectFlockIdDTO struct { type KandangWithProjectFlockIdDTO struct {
kandangDTO.KandangBaseDTO kandangDTO.KandangRelationDTO
ProjectFlockKandangId uint `json:"project_flock_kandang_id"` ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
Period int `json:"period"`
} }
type ProjectFlockDetailDTO struct { type ProjectFlockDetailDTO struct {
ProjectFlockListDTO ProjectFlockListDTO
} }
type FlockPeriodDTO struct {
Flock flockDTO.FlockBaseDTO `json:"flock"`
NextPeriod int `json:"next_period"`
}
type KandangPeriodSummaryDTO struct { type KandangPeriodSummaryDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@@ -58,9 +52,9 @@ type KandangPeriodSummaryDTO struct {
} }
func ToProjectFlockListDTOWithPeriod(e entity.ProjectFlock, period int) ProjectFlockListDTO { func ToProjectFlockListDTOWithPeriod(e entity.ProjectFlock, period int) ProjectFlockListDTO {
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
} }
@@ -69,43 +63,43 @@ func ToProjectFlockListDTOWithPeriod(e entity.ProjectFlock, period int) ProjectF
kandangSummaries = make([]KandangWithProjectFlockIdDTO, len(e.Kandangs)) kandangSummaries = make([]KandangWithProjectFlockIdDTO, len(e.Kandangs))
for i, kandang := range e.Kandangs { for i, kandang := range e.Kandangs {
var pfkId uint var (
pfkId uint
period int
)
for _, kh := range e.KandangHistory { for _, kh := range e.KandangHistory {
if kh.KandangId == kandang.Id { if kh.KandangId == kandang.Id {
pfkId = kh.Id pfkId = kh.Id
period = kh.Period
break break
} }
} }
kandangSummaries[i] = KandangWithProjectFlockIdDTO{ kandangSummaries[i] = KandangWithProjectFlockIdDTO{
KandangBaseDTO: kandangDTO.ToKandangBaseDTO(kandang), KandangRelationDTO: kandangDTO.ToKandangRelationDTO(kandang),
ProjectFlockKandangId: pfkId, ProjectFlockKandangId: pfkId,
Period: period,
} }
} }
} }
var areaSummary *areaDTO.AreaBaseDTO var areaSummary *areaDTO.AreaRelationDTO
if e.Area.Id != 0 { if e.Area.Id != 0 {
mapped := areaDTO.ToAreaBaseDTO(e.Area) mapped := areaDTO.ToAreaRelationDTO(e.Area)
areaSummary = &mapped areaSummary = &mapped
} }
var fcrSummary *fcrDTO.FcrBaseDTO var fcrSummary *fcrDTO.FcrRelationDTO
if e.Fcr.Id != 0 { if e.Fcr.Id != 0 {
mapped := fcrDTO.ToFcrBaseDTO(e.Fcr) mapped := fcrDTO.ToFcrRelationDTO(e.Fcr)
fcrSummary = &mapped fcrSummary = &mapped
} }
var locationSummary *locationDTO.LocationBaseDTO var locationSummary *locationDTO.LocationRelationDTO
if e.Location.Id != 0 { if e.Location.Id != 0 {
mapped := locationDTO.ToLocationBaseDTO(e.Location) mapped := locationDTO.ToLocationRelationDTO(e.Location)
locationSummary = &mapped locationSummary = &mapped
} }
var flockSummary *flockDTO.FlockBaseDTO
if flock != nil && flock.Id != 0 {
flockSummary = flock
}
latestApproval := defaultProjectFlockLatestApproval(e) latestApproval := defaultProjectFlockLatestApproval(e)
if e.LatestApproval != nil { if e.LatestApproval != nil {
snapshot := approvalDTO.ToApprovalDTO(*e.LatestApproval) snapshot := approvalDTO.ToApprovalDTO(*e.LatestApproval)
@@ -113,24 +107,19 @@ func ToProjectFlockListDTOWithPeriod(e entity.ProjectFlock, period int) ProjectF
} }
return ProjectFlockListDTO{ return ProjectFlockListDTO{
ProjectFlockBaseDTO: createProjectFlockBaseDTO(e, period), ProjectFlockRelationDTO: createProjectFlockRelationDTO(e, period),
// Flock: flockSummary, Area: areaSummary,
Area: areaSummary, Kandangs: kandangSummaries,
Kandangs: kandangSummaries, Category: e.Category,
Category: e.Category, Fcr: fcrSummary,
Fcr: fcrSummary, Location: locationSummary,
Location: locationSummary, CreatedAt: e.CreatedAt,
CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt,
UpdatedAt: e.UpdatedAt, CreatedUser: createdUser,
CreatedUser: createdUser, Approval: latestApproval,
Approval: latestApproval,
} }
} }
func ToProjectFlockListDTOWithFlock(e entity.ProjectFlock, flock *flockDTO.FlockBaseDTO) ProjectFlockListDTO {
return ToProjectFlockListDTO(e, flock)
}
func ToProjectFlockListDTOs(items []entity.ProjectFlock) []ProjectFlockListDTO { func ToProjectFlockListDTOs(items []entity.ProjectFlock) []ProjectFlockListDTO {
result := make([]ProjectFlockListDTO, len(items)) result := make([]ProjectFlockListDTO, len(items))
for i, item := range items { for i, item := range items {
@@ -157,31 +146,14 @@ func ToProjectFlockListDTOsWithPeriods(items []entity.ProjectFlock, periods map[
return result return result
} }
func ToProjectFlockListDTOsWithFlocks(items []entity.ProjectFlock, flocks map[uint]*entity.Flock) []ProjectFlockListDTO { func ToProjectFlockDetailDTO(e entity.ProjectFlock) ProjectFlockDetailDTO {
result := make([]ProjectFlockListDTO, len(items))
for i, item := range items {
var flock *flockDTO.FlockBaseDTO
if flocks != nil {
if f := flocks[item.Id]; f != nil {
flock = &flockDTO.FlockBaseDTO{
Id: f.Id,
Name: f.Name,
}
}
}
result[i] = ToProjectFlockListDTOWithFlock(item, flock)
}
return result
}
func ToProjectFlockDetailDTO(e entity.ProjectFlock, flock *flockDTO.FlockBaseDTO) ProjectFlockDetailDTO {
return ProjectFlockDetailDTO{ return ProjectFlockDetailDTO{
ProjectFlockListDTO: ToProjectFlockListDTOWithPeriod(e, 0), ProjectFlockListDTO: ToProjectFlockListDTOWithPeriod(e, 0),
} }
} }
func defaultProjectFlockLatestApproval(e entity.ProjectFlock) approvalDTO.ApprovalBaseDTO { func defaultProjectFlockLatestApproval(e entity.ProjectFlock) approvalDTO.ApprovalRelationDTO {
result := approvalDTO.ApprovalBaseDTO{} result := approvalDTO.ApprovalRelationDTO{}
step := utils.ProjectFlockStepPengajuan step := utils.ProjectFlockStepPengajuan
if step > 0 { if step > 0 {
@@ -194,9 +166,9 @@ func defaultProjectFlockLatestApproval(e entity.ProjectFlock) approvalDTO.Approv
} }
if e.CreatedUser.Id != 0 { if e.CreatedUser.Id != 0 {
result.ActionBy = userDTO.ToUserBaseDTO(e.CreatedUser) result.ActionBy = userDTO.ToUserRelationDTO(e.CreatedUser)
} else if e.CreatedBy != 0 { } else if e.CreatedBy != 0 {
result.ActionBy = userDTO.UserBaseDTO{ result.ActionBy = userDTO.UserRelationDTO{
Id: e.CreatedBy, Id: e.CreatedBy,
IdUser: int64(e.CreatedBy), IdUser: int64(e.CreatedBy),
} }
@@ -205,24 +177,10 @@ func defaultProjectFlockLatestApproval(e entity.ProjectFlock) approvalDTO.Approv
return result return result
} }
func createProjectFlockBaseDTO(e entity.ProjectFlock, period int) ProjectFlockBaseDTO { func createProjectFlockRelationDTO(e entity.ProjectFlock, period int) ProjectFlockRelationDTO {
return ProjectFlockBaseDTO{ return ProjectFlockRelationDTO{
Id: e.Id, Id: e.Id,
Period: period, Period: period,
FlockName: e.FlockName, FlockName: e.FlockName,
} }
} }
func ToFlockSummaryDTO(e entity.Flock) flockDTO.FlockBaseDTO {
return flockDTO.FlockBaseDTO{
Id: e.Id,
Name: e.Name,
}
}
func ToFlockPeriodSummaryDTO(flock entity.Flock, next int) FlockPeriodDTO {
return FlockPeriodDTO{
Flock: ToFlockSummaryDTO(flock),
NextPeriod: next,
}
}
@@ -4,43 +4,40 @@ import (
entity "gitlab.com/mbugroup/lti-api.git/internal/entities" entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto" areaDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto"
fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto" fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto"
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto" kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto"
locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto" locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto"
pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils"
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
) )
type KandangWithPivotDTO struct { type KandangWithPivotDTO struct {
kandangDTO.KandangBaseDTO kandangDTO.KandangRelationDTO
AvailableQuantity float64 `json:"available_quantity"` AvailableQuantity float64 `json:"available_quantity"`
} }
type ProjectFlockWithPivotDTO struct { type ProjectFlockWithPivotDTO struct {
ProjectFlockBaseDTO ProjectFlockRelationDTO
Flock *flockDTO.FlockBaseDTO `json:"flock,omitempty"` Area *areaDTO.AreaRelationDTO `json:"area,omitempty"`
Area *areaDTO.AreaBaseDTO `json:"area,omitempty"` Category string `json:"category"`
Category string `json:"category"` Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"`
Fcr *fcrDTO.FcrBaseDTO `json:"fcr,omitempty"` Location *locationDTO.LocationRelationDTO `json:"location,omitempty"`
Location *locationDTO.LocationBaseDTO `json:"location,omitempty"` Kandangs []KandangWithPivotDTO `json:"kandangs,omitempty"`
Kandangs []KandangWithPivotDTO `json:"kandangs,omitempty"` CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"`
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
} }
type ProjectFlockKandangDTO struct { type ProjectFlockKandangDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
ProjectFlockKandangId uint `json:"project_flock_kandang_id"` ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
ProjectFlockId uint `json:"project_flock_id"` ProjectFlockId uint `json:"project_flock_id"`
KandangId uint `json:"kandang_id"` KandangId uint `json:"kandang_id"`
Kandang *kandangDTO.KandangBaseDTO `json:"kandang,omitempty"` Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"`
ProjectFlock *ProjectFlockWithPivotDTO `json:"project_flock,omitempty"` ProjectFlock *ProjectFlockWithPivotDTO `json:"project_flock,omitempty"`
AvailableQuantity float64 `json:"available_quantity"` AvailableQuantity float64 `json:"available_quantity"`
} }
func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO { func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO {
var kandang *kandangDTO.KandangBaseDTO var kandang *kandangDTO.KandangRelationDTO
if e.Kandang.Id != 0 { if e.Kandang.Id != 0 {
mapped := kandangDTO.ToKandangBaseDTO(e.Kandang) mapped := kandangDTO.ToKandangRelationDTO(e.Kandang)
kandang = &mapped kandang = &mapped
} }
@@ -48,7 +45,7 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
if e.ProjectFlock.Id != 0 { if e.ProjectFlock.Id != 0 {
pfLocal := ProjectFlockWithPivotDTO{ pfLocal := ProjectFlockWithPivotDTO{
ProjectFlockBaseDTO: ProjectFlockBaseDTO{ ProjectFlockRelationDTO: ProjectFlockRelationDTO{
Id: e.ProjectFlock.Id, Id: e.ProjectFlock.Id,
Period: e.Period, Period: e.Period,
FlockName: e.ProjectFlock.FlockName, FlockName: e.ProjectFlock.FlockName,
@@ -56,32 +53,28 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
Category: e.ProjectFlock.Category, Category: e.ProjectFlock.Category,
} }
if base := pfutils.DeriveBaseName(e.ProjectFlock.FlockName); base != "" {
summary := flockDTO.FlockBaseDTO{Id: 0, Name: base}
pfLocal.Flock = &summary
}
if e.ProjectFlock.Area.Id != 0 { if e.ProjectFlock.Area.Id != 0 {
mapped := areaDTO.ToAreaBaseDTO(e.ProjectFlock.Area) mapped := areaDTO.ToAreaRelationDTO(e.ProjectFlock.Area)
pfLocal.Area = &mapped pfLocal.Area = &mapped
} }
if e.ProjectFlock.Fcr.Id != 0 { if e.ProjectFlock.Fcr.Id != 0 {
mapped := fcrDTO.ToFcrBaseDTO(e.ProjectFlock.Fcr) mapped := fcrDTO.ToFcrRelationDTO(e.ProjectFlock.Fcr)
pfLocal.Fcr = &mapped pfLocal.Fcr = &mapped
} }
if e.ProjectFlock.Location.Id != 0 { if e.ProjectFlock.Location.Id != 0 {
mapped := locationDTO.ToLocationBaseDTO(e.ProjectFlock.Location) mapped := locationDTO.ToLocationRelationDTO(e.ProjectFlock.Location)
pfLocal.Location = &mapped pfLocal.Location = &mapped
} }
if e.ProjectFlock.CreatedUser.Id != 0 { if e.ProjectFlock.CreatedUser.Id != 0 {
mapped := userDTO.ToUserBaseDTO(e.ProjectFlock.CreatedUser) mapped := userDTO.ToUserRelationDTO(e.ProjectFlock.CreatedUser)
pfLocal.CreatedUser = &mapped pfLocal.CreatedUser = &mapped
} }
for _, k := range e.ProjectFlock.Kandangs { for _, k := range e.ProjectFlock.Kandangs {
kb := kandangDTO.ToKandangBaseDTO(k) kb := kandangDTO.ToKandangRelationDTO(k)
pfLocal.Kandangs = append(pfLocal.Kandangs, KandangWithPivotDTO{ pfLocal.Kandangs = append(pfLocal.Kandangs, KandangWithPivotDTO{
KandangBaseDTO: kb, KandangRelationDTO: kb,
AvailableQuantity: 0, AvailableQuantity: 0,
}) })
} }
@@ -11,17 +11,24 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
const baseNameExpression = "LOWER(TRIM(regexp_replace(flock_name, '\\\\s+\\\\d+(\\\\s+\\\\d+)*$', '', 'g')))"
type ProjectflockRepository interface { type ProjectflockRepository interface {
repository.BaseRepository[entity.ProjectFlock] repository.BaseRepository[entity.ProjectFlock]
GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error) GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error)
WithDefaultRelations() func(*gorm.DB) *gorm.DB WithDefaultRelations() func(*gorm.DB) *gorm.DB
ExistsByFlockName(ctx context.Context, flockName string, excludeID *uint) (bool, error) ExistsByFlockName(ctx context.Context, flockName string, excludeID *uint) (bool, error)
GetNextPeriodsForKandangs(ctx context.Context, kandangIDs []uint) (map[uint]int, error)
GetCurrentProjectPeriod(ctx context.Context, projectFlockID uint) (int, error)
GetKandangPeriodSummaryRows(ctx context.Context, locationID uint) ([]KandangPeriodRow, error)
AreaExists(ctx context.Context, id uint) (bool, error) AreaExists(ctx context.Context, id uint) (bool, error)
FcrExists(ctx context.Context, id uint) (bool, error) FcrExists(ctx context.Context, id uint) (bool, error)
LocationExists(ctx context.Context, id uint) (bool, error) LocationExists(ctx context.Context, id uint) (bool, error)
} }
type KandangPeriodRow struct {
Id uint
Name string
LatestPeriod int
}
type ProjectflockRepositoryImpl struct { type ProjectflockRepositoryImpl struct {
*repository.BaseRepositoryImpl[entity.ProjectFlock] *repository.BaseRepositoryImpl[entity.ProjectFlock]
@@ -35,19 +42,13 @@ func NewProjectflockRepository(db *gorm.DB) ProjectflockRepository {
func (r *ProjectflockRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error) { func (r *ProjectflockRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB { return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB {
db = r.withDefaultRelations(db)
return r.applyQueryFilters(db, params) return r.applyQueryFilters(db, params)
}) })
} }
func (r *ProjectflockRepositoryImpl) WithDefaultRelations() func(*gorm.DB) *gorm.DB { func (r *ProjectflockRepositoryImpl) WithDefaultRelations() func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB {
return r.withDefaultRelations(db) return db.
}
}
func (r *ProjectflockRepositoryImpl) withDefaultRelations(db *gorm.DB) *gorm.DB {
return db.
Preload("CreatedUser"). Preload("CreatedUser").
Preload("Area"). Preload("Area").
Preload("Fcr"). Preload("Fcr").
@@ -55,8 +56,10 @@ func (r *ProjectflockRepositoryImpl) withDefaultRelations(db *gorm.DB) *gorm.DB
Preload("Kandangs"). Preload("Kandangs").
Preload("KandangHistory"). Preload("KandangHistory").
Preload("KandangHistory.Kandang") Preload("KandangHistory.Kandang")
}
} }
func (r *ProjectflockRepositoryImpl) applyQueryFilters(db *gorm.DB, params *validation.Query) *gorm.DB { func (r *ProjectflockRepositoryImpl) applyQueryFilters(db *gorm.DB, params *validation.Query) *gorm.DB {
if params == nil { if params == nil {
return db return db
@@ -163,6 +166,88 @@ func (r *ProjectflockRepositoryImpl) LocationExists(ctx context.Context, id uint
return repository.Exists[entity.Location](ctx, r.DB(), id) return repository.Exists[entity.Location](ctx, r.DB(), id)
} }
func (r *ProjectflockRepositoryImpl) GetNextPeriodsForKandangs(ctx context.Context, kandangIDs []uint) (map[uint]int, error) {
result := make(map[uint]int)
if len(kandangIDs) == 0 {
return result, nil
}
unique := uniqueUintSlice(kandangIDs)
for _, id := range unique {
result[id] = 1
}
type periodRow struct {
KandangID uint
MaxPeriod int
}
var rows []periodRow
if err := r.DB().WithContext(ctx).
Table("project_flock_kandangs").
Select("kandang_id, COALESCE(MAX(period), 0) AS max_period").
Where("kandang_id IN ?", unique).
Group("kandang_id").
Scan(&rows).Error; err != nil {
return nil, err
}
for _, row := range rows {
if row.MaxPeriod > 0 {
result[row.KandangID] = row.MaxPeriod + 1
}
}
return result, nil
}
func (r *ProjectflockRepositoryImpl) GetCurrentProjectPeriod(ctx context.Context, projectFlockID uint) (int, error) {
if projectFlockID == 0 {
return 0, nil
}
var currentPeriod int
if err := r.DB().WithContext(ctx).
Table("project_flock_kandangs").
Where("project_flock_id = ?", projectFlockID).
Select("COALESCE(MAX(period), 0)").
Scan(&currentPeriod).Error; err != nil {
return 0, err
}
return currentPeriod, nil
}
func (r *ProjectflockRepositoryImpl) GetKandangPeriodSummaryRows(ctx context.Context, locationID uint) ([]KandangPeriodRow, error) {
rows := make([]KandangPeriodRow, 0)
if err := r.DB().WithContext(ctx).
Table("kandangs AS k").
Select("k.id, k.name, COALESCE(MAX(pfk.period), 0) AS latest_period").
Joins("LEFT JOIN project_flock_kandangs AS pfk ON pfk.kandang_id = k.id").
Where("k.location_id = ?", locationID).
Where("k.deleted_at IS NULL").
Group("k.id, k.name").
Order("k.id ASC").
Scan(&rows).Error; err != nil {
return nil, err
}
return rows, nil
}
func uniqueUintSlice(values []uint) []uint {
seen := make(map[uint]struct{}, len(values))
result := make([]uint, 0, len(values))
for _, v := range values {
if _, ok := seen[v]; ok {
continue
}
seen[v] = struct{}{}
result = append(result, v)
}
return result
}
func (r *ProjectflockRepositoryImpl) buildOrderExpressions(sortBy, sortOrder string) []string { func (r *ProjectflockRepositoryImpl) buildOrderExpressions(sortBy, sortOrder string) []string {
direction := "ASC" direction := "ASC"
if strings.ToLower(sortOrder) == "desc" { if strings.ToLower(sortOrder) == "desc" {
@@ -13,6 +13,7 @@ import (
type ProjectFlockKandangRepository interface { type ProjectFlockKandangRepository interface {
GetByID(ctx context.Context, id uint) (*entity.ProjectFlockKandang, error) GetByID(ctx context.Context, id uint) (*entity.ProjectFlockKandang, error)
GetByProjectFlockAndKandang(ctx context.Context, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error) GetByProjectFlockAndKandang(ctx context.Context, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, error)
GetActiveByKandangID(ctx context.Context, kandangID uint) (*entity.ProjectFlockKandang, error)
CreateMany(ctx context.Context, records []*entity.ProjectFlockKandang) error CreateMany(ctx context.Context, records []*entity.ProjectFlockKandang) error
DeleteMany(ctx context.Context, projectFlockID uint, kandangIDs []uint) error DeleteMany(ctx context.Context, projectFlockID uint, kandangIDs []uint) error
GetAll(ctx context.Context, offset int, limit int, modifier func(*gorm.DB) *gorm.DB) ([]entity.ProjectFlockKandang, int64, error) GetAll(ctx context.Context, offset int, limit int, modifier func(*gorm.DB) *gorm.DB) ([]entity.ProjectFlockKandang, int64, error)
@@ -221,6 +222,35 @@ func (r *projectFlockKandangRepositoryImpl) GetByProjectFlockAndKandang(ctx cont
return record, nil return record, nil
} }
func (r *projectFlockKandangRepositoryImpl) GetActiveByKandangID(ctx context.Context, kandangID uint) (*entity.ProjectFlockKandang, error) {
record := new(entity.ProjectFlockKandang)
if err := r.db.WithContext(ctx).
Joins("JOIN project_flocks ON project_flocks.id = project_flock_kandangs.project_flock_id").
Joins(`
INNER JOIN (
SELECT DISTINCT ON (approvable_id) approvable_id, step_name, action_at
FROM approvals
WHERE approvable_type = 'PROJECT_FLOCKS'
ORDER BY approvable_id, action_at DESC
) latest_approval ON latest_approval.approvable_id = project_flocks.id
`).
Where("project_flock_kandangs.kandang_id = ?", kandangID).
Where("LOWER(latest_approval.step_name) = LOWER(?)", "Aktif").
Order("project_flock_kandangs.id DESC").
Preload("ProjectFlock").
Preload("ProjectFlock.Fcr").
Preload("ProjectFlock.Area").
Preload("ProjectFlock.Location").
Preload("ProjectFlock.CreatedUser").
Preload("ProjectFlock.Kandangs").
Preload("ProjectFlock.KandangHistory").
Preload("Kandang").
First(record).Error; err != nil {
return nil, err
}
return record, nil
}
func (r *projectFlockKandangRepositoryImpl) ListExistingKandangIDs(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]uint, error) { func (r *projectFlockKandangRepositoryImpl) ListExistingKandangIDs(ctx context.Context, projectFlockID uint, kandangIDs []uint) ([]uint, error) {
if len(kandangIDs) == 0 { if len(kandangIDs) == 0 {
return nil, nil return nil, nil
@@ -20,9 +20,8 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj
route.Get("/:id", ctrl.GetOne) route.Get("/:id", ctrl.GetOne)
route.Patch("/:id", ctrl.UpdateOne) route.Patch("/:id", ctrl.UpdateOne)
route.Delete("/:id", ctrl.DeleteOne) route.Delete("/:id", ctrl.DeleteOne)
route.Get("/kandangs/:project_flock_kandang_id/periods", ctrl.GetFlockPeriodSummary)
route.Get("/kandangs/lookup", ctrl.LookupProjectFlockKandang) route.Get("/kandangs/lookup", ctrl.LookupProjectFlockKandang)
route.Post("/approvals", ctrl.Approval) route.Post("/approvals", ctrl.Approval)
route.Get("/kandangs/:location_id/periods", ctrl.GetFlockPeriodSummary) route.Get("/locations/:location_id/periods", ctrl.GetPeriodSummary)
} }
@@ -10,7 +10,8 @@ import (
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities" entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
authmiddleware "gitlab.com/mbugroup/lti-api.git/internal/middleware"
// authmiddleware "gitlab.com/mbugroup/lti-api.git/internal/middleware"
productWarehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" productWarehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto" flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories" flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
@@ -29,14 +30,14 @@ import (
) )
type ProjectflockService interface { type ProjectflockService interface {
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, map[uint]*flockDTO.FlockBaseDTO, error) GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, map[uint]*flockDTO.FlockRelationDTO, error)
GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, *flockDTO.FlockBaseDTO, error) GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, *flockDTO.FlockRelationDTO, error)
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectFlock, error) CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectFlock, error)
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error)
GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error) GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID uint) (float64, error)
DeleteOne(ctx *fiber.Ctx, id uint) error DeleteOne(ctx *fiber.Ctx, id uint) error
GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, float64, error) GetProjectFlockKandangByProjectAndKandang(ctx *fiber.Ctx, projectFlockID uint, kandangID uint) (*entity.ProjectFlockKandang, float64, error)
GetFlockPeriodSummary(ctx *fiber.Ctx, locationID uint) ([]KandangPeriodSummary, error) GetPeriodSummary(ctx *fiber.Ctx, locationID uint) ([]KandangPeriodSummary, error)
GetProjectPeriods(ctx *fiber.Ctx, projectIDs []uint) (map[uint]int, error) GetProjectPeriods(ctx *fiber.Ctx, projectIDs []uint) (map[uint]int, error)
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error) Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectFlock, error)
} }
@@ -84,19 +85,7 @@ func NewProjectflockService(
} }
} }
func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB { func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, map[uint]*flockDTO.FlockRelationDTO, error) {
return db.
Preload("CreatedUser").
Preload("Flock").
Preload("Area").
Preload("Fcr").
Preload("Location").
Preload("Kandangs").
Preload("KandangHistory").
Preload("KandangHistory.Kandang")
}
func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, map[uint]*flockDTO.FlockBaseDTO, error) {
if err := s.Validate.Struct(params); err != nil { if err := s.Validate.Struct(params); err != nil {
return nil, 0, nil, err return nil, 0, nil, err
} }
@@ -123,9 +112,7 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
ids[i] = item.Id ids[i] = item.Id
} }
latestMap, err := s.ApprovalSvc.LatestByTargets(c.Context(), s.approvalWorkflow, ids, func(db *gorm.DB) *gorm.DB { latestMap, err := s.ApprovalSvc.LatestByTargets(c.Context(), s.approvalWorkflow, ids, s.Repository.WithDefaultRelations())
return db.Preload("ActionUser")
})
if err != nil { if err != nil {
s.Log.Warnf("Unable to load latest approvals for projectflocks: %+v", err) s.Log.Warnf("Unable to load latest approvals for projectflocks: %+v", err)
} else if len(latestMap) > 0 { } else if len(latestMap) > 0 {
@@ -137,7 +124,7 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
} }
} }
flockMap := make(map[uint]*flockDTO.FlockBaseDTO) flockMap := make(map[uint]*flockDTO.FlockRelationDTO)
for i := range projectflocks { for i := range projectflocks {
if projectflocks[i].FlockName != "" { if projectflocks[i].FlockName != "" {
baseName := pfutils.DeriveBaseName(projectflocks[i].FlockName) baseName := pfutils.DeriveBaseName(projectflocks[i].FlockName)
@@ -146,7 +133,7 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Warnf("Failed to fetch flock %q: %+v", baseName, err) s.Log.Warnf("Failed to fetch flock %q: %+v", baseName, err)
} else if flock != nil { } else if flock != nil {
flockMap[projectflocks[i].Id] = &flockDTO.FlockBaseDTO{ flockMap[projectflocks[i].Id] = &flockDTO.FlockRelationDTO{
Id: flock.Id, Id: flock.Id,
Name: flock.Name, Name: flock.Name,
} }
@@ -169,9 +156,7 @@ func (s projectflockService) getOneEntityOnly(c *fiber.Ctx, id uint) (*entity.Pr
} }
if s.ApprovalSvc != nil { if s.ApprovalSvc != nil {
approvals, err := s.ApprovalSvc.ListByTarget(c.Context(), s.approvalWorkflow, id, func(db *gorm.DB) *gorm.DB { approvals, err := s.ApprovalSvc.ListByTarget(c.Context(), s.approvalWorkflow, id, s.Repository.WithDefaultRelations())
return db.Preload("ActionUser")
})
if err != nil { if err != nil {
s.Log.Warnf("Unable to load approvals for projectflock %d: %+v", id, err) s.Log.Warnf("Unable to load approvals for projectflock %d: %+v", id, err)
} else if len(approvals) > 0 { } else if len(approvals) > 0 {
@@ -187,7 +172,7 @@ func (s projectflockService) getOneEntityOnly(c *fiber.Ctx, id uint) (*entity.Pr
return projectflock, nil return projectflock, nil
} }
func (s projectflockService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock, *flockDTO.FlockBaseDTO, error) { func (s projectflockService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock, *flockDTO.FlockRelationDTO, error) {
projectflock, err := s.Repository.GetByID(c.Context(), id, s.Repository.WithDefaultRelations()) projectflock, err := s.Repository.GetByID(c.Context(), id, s.Repository.WithDefaultRelations())
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found") return nil, nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
@@ -198,9 +183,7 @@ func (s projectflockService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock
} }
if s.ApprovalSvc != nil { if s.ApprovalSvc != nil {
approvals, err := s.ApprovalSvc.ListByTarget(c.Context(), s.approvalWorkflow, id, func(db *gorm.DB) *gorm.DB { approvals, err := s.ApprovalSvc.ListByTarget(c.Context(), s.approvalWorkflow, id, s.Repository.WithDefaultRelations())
return db.Preload("ActionUser")
})
if err != nil { if err != nil {
s.Log.Warnf("Unable to load approvals for projectflock %d: %+v", id, err) s.Log.Warnf("Unable to load approvals for projectflock %d: %+v", id, err)
} else if len(approvals) > 0 { } else if len(approvals) > 0 {
@@ -214,7 +197,7 @@ func (s projectflockService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock
} }
// Fetch Flock master data for this ProjectFlock // Fetch Flock master data for this ProjectFlock
var flockResult *flockDTO.FlockBaseDTO var flockResult *flockDTO.FlockRelationDTO
if projectflock.FlockName != "" { if projectflock.FlockName != "" {
baseName := pfutils.DeriveBaseName(projectflock.FlockName) baseName := pfutils.DeriveBaseName(projectflock.FlockName)
if baseName != "" { if baseName != "" {
@@ -222,7 +205,7 @@ func (s projectflockService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Warnf("Failed to fetch flock %q: %+v", baseName, err) s.Log.Warnf("Failed to fetch flock %q: %+v", baseName, err)
} else if flock != nil { } else if flock != nil {
flockResult = &flockDTO.FlockBaseDTO{ flockResult = &flockDTO.FlockRelationDTO{
Id: flock.Id, Id: flock.Id,
Name: flock.Name, Name: flock.Name,
} }
@@ -319,13 +302,12 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*
return err return err
} }
// Compute period based on location history (max period in that location + 1), // Compute period per kandang so every kandang maintains its own cycle history.
// and store it on project_flock_kandangs only. periods, err := projectRepo.GetNextPeriodsForKandangs(c.Context(), kandangIDs)
nextPeriod, err := s.nextLocationPeriod(c.Context(), dbTransaction, req.LocationId)
if err != nil { if err != nil {
return err return err
} }
if err := s.attachKandangs(c.Context(), dbTransaction, createBody.Id, kandangIDs, nextPeriod); err != nil { if err := s.attachKandangs(c.Context(), dbTransaction, createBody.Id, kandangIDs, periods); err != nil {
return err return err
} }
@@ -543,19 +525,24 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id
} }
if len(toAttach) > 0 { if len(toAttach) > 0 {
var currentPeriod int currentPeriod, err := projectRepo.GetCurrentProjectPeriod(c.Context(), id)
if err := dbTransaction.WithContext(c.Context()). if err != nil {
Table("project_flock_kandangs").
Where("project_flock_id = ?", id).
Select("COALESCE(MAX(period), 0)").
Scan(&currentPeriod).Error; err != nil {
return err return err
} }
if currentPeriod <= 0 {
currentPeriod = 1 periods := make(map[uint]int, len(toAttach))
if currentPeriod > 0 {
for _, kid := range toAttach {
periods[kid] = currentPeriod
}
} else {
periods, err = projectRepo.GetNextPeriodsForKandangs(c.Context(), toAttach)
if err != nil {
return err
}
} }
if err := s.attachKandangs(c.Context(), dbTransaction, id, toAttach, currentPeriod); err != nil { if err := s.attachKandangs(c.Context(), dbTransaction, id, toAttach, periods); err != nil {
return err return err
} }
} }
@@ -831,32 +818,6 @@ func (s projectflockService) GetAvailableDocQuantity(ctx *fiber.Ctx, kandangID u
return total, nil return total, nil
} }
// nextLocationPeriod computes the next period number for a given location
// based on the maximum period that has ever been used by any kandang in that location.
func (s projectflockService) nextLocationPeriod(ctx context.Context, tx *gorm.DB, locationID uint) (int, error) {
if locationID == 0 {
return 0, fiber.NewError(fiber.StatusBadRequest, "location_id is required to compute period")
}
db := s.Repository.DB()
if tx != nil {
db = tx
}
var maxPeriod int
if err := db.WithContext(ctx).
Table("project_flock_kandangs pfk").
Joins("JOIN kandangs k ON k.id = pfk.kandang_id").
Where("k.location_id = ?", locationID).
Select("COALESCE(MAX(pfk.period), 0)").
Scan(&maxPeriod).Error; err != nil {
s.Log.Errorf("Failed to compute max period for location %d: %+v", locationID, err)
return 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to compute period for location")
}
return maxPeriod + 1, nil
}
func (s projectflockService) GetProjectPeriods(c *fiber.Ctx, projectIDs []uint) (map[uint]int, error) { func (s projectflockService) GetProjectPeriods(c *fiber.Ctx, projectIDs []uint) (map[uint]int, error) {
if len(projectIDs) == 0 { if len(projectIDs) == 0 {
return map[uint]int{}, nil return map[uint]int{}, nil
@@ -864,7 +825,7 @@ func (s projectflockService) GetProjectPeriods(c *fiber.Ctx, projectIDs []uint)
return s.pivotRepo().ProjectPeriodsByProjectIDs(c.Context(), projectIDs) return s.pivotRepo().ProjectPeriodsByProjectIDs(c.Context(), projectIDs)
} }
func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, locationID uint) ([]KandangPeriodSummary, error) { func (s projectflockService) GetPeriodSummary(c *fiber.Ctx, locationID uint) ([]KandangPeriodSummary, error) {
if locationID == 0 { if locationID == 0 {
return nil, fiber.NewError(fiber.StatusBadRequest, "location_id is required") return nil, fiber.NewError(fiber.StatusBadRequest, "location_id is required")
} }
@@ -878,24 +839,8 @@ func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, locationID uint
return nil, fiber.NewError(fiber.StatusNotFound, "Location not found") return nil, fiber.NewError(fiber.StatusNotFound, "Location not found")
} }
type kandangPeriodRow struct { rows, err := s.Repository.GetKandangPeriodSummaryRows(c.Context(), locationID)
Id uint if err != nil {
Name string
LatestPeriod int
}
var rows []kandangPeriodRow
db := s.Repository.DB().WithContext(c.Context())
if err := db.
Table("kandangs AS k").
Select("k.id, k.name, COALESCE(MAX(pfk.period), 0) AS latest_period").
Joins("LEFT JOIN project_flock_kandangs AS pfk ON pfk.kandang_id = k.id").
Where("k.location_id = ?", locationID).
Where("k.deleted_at IS NULL").
Group("k.id, k.name").
Order("k.id ASC").
Scan(&rows).Error; err != nil {
s.Log.Errorf("Failed to fetch kandang period summary for location %d: %+v", locationID, err) s.Log.Errorf("Failed to fetch kandang period summary for location %d: %+v", locationID, err)
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch kandang period summary") return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch kandang period summary")
} }
@@ -990,7 +935,7 @@ func (s projectflockService) ensureFlockByName(ctx context.Context, actorID uint
return newFlock, nil return newFlock, nil
} }
func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *gorm.DB, projectFlockID uint, kandangIDs []uint, period int) error { func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *gorm.DB, projectFlockID uint, kandangIDs []uint, periods map[uint]int) error {
if len(kandangIDs) == 0 { if len(kandangIDs) == 0 {
return nil return nil
} }
@@ -1025,6 +970,10 @@ func (s projectflockService) attachKandangs(ctx context.Context, dbTransaction *
records := make([]*entity.ProjectFlockKandang, 0, len(toAttach)) records := make([]*entity.ProjectFlockKandang, 0, len(toAttach))
for _, id := range toAttach { for _, id := range toAttach {
period := periods[id]
if period <= 0 {
period = 1
}
records = append(records, &entity.ProjectFlockKandang{ records = append(records, &entity.ProjectFlockKandang{
ProjectFlockId: projectFlockID, ProjectFlockId: projectFlockID,
KandangId: id, KandangId: id,
@@ -1098,10 +1047,11 @@ func (s projectflockService) kandangRepoWithTx(tx *gorm.DB) kandangRepository.Ka
return kandangRepository.NewKandangRepository(s.Repository.DB()) return kandangRepository.NewKandangRepository(s.Repository.DB())
} }
func actorIDFromContext(c *fiber.Ctx) (uint, error) { func actorIDFromContext(_ *fiber.Ctx) (uint, error) {
user, ok := authmiddleware.AuthenticatedUser(c) // user, ok := authmiddleware.AuthenticatedUser(c)
if !ok || user == nil || user.Id == 0 { // if !ok || user == nil || user.Id == 0 {
return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate") // return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
} // }
return user.Id, nil // return user.Id, nil
return 1, nil
} }
@@ -15,30 +15,30 @@ import (
// === DTO Structs === // === DTO Structs ===
type RecordingBaseDTO struct { type RecordingRelationDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
ProjectFlockKandangId uint `json:"project_flock_kandang_id"` ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
RecordDatetime time.Time `json:"record_datetime"` RecordDatetime time.Time `json:"record_datetime"`
Day int `json:"day"` Day int `json:"day"`
ProjectFlockCategory string `json:"project_flock_category"` ProjectFlockCategory string `json:"project_flock_category"`
TotalDepletionQty float64 `json:"total_depletion_qty"` TotalDepletionQty float64 `json:"total_depletion_qty"`
CumDepletionRate float64 `json:"cum_depletion_rate"` CumDepletionRate float64 `json:"cum_depletion_rate"`
DailyGain float64 `json:"daily_gain"` DailyGain float64 `json:"daily_gain"`
AvgDailyGain float64 `json:"avg_daily_gain"` AvgDailyGain float64 `json:"avg_daily_gain"`
CumIntake int `json:"cum_intake"` CumIntake int `json:"cum_intake"`
FcrValue float64 `json:"fcr_value"` FcrValue float64 `json:"fcr_value"`
TotalChickQty float64 `json:"total_chick_qty"` TotalChickQty float64 `json:"total_chick_qty"`
Approval approvalDTO.ApprovalBaseDTO `json:"approval"` Approval approvalDTO.ApprovalRelationDTO `json:"approval"`
EggGradingStatus *string `json:"egg_grading_status"` EggGradingStatus *string `json:"egg_grading_status"`
EggGradingPendingQty *int `json:"egg_grading_pending_qty"` EggGradingPendingQty *int `json:"egg_grading_pending_qty"`
EggGradingCompletedQty *int `json:"egg_grading_completed_qty"` EggGradingCompletedQty *int `json:"egg_grading_completed_qty"`
} }
type RecordingListDTO struct { type RecordingListDTO struct {
RecordingBaseDTO RecordingRelationDTO
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 RecordingDetailDTO struct { type RecordingDetailDTO struct {
@@ -91,7 +91,7 @@ type RecordingEggGradingDTO struct {
// === Mapper Functions === // === Mapper Functions ===
func ToRecordingBaseDTO(e entity.Recording) RecordingBaseDTO { func ToRecordingRelationDTO(e entity.Recording) RecordingRelationDTO {
var ( var (
projectFlockCategory string projectFlockCategory string
day int day int
@@ -142,7 +142,7 @@ func ToRecordingBaseDTO(e entity.Recording) RecordingBaseDTO {
gradingStatus, gradingPending, gradingCompleted := computeEggGradingStatus(e) gradingStatus, gradingPending, gradingCompleted := computeEggGradingStatus(e)
return RecordingBaseDTO{ return RecordingRelationDTO{
Id: e.Id, Id: e.Id,
ProjectFlockKandangId: e.ProjectFlockKandangId, ProjectFlockKandangId: e.ProjectFlockKandangId,
RecordDatetime: e.RecordDatetime, RecordDatetime: e.RecordDatetime,
@@ -163,17 +163,17 @@ func ToRecordingBaseDTO(e entity.Recording) RecordingBaseDTO {
} }
func ToRecordingListDTO(e entity.Recording) RecordingListDTO { func ToRecordingListDTO(e entity.Recording) RecordingListDTO {
var createdUser *userDTO.UserBaseDTO var createdUser *userDTO.UserRelationDTO
if e.CreatedUser != nil && e.CreatedUser.Id != 0 { if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
mapped := userDTO.ToUserBaseDTO(*e.CreatedUser) mapped := userDTO.ToUserRelationDTO(*e.CreatedUser)
createdUser = &mapped createdUser = &mapped
} }
return RecordingListDTO{ return RecordingListDTO{
RecordingBaseDTO: ToRecordingBaseDTO(e), RecordingRelationDTO: ToRecordingRelationDTO(e),
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt, UpdatedAt: e.UpdatedAt,
CreatedUser: createdUser, CreatedUser: createdUser,
} }
} }
@@ -344,8 +344,8 @@ func filterGoodEggs(eggs []entity.RecordingEgg) []entity.RecordingEgg {
return result return result
} }
func defaultRecordingLatestApproval(e entity.Recording) approvalDTO.ApprovalBaseDTO { func defaultRecordingLatestApproval(e entity.Recording) approvalDTO.ApprovalRelationDTO {
result := approvalDTO.ApprovalBaseDTO{} result := approvalDTO.ApprovalRelationDTO{}
step := utils.RecordingStepPengajuan step := utils.RecordingStepPengajuan
result.StepNumber = uint16(step) result.StepNumber = uint16(step)
@@ -356,9 +356,9 @@ func defaultRecordingLatestApproval(e entity.Recording) approvalDTO.ApprovalBase
} }
if e.CreatedUser != nil && e.CreatedUser.Id != 0 { if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
result.ActionBy = userDTO.ToUserBaseDTO(*e.CreatedUser) result.ActionBy = userDTO.ToUserRelationDTO(*e.CreatedUser)
} else if e.CreatedBy != 0 { } else if e.CreatedBy != 0 {
result.ActionBy = userDTO.UserBaseDTO{ result.ActionBy = userDTO.UserRelationDTO{
Id: e.CreatedBy, Id: e.CreatedBy,
IdUser: int64(e.CreatedBy), IdUser: int64(e.CreatedBy),
} }
@@ -10,7 +10,7 @@ import (
// === DTO Structs === // === DTO Structs ===
type TransferLayingBaseDTO struct { type TransferLayingRelationDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
TransferNumber string `json:"transfer_number"` TransferNumber string `json:"transfer_number"`
TransferDate time.Time `json:"transfer_date"` TransferDate time.Time `json:"transfer_date"`
@@ -64,22 +64,22 @@ type LayingTransferTargetDTO struct {
} }
type TransferLayingListDTO struct { type TransferLayingListDTO struct {
TransferLayingBaseDTO TransferLayingRelationDTO
FromProjectFlock *ProjectFlockSummaryDTO `json:"from_project_flock,omitempty"` FromProjectFlock *ProjectFlockSummaryDTO `json:"from_project_flock,omitempty"`
ToProjectFlock *ProjectFlockSummaryDTO `json:"to_project_flock,omitempty"` ToProjectFlock *ProjectFlockSummaryDTO `json:"to_project_flock,omitempty"`
PendingUsageQty *float64 `json:"pending_usage_qty"` PendingUsageQty *float64 `json:"pending_usage_qty"`
UsageQty *float64 `json:"usage_qty"` UsageQty *float64 `json:"usage_qty"`
CreatedBy uint `json:"created_by"` CreatedBy uint `json:"created_by"`
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"`
Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"` Approval *approvalDTO.ApprovalRelationDTO `json:"approval,omitempty"`
} }
type TransferLayingDetailDTO struct { type TransferLayingDetailDTO struct {
TransferLayingListDTO TransferLayingListDTO
Sources []LayingTransferSourceDTO `json:"sources,omitempty"` Sources []LayingTransferSourceDTO `json:"sources,omitempty"`
Targets []LayingTransferTargetDTO `json:"targets,omitempty"` Targets []LayingTransferTargetDTO `json:"targets,omitempty"`
Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"` Approval *approvalDTO.ApprovalRelationDTO `json:"approval,omitempty"`
} }
// === Available Quantity DTOs === // === Available Quantity DTOs ===
@@ -203,8 +203,8 @@ func ToLayingTransferTargetDTOs(targets []entity.LayingTransferTarget) []LayingT
return result return result
} }
func ToTransferLayingBaseDTO(e entity.LayingTransfer) TransferLayingBaseDTO { func ToTransferLayingRelationDTO(e entity.LayingTransfer) TransferLayingRelationDTO {
return TransferLayingBaseDTO{ return TransferLayingRelationDTO{
Id: e.Id, Id: e.Id,
TransferNumber: e.TransferNumber, TransferNumber: e.TransferNumber,
TransferDate: e.TransferDate, TransferDate: e.TransferDate,
@@ -213,26 +213,26 @@ func ToTransferLayingBaseDTO(e entity.LayingTransfer) TransferLayingBaseDTO {
} }
func ToTransferLayingListDTO(e entity.LayingTransfer) TransferLayingListDTO { func ToTransferLayingListDTO(e entity.LayingTransfer) TransferLayingListDTO {
var createdUser *userDTO.UserBaseDTO var createdUser *userDTO.UserRelationDTO
if e.CreatedUser != nil && e.CreatedUser.Id != 0 { if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
mapped := userDTO.ToUserBaseDTO(*e.CreatedUser) mapped := userDTO.ToUserRelationDTO(*e.CreatedUser)
createdUser = &mapped createdUser = &mapped
} }
return TransferLayingListDTO{ return TransferLayingListDTO{
TransferLayingBaseDTO: ToTransferLayingBaseDTO(e), TransferLayingRelationDTO: ToTransferLayingRelationDTO(e),
FromProjectFlock: ToProjectFlockSummaryDTO(e.FromProjectFlock), FromProjectFlock: ToProjectFlockSummaryDTO(e.FromProjectFlock),
ToProjectFlock: ToProjectFlockSummaryDTO(e.ToProjectFlock), ToProjectFlock: ToProjectFlockSummaryDTO(e.ToProjectFlock),
PendingUsageQty: e.PendingUsageQty, PendingUsageQty: e.PendingUsageQty,
UsageQty: e.UsageQty, UsageQty: e.UsageQty,
CreatedBy: e.CreatedBy, CreatedBy: e.CreatedBy,
CreatedUser: createdUser, CreatedUser: createdUser,
CreatedAt: e.CreatedAt, CreatedAt: e.CreatedAt,
} }
} }
func ToTransferLayingDetailDTO(e entity.LayingTransfer, approvals []entity.Approval) TransferLayingDetailDTO { func ToTransferLayingDetailDTO(e entity.LayingTransfer, approvals []entity.Approval) TransferLayingDetailDTO {
var latestApproval *approvalDTO.ApprovalBaseDTO var latestApproval *approvalDTO.ApprovalRelationDTO
if e.LatestApproval != nil { if e.LatestApproval != nil {
mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval) mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval)
@@ -252,7 +252,7 @@ func ToTransferLayingDetailDTO(e entity.LayingTransfer, approvals []entity.Appro
} }
func ToTransferLayingDetailDTOWithSingleApproval(e entity.LayingTransfer, approval *entity.Approval) TransferLayingDetailDTO { func ToTransferLayingDetailDTOWithSingleApproval(e entity.LayingTransfer, approval *entity.Approval) TransferLayingDetailDTO {
var mappedApproval *approvalDTO.ApprovalBaseDTO var mappedApproval *approvalDTO.ApprovalRelationDTO
// Prefer LatestApproval from entity // Prefer LatestApproval from entity
if e.LatestApproval != nil && e.LatestApproval.Id != 0 { if e.LatestApproval != nil && e.LatestApproval.Id != 0 {
+47 -47
View File
@@ -13,52 +13,52 @@ import (
) )
type PurchaseListItemDTO struct { type PurchaseListItemDTO struct {
Id uint64 `json:"id"` Id uint64 `json:"id"`
PrNumber string `json:"pr_number"` PrNumber string `json:"pr_number"`
PoNumber *string `json:"po_number"` PoNumber *string `json:"po_number"`
Supplier *supplierDTO.SupplierBaseDTO `json:"supplier"` Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
CreditTerm *int `json:"credit_term"` CreditTerm *int `json:"credit_term"`
DueDate *time.Time `json:"due_date"` DueDate *time.Time `json:"due_date"`
PoDate *time.Time `json:"po_date"` PoDate *time.Time `json:"po_date"`
GrandTotal float64 `json:"grand_total"` GrandTotal float64 `json:"grand_total"`
Notes *string `json:"notes"` Notes *string `json:"notes"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
Approval *approvalDTO.ApprovalBaseDTO `json:"approval"` Approval *approvalDTO.ApprovalRelationDTO `json:"approval"`
} }
type PurchaseDetailDTO struct { type PurchaseDetailDTO struct {
Id uint64 `json:"id"` Id uint64 `json:"id"`
PrNumber string `json:"pr_number"` PrNumber string `json:"pr_number"`
PoNumber *string `json:"po_number"` PoNumber *string `json:"po_number"`
Supplier *supplierDTO.SupplierBaseDTO `json:"supplier"` Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
CreditTerm *int `json:"credit_term"` CreditTerm *int `json:"credit_term"`
DueDate *time.Time `json:"due_date"` DueDate *time.Time `json:"due_date"`
PoDate *time.Time `json:"po_date"` PoDate *time.Time `json:"po_date"`
GrandTotal float64 `json:"grand_total"` GrandTotal float64 `json:"grand_total"`
Notes *string `json:"notes"` Notes *string `json:"notes"`
Items []PurchaseItemDTO `json:"items"` Items []PurchaseItemDTO `json:"items"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
Approval *approvalDTO.ApprovalBaseDTO `json:"approval"` Approval *approvalDTO.ApprovalRelationDTO `json:"approval"`
} }
type PurchaseItemDTO struct { type PurchaseItemDTO struct {
Id uint64 `json:"id"` Id uint64 `json:"id"`
ProductID uint64 `json:"product_id"` ProductID uint64 `json:"product_id"`
Product *productDTO.ProductBaseDTO `json:"product"` Product *productDTO.ProductRelationDTO `json:"product"`
WarehouseID uint64 `json:"warehouse_id"` WarehouseID uint64 `json:"warehouse_id"`
Warehouse *warehouseDTO.WarehouseBaseDTO `json:"warehouse"` Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse"`
ProductWarehouseID *uint64 `json:"product_warehouse_id"` ProductWarehouseID *uint64 `json:"product_warehouse_id"`
SubQty float64 `json:"sub_qty"` SubQty float64 `json:"sub_qty"`
TotalQty float64 `json:"total_qty"` TotalQty float64 `json:"total_qty"`
TotalUsed float64 `json:"total_used"` TotalUsed float64 `json:"total_used"`
Price float64 `json:"price"` Price float64 `json:"price"`
TotalPrice float64 `json:"total_price"` TotalPrice float64 `json:"total_price"`
ReceivedDate *time.Time `json:"received_date"` ReceivedDate *time.Time `json:"received_date"`
TravelNumber *string `json:"travel_number"` TravelNumber *string `json:"travel_number"`
TravelDocumentPath *string `json:"travel_document_path"` TravelDocumentPath *string `json:"travel_document_path"`
VehicleNumber *string `json:"vehicle_number"` VehicleNumber *string `json:"vehicle_number"`
} }
func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO { func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO {
@@ -78,17 +78,17 @@ func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO {
VehicleNumber: item.VehicleNumber, VehicleNumber: item.VehicleNumber,
} }
if item.Product != nil && item.Product.Id != 0 { if item.Product != nil && item.Product.Id != 0 {
summary := productDTO.ToProductBaseDTO(*item.Product) summary := productDTO.ToProductRelationDTO(*item.Product)
dto.Product = &summary dto.Product = &summary
} }
if item.Warehouse != nil && item.Warehouse.Id != 0 { if item.Warehouse != nil && item.Warehouse.Id != 0 {
summary := warehouseDTO.ToWarehouseBaseDTO(*item.Warehouse) summary := warehouseDTO.ToWarehouseRelationDTO(*item.Warehouse)
if item.Warehouse.Area.Id != 0 { if item.Warehouse.Area.Id != 0 {
areaSummary := areaDTO.ToAreaBaseDTO(item.Warehouse.Area) areaSummary := areaDTO.ToAreaRelationDTO(item.Warehouse.Area)
summary.Area = &areaSummary summary.Area = &areaSummary
} }
if item.Warehouse.Location != nil && item.Warehouse.Location.Id != 0 { if item.Warehouse.Location != nil && item.Warehouse.Location.Id != 0 {
locationSummary := locationDTO.ToLocationBaseDTO(*item.Warehouse.Location) locationSummary := locationDTO.ToLocationRelationDTO(*item.Warehouse.Location)
summary.Location = &locationSummary summary.Location = &locationSummary
} }
dto.Warehouse = &summary dto.Warehouse = &summary
@@ -145,11 +145,11 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListItemDTO {
return dto return dto
} }
func mapSupplier(s entity.Supplier) *supplierDTO.SupplierBaseDTO { func mapSupplier(s entity.Supplier) *supplierDTO.SupplierRelationDTO {
if s.Id == 0 { if s.Id == 0 {
return nil return nil
} }
summary := supplierDTO.ToSupplierBaseDTO(s) summary := supplierDTO.ToSupplierRelationDTO(s)
return &summary return &summary
} }
@@ -164,7 +164,7 @@ func ToPurchaseListDTOs(items []entity.Purchase) []PurchaseListItemDTO {
return result return result
} }
func toPurchaseApprovalDTO(p entity.Purchase) *approvalDTO.ApprovalBaseDTO { func toPurchaseApprovalDTO(p entity.Purchase) *approvalDTO.ApprovalRelationDTO {
if p.LatestApproval == nil || p.LatestApproval.Id == 0 { if p.LatestApproval == nil || p.LatestApproval.Id == 0 {
return nil return nil
} }
@@ -11,6 +11,7 @@ import (
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities" entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
authmiddleware "gitlab.com/mbugroup/lti-api.git/internal/middleware"
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"
rProduct "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories" rProduct "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories"
rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories" rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories"
@@ -167,6 +168,11 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase
return nil, err return nil, err
} }
actorID, err := actorIDFromContext(c)
if err != nil {
return nil, err
}
ctx := c.Context() ctx := c.Context()
if _, err := s.SupplierRepo.GetByID(ctx, req.SupplierID, nil); err != nil { if _, err := s.SupplierRepo.GetByID(ctx, req.SupplierID, nil); err != nil {
@@ -257,7 +263,7 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase
DueDate: dueDate, DueDate: dueDate,
GrandTotal: 0, GrandTotal: 0,
Notes: req.Notes, Notes: req.Notes,
CreatedBy: 1, // TODO: replace with authenticated user id once available CreatedBy: uint64(actorID),
} }
items := make([]*entity.PurchaseItem, 0, len(aggregated)) items := make([]*entity.PurchaseItem, 0, len(aggregated))
@@ -326,7 +332,10 @@ func (s *purchaseService) processStaffPurchaseApproval(c *fiber.Ctx, id uint64,
return nil, err return nil, err
} }
actorID := uint(1) // TODO: replace with authenticated user id once available actorID, err := actorIDFromContext(c)
if err != nil {
return nil, err
}
ctx := c.Context() ctx := c.Context()
purchase, err := s.PurchaseRepo.GetByIDWithRelations(ctx, id) purchase, err := s.PurchaseRepo.GetByIDWithRelations(ctx, id)
@@ -470,6 +479,11 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint64, req *v
return nil, err return nil, err
} }
actorID, err := actorIDFromContext(c)
if err != nil {
return nil, err
}
ctx := c.Context() ctx := c.Context()
purchase, err := s.PurchaseRepo.GetByIDWithRelations(ctx, id) purchase, err := s.PurchaseRepo.GetByIDWithRelations(ctx, id)
@@ -490,7 +504,6 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint64, req *v
return nil, fiber.NewError(fiber.StatusBadRequest, "Purchase must reach staff purchase step before manager approval") return nil, fiber.NewError(fiber.StatusBadRequest, "Purchase must reach staff purchase step before manager approval")
} }
actorID := uint(1)
action := entity.ApprovalActionApproved action := entity.ApprovalActionApproved
now := time.Now().UTC() now := time.Now().UTC()
hasExistingPO := purchase.PoNumber != nil && strings.TrimSpace(*purchase.PoNumber) != "" hasExistingPO := purchase.PoNumber != nil && strings.TrimSpace(*purchase.PoNumber) != ""
@@ -571,6 +584,11 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati
return nil, err return nil, err
} }
actorID, err := actorIDFromContext(c)
if err != nil {
return nil, err
}
ctx := c.Context() ctx := c.Context()
purchase, err := s.PurchaseRepo.GetByIDWithRelations(ctx, id) purchase, err := s.PurchaseRepo.GetByIDWithRelations(ctx, id)
@@ -668,7 +686,6 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati
receivingAction := entity.ApprovalActionApproved receivingAction := entity.ApprovalActionApproved
completedAction := entity.ApprovalActionApproved completedAction := entity.ApprovalActionApproved
actorID := uint(1)
approvalSvc := s.approvalServiceForDB(nil) approvalSvc := s.approvalServiceForDB(nil)
if approvalSvc != nil { if approvalSvc != nil {
@@ -1053,6 +1070,14 @@ func (s *purchaseService) notifyExpenseItemsDeleted(ctx context.Context, purchas
} }
} }
func actorIDFromContext(c *fiber.Ctx) (uint, error) {
user, ok := authmiddleware.AuthenticatedUser(c)
if !ok || user == nil || user.Id == 0 {
return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
}
return user.Id, nil
}
func (s *purchaseService) buildStaffAdjustmentPayload( func (s *purchaseService) buildStaffAdjustmentPayload(
ctx context.Context, ctx context.Context,
purchase *entity.Purchase, purchase *entity.Purchase,
+7 -7
View File
@@ -8,7 +8,7 @@ import (
// === DTO Structs === // === DTO Structs ===
type UserBaseDTO struct { type UserRelationDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
IdUser int64 `json:"id_user"` IdUser int64 `json:"id_user"`
Email string `json:"email"` Email string `json:"email"`
@@ -16,7 +16,7 @@ type UserBaseDTO struct {
} }
type UserListDTO struct { type UserListDTO struct {
UserBaseDTO UserRelationDTO
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
} }
@@ -27,8 +27,8 @@ type UserDetailDTO struct {
// === Mapper Functions === // === Mapper Functions ===
func ToUserBaseDTO(m entity.User) UserBaseDTO { func ToUserRelationDTO(m entity.User) UserRelationDTO {
return UserBaseDTO{ return UserRelationDTO{
Id: m.Id, Id: m.Id,
IdUser: m.IdUser, IdUser: m.IdUser,
Email: m.Email, Email: m.Email,
@@ -38,9 +38,9 @@ func ToUserBaseDTO(m entity.User) UserBaseDTO {
func ToUserListDTO(m entity.User) UserListDTO { func ToUserListDTO(m entity.User) UserListDTO {
return UserListDTO{ return UserListDTO{
UserBaseDTO: ToUserBaseDTO(m), UserRelationDTO: ToUserRelationDTO(m),
CreatedAt: m.CreatedAt, CreatedAt: m.CreatedAt,
UpdatedAt: m.UpdatedAt, UpdatedAt: m.UpdatedAt,
} }
} }
+6 -2
View File
@@ -260,12 +260,16 @@ const (
ExpenseStepPengajuan approvalutils.ApprovalStep = 1 ExpenseStepPengajuan approvalutils.ApprovalStep = 1
ExpenseStepManager approvalutils.ApprovalStep = 2 ExpenseStepManager approvalutils.ApprovalStep = 2
ExpenseStepFinance approvalutils.ApprovalStep = 3 ExpenseStepFinance approvalutils.ApprovalStep = 3
ExpenseStepRealisasi approvalutils.ApprovalStep = 4
ExpenseStepSelesai approvalutils.ApprovalStep = 5
) )
var ExpenseApprovalSteps = map[approvalutils.ApprovalStep]string{ var ExpenseApprovalSteps = map[approvalutils.ApprovalStep]string{
ExpenseStepPengajuan: "Pengajuan", ExpenseStepPengajuan: "Pengajuan",
ExpenseStepManager: "Manager", ExpenseStepManager: "Approval Manager",
ExpenseStepFinance: "Finance", ExpenseStepFinance: "Approval Finance",
ExpenseStepRealisasi: "Realisasi",
ExpenseStepSelesai: "Selesai",
} }
// ------------------------------------------------------------------- // -------------------------------------------------------------------
-27
View File
@@ -1,27 +0,0 @@
package test
import (
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
)
func TestAreaIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
t.Run("create area trims name", func(t *testing.T) {
areaID := createArea(t, app, " Area Trim ")
if got := fetchAreaName(t, db, areaID); got != "Area Trim" {
t.Fatalf("expected trimmed name, got %q", got)
}
})
t.Run("duplicate area returns conflict", func(t *testing.T) {
createArea(t, app, "Duplicate Area")
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/areas", map[string]any{"name": "Duplicate Area"})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 conflict, got %d: %s", resp.StatusCode, string(body))
}
})
}
-127
View File
@@ -1,127 +0,0 @@
package test
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"gitlab.com/mbugroup/lti-api.git/internal/entities"
)
func TestBankIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
const bankAlias = "BNI"
const bankName = "Bank Negara Indonesia"
const bankOwner = "John Doe"
const bankAccountNumber = "1234567890"
var bankID uint
t.Run("creating bank succeeds", func(t *testing.T) {
bankID = createBank(t, app, bankName, bankAlias, bankAccountNumber, bankOwner)
bank := fetchBank(t, db, bankID)
if bank.Alias != bankAlias {
t.Fatalf("expected alias %q, got %q", bankAlias, bank.Alias)
}
if bank.AccountNumber != bankAccountNumber {
t.Fatalf("expected account number %q, got %q", bankAccountNumber, bank.AccountNumber)
}
})
t.Run("creating bank with duplicate name fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/banks", map[string]any{
"name": bankName,
"alias": "NEWALIAS",
"owner": "Owner Name",
"account_number": "1122334455",
})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 when creating duplicate bank name, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("getting existing bank succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/banks/%d", bankID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when fetching bank, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Id uint `json:"id"`
Name string `json:"name"`
Alias string `json:"alias"`
AccountNumber string `json:"account_number"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse response: %v", err)
}
if payload.Data.Id != bankID {
t.Fatalf("expected id %d, got %d", bankID, payload.Data.Id)
}
if payload.Data.Alias != bankAlias {
t.Fatalf("expected alias %q, got %q", bankAlias, payload.Data.Alias)
}
if payload.Data.AccountNumber != bankAccountNumber {
t.Fatalf("expected account number %q, got %q", bankAccountNumber, payload.Data.AccountNumber)
}
})
const updatedName = "BNI Updated"
t.Run("updating bank name succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/banks/%d", bankID), map[string]any{
"name": updatedName,
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when updating bank, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Name string `json:"name"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse response: %v", err)
}
if payload.Data.Name != updatedName {
t.Fatalf("expected updated name %q, got %q", updatedName, payload.Data.Name)
}
bank := fetchBank(t, db, bankID)
if bank.Name != updatedName {
t.Fatalf("expected persisted name %q, got %q", updatedName, bank.Name)
}
})
t.Run("updating non existent bank returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, "/api/master-data/banks/9999", map[string]any{
"name": "Does Not Matter",
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when updating missing bank, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("deleting bank succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/banks/%d", bankID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when deleting bank, got %d: %s", resp.StatusCode, string(body))
}
var bank entities.Bank
if err := db.First(&bank, bankID).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("expected bank to be deleted, got error %v", err)
}
})
t.Run("deleting non existent bank returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/banks/%d", bankID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when deleting missing bank, got %d: %s", resp.StatusCode, string(body))
}
})
}
@@ -1,189 +0,0 @@
package test
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
)
func TestCustomerIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
t.Run("creating customer without existing pic fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
"name": "Invalid Customer",
"pic_id": 9999,
"type": utils.CustomerSupplierTypeBisnis,
"address": "Somewhere",
"phone": "0800000000",
"email": "Invalid@example.com",
"account_number": "ACC-INVALID",
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when pic is missing, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("creating customer with Invalid type fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
"name": "Invalid Type",
"pic_id": 1,
"type": "UNKNOWN",
"address": "Somewhere",
"phone": "081234567891",
"email": "Invalid-type@example.com",
"account_number": "ACC-INVALID-TYPE",
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when type is Invalid, got %d: %s", resp.StatusCode, string(body))
}
})
const customerName = "Customer Alpha"
var customerID uint
t.Run("creating customer succeeds", func(t *testing.T) {
customerID = createCustomer(t, app, customerName, 1)
customer := fetchCustomer(t, db, customerID)
if customer.Name != customerName {
t.Fatalf("expected name %q, got %q", customerName, customer.Name)
}
if customer.PicId != 1 {
t.Fatalf("expected pic id 1, got %d", customer.PicId)
}
if customer.Type != string(utils.CustomerSupplierTypeBisnis) {
t.Fatalf("expected type %q, got %q", utils.CustomerSupplierTypeBisnis, customer.Type)
}
})
t.Run("creating customer with duplicate name fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
"name": customerName,
"pic_id": 1,
"type": utils.CustomerSupplierTypeBisnis,
"address": "Duplicate address",
"phone": "0811111111",
"email": "duplicate@example.com",
"account_number": "ACC-DUP",
})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 when creating duplicate customer, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("getting existing customer succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when fetching customer, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Id uint `json:"id"`
Name string `json:"name"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse customer response: %v", err)
}
if payload.Data.Id != customerID {
t.Fatalf("expected id %d, got %d", customerID, payload.Data.Id)
}
if payload.Data.Name != customerName {
t.Fatalf("expected name %q, got %q", customerName, payload.Data.Name)
}
})
const updatedName = "Customer Gamma"
t.Run("updating customer name succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/customers/%d", customerID), map[string]any{
"name": updatedName,
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when updating customer, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Name string `json:"name"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse update response: %v", err)
}
if payload.Data.Name != updatedName {
t.Fatalf("expected updated name %q, got %q", updatedName, payload.Data.Name)
}
customer := fetchCustomer(t, db, customerID)
if customer.Name != updatedName {
t.Fatalf("expected persisted name %q, got %q", updatedName, customer.Name)
}
})
t.Run("updating customer type succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/customers/%d", customerID), map[string]any{
"type": utils.CustomerSupplierTypeIndividual,
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when updating customer type, got %d: %s", resp.StatusCode, string(body))
}
customer := fetchCustomer(t, db, customerID)
if customer.Type != string(utils.CustomerSupplierTypeIndividual) {
t.Fatalf("expected persisted type %q, got %q", utils.CustomerSupplierTypeIndividual, customer.Type)
}
})
t.Run("updating customer with Invalid type fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/customers/%d", customerID), map[string]any{
"type": "random-type",
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when updating with Invalid type, got %d: %s", resp.StatusCode, string(body))
}
customer := fetchCustomer(t, db, customerID)
if customer.Type != string(utils.CustomerSupplierTypeIndividual) {
t.Fatalf("expected type to remain %q after Invalid update, got %q", utils.CustomerSupplierTypeIndividual, customer.Type)
}
})
t.Run("updating non existent customer returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, "/api/master-data/customers/9999", map[string]any{
"name": "Does Not Matter",
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when updating missing customer, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("deleting customer succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when deleting customer, got %d: %s", resp.StatusCode, string(body))
}
var customer entities.Customer
if err := db.First(&customer, customerID).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("expected customer to be deleted, got error %v", err)
}
})
t.Run("deleting non existent customer returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when deleting missing customer, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("getting deleted customer returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/customers/%d", customerID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when fetching deleted customer, got %d: %s", resp.StatusCode, string(body))
}
})
}
-218
View File
@@ -1,218 +0,0 @@
package test
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"gitlab.com/mbugroup/lti-api.git/internal/entities"
)
func TestFcrIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
t.Run("creating fcr without standards fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/fcrs", map[string]any{
"name": "FCR Alpha",
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when standards missing, got %d: %s", resp.StatusCode, string(body))
}
})
initialStandards := []map[string]any{
{
"weight": 7.2,
"fcr_number": 400.0,
"mortality": 12.0,
},
{
"weight": 7.4,
"fcr_number": 410.0,
"mortality": 11.5,
},
}
var fcrID uint
t.Run("creating fcr succeeds", func(t *testing.T) {
fcrID = createFcr(t, app, "FCR Layer", initialStandards)
fcr := fetchFcr(t, db, fcrID)
if fcr.Name != "FCR Layer" {
t.Fatalf("expected name FCR Layer, got %q", fcr.Name)
}
if len(fcr.Standards) != len(initialStandards) {
t.Fatalf("expected %d standards, got %d", len(initialStandards), len(fcr.Standards))
}
if fcr.CreatedBy != 1 {
t.Fatalf("expected created_by 1, got %d", fcr.CreatedBy)
}
})
t.Run("creating duplicate fcr name fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/fcrs", map[string]any{
"name": "FCR Layer",
"fcr_standards": initialStandards,
})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 when creating duplicate, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("getting fcr detail succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/fcrs/%d", fcrID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when fetching fcr, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Id uint `json:"id"`
Name string `json:"name"`
FcrStandards []map[string]any `json:"fcr_standards"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse fcr detail: %v", err)
}
if payload.Data.Id != fcrID {
t.Fatalf("expected id %d, got %d", fcrID, payload.Data.Id)
}
if len(payload.Data.FcrStandards) != len(initialStandards) {
t.Fatalf("expected %d standards in response, got %d", len(initialStandards), len(payload.Data.FcrStandards))
}
})
updatedStandards := []map[string]any{
{
"weight": 7.2,
"fcr_number": 395.0,
"mortality": 10.0,
},
{
"weight": 7.5,
"fcr_number": 420.0,
"mortality": 13.0,
},
}
t.Run("updating fcr name and standards succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/fcrs/%d", fcrID), map[string]any{
"name": "FCR Layer Updated",
"fcr_standards": updatedStandards,
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when updating fcr, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Name string `json:"name"`
FcrStandards []map[string]any `json:"fcr_standards"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse update response: %v", err)
}
if payload.Data.Name != "FCR Layer Updated" {
t.Fatalf("expected updated name, got %q", payload.Data.Name)
}
if len(payload.Data.FcrStandards) != len(updatedStandards) {
t.Fatalf("expected %d standards after update, got %d", len(updatedStandards), len(payload.Data.FcrStandards))
}
fcr := fetchFcr(t, db, fcrID)
if fcr.Name != "FCR Layer Updated" {
t.Fatalf("expected persisted name FCR Layer Updated, got %q", fcr.Name)
}
if len(fcr.Standards) != len(updatedStandards) {
t.Fatalf("expected %d persisted standards, got %d", len(updatedStandards), len(fcr.Standards))
}
if fcr.Standards[0].FcrNumber != 395.0 {
t.Fatalf("expected first standard fcr_number 395, got %f", fcr.Standards[0].FcrNumber)
}
})
var otherFcrID uint
t.Run("creating another fcr for duplicate update", func(t *testing.T) {
otherFcrID = createFcr(t, app, "FCR Grower", []map[string]any{
{
"weight": 8.0,
"fcr_number": 430.0,
"mortality": 9.0,
},
})
if otherFcrID == 0 {
t.Fatal("expected other fcr id to be non zero")
}
})
t.Run("updating fcr with duplicate name fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/fcrs/%d", fcrID), map[string]any{
"name": "FCR Grower",
})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 when renaming to existing fcr, got %d: %s", resp.StatusCode, string(body))
}
fcr := fetchFcr(t, db, fcrID)
if fcr.Name != "FCR Layer Updated" {
t.Fatalf("expected name unchanged after failed update, got %q", fcr.Name)
}
})
t.Run("updating fcr with invalid standards fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/fcrs/%d", fcrID), map[string]any{
"fcr_standards": []map[string]any{
{
"weight": -1,
"fcr_number": 300,
"mortality": 5,
},
},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when updating with invalid standard, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("deleting fcr succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/fcrs/%d", fcrID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when deleting fcr, got %d: %s", resp.StatusCode, string(body))
}
var fcr entities.Fcr
if err := db.First(&fcr, fcrID).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("expected fcr to be deleted, got error %v", err)
}
var standardsCount int64
if err := db.Model(&entities.FcrStandard{}).Where("fcr_id = ?", fcrID).Count(&standardsCount).Error; err != nil {
t.Fatalf("failed counting standards: %v", err)
}
if standardsCount != 0 {
t.Fatalf("expected standards removed, found %d", standardsCount)
}
})
t.Run("deleting fcr again returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/fcrs/%d", fcrID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when deleting missing fcr, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("getting deleted fcr returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/fcrs/%d", fcrID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when fetching deleted fcr, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("cleanup other fcr", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/fcrs/%d", otherFcrID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when deleting other fcr, got %d: %s", resp.StatusCode, string(body))
}
})
}
@@ -1,96 +0,0 @@
package test
import (
"encoding/json"
"fmt"
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
"gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
)
func TestKandangIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
areaID := createArea(t, app, "Area Kandang")
locationID := createLocation(t, app, "Location For Kandang", "Address", areaID)
t.Run("create kandang success", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
"name": "Kandang OK",
"location_id": locationID,
"pic_id": 1,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
}
var createResp struct {
Data struct {
Status string `json:"status"`
} `json:"data"`
}
if err := json.Unmarshal(body, &createResp); err != nil {
t.Fatalf("failed to parse create response: %v", err)
}
if createResp.Data.Status == "" {
t.Fatalf("expected default status to be returned, got empty")
}
})
t.Run("create kandang with unknown location fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
"name": "Kandang Fail",
"status": "ACTIVE",
"location_id": 999,
"pic_id": 1,
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("cannot assign project floc with existing active kandang", func(t *testing.T) {
fcrID := createFcr(t, app, "FCR For Floc", []map[string]any{
{"weight": 1.0, "fcr_number": 1.5, "mortality": 2.0},
})
flocID := createFlock(t, app, "Floc Test")
projectFloc := entities.ProjectFlock{
FlockName: fmt.Sprintf("Project Flock %d", flocID),
AreaId: areaID,
Category: string(utils.ProjectFlockCategoryGrowing),
FcrId: fcrID,
LocationId: locationID,
Period: 1,
CreatedBy: 1,
}
if err := db.Create(&projectFloc).Error; err != nil {
t.Fatalf("failed to seed project floc: %v", err)
}
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
"name": "Kandang Active 1",
"status": "ACTIVE",
"location_id": locationID,
"pic_id": 1,
"project_flock_id": projectFloc.Id,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating first kandang, got %d: %s", resp.StatusCode, string(body))
}
resp, body = doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
"name": "Kandang Active 2",
"status": "ACTIVE",
"location_id": locationID,
"pic_id": 1,
"project_flock_id": projectFloc.Id,
})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 when creating second active kandang, got %d: %s", resp.StatusCode, string(body))
}
})
}
@@ -1,35 +0,0 @@
package test
import (
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
)
func TestLocationIntegration(t *testing.T) {
app, _ := setupIntegrationApp(t)
t.Run("creating location without existing area fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/locations", map[string]any{
"name": "Loc A",
"address": "Address",
"area_id": 999, // non-existent
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("creating location succeeds", func(t *testing.T) {
areaID := createArea(t, app, "Area For Location")
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/locations", map[string]any{
"name": "Location OK",
"address": "Addr",
"area_id": areaID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
}
})
}
-401
View File
@@ -1,401 +0,0 @@
package test
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"time"
"github.com/glebarez/sqlite"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/route"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
)
func setupIntegrationApp(t *testing.T) (*fiber.App, *gorm.DB) {
t.Helper()
dir := t.TempDir()
dsn := filepath.Join(dir, "integration.db")
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
if err != nil {
t.Fatalf("failed to open sqlite database: %v", err)
}
if err := db.Exec("PRAGMA foreign_keys = ON").Error; err != nil {
t.Fatalf("failed to enable foreign keys: %v", err)
}
if err := db.AutoMigrate(
&entities.User{},
&entities.Area{},
&entities.Location{},
&entities.Flock{},
&entities.ProjectFlock{},
&entities.ProjectFlockKandang{},
&entities.Kandang{},
&entities.Warehouse{},
&entities.Uom{},
&entities.Customer{},
&entities.Supplier{},
&entities.Flag{},
&entities.ProductCategory{},
&entities.Nonstock{},
&entities.NonstockSupplier{},
&entities.Product{},
&entities.ProductSupplier{},
&entities.Fcr{},
&entities.FcrStandard{},
&entities.Bank{},
); err != nil {
t.Fatalf("auto migrate failed: %v", err)
}
seedUser := entities.User{
Id: 1,
IdUser: 1001,
Email: "tester@example.com",
Name: "Tester",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := db.Create(&seedUser).Error; err != nil {
t.Fatalf("failed to seed user: %v", err)
}
app := fiber.New(fiber.Config{ErrorHandler: utils.ErrorHandler})
route.Routes(app, db)
return app, db
}
func doJSONRequest(t *testing.T, app *fiber.App, method, path string, payload any) (*http.Response, []byte) {
t.Helper()
var body io.Reader
if payload != nil {
buf := &bytes.Buffer{}
if err := json.NewEncoder(buf).Encode(payload); err != nil {
t.Fatalf("failed to encode payload: %v", err)
}
body = buf
}
req := httptest.NewRequest(method, path, body)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
resp, err := app.Test(req, -1)
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
return resp, data
}
func parseID(t *testing.T, body []byte) uint {
t.Helper()
var resp struct {
Data struct {
Id uint `json:"id"`
} `json:"data"`
}
if err := json.Unmarshal(body, &resp); err != nil {
t.Fatalf("failed to parse response: %v", err)
}
return resp.Data.Id
}
func createArea(t *testing.T, app *fiber.App, name string) uint {
t.Helper()
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/areas", map[string]any{"name": name})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating area, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func createLocation(t *testing.T, app *fiber.App, name, address string, areaID uint) uint {
t.Helper()
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/locations", map[string]any{
"name": name,
"address": address,
"area_id": areaID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating location, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func createUom(t *testing.T, app *fiber.App, name string) uint {
t.Helper()
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/uoms", map[string]any{"name": name})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating uom, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func createKandang(t *testing.T, app *fiber.App, name string, locationID, picID uint) uint {
t.Helper()
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
"name": name,
"status": "ACTIVE",
"location_id": locationID,
"pic_id": picID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating kandang, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func createCustomer(t *testing.T, app *fiber.App, name string, picID uint) uint {
t.Helper()
identifier := strings.ToLower(strings.ReplaceAll(name, " ", "_"))
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/customers", map[string]any{
"name": name,
"pic_id": picID,
"type": utils.CustomerSupplierTypeBisnis,
"address": "Customer address",
"phone": "081234567890",
"email": fmt.Sprintf("%s@example.com", identifier),
"account_number": fmt.Sprintf("ACC-%s", identifier),
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating customer, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func fetchCustomer(t *testing.T, db *gorm.DB, id uint) entities.Customer {
t.Helper()
var customer entities.Customer
if err := db.Preload("Pic").Preload("CreatedUser").First(&customer, id).Error; err != nil {
t.Fatalf("failed to fetch customer: %v", err)
}
return customer
}
func fetchKandang(t *testing.T, db *gorm.DB, id uint) entities.Kandang {
t.Helper()
var kandang entities.Kandang
if err := db.Preload("ProjectFlock").First(&kandang, id).Error; err != nil {
t.Fatalf("failed to fetch kandang: %v", err)
}
return kandang
}
func createSupplier(t *testing.T, app *fiber.App, name, alias, category string) uint {
t.Helper()
identifier := strings.ToLower(strings.ReplaceAll(name, " ", "_"))
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/suppliers", map[string]any{
"name": name,
"alias": alias,
"pic": "John Doe",
"type": utils.CustomerSupplierTypeBisnis,
"category": category,
"hatchery": "Hatchery A",
"phone": "081234567890",
"email": fmt.Sprintf("%s@supplier.com", identifier),
"address": "Supplier address",
"npwp": "NPWP-123",
"account_number": "ACC-SUPPLIER",
"due_date": 30,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating supplier, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func createProductCategory(t *testing.T, app *fiber.App, name, code string) uint {
t.Helper()
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/product-categories", map[string]any{
"name": name,
"code": code,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating product category, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func fetchProductCategory(t *testing.T, db *gorm.DB, id uint) entities.ProductCategory {
t.Helper()
var pc entities.ProductCategory
if err := db.Preload("CreatedUser").First(&pc, id).Error; err != nil {
t.Fatalf("failed to fetch product category: %v", err)
}
return pc
}
func createProduct(t *testing.T, app *fiber.App, name, brand string, sku *string, uomID, categoryID uint, productPrice float64, supplierIDs []uint, flags []string) uint {
t.Helper()
payload := map[string]any{
"name": name,
"brand": brand,
"uom_id": uomID,
"product_category_id": categoryID,
"product_price": productPrice,
"supplier_ids": supplierIDs,
}
if sku != nil {
payload["sku"] = *sku
}
if len(flags) > 0 {
payload["flags"] = flags
}
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/products", payload)
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating product, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func fetchProduct(t *testing.T, db *gorm.DB, id uint) entities.Product {
t.Helper()
var product entities.Product
if err := db.Preload("CreatedUser").
Preload("Uom").
Preload("ProductCategory").
Preload("Suppliers", func(tx *gorm.DB) *gorm.DB { return tx.Order("suppliers.name ASC") }).
Preload("Flags", func(tx *gorm.DB) *gorm.DB { return tx.Order("flags.name ASC") }).
First(&product, id).Error; err != nil {
t.Fatalf("failed to fetch product: %v", err)
}
return product
}
func fetchSupplier(t *testing.T, db *gorm.DB, id uint) entities.Supplier {
t.Helper()
var supplier entities.Supplier
if err := db.Preload("CreatedUser").First(&supplier, id).Error; err != nil {
t.Fatalf("failed to fetch supplier: %v", err)
}
return supplier
}
func createFcr(t *testing.T, app *fiber.App, name string, standards []map[string]any) uint {
t.Helper()
payload := map[string]any{
"name": name,
"fcr_standards": standards,
}
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/fcrs", payload)
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating fcr, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func createFlock(t *testing.T, app *fiber.App, name string) uint {
t.Helper()
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/flocks", map[string]any{
"name": name,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating flock, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func fetchFcr(t *testing.T, db *gorm.DB, id uint) entities.Fcr {
t.Helper()
var fcr entities.Fcr
if err := db.Preload("CreatedUser").
Preload("Standards", func(tx *gorm.DB) *gorm.DB {
return tx.Order("weight ASC")
}).
First(&fcr, id).Error; err != nil {
t.Fatalf("failed to fetch fcr: %v", err)
}
return fcr
}
func createNonstock(t *testing.T, app *fiber.App, name string, uomID uint, supplierIDs []uint, flags []string) uint {
t.Helper()
payload := map[string]any{
"name": name,
"uom_id": uomID,
"supplier_ids": supplierIDs,
}
if len(flags) > 0 {
payload["flags"] = flags
}
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/nonstocks", payload)
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating nonstock, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func fetchNonstock(t *testing.T, db *gorm.DB, id uint) entities.Nonstock {
t.Helper()
var nonstock entities.Nonstock
if err := db.Preload("CreatedUser").
Preload("Uom").
Preload("Suppliers", func(tx *gorm.DB) *gorm.DB { return tx.Order("suppliers.name ASC") }).
Preload("Flags", func(tx *gorm.DB) *gorm.DB { return tx.Order("flags.name ASC") }).
First(&nonstock, id).Error; err != nil {
t.Fatalf("failed to fetch nonstock: %v", err)
}
return nonstock
}
func fetchAreaName(t *testing.T, db *gorm.DB, id uint) string {
t.Helper()
var area entities.Area
if err := db.First(&area, id).Error; err != nil {
t.Fatalf("failed to fetch area: %v", err)
}
return area.Name
}
func fetchWarehouse(t *testing.T, db *gorm.DB, id uint) entities.Warehouse {
t.Helper()
var wh entities.Warehouse
if err := db.First(&wh, id).Error; err != nil {
t.Fatalf("failed to fetch warehouse: %v", err)
}
return wh
}
func createBank(t *testing.T, app *fiber.App, name, alias, accountNumber string, owner any) uint {
t.Helper()
payload := map[string]any{
"name": name,
"alias": alias,
"account_number": accountNumber,
"owner": owner,
}
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/banks", payload)
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when creating bank, got %d: %s", resp.StatusCode, string(body))
}
return parseID(t, body)
}
func fetchBank(t *testing.T, db *gorm.DB, id uint) entities.Bank {
t.Helper()
var bank entities.Bank
if err := db.Preload("CreatedUser").First(&bank, id).Error; err != nil {
t.Fatalf("failed to fetch bank: %v", err)
}
return bank
}
@@ -1,309 +0,0 @@
package test
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"testing"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
)
func TestNonstockIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
uomID := createUom(t, app, "Unit Piece")
altUomID := createUom(t, app, "Unit Box")
supplierID1 := createSupplier(t, app, "Nonstock Supplier One", "ns1", string(utils.SupplierCategoryBOP))
supplierID2 := createSupplier(t, app, "Nonstock Supplier Two", "ns2", string(utils.SupplierCategoryBOP))
supplierID3 := createSupplier(t, app, "Nonstock Supplier Three", "ns3", string(utils.SupplierCategoryBOP))
sapronakSupplierID := createSupplier(t, app, "SAPRONAK Supplier", "sap1", string(utils.SupplierCategorySapronak))
nonstockFlags := []string{string(utils.FlagEkspedisi)}
t.Run("create nonstock without suppliers succeeds with empty relations", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/nonstocks", map[string]any{
"name": "Supplierless Nonstock",
"uom_id": uomID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when suppliers omitted, got %d: %s", resp.StatusCode, string(body))
}
id := parseID(t, body)
ns := fetchNonstock(t, db, id)
if len(ns.Suppliers) != 0 {
t.Fatalf("expected no suppliers persisted, found %d", len(ns.Suppliers))
}
if len(ns.Flags) != 0 {
t.Fatalf("expected no flags persisted, found %d", len(ns.Flags))
}
resp, _ = doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/nonstocks/%d", id), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected cleanup delete to succeed, got %d", resp.StatusCode)
}
})
t.Run("create nonstock with unknown supplier fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/nonstocks", map[string]any{
"name": "Unknown Supplier Nonstock",
"uom_id": uomID,
"supplier_ids": []uint{99999},
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when supplier missing, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("create nonstock with sapronak supplier fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/nonstocks", map[string]any{
"name": "Invalid Category Nonstock",
"uom_id": uomID,
"supplier_ids": []uint{sapronakSupplierID},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when supplier category is not BOP, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("create nonstock with invalid flags fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/nonstocks", map[string]any{
"name": "Invalid Flag Nonstock",
"uom_id": uomID,
"supplier_ids": []uint{supplierID1},
"flags": []string{"UNKNOWN"},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when flags invalid, got %d: %s", resp.StatusCode, string(body))
}
})
var nonstockID uint
t.Run("create nonstock succeeds", func(t *testing.T) {
nonstockID = createNonstock(t, app, "Layer Feed", uomID, []uint{supplierID1, supplierID2, supplierID1}, nonstockFlags)
if nonstockID == 0 {
t.Fatal("expected nonstock id to be non zero")
}
ns := fetchNonstock(t, db, nonstockID)
if ns.Name != "Layer Feed" {
t.Fatalf("expected name Layer Feed, got %q", ns.Name)
}
if ns.UomId != uomID {
t.Fatalf("expected uom_id %d, got %d", uomID, ns.UomId)
}
if ns.CreatedBy != 1 {
t.Fatalf("expected created_by 1, got %d", ns.CreatedBy)
}
if len(ns.Suppliers) != 2 {
t.Fatalf("expected 2 unique suppliers, got %d", len(ns.Suppliers))
}
if len(ns.Flags) != len(nonstockFlags) {
t.Fatalf("expected %d flags, got %d", len(nonstockFlags), len(ns.Flags))
}
expectedFlags := make(map[string]struct{}, len(nonstockFlags))
for _, flag := range nonstockFlags {
expectedFlags[strings.ToUpper(flag)] = struct{}{}
}
for _, flag := range ns.Flags {
upper := strings.ToUpper(flag.Name)
if _, ok := expectedFlags[upper]; !ok {
t.Fatalf("unexpected flag stored: %s", upper)
}
delete(expectedFlags, upper)
}
if len(expectedFlags) != 0 {
t.Fatalf("missing flags after create: %v", expectedFlags)
}
})
t.Run("get nonstock detail includes suppliers", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when fetching nonstock, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Id uint `json:"id"`
Name string `json:"name"`
UomID uint `json:"uom_id"`
Suppliers []map[string]any `json:"suppliers"`
Flags []string `json:"flags"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse nonstock detail: %v", err)
}
if payload.Data.Id != nonstockID {
t.Fatalf("expected id %d, got %d", nonstockID, payload.Data.Id)
}
if payload.Data.UomID != uomID {
t.Fatalf("expected response uom_id %d, got %d", uomID, payload.Data.UomID)
}
if len(payload.Data.Suppliers) != 2 {
t.Fatalf("expected 2 suppliers in response, got %d", len(payload.Data.Suppliers))
}
if len(payload.Data.Flags) != len(nonstockFlags) {
t.Fatalf("expected %d flags in response, got %d", len(nonstockFlags), len(payload.Data.Flags))
}
expected := make(map[string]struct{}, len(nonstockFlags))
for _, flag := range nonstockFlags {
expected[strings.ToUpper(flag)] = struct{}{}
}
for _, flag := range payload.Data.Flags {
flag = strings.ToUpper(flag)
if _, ok := expected[flag]; !ok {
t.Fatalf("unexpected flag %s returned", flag)
}
delete(expected, flag)
}
if len(expected) != 0 {
t.Fatalf("missing flags in response: %v", expected)
}
})
t.Run("update nonstock with invalid uom fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"uom_id": 99999,
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when updating with invalid uom, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("update nonstock with invalid supplier fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"supplier_ids": []uint{supplierID1, 99999},
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when updating with invalid supplier, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("update nonstock with sapronak supplier fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"supplier_ids": []uint{sapronakSupplierID},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when updating with non-BOP supplier, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("update nonstock with invalid flags fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"flags": []string{"BAD"},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when updating flags invalid, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("update nonstock name uom and suppliers succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"name": "Layer Feed Premium",
"uom_id": altUomID,
"supplier_ids": []uint{supplierID3},
"flags": nonstockFlags,
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when updating nonstock, got %d: %s", resp.StatusCode, string(body))
}
ns := fetchNonstock(t, db, nonstockID)
if ns.Name != "Layer Feed Premium" {
t.Fatalf("expected name Layer Feed Premium, got %q", ns.Name)
}
if ns.UomId != altUomID {
t.Fatalf("expected uom_id %d, got %d", altUomID, ns.UomId)
}
if len(ns.Suppliers) != 1 || ns.Suppliers[0].Id != supplierID3 {
t.Fatalf("expected suppliers to contain only %d", supplierID3)
}
if len(ns.Flags) != len(nonstockFlags) {
t.Fatalf("expected flags retained, got %d", len(ns.Flags))
}
})
t.Run("clear suppliers succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"supplier_ids": []uint{},
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when clearing suppliers, got %d: %s", resp.StatusCode, string(body))
}
ns := fetchNonstock(t, db, nonstockID)
if len(ns.Suppliers) != 0 {
t.Fatalf("expected suppliers to be cleared, got %d entries", len(ns.Suppliers))
}
if len(ns.Flags) != len(nonstockFlags) {
t.Fatalf("expected flags unaffected, got %d", len(ns.Flags))
}
})
t.Run("clear nonstock flags succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), map[string]any{
"flags": []string{},
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when clearing nonstock flags, got %d: %s", resp.StatusCode, string(body))
}
ns := fetchNonstock(t, db, nonstockID)
if len(ns.Flags) != 0 {
t.Fatalf("expected flags cleared, got %d", len(ns.Flags))
}
})
t.Run("delete nonstock succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when deleting nonstock, got %d: %s", resp.StatusCode, string(body))
}
var ns entities.Nonstock
if err := db.First(&ns, nonstockID).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("expected nonstock to be deleted, got error %v", err)
}
var links int64
if err := db.Model(&entities.NonstockSupplier{}).Where("nonstock_id = ?", nonstockID).Count(&links).Error; err != nil {
t.Fatalf("failed counting nonstock suppliers: %v", err)
}
if links != 0 {
t.Fatalf("expected link table cleared, found %d rows", links)
}
var flagCount int64
if err := db.Model(&entities.Flag{}).
Where("flagable_id = ? AND flagable_type = ?", nonstockID, entities.FlagableTypeNonstock).
Count(&flagCount).Error; err != nil {
t.Fatalf("failed counting nonstock flags: %v", err)
}
if flagCount != 0 {
t.Fatalf("expected flags removed, found %d", flagCount)
}
})
t.Run("deleting nonstock twice returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when deleting missing nonstock, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("fetching deleted nonstock returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/nonstocks/%d", nonstockID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when fetching deleted nonstock, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("cleanup additional supplier references", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/suppliers/%d", supplierID3), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when deleting supplier, got %d: %s", resp.StatusCode, string(body))
}
doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/suppliers/%d", sapronakSupplierID), nil)
})
}
@@ -1,150 +0,0 @@
package test
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"gitlab.com/mbugroup/lti-api.git/internal/entities"
)
func TestProductCategoryIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
t.Run("create product category missing code fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/product-categories", map[string]any{
"name": "Layer Feed",
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when code missing, got %d: %s", resp.StatusCode, string(body))
}
})
var categoryID uint
t.Run("create product category succeeds", func(t *testing.T) {
categoryID = createProductCategory(t, app, "Layer Feed", "LFD")
pc := fetchProductCategory(t, db, categoryID)
if pc.Name != "Layer Feed" {
t.Fatalf("expected name Layer Feed, got %q", pc.Name)
}
if pc.Code != "LFD" {
t.Fatalf("expected code LFD, got %q", pc.Code)
}
if pc.CreatedBy != 1 {
t.Fatalf("expected created_by 1, got %d", pc.CreatedBy)
}
})
t.Run("creating duplicate name fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/product-categories", map[string]any{
"name": "Layer Feed",
"code": "LF2",
})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 when creating duplicate name, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("creating duplicate code fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/product-categories", map[string]any{
"name": "Layer Feed Premium",
"code": "LFD",
})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 when creating duplicate code, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("get product category detail", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/product-categories/%d", categoryID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when fetching product category, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Id uint `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse response: %v", err)
}
if payload.Data.Id != categoryID {
t.Fatalf("expected id %d, got %d", categoryID, payload.Data.Id)
}
if payload.Data.Code != "LFD" {
t.Fatalf("expected code LFD, got %q", payload.Data.Code)
}
})
t.Run("update product category with duplicate name fails", func(t *testing.T) {
otherID := createProductCategory(t, app, "Layer Feed Alt", "LFA")
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/product-categories/%d", otherID), map[string]any{
"name": "Layer Feed",
})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 when updating with duplicate name, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("update product category succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/product-categories/%d", categoryID), map[string]any{
"name": "Layer Feed Updated",
"code": "LFU",
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when updating product category, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Name string `json:"name"`
Code string `json:"code"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse update response: %v", err)
}
if payload.Data.Name != "Layer Feed Updated" {
t.Fatalf("expected updated name, got %q", payload.Data.Name)
}
if payload.Data.Code != "LFU" {
t.Fatalf("expected code LFU, got %q", payload.Data.Code)
}
pc := fetchProductCategory(t, db, categoryID)
if pc.Code != "LFU" {
t.Fatalf("expected persisted code LFU, got %q", pc.Code)
}
})
t.Run("delete product category", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/product-categories/%d", categoryID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when deleting product category, got %d: %s", resp.StatusCode, string(body))
}
var pc entities.ProductCategory
if err := db.First(&pc, categoryID).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("expected product category deleted, got error %v", err)
}
})
t.Run("delete non existing product category returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/product-categories/%d", categoryID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when deleting missing product category, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("get deleted product category returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/product-categories/%d", categoryID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when fetching deleted product category, got %d: %s", resp.StatusCode, string(body))
}
})
}
@@ -1,410 +0,0 @@
package test
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"testing"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
)
func TestProductIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
uomID := createUom(t, app, "Kilogram")
categoryID := createProductCategory(t, app, "Feed", "FED")
sapSupplier1 := createSupplier(t, app, "Feed Supplier One", "fs1", string(utils.SupplierCategorySapronak))
sapSupplier2 := createSupplier(t, app, "Feed Supplier Two", "fs2", string(utils.SupplierCategorySapronak))
bopSupplier := createSupplier(t, app, "BOP Supplier", "bop1", string(utils.SupplierCategoryBOP))
productFlags := []string{string(utils.FlagDOC), string(utils.FlagPakan)}
updatedProductFlags := []string{string(utils.FlagFinisher), string(utils.FlagObat)}
t.Run("create product without suppliers succeeds with empty relations", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/products", map[string]any{
"name": "Supplierless Product",
"brand": "Brand A",
"uom_id": uomID,
"product_category_id": categoryID,
"product_price": 12000,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201 when suppliers omitted, got %d: %s", resp.StatusCode, string(body))
}
id := parseID(t, body)
product := fetchProduct(t, db, id)
if len(product.Suppliers) != 0 {
t.Fatalf("expected no suppliers persisted, found %d", len(product.Suppliers))
}
if len(product.Flags) != 0 {
t.Fatalf("expected no flags persisted, found %d", len(product.Flags))
}
resp, _ = doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/products/%d", id), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected cleanup delete to succeed, got %d", resp.StatusCode)
}
})
t.Run("create product with invalid uom fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/products", map[string]any{
"name": "Layer Feed Invalid UOM",
"brand": "Brand A",
"uom_id": 9999,
"product_category_id": categoryID,
"product_price": 12000,
"supplier_ids": []uint{sapSupplier1},
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when uom missing, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("create product with invalid category fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/products", map[string]any{
"name": "Layer Feed Invalid Category",
"brand": "Brand A",
"uom_id": uomID,
"product_category_id": 9999,
"product_price": 12000,
"supplier_ids": []uint{sapSupplier1},
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when category missing, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("create product with invalid supplier fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/products", map[string]any{
"name": "Layer Feed Missing Supplier",
"brand": "Brand A",
"uom_id": uomID,
"product_category_id": categoryID,
"product_price": 12000,
"supplier_ids": []uint{99999},
})
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when supplier missing, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("create product with BOP supplier fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/products", map[string]any{
"name": "Layer Feed Wrong Supplier",
"brand": "Brand A",
"uom_id": uomID,
"product_category_id": categoryID,
"product_price": 12000,
"supplier_ids": []uint{bopSupplier},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when supplier category invalid, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("create product with invalid flags fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/products", map[string]any{
"name": "Layer Feed Invalid Flags",
"brand": "Brand A",
"uom_id": uomID,
"product_category_id": categoryID,
"product_price": 12000,
"supplier_ids": []uint{sapSupplier1},
"flags": []string{"INVALID"},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when flags invalid, got %d: %s", resp.StatusCode, string(body))
}
})
var productID uint
t.Run("create product succeeds", func(t *testing.T) {
sku := "lfd-001"
productID = createProduct(t, app, "Layer Feed", "Brand A", &sku, uomID, categoryID, 15000, []uint{sapSupplier1, sapSupplier2}, productFlags)
product := fetchProduct(t, db, productID)
if product.Name != "Layer Feed" {
t.Fatalf("expected name Layer Feed, got %q", product.Name)
}
if product.Brand != "Brand A" {
t.Fatalf("expected brand Brand A, got %q", product.Brand)
}
if product.Sku == nil || *product.Sku != strings.ToUpper(sku) {
t.Fatalf("expected sku %q, got %+v", strings.ToUpper(sku), product.Sku)
}
if product.UomId != uomID {
t.Fatalf("expected uom_id %d, got %d", uomID, product.UomId)
}
if product.ProductCategoryId != categoryID {
t.Fatalf("expected product_category_id %d, got %d", categoryID, product.ProductCategoryId)
}
if product.CreatedBy != 1 {
t.Fatalf("expected created_by 1, got %d", product.CreatedBy)
}
if len(product.Suppliers) != 2 {
t.Fatalf("expected 2 suppliers, got %d", len(product.Suppliers))
}
if len(product.Flags) != len(productFlags) {
t.Fatalf("expected %d flags, got %d", len(productFlags), len(product.Flags))
}
expectedFlags := make(map[string]struct{}, len(productFlags))
for _, flag := range productFlags {
expectedFlags[strings.ToUpper(flag)] = struct{}{}
}
for _, flag := range product.Flags {
if _, ok := expectedFlags[strings.ToUpper(flag.Name)]; !ok {
t.Fatalf("unexpected flag %s present", flag.Name)
}
delete(expectedFlags, strings.ToUpper(flag.Name))
}
if len(expectedFlags) != 0 {
t.Fatalf("missing expected flags: %v", expectedFlags)
}
})
t.Run("creating duplicate name fails", func(t *testing.T) {
sku := "lfd-002"
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/products", map[string]any{
"name": "Layer Feed",
"brand": "Brand B",
"sku": sku,
"uom_id": uomID,
"product_category_id": categoryID,
"product_price": 16000,
"supplier_ids": []uint{sapSupplier1},
})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 when creating duplicate name, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("creating duplicate sku fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/products", map[string]any{
"name": "Layer Feed Premium",
"brand": "Brand B",
"sku": "LFD-001",
"uom_id": uomID,
"product_category_id": categoryID,
"product_price": 17000,
"supplier_ids": []uint{sapSupplier1},
})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 when creating duplicate sku, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("get product detail returns nested data", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/products/%d", productID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when fetching product, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Id uint `json:"id"`
Name string `json:"name"`
Brand string `json:"brand"`
Uom *struct {
Id uint `json:"id"`
} `json:"uom"`
Suppliers []map[string]any `json:"suppliers"`
Flags []string `json:"flags"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse product detail: %v", err)
}
if payload.Data.Id != productID {
t.Fatalf("expected id %d, got %d", productID, payload.Data.Id)
}
if payload.Data.Uom == nil || payload.Data.Uom.Id != uomID {
t.Fatalf("expected uom id %d, got %+v", uomID, payload.Data.Uom)
}
if len(payload.Data.Suppliers) != 2 {
t.Fatalf("expected 2 suppliers in response, got %d", len(payload.Data.Suppliers))
}
if len(payload.Data.Flags) != len(productFlags) {
t.Fatalf("expected %d flags in response, got %d", len(productFlags), len(payload.Data.Flags))
}
expected := make(map[string]struct{}, len(productFlags))
for _, flag := range productFlags {
expected[strings.ToUpper(flag)] = struct{}{}
}
for _, flag := range payload.Data.Flags {
flag = strings.ToUpper(flag)
if _, ok := expected[flag]; !ok {
t.Fatalf("unexpected flag %s returned", flag)
}
delete(expected, flag)
}
if len(expected) != 0 {
t.Fatalf("missing expected flags in response: %v", expected)
}
})
t.Run("update product with invalid supplier category fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/products/%d", productID), map[string]any{
"supplier_ids": []uint{bopSupplier},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when updating with non SAPRONAK supplier, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("update product with invalid flags fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/products/%d", productID), map[string]any{
"flags": []string{"UNKNOWN"},
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when updating with invalid flags, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("update product succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/products/%d", productID), map[string]any{
"name": "Layer Feed Updated",
"brand": "Brand C",
"sku": "lfd-100",
"product_price": 18000,
"selling_price": 19000,
"tax": 1000,
"expiry_period": 30,
"supplier_ids": []uint{sapSupplier1},
"flags": updatedProductFlags,
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when updating product, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Name string `json:"name"`
Brand string `json:"brand"`
Sku *string `json:"sku"`
ProductPrice float64 `json:"product_price"`
SupplierIds []map[string]any `json:"suppliers"`
Flags []string `json:"flags"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse update response: %v", err)
}
if payload.Data.Name != "Layer Feed Updated" {
t.Fatalf("expected updated name, got %q", payload.Data.Name)
}
if len(payload.Data.Flags) != len(updatedProductFlags) {
t.Fatalf("expected %d flags in response, got %d", len(updatedProductFlags), len(payload.Data.Flags))
}
respFlags := make(map[string]struct{}, len(payload.Data.Flags))
for _, flag := range payload.Data.Flags {
respFlags[strings.ToUpper(flag)] = struct{}{}
}
for _, flag := range updatedProductFlags {
if _, ok := respFlags[strings.ToUpper(flag)]; !ok {
t.Fatalf("missing flag %s in response", flag)
}
}
product := fetchProduct(t, db, productID)
if product.Brand != "Brand C" {
t.Fatalf("expected persisted brand Brand C, got %q", product.Brand)
}
if product.Sku == nil || *product.Sku != "LFD-100" {
t.Fatalf("expected persisted sku LFD-100, got %+v", product.Sku)
}
if len(product.Suppliers) != 1 || product.Suppliers[0].Id != sapSupplier1 {
t.Fatalf("expected supplier to be %d", sapSupplier1)
}
if len(product.Flags) != len(updatedProductFlags) {
t.Fatalf("expected %d flags after update, got %d", len(updatedProductFlags), len(product.Flags))
}
expectedFlags := make(map[string]struct{}, len(updatedProductFlags))
for _, flag := range updatedProductFlags {
expectedFlags[strings.ToUpper(flag)] = struct{}{}
}
for _, flag := range product.Flags {
upper := strings.ToUpper(flag.Name)
if _, ok := expectedFlags[upper]; !ok {
t.Fatalf("unexpected flag after update: %s", upper)
}
delete(expectedFlags, upper)
}
if len(expectedFlags) != 0 {
t.Fatalf("missing flags after update: %v", expectedFlags)
}
})
t.Run("clear suppliers succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/products/%d", productID), map[string]any{
"supplier_ids": []uint{},
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when clearing suppliers, got %d: %s", resp.StatusCode, string(body))
}
product := fetchProduct(t, db, productID)
if len(product.Suppliers) != 0 {
t.Fatalf("expected no suppliers after clearing, got %d", len(product.Suppliers))
}
if len(product.Flags) != len(updatedProductFlags) {
t.Fatalf("expected flags untouched after clearing suppliers, got %d", len(product.Flags))
}
})
t.Run("clear flags succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/products/%d", productID), map[string]any{
"flags": []string{},
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when clearing flags, got %d: %s", resp.StatusCode, string(body))
}
product := fetchProduct(t, db, productID)
if len(product.Flags) != 0 {
t.Fatalf("expected all flags cleared, got %d", len(product.Flags))
}
})
t.Run("delete product succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/products/%d", productID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when deleting product, got %d: %s", resp.StatusCode, string(body))
}
var product entities.Product
if err := db.First(&product, productID).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("expected product deleted, got error %v", err)
}
var links int64
if err := db.Model(&entities.ProductSupplier{}).Where("product_id = ?", productID).Count(&links).Error; err != nil {
t.Fatalf("failed counting product suppliers: %v", err)
}
if links != 0 {
t.Fatalf("expected pivot cleared, found %d rows", links)
}
var flagCount int64
if err := db.Model(&entities.Flag{}).
Where("flagable_id = ? AND flagable_type = ?", productID, entities.FlagableTypeProduct).
Count(&flagCount).Error; err != nil {
t.Fatalf("failed counting product flags: %v", err)
}
if flagCount != 0 {
t.Fatalf("expected flags removed, found %d", flagCount)
}
})
t.Run("delete missing product returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/products/%d", productID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when deleting missing product, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("get deleted product returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/products/%d", productID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when fetching deleted product, got %d: %s", resp.StatusCode, string(body))
}
})
}
@@ -1,417 +0,0 @@
package test
// import (
// "encoding/json"
// "fmt"
// "net/http"
// "net/url"
// "testing"
// "github.com/gofiber/fiber/v2"
// "gitlab.com/mbugroup/lti-api.git/internal/entities"
// "gitlab.com/mbugroup/lti-api.git/internal/utils"
// )
// func TestProjectFlockSummary(t *testing.T) {
// app, db := setupIntegrationApp(t)
// areaID := createArea(t, app, "Area Project")
// locationID := createLocation(t, app, "Location Project", "Address", areaID)
// flockID := createFlock(t, app, "Flock Summary")
// fcrID := createFcr(t, app, "FCR Summary", []map[string]any{
// {"weight": 1.0, "fcr_number": 1.5, "mortality": 2.0},
// })
// kandangID := createKandang(t, app, "Kandang Summary", locationID, 1)
// createPayload := map[string]any{
// "flock_id": flockID,
// "area_id": areaID,
// "category": "growing",
// "fcr_id": fcrID,
// "location_id": locationID,
// "kandang_ids": []uint{kandangID},
// }
// resp, body := doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", createPayload)
// if resp.StatusCode != fiber.StatusCreated {
// t.Fatalf("expected 201 when creating project flock, got %d: %s", resp.StatusCode, string(body))
// }
// var createResp struct {
// Data struct {
// Id uint `json:"id"`
// Period int `json:"period"`
// Category string `json:"category"`
// Flock struct {
// Id uint `json:"id"`
// Name string `json:"name"`
// } `json:"flock"`
// Area struct {
// Id uint `json:"id"`
// Name string `json:"name"`
// } `json:"area"`
// Fcr struct {
// Id uint `json:"id"`
// Name string `json:"name"`
// } `json:"fcr"`
// Location struct {
// Id uint `json:"id"`
// Name string `json:"name"`
// Address string `json:"address"`
// } `json:"location"`
// Kandangs []struct {
// Id uint `json:"id"`
// Name string `json:"name"`
// Status string `json:"status"`
// } `json:"kandangs"`
// CreatedUser struct {
// Id uint `json:"id"`
// IdUser uint `json:"id_user"`
// Email string `json:"email"`
// Name string `json:"name"`
// } `json:"created_user"`
// } `json:"data"`
// }
// if err := json.Unmarshal(body, &createResp); err != nil {
// t.Fatalf("failed to parse create response: %v", err)
// }
// if createResp.Data.Flock.Id != flockID || createResp.Data.Flock.Name == "" {
// t.Fatalf("expected flock detail to be present, got %+v", createResp.Data.Flock)
// }
// if createResp.Data.Area.Id != areaID || createResp.Data.Area.Name == "" {
// t.Fatalf("expected area detail to be present, got %+v", createResp.Data.Area)
// }
// if createResp.Data.Category != string(utils.ProjectFlockCategoryGrowing) {
// t.Fatalf("expected category to be %s, got %s", utils.ProjectFlockCategoryGrowing, createResp.Data.Category)
// }
// if createResp.Data.Location.Id != locationID || createResp.Data.Location.Name == "" {
// t.Fatalf("expected location detail to be present, got %+v", createResp.Data.Location)
// }
// if len(createResp.Data.Kandangs) != 1 || createResp.Data.Kandangs[0].Id != kandangID {
// t.Fatalf("expected kandang detail to be present, got %+v", createResp.Data.Kandangs)
// }
// if createResp.Data.Kandangs[0].Status != string(utils.KandangStatusPengajuan) {
// t.Fatalf("expected kandang status to be PENGAJUAN, got %s", createResp.Data.Kandangs[0].Status)
// }
// if createResp.Data.Period != 1 {
// t.Fatalf("expected period 1 to be assigned automatically, got %d", createResp.Data.Period)
// }
// createdKandang := fetchKandang(t, db, kandangID)
// if createdKandang.Status != string(utils.KandangStatusPengajuan) {
// t.Fatalf("expected kandang status in DB to be PENGAJUAN, got %s", createdKandang.Status)
// }
// var pivotRecords []entities.ProjectFlockKandang
// if err := db.Where("project_flock_id = ?", createResp.Data.Id).Find(&pivotRecords).Error; err != nil {
// t.Fatalf("failed to fetch pivot records: %v", err)
// }
// if len(pivotRecords) != 1 {
// t.Fatalf("expected 1 pivot record, got %d", len(pivotRecords))
// }
// firstPivotRecord := pivotRecords[0]
// if firstPivotRecord.KandangId != kandangID {
// t.Fatalf("expected pivot kandang id %d, got %d", kandangID, firstPivotRecord.KandangId)
// }
// secondKandangID := createKandang(t, app, "Kandang Summary 2", locationID, 1)
// secondPayload := map[string]any{
// "flock_id": flockID,
// "area_id": areaID,
// "category": "laying",
// "fcr_id": fcrID,
// "location_id": locationID,
// "kandang_ids": []uint{secondKandangID},
// }
// resp, body = doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", secondPayload)
// if resp.StatusCode != fiber.StatusCreated {
// t.Fatalf("expected 201 when creating second project flock, got %d: %s", resp.StatusCode, string(body))
// }
// var createRespSecond struct {
// Data struct {
// Id uint `json:"id"`
// Period int `json:"period"`
// Category string `json:"category"`
// } `json:"data"`
// }
// if err := json.Unmarshal(body, &createRespSecond); err != nil {
// t.Fatalf("failed to parse second create response: %v", err)
// }
// if createRespSecond.Data.Period != 2 {
// t.Fatalf("expected second period to be 2, got %d", createRespSecond.Data.Period)
// }
// if createRespSecond.Data.Category != string(utils.ProjectFlockCategoryLaying) {
// t.Fatalf("expected category to be %s, got %s", utils.ProjectFlockCategoryLaying, createRespSecond.Data.Category)
// }
// pivotRecords = nil
// if err := db.Where("project_flock_id = ?", createRespSecond.Data.Id).Find(&pivotRecords).Error; err != nil {
// t.Fatalf("failed to fetch second pivot records: %v", err)
// }
// if len(pivotRecords) != 1 {
// t.Fatalf("expected 1 pivot record for second project, got %d", len(pivotRecords))
// }
// secondPivotRecord := pivotRecords[0]
// if secondPivotRecord.KandangId != secondKandangID {
// t.Fatalf("expected second pivot kandang id %d, got %d", secondKandangID, secondPivotRecord.KandangId)
// }
// secondKandang := fetchKandang(t, db, secondKandangID)
// if secondKandang.Status != string(utils.KandangStatusPengajuan) {
// t.Fatalf("expected second kandang status in DB to be PENGAJUAN, got %s", secondKandang.Status)
// }
// resp, body = doJSONRequest(t, app, http.MethodGet, "/api/production/project_flocks/flocks/"+uintToString(flockID)+"/periods", nil)
// if resp.StatusCode != fiber.StatusOK {
// t.Fatalf("expected 200 when fetching summary, got %d: %s", resp.StatusCode, string(body))
// }
// var summary struct {
// Data struct {
// NextPeriod int `json:"next_period"`
// } `json:"data"`
// }
// if err := json.Unmarshal(body, &summary); err != nil {
// t.Fatalf("failed to parse summary response: %v", err)
// }
// if summary.Data.NextPeriod != 3 {
// t.Fatalf("expected next_period 3, got %d", summary.Data.NextPeriod)
// }
// resp, body = doJSONRequest(t, app, http.MethodDelete, "/api/production/project_flocks/"+uintToString(createResp.Data.Id), nil)
// if resp.StatusCode != fiber.StatusOK {
// t.Fatalf("expected 200 when deleting first project flock, got %d: %s", resp.StatusCode, string(body))
// }
// firstKandang := fetchKandang(t, db, kandangID)
// if firstKandang.ProjectFlockId != nil {
// t.Fatalf("expected project_flock_id to be nil after delete, got %v", *firstKandang.ProjectFlockId)
// }
// if firstKandang.Status != string(utils.KandangStatusNonActive) {
// t.Fatalf("expected kandang status to revert to NON_ACTIVE, got %s", firstKandang.Status)
// }
// var remainingFirst int64
// if err := db.Model(&entities.ProjectFlockKandang{}).
// Where("project_flock_id = ? AND kandang_id = ?", createResp.Data.Id, kandangID).
// Count(&remainingFirst).Error; err != nil {
// t.Fatalf("failed to count first pivot records after delete: %v", err)
// }
// if remainingFirst != 0 {
// t.Fatalf("expected no pivot records remaining after delete, found %d", remainingFirst)
// }
// resp, body = doJSONRequest(t, app, http.MethodDelete, "/api/production/project_flocks/"+uintToString(createRespSecond.Data.Id), nil)
// if resp.StatusCode != fiber.StatusOK {
// t.Fatalf("expected 200 when deleting second project flock, got %d: %s", resp.StatusCode, string(body))
// }
// secondKandang = fetchKandang(t, db, secondKandangID)
// if secondKandang.ProjectFlockId != nil {
// t.Fatalf("expected second project_flock_id to be nil after delete, got %v", *secondKandang.ProjectFlockId)
// }
// if secondKandang.Status != string(utils.KandangStatusNonActive) {
// t.Fatalf("expected second kandang status to revert to NON_ACTIVE, got %s", secondKandang.Status)
// }
// var remainingSecond int64
// if err := db.Model(&entities.ProjectFlockKandang{}).
// Where("project_flock_id = ? AND kandang_id = ?", createRespSecond.Data.Id, secondKandangID).
// Count(&remainingSecond).Error; err != nil {
// t.Fatalf("failed to count second pivot records after delete: %v", err)
// }
// if remainingSecond != 0 {
// t.Fatalf("expected no second pivot records remaining after delete, found %d", remainingSecond)
// }
// resp, body = doJSONRequest(t, app, http.MethodGet, "/api/production/project_flocks/flocks/"+uintToString(flockID)+"/periods", nil)
// if resp.StatusCode != fiber.StatusOK {
// t.Fatalf("expected 200 when fetching summary after delete, got %d: %s", resp.StatusCode, string(body))
// }
// if err := json.Unmarshal(body, &summary); err != nil {
// t.Fatalf("failed to parse summary response after delete: %v", err)
// }
// if summary.Data.NextPeriod != 1 {
// t.Fatalf("expected next_period 1 after soft deletes, got %d", summary.Data.NextPeriod)
// }
// }
// func uintToString(v uint) string {
// return fmt.Sprintf("%d", v)
// }
// func TestProjectFlockSearchByRelatedFields(t *testing.T) {
// app, _ := setupIntegrationApp(t)
// areaID := createArea(t, app, "Area Search Target")
// locationID := createLocation(t, app, "Location Search Target", "Location Address Target", areaID)
// flockID := createFlock(t, app, "Flock Search Target")
// fcrID := createFcr(t, app, "FCR Search Target", []map[string]any{
// {"weight": 1.0, "fcr_number": 1.5, "mortality": 2.0},
// })
// kandangID := createKandang(t, app, "Kandang Search Target", locationID, 1)
// createPayload := map[string]any{
// "flock_id": flockID,
// "area_id": areaID,
// "category": "growing",
// "fcr_id": fcrID,
// "location_id": locationID,
// "kandang_ids": []uint{kandangID},
// }
// resp, body := doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", createPayload)
// if resp.StatusCode != fiber.StatusCreated {
// t.Fatalf("expected 201 when creating project flock, got %d: %s", resp.StatusCode, string(body))
// }
// var createResp struct {
// Data struct {
// Id uint `json:"id"`
// } `json:"data"`
// }
// if err := json.Unmarshal(body, &createResp); err != nil {
// t.Fatalf("failed to parse create response: %v", err)
// }
// searchTerms := []string{
// "Flock Search Target",
// "Area Search Target",
// string(utils.ProjectFlockCategoryGrowing),
// "growing",
// "FCR Search Target",
// "Kandang Search Target",
// "Location Search Target",
// "Location Address Target",
// "Tester",
// "1",
// }
// for _, term := range searchTerms {
// path := "/api/production/project_flocks?search=" + url.QueryEscape(term)
// resp, body := doJSONRequest(t, app, http.MethodGet, path, nil)
// if resp.StatusCode != fiber.StatusOK {
// t.Fatalf("expected 200 when searching for %q, got %d: %s", term, resp.StatusCode, string(body))
// }
// var listResp struct {
// Data []struct {
// Id uint `json:"id"`
// } `json:"data"`
// Meta struct {
// TotalResults int64 `json:"total_results"`
// } `json:"meta"`
// }
// if err := json.Unmarshal(body, &listResp); err != nil {
// t.Fatalf("failed to parse list response for %q: %v", term, err)
// }
// if listResp.Meta.TotalResults == 0 {
// t.Fatalf("expected at least one result when searching for %q", term)
// }
// if len(listResp.Data) == 0 {
// t.Fatalf("expected data when searching for %q", term)
// }
// if listResp.Data[0].Id != createResp.Data.Id {
// t.Fatalf("expected project flock id %d for search term %q, got %d", createResp.Data.Id, term, listResp.Data[0].Id)
// }
// }
// }
// func TestProjectFlockSorting(t *testing.T) {
// app, _ := setupIntegrationApp(t)
// areaA := createArea(t, app, "Area Alpha")
// areaB := createArea(t, app, "Area Beta")
// locationA := createLocation(t, app, "Location Alpha", "Address Alpha", areaA)
// locationB := createLocation(t, app, "Location Beta", "Address Beta", areaB)
// flockOne := createFlock(t, app, "Flock Sort One")
// flockTwo := createFlock(t, app, "Flock Sort Two")
// fcrID := createFcr(t, app, "FCR Sort", []map[string]any{
// {"weight": 1.0, "fcr_number": 1.5, "mortality": 2.0},
// })
// kandangOne := createKandang(t, app, "Kandang Sort One", locationA, 1)
// kandangTwo := createKandang(t, app, "Kandang Sort Two", locationB, 1)
// kandangThree := createKandang(t, app, "Kandang Sort Three", locationB, 1)
// projectOnePayload := map[string]any{
// "flock_id": flockOne,
// "area_id": areaA,
// "category": "growing",
// "fcr_id": fcrID,
// "location_id": locationA,
// "kandang_ids": []uint{kandangOne},
// }
// resp, body := doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", projectOnePayload)
// if resp.StatusCode != fiber.StatusCreated {
// t.Fatalf("expected 201 for project one, got %d: %s", resp.StatusCode, string(body))
// }
// projectOneID := parseProjectFlockID(t, body)
// projectTwoPayload := map[string]any{
// "flock_id": flockTwo,
// "area_id": areaB,
// "category": "laying",
// "fcr_id": fcrID,
// "location_id": locationB,
// "kandang_ids": []uint{kandangTwo, kandangThree},
// }
// resp, body = doJSONRequest(t, app, http.MethodPost, "/api/production/project_flocks", projectTwoPayload)
// if resp.StatusCode != fiber.StatusCreated {
// t.Fatalf("expected 201 for project two, got %d: %s", resp.StatusCode, string(body))
// }
// projectTwoID := parseProjectFlockID(t, body)
// updatePeriodPayload := map[string]any{"period": 5}
// resp, body = doJSONRequest(t, app, http.MethodPatch, "/api/production/project_flocks/"+uintToString(projectTwoID), updatePeriodPayload)
// if resp.StatusCode != fiber.StatusOK {
// t.Fatalf("expected 200 when updating period, got %d: %s", resp.StatusCode, string(body))
// }
// assertOrder := func(t *testing.T, app *fiber.App, query string, expectedFirst uint) {
// t.Helper()
// resp, body := doJSONRequest(t, app, http.MethodGet, "/api/production/project_flocks?"+query, nil)
// if resp.StatusCode != fiber.StatusOK {
// t.Fatalf("expected 200 for query %q, got %d: %s", query, resp.StatusCode, string(body))
// }
// var listResp struct {
// Data []struct {
// Id uint `json:"id"`
// } `json:"data"`
// }
// if err := json.Unmarshal(body, &listResp); err != nil {
// t.Fatalf("failed to parse list response for %q: %v", query, err)
// }
// if len(listResp.Data) == 0 {
// t.Fatalf("expected data for query %q", query)
// }
// if listResp.Data[0].Id != expectedFirst {
// t.Fatalf("expected first id %d for query %q, got %d", expectedFirst, query, listResp.Data[0].Id)
// }
// }
// assertOrder(t, app, "sort_by=area&sort_order=asc", projectOneID)
// assertOrder(t, app, "sort_by=location&sort_order=desc", projectTwoID)
// assertOrder(t, app, "sort_by=period&sort_order=desc", projectTwoID)
// assertOrder(t, app, "sort_by=kandangs&sort_order=desc", projectTwoID)
// assertOrder(t, app, "sort_by=kandangs&sort_order=asc", projectOneID)
// }
// func parseProjectFlockID(t *testing.T, body []byte) uint {
// t.Helper()
// var resp struct {
// Data struct {
// Id uint `json:"id"`
// } `json:"data"`
// }
// if err := json.Unmarshal(body, &resp); err != nil {
// t.Fatalf("failed to parse project flock response: %v", err)
// }
// return resp.Data.Id
// }
@@ -1,238 +0,0 @@
package test
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"testing"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
"gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
)
func TestSupplierIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
t.Run("creating supplier with invalid type fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/suppliers", map[string]any{
"name": "Invalid Supplier",
"alias": "inv01",
"pic": "Jane Doe",
"type": "random-type",
"category": utils.SupplierCategoryBOP,
"hatchery": "Hatchery X",
"phone": "081234567891",
"email": "invalid@supplier.com",
"address": "Somewhere",
"npwp": "NPWP-INVALID",
"account_number": "ACC-INVALID",
"due_date": 30,
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when type is invalid, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("creating supplier with invalid category fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/suppliers", map[string]any{
"name": "Invalid Category Supplier",
"alias": "cat01",
"pic": "Jane Doe",
"type": utils.CustomerSupplierTypeBisnis,
"category": "invalid",
"phone": "081234567892",
"email": "invalid-category@supplier.com",
"address": "Somewhere",
"due_date": 30,
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when category is invalid, got %d: %s", resp.StatusCode, string(body))
}
})
const supplierName = "Supplier Alpha"
const alias = "al001"
var supplierID uint
t.Run("creating supplier succeeds", func(t *testing.T) {
supplierID = createSupplier(t, app, supplierName, alias, string(utils.SupplierCategoryBOP))
supplier := fetchSupplier(t, db, supplierID)
if supplier.Name != supplierName {
t.Fatalf("expected name %q, got %q", supplierName, supplier.Name)
}
if supplier.Alias != strings.ToUpper(alias) {
t.Fatalf("expected alias %q, got %q", strings.ToUpper(alias), supplier.Alias)
}
if supplier.Type != string(utils.CustomerSupplierTypeBisnis) {
t.Fatalf("expected type %q, got %q", utils.CustomerSupplierTypeBisnis, supplier.Type)
}
if supplier.Category != string(utils.SupplierCategoryBOP) {
t.Fatalf("expected category %q, got %q", utils.SupplierCategoryBOP, supplier.Category)
}
if supplier.DueDate != 30 {
t.Fatalf("expected due date 30, got %d", supplier.DueDate)
}
if supplier.CreatedUser.Id != 1 {
t.Fatalf("expected created user id 1, got %d", supplier.CreatedUser.Id)
}
})
t.Run("creating supplier with duplicate name fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/suppliers", map[string]any{
"name": supplierName,
"alias": "dup01",
"pic": "Jane Doe",
"type": utils.CustomerSupplierTypeBisnis,
"category": utils.SupplierCategoryBOP,
"phone": "0811111111",
"email": "duplicate@supplier.com",
"address": "Duplicate address",
"due_date": 15,
})
if resp.StatusCode != fiber.StatusConflict {
t.Fatalf("expected 409 when creating duplicate supplier, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("getting existing supplier succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/suppliers/%d", supplierID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when fetching supplier, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Id uint `json:"id"`
Name string `json:"name"`
Alias string `json:"alias"`
Type string `json:"type"`
Category string `json:"category"`
CreatedUser *struct {
Id uint `json:"id"`
} `json:"created_user"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse supplier response: %v", err)
}
if payload.Data.Id != supplierID {
t.Fatalf("expected id %d, got %d", supplierID, payload.Data.Id)
}
if payload.Data.Name != supplierName {
t.Fatalf("expected name %q, got %q", supplierName, payload.Data.Name)
}
if payload.Data.Alias != strings.ToUpper(alias) {
t.Fatalf("expected alias %q, got %q", strings.ToUpper(alias), payload.Data.Alias)
}
if payload.Data.Type != string(utils.CustomerSupplierTypeBisnis) {
t.Fatalf("expected type %q, got %q", utils.CustomerSupplierTypeBisnis, payload.Data.Type)
}
if payload.Data.Category != string(utils.SupplierCategoryBOP) {
t.Fatalf("expected category %q, got %q", utils.SupplierCategoryBOP, payload.Data.Category)
}
if payload.Data.CreatedUser == nil || payload.Data.CreatedUser.Id != 1 {
t.Fatalf("expected created_user id 1, got %+v", payload.Data.CreatedUser)
}
})
const updatedAlias = "beta1"
t.Run("updating supplier fields succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/suppliers/%d", supplierID), map[string]any{
"alias": updatedAlias,
"type": utils.CustomerSupplierTypeIndividual,
"category": utils.SupplierCategorySapronak,
"due_date": 45,
})
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when updating supplier, got %d: %s", resp.StatusCode, string(body))
}
var payload struct {
Data struct {
Alias string `json:"alias"`
Type string `json:"type"`
Category string `json:"category"`
} `json:"data"`
}
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("failed to parse update response: %v", err)
}
if payload.Data.Alias != strings.ToUpper(updatedAlias) {
t.Fatalf("expected alias %q, got %q", strings.ToUpper(updatedAlias), payload.Data.Alias)
}
if payload.Data.Type != string(utils.CustomerSupplierTypeIndividual) {
t.Fatalf("expected type %q, got %q", utils.CustomerSupplierTypeIndividual, payload.Data.Type)
}
if payload.Data.Category != string(utils.SupplierCategorySapronak) {
t.Fatalf("expected category %q, got %q", utils.SupplierCategorySapronak, payload.Data.Category)
}
supplier := fetchSupplier(t, db, supplierID)
if supplier.Alias != strings.ToUpper(updatedAlias) {
t.Fatalf("expected persisted alias %q, got %q", strings.ToUpper(updatedAlias), supplier.Alias)
}
if supplier.Type != string(utils.CustomerSupplierTypeIndividual) {
t.Fatalf("expected persisted type %q, got %q", utils.CustomerSupplierTypeIndividual, supplier.Type)
}
if supplier.Category != string(utils.SupplierCategorySapronak) {
t.Fatalf("expected persisted category %q, got %q", utils.SupplierCategorySapronak, supplier.Category)
}
if supplier.DueDate != 45 {
t.Fatalf("expected due date 45, got %d", supplier.DueDate)
}
})
t.Run("updating supplier with invalid type fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/suppliers/%d", supplierID), map[string]any{
"type": "invalid-type",
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when updating with invalid type, got %d: %s", resp.StatusCode, string(body))
}
supplier := fetchSupplier(t, db, supplierID)
if supplier.Type != string(utils.CustomerSupplierTypeIndividual) {
t.Fatalf("expected type to remain %q after invalid update, got %q", utils.CustomerSupplierTypeIndividual, supplier.Type)
}
})
t.Run("updating supplier with invalid category fails", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPatch, fmt.Sprintf("/api/master-data/suppliers/%d", supplierID), map[string]any{
"category": "invalid",
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400 when updating with invalid category, got %d: %s", resp.StatusCode, string(body))
}
supplier := fetchSupplier(t, db, supplierID)
if supplier.Category != string(utils.SupplierCategorySapronak) {
t.Fatalf("expected category to remain %q after invalid update, got %q", utils.SupplierCategorySapronak, supplier.Category)
}
})
t.Run("deleting supplier succeeds", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/suppliers/%d", supplierID), nil)
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected 200 when deleting supplier, got %d: %s", resp.StatusCode, string(body))
}
var supplier entities.Supplier
if err := db.First(&supplier, supplierID).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("expected supplier to be deleted, got error %v", err)
}
})
t.Run("deleting non existent supplier returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodDelete, fmt.Sprintf("/api/master-data/suppliers/%d", supplierID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when deleting missing supplier, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("getting deleted supplier returns 404", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodGet, fmt.Sprintf("/api/master-data/suppliers/%d", supplierID), nil)
if resp.StatusCode != fiber.StatusNotFound {
t.Fatalf("expected 404 when fetching deleted supplier, got %d: %s", resp.StatusCode, string(body))
}
})
}
@@ -1,96 +0,0 @@
package test
import (
"net/http"
"testing"
"github.com/gofiber/fiber/v2"
)
func TestWarehouseIntegration(t *testing.T) {
app, db := setupIntegrationApp(t)
areaID := createArea(t, app, "Warehouse Area")
locationID := createLocation(t, app, "Location WH", "Addr", areaID)
kandangID := createKandang(t, app, "Kandang WH", locationID, 1)
t.Run("type AREA only needs area_id", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
"name": "WH Area",
"type": "AREA",
"area_id": areaID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("type AREA rejects location_id", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
"name": "WH Area Invalid",
"type": "AREA",
"area_id": areaID,
"location_id": locationID,
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("type LOKASI requires location_id", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
"name": "WH Lokasi Fail",
"type": "LOKASI",
"area_id": areaID,
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("type LOKASI success", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
"name": "WH Lokasi",
"type": "LOKASI",
"area_id": areaID,
"location_id": locationID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
}
whID := parseID(t, body)
wh := fetchWarehouse(t, db, whID)
if wh.LocationId == nil || *wh.LocationId != locationID {
t.Fatalf("expected location_id %d, got %v", locationID, wh.LocationId)
}
})
t.Run("type KANDANG requires all ids", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
"name": "WH Kandang Fail",
"type": "KANDANG",
"area_id": areaID,
"location_id": locationID,
})
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected 400, got %d: %s", resp.StatusCode, string(body))
}
})
t.Run("type KANDANG success", func(t *testing.T) {
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/warehouses", map[string]any{
"name": "WH Kandang",
"type": "KANDANG",
"area_id": areaID,
"location_id": locationID,
"kandang_id": kandangID,
})
if resp.StatusCode != fiber.StatusCreated {
t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(body))
}
whID := parseID(t, body)
wh := fetchWarehouse(t, db, whID)
if wh.KandangId == nil || *wh.KandangId != kandangID {
t.Fatalf("expected kandang_id %d, got %v", kandangID, wh.KandangId)
}
})
}
+6 -6
View File
@@ -9,7 +9,7 @@ import (
// === DTO Structs === // === DTO Structs ===
type {{Pascal .Entity}}BaseDTO struct { type {{Pascal .Entity}}RelationDTO struct {
Id uint `json:"id"` Id uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`
} }
@@ -17,7 +17,7 @@ type {{Pascal .Entity}}BaseDTO struct {
type {{Pascal .Entity}}ListDTO struct { type {{Pascal .Entity}}ListDTO 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"`
} }
@@ -28,17 +28,17 @@ type {{Pascal .Entity}}DetailDTO struct {
// === Mapper Functions === // === Mapper Functions ===
func To{{Pascal .Entity}}BaseDTO(e entity.{{Pascal .Entity}}) {{Pascal .Entity}}BaseDTO { func To{{Pascal .Entity}}RelationDTO(e entity.{{Pascal .Entity}}) {{Pascal .Entity}}RelationDTO {
return {{Pascal .Entity}}BaseDTO{ return {{Pascal .Entity}}RelationDTO{
Id: e.Id, Id: e.Id,
Name: e.Name, Name: e.Name,
} }
} }
func To{{Pascal .Entity}}ListDTO(e entity.{{Pascal .Entity}}) {{Pascal .Entity}}ListDTO { func To{{Pascal .Entity}}ListDTO(e entity.{{Pascal .Entity}}) {{Pascal .Entity}}ListDTO {
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
} }