mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'dev/ragil-before-sso' into 'feat/BE/US-74/pengajuan-flock'
[FEAT/BE][US#74/TASK#107,109,110,142,112] Complete Flock API, DB schema, validation See merge request mbugroup/lti-api!16
This commit is contained in:
@@ -57,7 +57,7 @@ wait-db:
|
|||||||
# Contoh: make migration-create_users_table
|
# Contoh: make migration-create_users_table
|
||||||
# ":" akan diubah ke "_" (biar aman untuk nama file)
|
# ":" akan diubah ke "_" (biar aman untuk nama file)
|
||||||
migration-%:
|
migration-%:
|
||||||
@migrate create -ext sql -dir internal/database/migrations $(subst :,_,$*)
|
@migrate create -ext sql -dir $(MIGRATIONS_DIR) $(subst :,_,$*)
|
||||||
|
|
||||||
# --- Migration (apply via docker image 'migrate') ---
|
# --- Migration (apply via docker image 'migrate') ---
|
||||||
migrate-up: db-up wait-db
|
migrate-up: db-up wait-db
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ go 1.23
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
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
|
||||||
@@ -28,7 +29,6 @@ require (
|
|||||||
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/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
github.com/glebarez/sqlite v1.11.0 // 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
|
||||||
@@ -47,7 +47,6 @@ require (
|
|||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
|
||||||
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
|
||||||
@@ -76,7 +75,6 @@ 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
|
||||||
gorm.io/driver/sqlite v1.5.5 // indirect
|
|
||||||
modernc.org/libc v1.22.5 // indirect
|
modernc.org/libc v1.22.5 // indirect
|
||||||
modernc.org/mathutil v1.5.0 // indirect
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
modernc.org/memory v1.5.0 // indirect
|
modernc.org/memory v1.5.0 // indirect
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ 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=
|
||||||
@@ -69,8 +71,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
@@ -88,8 +90,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
@@ -211,8 +211,6 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
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/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
|
||||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
|
||||||
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 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE kandangs
|
||||||
|
DROP COLUMN IF EXISTS status;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
ALTER TABLE kandangs
|
||||||
|
ADD COLUMN status VARCHAR(20);
|
||||||
|
|
||||||
|
UPDATE kandangs
|
||||||
|
SET status = 'NON_ACTIVE'
|
||||||
|
WHERE status IS NULL;
|
||||||
|
|
||||||
|
ALTER TABLE kandangs
|
||||||
|
ALTER COLUMN status SET NOT NULL;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
ALTER TABLE kandangs
|
||||||
|
DROP COLUMN IF EXISTS project_flock_id;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS project_flocks;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS flocks;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
CREATE TABLE flocks (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX flocks_name_unique ON flocks (name)
|
||||||
|
WHERE
|
||||||
|
deleted_at IS NULL;
|
||||||
|
|
||||||
|
CREATE TABLE project_flocks (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
flock_id BIGINT NOT NULL REFERENCES flocks (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
area_id BIGINT NOT NULL REFERENCES areas (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
product_category_id BIGINT NOT NULL REFERENCES product_categories (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
fcr_id BIGINT NOT NULL REFERENCES fcrs (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
location_id BIGINT NOT NULL REFERENCES locations (id) ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
period INT NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
deleted_at TIMESTAMPTZ,
|
||||||
|
created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE kandangs
|
||||||
|
ADD COLUMN project_flock_id BIGINT REFERENCES project_flocks (id) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP INDEX IF EXISTS project_flocks_flock_period_unique;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
CREATE UNIQUE INDEX project_flocks_flock_period_unique
|
||||||
|
ON project_flocks (flock_id, period)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
@@ -35,7 +35,27 @@ func Run(db *gorm.DB) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
kandangs, err := seedKandangs(tx, adminID, locations, users)
|
productCategories, err := seedProductCategories(tx, adminID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
flocks, err := seedFlocks(tx, adminID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fcrs, err := seedFcr(tx, adminID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
projectFlocks, err := seedProjectFlocks(tx, adminID, flocks, areas, productCategories, fcrs, locations)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
kandangs, err := seedKandangs(tx, adminID, locations, users, projectFlocks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -44,11 +64,6 @@ func Run(db *gorm.DB) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
productCategories, err := seedProductCategories(tx, adminID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
suppliers, err := seedSuppliers(tx, adminID)
|
suppliers, err := seedSuppliers(tx, adminID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -58,10 +73,6 @@ func Run(db *gorm.DB) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := seedFcr(tx, adminID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := seedProducts(tx, adminID, uoms, productCategories, suppliers); err != nil {
|
if err := seedProducts(tx, adminID, uoms, productCategories, suppliers); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -194,16 +205,138 @@ func seedLocations(tx *gorm.DB, createdBy uint, areas map[string]uint) (map[stri
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func seedKandangs(tx *gorm.DB, createdBy uint, locations map[string]uint, users map[string]uint) (map[string]uint, error) {
|
func seedFlocks(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
|
||||||
|
names := []string{"Flock Priangan", "Flock Banten"}
|
||||||
|
result := make(map[string]uint, len(names))
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
var flock entity.Flock
|
||||||
|
err := tx.Where("name = ?", name).First(&flock).Error
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
flock = entity.Flock{
|
||||||
|
Name: name,
|
||||||
|
CreatedBy: createdBy,
|
||||||
|
}
|
||||||
|
if err := tx.Create(&flock).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
if err := tx.Model(&entity.Flock{}).Where("id = ?", flock.Id).Updates(map[string]any{
|
||||||
|
"created_by": createdBy,
|
||||||
|
}).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result[name] = flock.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func seedProjectFlocks(tx *gorm.DB, createdBy uint, flocks, areas, productCategories, fcrs, locations map[string]uint) (map[string]uint, error) {
|
||||||
seeds := []struct {
|
seeds := []struct {
|
||||||
Name string
|
Key string
|
||||||
Location string
|
Flock string
|
||||||
PicKey string
|
Area string
|
||||||
|
ProductCategory string
|
||||||
|
Fcr string
|
||||||
|
Location string
|
||||||
|
Period int
|
||||||
}{
|
}{
|
||||||
{"Singaparna 1", "Singaparna", "admin"},
|
{
|
||||||
{"Singaparna 2", "Singaparna", "admin"},
|
Key: "Singaparna Period 1",
|
||||||
{"Cikaum 1", "Cikaum", "admin"},
|
Flock: "Flock Priangan",
|
||||||
{"Cikaum 2", "Cikaum", "admin"},
|
Area: "Priangan",
|
||||||
|
ProductCategory: "Day Old Chick",
|
||||||
|
Fcr: "FCR Layer",
|
||||||
|
Location: "Singaparna",
|
||||||
|
Period: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "Cikaum Period 1",
|
||||||
|
Flock: "Flock Banten",
|
||||||
|
Area: "Banten",
|
||||||
|
ProductCategory: "Day Old Chick",
|
||||||
|
Fcr: "FCR Layer",
|
||||||
|
Location: "Cikaum",
|
||||||
|
Period: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]uint, len(seeds))
|
||||||
|
|
||||||
|
for _, seed := range seeds {
|
||||||
|
flockID, ok := flocks[seed.Flock]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("floc %s not seeded", seed.Flock)
|
||||||
|
}
|
||||||
|
areaID, ok := areas[seed.Area]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("area %s not seeded", seed.Area)
|
||||||
|
}
|
||||||
|
categoryID, ok := productCategories[seed.ProductCategory]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("product category %s not seeded", seed.ProductCategory)
|
||||||
|
}
|
||||||
|
fcrID, ok := fcrs[seed.Fcr]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("fcr %s not seeded", seed.Fcr)
|
||||||
|
}
|
||||||
|
locationID, ok := locations[seed.Location]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("location %s not seeded", seed.Location)
|
||||||
|
}
|
||||||
|
|
||||||
|
var projectFlock entity.ProjectFlock
|
||||||
|
err := tx.Where("flock_id = ? AND area_id = ? AND product_category_id = ? AND fcr_id = ? AND location_id = ? AND period = ?",
|
||||||
|
flockID, areaID, categoryID, fcrID, locationID, seed.Period).First(&projectFlock).Error
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
projectFlock = entity.ProjectFlock{
|
||||||
|
FlockId: flockID,
|
||||||
|
AreaId: areaID,
|
||||||
|
ProductCategoryId: categoryID,
|
||||||
|
FcrId: fcrID,
|
||||||
|
LocationId: locationID,
|
||||||
|
Period: seed.Period,
|
||||||
|
CreatedBy: createdBy,
|
||||||
|
}
|
||||||
|
if err := tx.Create(&projectFlock).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
if err := tx.Model(&entity.ProjectFlock{}).Where("id = ?", projectFlock.Id).Updates(map[string]any{
|
||||||
|
"flock_id": flockID,
|
||||||
|
"area_id": areaID,
|
||||||
|
"product_category_id": categoryID,
|
||||||
|
"fcr_id": fcrID,
|
||||||
|
"location_id": locationID,
|
||||||
|
"period": seed.Period,
|
||||||
|
}).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result[seed.Key] = projectFlock.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func seedKandangs(tx *gorm.DB, createdBy uint, locations map[string]uint, users map[string]uint, projectFlocks map[string]uint) (map[string]uint, error) {
|
||||||
|
seeds := []struct {
|
||||||
|
Name string
|
||||||
|
Status utils.KandangStatus
|
||||||
|
Location string
|
||||||
|
PicKey string
|
||||||
|
ProjectFlockKey *string
|
||||||
|
}{
|
||||||
|
{Name: "Singaparna 1", Status: utils.KandangStatusActive, Location: "Singaparna", PicKey: "admin", ProjectFlockKey: strPtr("Singaparna Period 1")},
|
||||||
|
{Name: "Singaparna 2", Status: utils.KandangStatusNonActive, Location: "Singaparna", PicKey: "admin", ProjectFlockKey: strPtr("Singaparna Period 1")},
|
||||||
|
{Name: "Cikaum 1", Status: utils.KandangStatusActive, Location: "Cikaum", PicKey: "admin", ProjectFlockKey: strPtr("Cikaum Period 1")},
|
||||||
|
{Name: "Cikaum 2", Status: utils.KandangStatusPengajuan, Location: "Cikaum", PicKey: "admin"},
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make(map[string]uint, len(seeds))
|
result := make(map[string]uint, len(seeds))
|
||||||
@@ -218,20 +351,45 @@ func seedKandangs(tx *gorm.DB, createdBy uint, locations map[string]uint, users
|
|||||||
return nil, fmt.Errorf("user %s not seeded", seed.PicKey)
|
return nil, fmt.Errorf("user %s not seeded", seed.PicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var projectFlockID *uint
|
||||||
|
if seed.ProjectFlockKey != nil {
|
||||||
|
pfID, ok := projectFlocks[*seed.ProjectFlockKey]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("project flock %s not seeded", *seed.ProjectFlockKey)
|
||||||
|
}
|
||||||
|
projectFlockID = uintPtr(pfID)
|
||||||
|
}
|
||||||
|
|
||||||
var kandang entity.Kandang
|
var kandang entity.Kandang
|
||||||
err := tx.Where("name = ?", seed.Name).First(&kandang).Error
|
err := tx.Where("name = ?", seed.Name).First(&kandang).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
kandang = entity.Kandang{
|
kandang = entity.Kandang{
|
||||||
Name: seed.Name,
|
Name: seed.Name,
|
||||||
LocationId: locID,
|
Status: string(seed.Status),
|
||||||
PicId: picID,
|
LocationId: locID,
|
||||||
CreatedBy: createdBy,
|
PicId: picID,
|
||||||
|
ProjectFlockId: projectFlockID,
|
||||||
|
CreatedBy: createdBy,
|
||||||
}
|
}
|
||||||
if err := tx.Create(&kandang).Error; err != nil {
|
if err := tx.Create(&kandang).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else {
|
||||||
|
updates := map[string]any{
|
||||||
|
"location_id": locID,
|
||||||
|
"pic_id": picID,
|
||||||
|
"status": string(seed.Status),
|
||||||
|
}
|
||||||
|
if projectFlockID != nil {
|
||||||
|
updates["project_flock_id"] = *projectFlockID
|
||||||
|
} else {
|
||||||
|
updates["project_flock_id"] = nil
|
||||||
|
}
|
||||||
|
if err := tx.Model(&entity.Kandang{}).Where("id = ?", kandang.Id).Updates(updates).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result[seed.Name] = kandang.Id
|
result[seed.Name] = kandang.Id
|
||||||
}
|
}
|
||||||
@@ -430,7 +588,7 @@ func seedCustomers(tx *gorm.DB, createdBy uint, users map[string]uint) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func seedFcr(tx *gorm.DB, createdBy uint) error {
|
func seedFcr(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
|
||||||
seeds := []struct {
|
seeds := []struct {
|
||||||
Name string
|
Name string
|
||||||
Standards []struct {
|
Standards []struct {
|
||||||
@@ -452,17 +610,20 @@ func seedFcr(tx *gorm.DB, createdBy uint) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result := make(map[string]uint, len(seeds))
|
||||||
|
|
||||||
for _, seed := range seeds {
|
for _, seed := range seeds {
|
||||||
var fcr entity.Fcr
|
var fcr entity.Fcr
|
||||||
err := tx.Where("name = ?", seed.Name).First(&fcr).Error
|
err := tx.Where("name = ?", seed.Name).First(&fcr).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
fcr = entity.Fcr{Name: seed.Name, CreatedBy: createdBy}
|
fcr = entity.Fcr{Name: seed.Name, CreatedBy: createdBy}
|
||||||
if err := tx.Create(&fcr).Error; err != nil {
|
if err := tx.Create(&fcr).Error; err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
result[seed.Name] = fcr.Id
|
||||||
|
|
||||||
for _, std := range seed.Standards {
|
for _, std := range seed.Standards {
|
||||||
var standard entity.FcrStandard
|
var standard entity.FcrStandard
|
||||||
@@ -475,22 +636,22 @@ func seedFcr(tx *gorm.DB, createdBy uint) error {
|
|||||||
Mortality: std.Mortality,
|
Mortality: std.Mortality,
|
||||||
}
|
}
|
||||||
if err := tx.Create(&standard).Error; err != nil {
|
if err := tx.Create(&standard).Error; err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
if err := tx.Model(&entity.FcrStandard{}).Where("id = ?", standard.Id).Updates(map[string]any{
|
if err := tx.Model(&entity.FcrStandard{}).Where("id = ?", standard.Id).Updates(map[string]any{
|
||||||
"fcr_number": std.FcrNumber,
|
"fcr_number": std.FcrNumber,
|
||||||
"mortality": std.Mortality,
|
"mortality": std.Mortality,
|
||||||
}).Error; err != nil {
|
}).Error; err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories map[string]uint, suppliers map[string]uint) error {
|
func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories map[string]uint, suppliers map[string]uint) error {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Flock struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
Name string `gorm:"not null;uniqueIndex:flocks_name_unique,where:deleted_at IS NULL"`
|
||||||
|
CreatedBy uint `gorm:"not null"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
|
}
|
||||||
@@ -7,16 +7,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Kandang struct {
|
type Kandang struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
Name string `gorm:"not null;uniqueIndex:kandangs_name_unique,where:deleted_at IS NULL"`
|
Name string `gorm:"not null;uniqueIndex:kandangs_name_unique,where:deleted_at IS NULL"`
|
||||||
LocationId uint `gorm:"not null"`
|
Status string `gorm:"type:varchar(50);not null"`
|
||||||
PicId uint `gorm:"not null"`
|
LocationId uint `gorm:"not null"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
PicId uint `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
ProjectFlockId *uint `gorm:"column:project_flock_id"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
CreatedBy uint `gorm:"not null"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
Location Location `gorm:"foreignKey:LocationId;references:Id"`
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
Pic User `gorm:"foreignKey:PicId;references:Id"`
|
Location Location `gorm:"foreignKey:LocationId;references:Id"`
|
||||||
|
Pic User `gorm:"foreignKey:PicId;references:Id"`
|
||||||
|
ProjectFlock *ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProjectFlock struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
FlockId uint `gorm:"not null;uniqueIndex:idx_project_flocks_flock_period,priority:1"`
|
||||||
|
AreaId uint `gorm:"not null"`
|
||||||
|
ProductCategoryId uint `gorm:"not null"`
|
||||||
|
FcrId uint `gorm:"not null"`
|
||||||
|
LocationId uint `gorm:"not null"`
|
||||||
|
Period int `gorm:"not null;uniqueIndex:idx_project_flocks_flock_period,priority:2"`
|
||||||
|
CreatedBy uint `gorm:"not null"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
Flock Flock `gorm:"foreignKey:FlockId;references:Id"`
|
||||||
|
Area Area `gorm:"foreignKey:AreaId;references:Id"`
|
||||||
|
ProductCategory ProductCategory `gorm:"foreignKey:ProductCategoryId;references:Id"`
|
||||||
|
Fcr Fcr `gorm:"foreignKey:FcrId;references:Id"`
|
||||||
|
Location Location `gorm:"foreignKey:LocationId;references:Id"`
|
||||||
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
|
Kandangs []Kandang `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Recording struct {
|
||||||
|
Id uint `gorm:"primaryKey"`
|
||||||
|
Name string `gorm:"not null;uniqueIndex:idx_name,where:deleted_at IS NULL"`
|
||||||
|
CreatedBy uint `gorm:"not null"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
|
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
|
}
|
||||||
+82
-38
@@ -1,55 +1,99 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"strings"
|
// "strings"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
// "gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
// service "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
// "gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
// "github.com/gofiber/fiber/v2"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func Auth(userService service.UserService, requiredRights ...string) fiber.Handler {
|
// func Auth(userService service.UserService, requiredRights ...string) fiber.Handler {
|
||||||
return func(c *fiber.Ctx) error {
|
// return func(c *fiber.Ctx) error {
|
||||||
authHeader := c.Get("Authorization")
|
// authHeader := c.Get("Authorization")
|
||||||
token := strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer "))
|
// token := strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer "))
|
||||||
|
|
||||||
if token == "" {
|
// if token == "" {
|
||||||
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
}
|
// }
|
||||||
|
|
||||||
userID, err := utils.VerifyToken(token, config.JWTSecret, config.TokenTypeAccess)
|
// userID, err := utils.VerifyToken(token, config.JWTSecret, config.TokenTypeAccess)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
}
|
// }
|
||||||
|
|
||||||
user, err := userService.GetOne(c, userID)
|
// // Only end-user subjects are allowed by this middleware. Service tokens
|
||||||
if err != nil || user == nil {
|
// if verification.UserID == 0 {
|
||||||
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
}
|
// }
|
||||||
|
|
||||||
c.Locals("user", user)
|
// // Fail-closed on revocation check errors for stricter security posture.
|
||||||
|
// if revoker := session.GetRevocationStore(); revoker != nil {
|
||||||
|
// if fingerprint := session.TokenFingerprint(token); fingerprint != "" {
|
||||||
|
// revoked, err := revoker.IsRevoked(c.Context(), fingerprint)
|
||||||
|
// if err != nil {
|
||||||
|
// utils.Log.WithError(err).Warn("failed to check token revocation")
|
||||||
|
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
|
// }
|
||||||
|
// if revoked {
|
||||||
|
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// if len(requiredRights) > 0 {
|
// user, err := userService.GetBySSOUserID(c, verification.UserID)
|
||||||
// userRights, hasRights := config.RoleRights[user.Role]
|
// if err != nil || user == nil {
|
||||||
// if (!hasRights || !hasAllRights(userRights, requiredRights)) && c.Params("userId") != userID {
|
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
// return fiber.NewError(fiber.StatusForbidden, "You don't have permission to access this resource")
|
// }
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return c.Next()
|
// if len(requiredRights) > 0 && verification.Claims != nil {
|
||||||
}
|
// if !hasAllScopes(verification.Claims.Scopes(), requiredRights) {
|
||||||
}
|
// return fiber.NewError(fiber.StatusForbidden, "Insufficient scope")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// func hasAllRights(userRights, requiredRights []string) bool {
|
// c.Locals("user", user)
|
||||||
// rightSet := make(map[string]struct{}, len(userRights))
|
|
||||||
// for _, right := range userRights {
|
// // if len(requiredRights) > 0 {
|
||||||
// rightSet[right] = struct{}{}
|
// // userRights, hasRights := config.RoleRights[user.Role]
|
||||||
|
// // if (!hasRights || !hasAllRights(userRights, requiredRights)) && c.Params("userId") != userID {
|
||||||
|
// // return fiber.NewError(fiber.StatusForbidden, "You don't have permission to access this resource")
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// return c.Next()
|
||||||
// }
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// for _, right := range requiredRights {
|
// // bearerToken extracts a Bearer token from the Authorization header using
|
||||||
// if _, exists := rightSet[right]; !exists {
|
// // case-insensitive scheme matching and tolerant whitespace handling.
|
||||||
|
// func bearerToken(c *fiber.Ctx) string {
|
||||||
|
// parts := strings.Fields(c.Get("Authorization"))
|
||||||
|
// if len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") {
|
||||||
|
// return strings.TrimSpace(parts[1])
|
||||||
|
// }
|
||||||
|
// return ""
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func hasAllScopes(have, required []string) bool {
|
||||||
|
// if len(required) == 0 {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// set := make(map[string]struct{}, len(have))
|
||||||
|
// for _, s := range have {
|
||||||
|
// s = strings.ToLower(strings.TrimSpace(s))
|
||||||
|
// if s != "" {
|
||||||
|
// set[s] = struct{}{}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// for _, r := range required {
|
||||||
|
// r = strings.ToLower(strings.TrimSpace(r))
|
||||||
|
// if r == "" {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// if _, ok := set[r]; !ok {
|
||||||
// return false
|
// return false
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto"
|
||||||
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/services"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FlockController struct {
|
||||||
|
FlockService service.FlockService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFlockController(flockService service.FlockService) *FlockController {
|
||||||
|
return &FlockController{
|
||||||
|
FlockService: flockService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *FlockController) GetAll(c *fiber.Ctx) error {
|
||||||
|
query := &validation.Query{
|
||||||
|
Page: c.QueryInt("page", 1),
|
||||||
|
Limit: c.QueryInt("limit", 10),
|
||||||
|
Search: c.Query("search", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
result, totalResults, err := u.FlockService.GetAll(c, query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.SuccessWithPaginate[dto.FlockListDTO]{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get all flocks successfully",
|
||||||
|
Meta: response.Meta{
|
||||||
|
Page: query.Page,
|
||||||
|
Limit: query.Limit,
|
||||||
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||||
|
TotalResults: totalResults,
|
||||||
|
},
|
||||||
|
Data: dto.ToFlockListDTOs(result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *FlockController) GetOne(c *fiber.Ctx) error {
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.FlockService.GetOne(c, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get flock successfully",
|
||||||
|
Data: dto.ToFlockListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *FlockController) CreateOne(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Create)
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.FlockService.CreateOne(c, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusCreated,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Create flock successfully",
|
||||||
|
Data: dto.ToFlockListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *FlockController) UpdateOne(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Update)
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.FlockService.UpdateOne(c, req, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Update flock successfully",
|
||||||
|
Data: dto.ToFlockListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *FlockController) DeleteOne(c *fiber.Ctx) error {
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.FlockService.DeleteOne(c, uint(id)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Common{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Delete flock successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// === DTO Structs ===
|
||||||
|
|
||||||
|
type FlockBaseDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlockListDTO struct {
|
||||||
|
FlockBaseDTO
|
||||||
|
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlockDetailDTO struct {
|
||||||
|
FlockListDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Mapper Functions ===
|
||||||
|
|
||||||
|
func ToFlockBaseDTO(e entity.Flock) FlockBaseDTO {
|
||||||
|
return FlockBaseDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
Name: e.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToFlockListDTO(e entity.Flock) FlockListDTO {
|
||||||
|
var createdUser *userDTO.UserBaseDTO
|
||||||
|
if e.CreatedUser.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||||
|
createdUser = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
return FlockListDTO{
|
||||||
|
FlockBaseDTO: ToFlockBaseDTO(e),
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
CreatedUser: createdUser,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToFlockListDTOs(e []entity.Flock) []FlockListDTO {
|
||||||
|
result := make([]FlockListDTO, len(e))
|
||||||
|
for i, r := range e {
|
||||||
|
result[i] = ToFlockListDTO(r)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToFlockDetailDTO(e entity.Flock) FlockDetailDTO {
|
||||||
|
return FlockDetailDTO{
|
||||||
|
FlockListDTO: ToFlockListDTO(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package flocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
rFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
||||||
|
sFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/services"
|
||||||
|
|
||||||
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FlockModule struct{}
|
||||||
|
|
||||||
|
func (FlockModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
flockRepo := rFlock.NewFlockRepository(db)
|
||||||
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
|
||||||
|
flockService := sFlock.NewFlockService(flockRepo, validate)
|
||||||
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
FlockRoutes(router, userService, flockService)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FlockRepository interface {
|
||||||
|
repository.BaseRepository[entity.Flock]
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlockRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.Flock]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFlockRepository(db *gorm.DB) FlockRepository {
|
||||||
|
return &FlockRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.Flock](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package flocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/controllers"
|
||||||
|
flock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/services"
|
||||||
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FlockRoutes(v1 fiber.Router, u user.UserService, s flock.FlockService) {
|
||||||
|
ctrl := controller.NewFlockController(s)
|
||||||
|
|
||||||
|
route := v1.Group("/flocks")
|
||||||
|
|
||||||
|
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
||||||
|
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||||
|
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
||||||
|
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
||||||
|
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
||||||
|
|
||||||
|
route.Get("/", ctrl.GetAll)
|
||||||
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
route.Get("/:id", ctrl.GetOne)
|
||||||
|
route.Patch("/:id", ctrl.UpdateOne)
|
||||||
|
route.Delete("/:id", ctrl.DeleteOne)
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FlockService interface {
|
||||||
|
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Flock, int64, error)
|
||||||
|
GetOne(ctx *fiber.Ctx, id uint) (*entity.Flock, error)
|
||||||
|
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Flock, error)
|
||||||
|
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Flock, error)
|
||||||
|
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type flockService struct {
|
||||||
|
Log *logrus.Logger
|
||||||
|
Validate *validator.Validate
|
||||||
|
Repository repository.FlockRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFlockService(repo repository.FlockRepository, validate *validator.Validate) FlockService {
|
||||||
|
return &flockService{
|
||||||
|
Log: utils.Log,
|
||||||
|
Validate: validate,
|
||||||
|
Repository: repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s flockService) withRelations(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Preload("CreatedUser")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s flockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Flock, int64, error) {
|
||||||
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
|
flocks, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
|
db = s.withRelations(db)
|
||||||
|
if params.Search != "" {
|
||||||
|
return db.Where("name LIKE ?", "%"+params.Search+"%")
|
||||||
|
}
|
||||||
|
return db.Order("created_at DESC").Order("updated_at DESC")
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get flocks: %+v", err)
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return flocks, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s flockService) GetOne(c *fiber.Ctx, id uint) (*entity.Flock, error) {
|
||||||
|
flock, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Flock not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed get flock by id: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return flock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *flockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Flock, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
createBody := &entity.Flock{
|
||||||
|
Name: req.Name,
|
||||||
|
CreatedBy: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to create flock: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.GetOne(c, createBody.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s flockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Flock, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBody := make(map[string]any)
|
||||||
|
|
||||||
|
if req.Name != nil {
|
||||||
|
updateBody["name"] = *req.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(updateBody) == 0 {
|
||||||
|
return s.GetOne(c, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Flock not found")
|
||||||
|
}
|
||||||
|
s.Log.Errorf("Failed to update flock: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.GetOne(c, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s flockService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||||
|
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Flock not found")
|
||||||
|
}
|
||||||
|
s.Log.Errorf("Failed to delete flock: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
type Create struct {
|
||||||
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Update struct {
|
||||||
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
Page int `query:"page" validate:"omitempty,number,min=1"`
|
||||||
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
||||||
|
Search string `query:"search" validate:"omitempty,max=50"`
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,6 +14,8 @@ type KandangRepository interface {
|
|||||||
LocationExists(ctx context.Context, areaId uint) (bool, error)
|
LocationExists(ctx context.Context, areaId uint) (bool, error)
|
||||||
PicExists(ctx context.Context, areaId uint) (bool, error)
|
PicExists(ctx context.Context, areaId uint) (bool, error)
|
||||||
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
NameExists(ctx context.Context, name string, excludeID *uint) (bool, error)
|
||||||
|
ProjectFlockExists(ctx context.Context, projectFlockID uint) (bool, error)
|
||||||
|
HasActiveKandangForProjectFlock(ctx context.Context, projectFlockID uint, excludeID *uint) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type KandangRepositoryImpl struct {
|
type KandangRepositoryImpl struct {
|
||||||
@@ -38,3 +41,31 @@ func (r *KandangRepositoryImpl) PicExists(ctx context.Context, picId uint) (bool
|
|||||||
func (r *KandangRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
func (r *KandangRepositoryImpl) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) {
|
||||||
return repository.ExistsByName[entity.Kandang](ctx, r.db, name, excludeID)
|
return repository.ExistsByName[entity.Kandang](ctx, r.db, name, excludeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *KandangRepositoryImpl) ProjectFlockExists(ctx context.Context, projectFlockID uint) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
if err := r.db.WithContext(ctx).
|
||||||
|
Model(&entity.ProjectFlock{}).
|
||||||
|
Where("id = ?", projectFlockID).
|
||||||
|
Where("deleted_at IS NULL").
|
||||||
|
Count(&count).Error; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *KandangRepositoryImpl) HasActiveKandangForProjectFlock(ctx context.Context, projectFlockID uint, excludeID *uint) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
q := r.db.WithContext(ctx).
|
||||||
|
Model(&entity.Kandang{}).
|
||||||
|
Where("project_flock_id = ?", projectFlockID).
|
||||||
|
Where("status = ?", utils.KandangStatusActive).
|
||||||
|
Where("deleted_at IS NULL")
|
||||||
|
if excludeID != nil {
|
||||||
|
q = q.Where("id <> ?", *excludeID)
|
||||||
|
}
|
||||||
|
if err := q.Count(&count).Error; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
common "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
@@ -100,13 +101,41 @@ func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
status := strings.ToUpper(req.Status)
|
||||||
|
if !utils.IsValidKandangStatus(status) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang status")
|
||||||
|
}
|
||||||
|
|
||||||
|
var projectFlockID *uint
|
||||||
|
if req.ProjectFlockId != nil {
|
||||||
|
if exists, err := s.Repository.ProjectFlockExists(c.Context(), *req.ProjectFlockId); err != nil {
|
||||||
|
s.Log.Errorf("Failed to check project flock existence: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check project flock")
|
||||||
|
} else if !exists {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Project flock with id %d not found", *req.ProjectFlockId))
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == string(utils.KandangStatusActive) {
|
||||||
|
if active, err := s.Repository.HasActiveKandangForProjectFlock(c.Context(), *req.ProjectFlockId, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to check kandang activity for project flock %d: %+v", *req.ProjectFlockId, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check active kandang for project flock")
|
||||||
|
} else if active {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, "Project flock already has an active kandang")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idCopy := *req.ProjectFlockId
|
||||||
|
projectFlockID = &idCopy
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: created by dummy
|
//TODO: created by dummy
|
||||||
createBody := &entity.Kandang{
|
createBody := &entity.Kandang{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
LocationId: req.LocationId,
|
LocationId: req.LocationId,
|
||||||
PicId: req.PicId,
|
Status: status,
|
||||||
CreatedBy: 1,
|
PicId: req.PicId,
|
||||||
|
ProjectFlockId: projectFlockID,
|
||||||
|
CreatedBy: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
@@ -122,6 +151,15 @@ func (s kandangService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
existing, err := s.Repository.GetByID(c.Context(), id, nil)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Kandang not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to fetch kandang %d before update: %+v", id, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch kandang")
|
||||||
|
}
|
||||||
|
|
||||||
updateBody := make(map[string]any)
|
updateBody := make(map[string]any)
|
||||||
|
|
||||||
if req.Name != nil {
|
if req.Name != nil {
|
||||||
@@ -149,6 +187,38 @@ func (s kandangService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
updateBody["pic_id"] = *req.PicId
|
updateBody["pic_id"] = *req.PicId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalStatus := strings.ToUpper(existing.Status)
|
||||||
|
if req.Status != nil {
|
||||||
|
status := strings.ToUpper(*req.Status)
|
||||||
|
if !utils.IsValidKandangStatus(status) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid kandang status")
|
||||||
|
}
|
||||||
|
updateBody["status"] = status
|
||||||
|
finalStatus = status
|
||||||
|
}
|
||||||
|
|
||||||
|
projectFlockIDToUse := existing.ProjectFlockId
|
||||||
|
if req.ProjectFlockId != nil {
|
||||||
|
if exists, err := s.Repository.ProjectFlockExists(c.Context(), *req.ProjectFlockId); err != nil {
|
||||||
|
s.Log.Errorf("Failed to check project flock existence: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check project flock")
|
||||||
|
} else if !exists {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Project flock with id %d not found", *req.ProjectFlockId))
|
||||||
|
}
|
||||||
|
idCopy := *req.ProjectFlockId
|
||||||
|
projectFlockIDToUse = &idCopy
|
||||||
|
updateBody["project_flock_id"] = idCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
if projectFlockIDToUse != nil && finalStatus == string(utils.KandangStatusActive) {
|
||||||
|
if active, err := s.Repository.HasActiveKandangForProjectFlock(c.Context(), *projectFlockIDToUse, &id); err != nil {
|
||||||
|
s.Log.Errorf("Failed to check kandang activity for project flock %d: %+v", *projectFlockIDToUse, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to check active kandang for project flock")
|
||||||
|
} else if active {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, "Project flock already has an active kandang")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(updateBody) == 0 {
|
if len(updateBody) == 0 {
|
||||||
return s.GetOne(c, id)
|
return s.GetOne(c, id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
package validation
|
package validation
|
||||||
|
|
||||||
type Create struct {
|
type Create struct {
|
||||||
Name string `json:"name" validate:"required_strict,min=3"`
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
|
Status string `json:"status" validate:"required_strict,min=3"`
|
||||||
PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"`
|
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
|
||||||
|
PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"`
|
||||||
|
ProjectFlockId *uint `json:"project_flock_id" validate:"omitempty,number,gt=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Name *string `json:"name,omitempty" validate:"omitempty"`
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
|
Status *string `json:"status,omitempty" validate:"omitempty,min=3"`
|
||||||
PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"`
|
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
|
PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
|
ProjectFlockId *uint `json:"project_flock_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
suppliers "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers"
|
suppliers "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers"
|
||||||
uoms "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms"
|
uoms "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms"
|
||||||
warehouses "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses"
|
warehouses "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses"
|
||||||
|
flocks "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks"
|
||||||
// MODULE IMPORTS
|
// MODULE IMPORTS
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Valida
|
|||||||
productcategories.ProductCategoryModule{},
|
productcategories.ProductCategoryModule{},
|
||||||
products.ProductModule{},
|
products.ProductModule{},
|
||||||
banks.BankModule{},
|
banks.BankModule{},
|
||||||
|
flocks.FlockModule{},
|
||||||
// MODULE REGISTRY
|
// MODULE REGISTRY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package production
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProductionModule struct{}
|
||||||
|
|
||||||
|
func (ProductionModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
RegisterRoutes(router, db, validate)
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProjectflockController struct {
|
||||||
|
ProjectflockService service.ProjectflockService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProjectflockController(projectflockService service.ProjectflockService) *ProjectflockController {
|
||||||
|
return &ProjectflockController{
|
||||||
|
ProjectflockService: projectflockService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ProjectflockController) GetAll(c *fiber.Ctx) error {
|
||||||
|
query := &validation.Query{
|
||||||
|
Page: c.QueryInt("page", 1),
|
||||||
|
Limit: c.QueryInt("limit", 10),
|
||||||
|
Search: c.Query("search", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
result, totalResults, err := u.ProjectflockService.GetAll(c, query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.SuccessWithPaginate[dto.ProjectFlockListDTO]{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get all projectflocks successfully",
|
||||||
|
Meta: response.Meta{
|
||||||
|
Page: query.Page,
|
||||||
|
Limit: query.Limit,
|
||||||
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||||
|
TotalResults: totalResults,
|
||||||
|
},
|
||||||
|
Data: dto.ToProjectFlockListDTOs(result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ProjectflockController) GetOne(c *fiber.Ctx) error {
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.ProjectflockService.GetOne(c, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get projectflock successfully",
|
||||||
|
Data: dto.ToProjectFlockListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ProjectflockController) CreateOne(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Create)
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.ProjectflockService.CreateOne(c, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusCreated,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Create projectflock successfully",
|
||||||
|
Data: dto.ToProjectFlockListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ProjectflockController) UpdateOne(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Update)
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.ProjectflockService.UpdateOne(c, req, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Update projectflock successfully",
|
||||||
|
Data: dto.ToProjectFlockListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ProjectflockController) DeleteOne(c *fiber.Ctx) error {
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.ProjectflockService.DeleteOne(c, uint(id)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Common{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Delete projectflock successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ProjectflockController) GetFlockPeriodSummary(c *fiber.Ctx) error {
|
||||||
|
param := c.Params("flock_id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Flock Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
summary, err := u.ProjectflockService.GetFlockPeriodSummary(c, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
responseBody := dto.ToFlockPeriodSummaryDTO(summary.Flock, summary.NextPeriod)
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get flock period summary successfully",
|
||||||
|
Data: responseBody,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProjectFlockBaseDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
// FlockId uint `json:"flock_id"`
|
||||||
|
// AreaId uint `json:"area_id"`
|
||||||
|
// ProductCategoryId uint `json:"product_category_id"`
|
||||||
|
// FcrId uint `json:"fcr_id"`
|
||||||
|
// LocationId uint `json:"location_id"`
|
||||||
|
Period int `json:"period"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProjectFlockBaseDTO(e entity.ProjectFlock) ProjectFlockBaseDTO {
|
||||||
|
return ProjectFlockBaseDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
// FlockId: e.FlockId,
|
||||||
|
// AreaId: e.AreaId,
|
||||||
|
// ProductCategoryId: e.ProductCategoryId,
|
||||||
|
// FcrId: e.FcrId,
|
||||||
|
// LocationId: e.LocationId,
|
||||||
|
Period: e.Period,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlockSummaryDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AreaSummaryDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductCategorySummaryDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FcrSummaryDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocationSummaryDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KandangSummaryDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProjectFlockListDTO struct {
|
||||||
|
ProjectFlockBaseDTO
|
||||||
|
Flock *FlockSummaryDTO `json:"flock,omitempty"`
|
||||||
|
Area *AreaSummaryDTO `json:"area,omitempty"`
|
||||||
|
ProductCategory *ProductCategorySummaryDTO `json:"product_category,omitempty"`
|
||||||
|
Fcr *FcrSummaryDTO `json:"fcr,omitempty"`
|
||||||
|
Location *LocationSummaryDTO `json:"location,omitempty"`
|
||||||
|
Kandangs []KandangSummaryDTO `json:"kandangs,omitempty"`
|
||||||
|
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProjectFlockDetailDTO struct {
|
||||||
|
ProjectFlockListDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlockPeriodSummaryDTO struct {
|
||||||
|
Flock FlockSummaryDTO `json:"flock"`
|
||||||
|
NextPeriod int `json:"next_period"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProjectFlockListDTO(e entity.ProjectFlock) ProjectFlockListDTO {
|
||||||
|
var createdUser *userDTO.UserBaseDTO
|
||||||
|
if e.CreatedUser.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||||
|
createdUser = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var flockSummary *FlockSummaryDTO
|
||||||
|
if e.Flock.Id != 0 {
|
||||||
|
summary := ToFlockSummaryDTO(e.Flock)
|
||||||
|
flockSummary = &summary
|
||||||
|
}
|
||||||
|
|
||||||
|
var areaSummary *AreaSummaryDTO
|
||||||
|
if e.Area.Id != 0 {
|
||||||
|
areaSummary = &AreaSummaryDTO{
|
||||||
|
Id: e.Area.Id,
|
||||||
|
Name: e.Area.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var categorySummary *ProductCategorySummaryDTO
|
||||||
|
if e.ProductCategory.Id != 0 {
|
||||||
|
categorySummary = &ProductCategorySummaryDTO{
|
||||||
|
Id: e.ProductCategory.Id,
|
||||||
|
Name: e.ProductCategory.Name,
|
||||||
|
Code: e.ProductCategory.Code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fcrSummary *FcrSummaryDTO
|
||||||
|
if e.Fcr.Id != 0 {
|
||||||
|
fcrSummary = &FcrSummaryDTO{
|
||||||
|
Id: e.Fcr.Id,
|
||||||
|
Name: e.Fcr.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var locationSummary *LocationSummaryDTO
|
||||||
|
if e.Location.Id != 0 {
|
||||||
|
locationSummary = &LocationSummaryDTO{
|
||||||
|
Id: e.Location.Id,
|
||||||
|
Name: e.Location.Name,
|
||||||
|
Address: e.Location.Address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kandangSummaries := make([]KandangSummaryDTO, len(e.Kandangs))
|
||||||
|
for i, kandang := range e.Kandangs {
|
||||||
|
kandangSummaries[i] = KandangSummaryDTO{
|
||||||
|
Id: kandang.Id,
|
||||||
|
Name: kandang.Name,
|
||||||
|
Status: kandang.Status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProjectFlockListDTO{
|
||||||
|
ProjectFlockBaseDTO: ToProjectFlockBaseDTO(e),
|
||||||
|
Flock: flockSummary,
|
||||||
|
Area: areaSummary,
|
||||||
|
ProductCategory: categorySummary,
|
||||||
|
Fcr: fcrSummary,
|
||||||
|
Location: locationSummary,
|
||||||
|
Kandangs: kandangSummaries,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
CreatedUser: createdUser,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProjectFlockListDTOs(items []entity.ProjectFlock) []ProjectFlockListDTO {
|
||||||
|
result := make([]ProjectFlockListDTO, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
result[i] = ToProjectFlockListDTO(item)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToProjectFlockDetailDTO(e entity.ProjectFlock) ProjectFlockDetailDTO {
|
||||||
|
return ProjectFlockDetailDTO{
|
||||||
|
ProjectFlockListDTO: ToProjectFlockListDTO(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToFlockSummaryDTO(e entity.Flock) FlockSummaryDTO {
|
||||||
|
return FlockSummaryDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
Name: e.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToFlockPeriodSummaryDTO(flock entity.Flock, next int) FlockPeriodSummaryDTO {
|
||||||
|
return FlockPeriodSummaryDTO{
|
||||||
|
Flock: ToFlockSummaryDTO(flock),
|
||||||
|
NextPeriod: next,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package project_flocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
rFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
||||||
|
rKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||||
|
rProjectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
|
sProjectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
|
||||||
|
|
||||||
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProjectflockModule struct{}
|
||||||
|
|
||||||
|
func (ProjectflockModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
flockRepo := rFlock.NewFlockRepository(db)
|
||||||
|
kandangRepo := rKandang.NewKandangRepository(db)
|
||||||
|
projectflockRepo := rProjectflock.NewProjectflockRepository(db)
|
||||||
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
|
||||||
|
projectflockService := sProjectflock.NewProjectflockService(projectflockRepo, flockRepo, kandangRepo, validate)
|
||||||
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
ProjectflockRoutes(router, userService, projectflockService)
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProjectflockRepository interface {
|
||||||
|
repository.BaseRepository[entity.ProjectFlock]
|
||||||
|
GetAllByFlock(ctx context.Context, flockID uint) ([]entity.ProjectFlock, error)
|
||||||
|
GetActiveByFlock(ctx context.Context, flockID uint) (*entity.ProjectFlock, error)
|
||||||
|
GetMaxPeriodByFlock(ctx context.Context, flockID uint) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProjectflockRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.ProjectFlock]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProjectflockRepository(db *gorm.DB) ProjectflockRepository {
|
||||||
|
return &ProjectflockRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlock](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) GetAllByFlock(ctx context.Context, flockID uint) ([]entity.ProjectFlock, error) {
|
||||||
|
var records []entity.ProjectFlock
|
||||||
|
if err := r.DB().WithContext(ctx).
|
||||||
|
Unscoped().
|
||||||
|
Where("flock_id = ?", flockID).
|
||||||
|
Order("period ASC").
|
||||||
|
Find(&records).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) GetActiveByFlock(ctx context.Context, flockID uint) (*entity.ProjectFlock, error) {
|
||||||
|
var record entity.ProjectFlock
|
||||||
|
err := r.DB().WithContext(ctx).
|
||||||
|
Where("flock_id = ?", flockID).
|
||||||
|
Order("period DESC").
|
||||||
|
First(&record).Error
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) GetMaxPeriodByFlock(ctx context.Context, flockID uint) (int, error) {
|
||||||
|
var max int
|
||||||
|
if err := r.DB().WithContext(ctx).
|
||||||
|
Model(&entity.ProjectFlock{}).
|
||||||
|
Where("flock_id = ?", flockID).
|
||||||
|
Select("COALESCE(MAX(period), 0)").
|
||||||
|
Scan(&max).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return max, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package project_flocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/controllers"
|
||||||
|
projectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
|
||||||
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.ProjectflockService) {
|
||||||
|
ctrl := controller.NewProjectflockController(s)
|
||||||
|
|
||||||
|
route := v1.Group("/project_flocks")
|
||||||
|
|
||||||
|
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
||||||
|
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||||
|
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
||||||
|
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
||||||
|
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
||||||
|
|
||||||
|
route.Get("/", ctrl.GetAll)
|
||||||
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
route.Get("/:id", ctrl.GetOne)
|
||||||
|
route.Patch("/:id", ctrl.UpdateOne)
|
||||||
|
route.Delete("/:id", ctrl.DeleteOne)
|
||||||
|
route.Get("/flocks/:flock_id/periods", ctrl.GetFlockPeriodSummary)
|
||||||
|
}
|
||||||
@@ -0,0 +1,387 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories"
|
||||||
|
kandangRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||||
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProjectflockService interface {
|
||||||
|
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error)
|
||||||
|
GetOne(ctx *fiber.Ctx, id uint) (*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)
|
||||||
|
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||||
|
GetFlockPeriodSummary(ctx *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type projectflockService struct {
|
||||||
|
Log *logrus.Logger
|
||||||
|
Validate *validator.Validate
|
||||||
|
Repository repository.ProjectflockRepository
|
||||||
|
FlockRepo flockRepository.FlockRepository
|
||||||
|
KandangRepo kandangRepository.KandangRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlockPeriodSummary struct {
|
||||||
|
Flock entity.Flock
|
||||||
|
NextPeriod int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProjectflockService(
|
||||||
|
repo repository.ProjectflockRepository,
|
||||||
|
flockRepo flockRepository.FlockRepository,
|
||||||
|
kandangRepo kandangRepository.KandangRepository,
|
||||||
|
validate *validator.Validate,
|
||||||
|
) ProjectflockService {
|
||||||
|
return &projectflockService{
|
||||||
|
Log: utils.Log,
|
||||||
|
Validate: validate,
|
||||||
|
Repository: repo,
|
||||||
|
FlockRepo: flockRepo,
|
||||||
|
KandangRepo: kandangRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) withRelations(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.
|
||||||
|
Preload("CreatedUser").
|
||||||
|
Preload("Flock").
|
||||||
|
Preload("Area").
|
||||||
|
Preload("ProductCategory").
|
||||||
|
Preload("Fcr").
|
||||||
|
Preload("Location").
|
||||||
|
Preload("Kandangs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectFlock, int64, error) {
|
||||||
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
|
projectflocks, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
|
db = s.withRelations(db)
|
||||||
|
return db.Order("created_at DESC").Order("updated_at DESC")
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get projectflocks: %+v", err)
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return projectflocks, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlock, error) {
|
||||||
|
projectflock, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed get projectflock by id: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return projectflock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.ProjectFlock, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.KandangIds) == 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
kandangIDs := uniqueUintSlice(req.KandangIds)
|
||||||
|
kandangs, err := s.KandangRepo.GetByIDs(c.Context(), kandangIDs, nil)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
||||||
|
}
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch kandangs")
|
||||||
|
}
|
||||||
|
if len(kandangs) != len(kandangIDs) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
||||||
|
}
|
||||||
|
for _, kandang := range kandangs {
|
||||||
|
if kandang.ProjectFlockId != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Kandang %s sudah memiliki project flock", kandang.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := s.Repository.DB().Begin()
|
||||||
|
if tx.Error != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextPeriod int
|
||||||
|
periodQuery := tx.Model(&entity.ProjectFlock{}).
|
||||||
|
Where("flock_id = ?", req.FlockId).
|
||||||
|
Clauses(clause.Locking{Strength: "UPDATE"})
|
||||||
|
if err := periodQuery.Select("COALESCE(MAX(period), 0)").Scan(&nextPeriod).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
s.Log.Errorf("Failed to determine next period for flock %d: %+v", req.FlockId, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to determine next period")
|
||||||
|
}
|
||||||
|
nextPeriod++
|
||||||
|
|
||||||
|
projectRepo := s.Repository.WithTx(tx)
|
||||||
|
createBody := &entity.ProjectFlock{
|
||||||
|
FlockId: req.FlockId,
|
||||||
|
AreaId: req.AreaId,
|
||||||
|
ProductCategoryId: req.ProductCategoryId,
|
||||||
|
FcrId: req.FcrId,
|
||||||
|
LocationId: req.LocationId,
|
||||||
|
Period: nextPeriod,
|
||||||
|
CreatedBy: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := projectRepo.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, "Project flock period already exists")
|
||||||
|
}
|
||||||
|
s.Log.Errorf("Failed to create projectflock: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Model(&entity.Kandang{}).
|
||||||
|
Where("id IN ?", kandangIDs).
|
||||||
|
Updates(map[string]any{"project_flock_id": createBody.Id}).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
s.Log.Errorf("Failed to assign kandangs to projectflock: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to assign kandangs")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit().Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to commit transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.GetOne(c, createBody.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectFlock, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
existing, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to fetch projectflock %d before update: %+v", id, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBody := make(map[string]any)
|
||||||
|
|
||||||
|
if req.FlockId != nil {
|
||||||
|
updateBody["flock_id"] = *req.FlockId
|
||||||
|
}
|
||||||
|
if req.AreaId != nil {
|
||||||
|
updateBody["area_id"] = *req.AreaId
|
||||||
|
}
|
||||||
|
if req.ProductCategoryId != nil {
|
||||||
|
updateBody["product_category_id"] = *req.ProductCategoryId
|
||||||
|
}
|
||||||
|
if req.FcrId != nil {
|
||||||
|
updateBody["fcr_id"] = *req.FcrId
|
||||||
|
}
|
||||||
|
if req.LocationId != nil {
|
||||||
|
updateBody["location_id"] = *req.LocationId
|
||||||
|
}
|
||||||
|
if req.Period != nil {
|
||||||
|
updateBody["period"] = *req.Period
|
||||||
|
}
|
||||||
|
|
||||||
|
var newKandangIDs []uint
|
||||||
|
if req.KandangIds != nil {
|
||||||
|
if len(req.KandangIds) == 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids cannot be empty")
|
||||||
|
}
|
||||||
|
newKandangIDs = uniqueUintSlice(req.KandangIds)
|
||||||
|
kandangs, err := s.KandangRepo.GetByIDs(c.Context(), newKandangIDs, nil)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
||||||
|
}
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch kandangs")
|
||||||
|
}
|
||||||
|
if len(kandangs) != len(newKandangIDs) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Some kandangs not found")
|
||||||
|
}
|
||||||
|
for _, k := range kandangs {
|
||||||
|
if k.ProjectFlockId != nil && *k.ProjectFlockId != id {
|
||||||
|
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Kandang %s sudah terikat dengan project flock lain", k.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := s.Repository.DB().Begin()
|
||||||
|
if tx.Error != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
projectRepo := s.Repository.WithTx(tx)
|
||||||
|
if len(updateBody) > 0 {
|
||||||
|
if err := projectRepo.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||||
|
}
|
||||||
|
s.Log.Errorf("Failed to update projectflock: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.KandangIds != nil {
|
||||||
|
existingIDs := make(map[uint]struct{}, len(existing.Kandangs))
|
||||||
|
for _, k := range existing.Kandangs {
|
||||||
|
existingIDs[k.Id] = struct{}{}
|
||||||
|
}
|
||||||
|
newSet := make(map[uint]struct{}, len(newKandangIDs))
|
||||||
|
for _, id := range newKandangIDs {
|
||||||
|
newSet[id] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var toDetach []uint
|
||||||
|
for id := range existingIDs {
|
||||||
|
if _, ok := newSet[id]; !ok {
|
||||||
|
toDetach = append(toDetach, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var toAttach []uint
|
||||||
|
for id := range newSet {
|
||||||
|
if _, ok := existingIDs[id]; !ok {
|
||||||
|
toAttach = append(toAttach, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(toDetach) > 0 {
|
||||||
|
if err := tx.Model(&entity.Kandang{}).
|
||||||
|
Where("id IN ?", toDetach).
|
||||||
|
Updates(map[string]any{"project_flock_id": nil}).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
s.Log.Errorf("Failed to detach kandangs: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(toAttach) > 0 {
|
||||||
|
if err := tx.Model(&entity.Kandang{}).
|
||||||
|
Where("id IN ?", toAttach).
|
||||||
|
Updates(map[string]any{"project_flock_id": id}).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
s.Log.Errorf("Failed to attach kandangs: %+v", err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit().Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to commit transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.GetOne(c, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||||
|
existing, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to fetch projectflock %d before delete: %+v", id, err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock")
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := s.Repository.DB().Begin()
|
||||||
|
if tx.Error != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(existing.Kandangs) > 0 {
|
||||||
|
ids := make([]uint, len(existing.Kandangs))
|
||||||
|
for i, k := range existing.Kandangs {
|
||||||
|
ids[i] = k.Id
|
||||||
|
}
|
||||||
|
if err := tx.Model(&entity.Kandang{}).
|
||||||
|
Where("id IN ?", ids).
|
||||||
|
Updates(map[string]any{"project_flock_id": nil}).Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
s.Log.Errorf("Failed to detach kandangs before delete: %+v", err)
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update kandangs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Repository.WithTx(tx).DeleteOne(c.Context(), id); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Projectflock not found")
|
||||||
|
}
|
||||||
|
s.Log.Errorf("Failed to delete projectflock: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit().Error; err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to commit transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s projectflockService) GetFlockPeriodSummary(c *fiber.Ctx, flockID uint) (*FlockPeriodSummary, error) {
|
||||||
|
flock, err := s.FlockRepo.GetByID(c.Context(), flockID, func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Preload("CreatedUser")
|
||||||
|
})
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Flock not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed get flock %d for period summary: %+v", flockID, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch flock")
|
||||||
|
}
|
||||||
|
|
||||||
|
maxPeriod, err := s.Repository.GetMaxPeriodByFlock(c.Context(), flockID)
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to compute next period for flock %d: %+v", flockID, err)
|
||||||
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to compute next period")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FlockPeriodSummary{
|
||||||
|
Flock: *flock,
|
||||||
|
NextPeriod: maxPeriod + 1,
|
||||||
|
}, 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
type Create struct {
|
||||||
|
FlockId uint `json:"flock_id" validate:"required_strict,number,gt=0"`
|
||||||
|
AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"`
|
||||||
|
ProductCategoryId uint `json:"product_category_id" validate:"required_strict,number,gt=0"`
|
||||||
|
FcrId uint `json:"fcr_id" validate:"required_strict,number,gt=0"`
|
||||||
|
LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"`
|
||||||
|
KandangIds []uint `json:"kandang_ids" validate:"required,min=1,dive,gt=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Update struct {
|
||||||
|
FlockId *uint `json:"flock_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
|
AreaId *uint `json:"area_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
|
ProductCategoryId *uint `json:"product_category_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
|
FcrId *uint `json:"fcr_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
|
LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
|
Period *int `json:"period,omitempty" validate:"omitempty,number,gt=0"`
|
||||||
|
KandangIds []uint `json:"kandang_ids,omitempty" validate:"omitempty,min=1,dive,gt=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
Page int `query:"page" validate:"omitempty,number,min=1"`
|
||||||
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
||||||
|
Search string `query:"search" validate:"omitempty,max=50"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/dto"
|
||||||
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RecordingController struct {
|
||||||
|
RecordingService service.RecordingService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordingController(recordingService service.RecordingService) *RecordingController {
|
||||||
|
return &RecordingController{
|
||||||
|
RecordingService: recordingService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
||||||
|
query := &validation.Query{
|
||||||
|
Page: c.QueryInt("page", 1),
|
||||||
|
Limit: c.QueryInt("limit", 10),
|
||||||
|
Search: c.Query("search", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
result, totalResults, err := u.RecordingService.GetAll(c, query)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.SuccessWithPaginate[dto.RecordingListDTO]{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get all recordings successfully",
|
||||||
|
Meta: response.Meta{
|
||||||
|
Page: query.Page,
|
||||||
|
Limit: query.Limit,
|
||||||
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||||
|
TotalResults: totalResults,
|
||||||
|
},
|
||||||
|
Data: dto.ToRecordingListDTOs(result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) GetOne(c *fiber.Ctx) error {
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.RecordingService.GetOne(c, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Get recording successfully",
|
||||||
|
Data: dto.ToRecordingListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) CreateOne(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Create)
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.RecordingService.CreateOne(c, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusCreated,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Create recording successfully",
|
||||||
|
Data: dto.ToRecordingListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) UpdateOne(c *fiber.Ctx) error {
|
||||||
|
req := new(validation.Update)
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := u.RecordingService.UpdateOne(c, req, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Success{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Update recording successfully",
|
||||||
|
Data: dto.ToRecordingListDTO(*result),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *RecordingController) DeleteOne(c *fiber.Ctx) error {
|
||||||
|
param := c.Params("id")
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(param)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.RecordingService.DeleteOne(c, uint(id)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).
|
||||||
|
JSON(response.Common{
|
||||||
|
Code: fiber.StatusOK,
|
||||||
|
Status: "success",
|
||||||
|
Message: "Delete recording successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// === DTO Structs ===
|
||||||
|
|
||||||
|
type RecordingBaseDTO struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordingListDTO struct {
|
||||||
|
RecordingBaseDTO
|
||||||
|
CreatedUser *userDTO.UserBaseDTO `json:"created_user"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordingDetailDTO struct {
|
||||||
|
RecordingListDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Mapper Functions ===
|
||||||
|
|
||||||
|
func ToRecordingBaseDTO(e entity.Recording) RecordingBaseDTO {
|
||||||
|
return RecordingBaseDTO{
|
||||||
|
Id: e.Id,
|
||||||
|
Name: e.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToRecordingListDTO(e entity.Recording) RecordingListDTO {
|
||||||
|
var createdUser *userDTO.UserBaseDTO
|
||||||
|
if e.CreatedUser.Id != 0 {
|
||||||
|
mapped := userDTO.ToUserBaseDTO(e.CreatedUser)
|
||||||
|
createdUser = &mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
return RecordingListDTO{
|
||||||
|
RecordingBaseDTO: ToRecordingBaseDTO(e),
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
CreatedUser: createdUser,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToRecordingListDTOs(e []entity.Recording) []RecordingListDTO {
|
||||||
|
result := make([]RecordingListDTO, len(e))
|
||||||
|
for i, r := range e {
|
||||||
|
result[i] = ToRecordingListDTO(r)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToRecordingDetailDTO(e entity.Recording) RecordingDetailDTO {
|
||||||
|
return RecordingDetailDTO{
|
||||||
|
RecordingListDTO: ToRecordingListDTO(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package recordings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
rRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||||
|
sRecording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
|
||||||
|
|
||||||
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RecordingModule struct{}
|
||||||
|
|
||||||
|
func (RecordingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
recordingRepo := rRecording.NewRecordingRepository(db)
|
||||||
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
|
||||||
|
recordingService := sRecording.NewRecordingService(recordingRepo, validate)
|
||||||
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
RecordingRoutes(router, userService, recordingService)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RecordingRepository interface {
|
||||||
|
repository.BaseRepository[entity.Recording]
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordingRepositoryImpl struct {
|
||||||
|
*repository.BaseRepositoryImpl[entity.Recording]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordingRepository(db *gorm.DB) RecordingRepository {
|
||||||
|
return &RecordingRepositoryImpl{
|
||||||
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.Recording](db),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package recordings
|
||||||
|
|
||||||
|
import (
|
||||||
|
// m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
|
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/controllers"
|
||||||
|
recording "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
|
||||||
|
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RecordingRoutes(v1 fiber.Router, u user.UserService, s recording.RecordingService) {
|
||||||
|
ctrl := controller.NewRecordingController(s)
|
||||||
|
|
||||||
|
route := v1.Group("/recordings")
|
||||||
|
|
||||||
|
// route.Get("/", m.Auth(u), ctrl.GetAll)
|
||||||
|
// route.Post("/", m.Auth(u), ctrl.CreateOne)
|
||||||
|
// route.Get("/:id", m.Auth(u), ctrl.GetOne)
|
||||||
|
// route.Patch("/:id", m.Auth(u), ctrl.UpdateOne)
|
||||||
|
// route.Delete("/:id", m.Auth(u), ctrl.DeleteOne)
|
||||||
|
|
||||||
|
route.Get("/", ctrl.GetAll)
|
||||||
|
route.Post("/", ctrl.CreateOne)
|
||||||
|
route.Get("/:id", ctrl.GetOne)
|
||||||
|
route.Patch("/:id", ctrl.UpdateOne)
|
||||||
|
route.Delete("/:id", ctrl.DeleteOne)
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
||||||
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RecordingService interface {
|
||||||
|
GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Recording, int64, error)
|
||||||
|
GetOne(ctx *fiber.Ctx, id uint) (*entity.Recording, error)
|
||||||
|
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Recording, error)
|
||||||
|
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Recording, error)
|
||||||
|
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type recordingService struct {
|
||||||
|
Log *logrus.Logger
|
||||||
|
Validate *validator.Validate
|
||||||
|
Repository repository.RecordingRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRecordingService(repo repository.RecordingRepository, validate *validator.Validate) RecordingService {
|
||||||
|
return &recordingService{
|
||||||
|
Log: utils.Log,
|
||||||
|
Validate: validate,
|
||||||
|
Repository: repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s recordingService) withRelations(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Preload("CreatedUser")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s recordingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Recording, int64, error) {
|
||||||
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
|
recordings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
|
db = s.withRelations(db)
|
||||||
|
if params.Search != "" {
|
||||||
|
return db.Where("name LIKE ?", "%"+params.Search+"%")
|
||||||
|
}
|
||||||
|
return db.Order("created_at DESC").Order("updated_at DESC")
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed to get recordings: %+v", err)
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return recordings, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s recordingService) GetOne(c *fiber.Ctx, id uint) (*entity.Recording, error) {
|
||||||
|
recording, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Recording not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
s.Log.Errorf("Failed get recording by id: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return recording, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Recording, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
createBody := &entity.Recording{
|
||||||
|
Name: req.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to create recording: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.GetOne(c, createBody.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Recording, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBody := make(map[string]any)
|
||||||
|
|
||||||
|
if req.Name != nil {
|
||||||
|
updateBody["name"] = *req.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(updateBody) == 0 {
|
||||||
|
return s.GetOne(c, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Recording not found")
|
||||||
|
}
|
||||||
|
s.Log.Errorf("Failed to update recording: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.GetOne(c, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||||
|
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Recording not found")
|
||||||
|
}
|
||||||
|
s.Log.Errorf("Failed to delete recording: %+v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package validation
|
||||||
|
|
||||||
|
type Create struct {
|
||||||
|
Name string `json:"name" validate:"required_strict,min=3"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Update struct {
|
||||||
|
Name *string `json:"name,omitempty" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
Page int `query:"page" validate:"omitempty,number,min=1"`
|
||||||
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
||||||
|
Search string `query:"search" validate:"omitempty,max=50"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package production
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
projectflocks "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks"
|
||||||
|
recordings "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings"
|
||||||
|
// MODULE IMPORTS
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
|
group := router.Group("/production")
|
||||||
|
|
||||||
|
allModules := []modules.Module{
|
||||||
|
projectflocks.ProjectflockModule{},
|
||||||
|
recordings.RecordingModule{},
|
||||||
|
// MODULE REGISTRY
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range allModules {
|
||||||
|
m.RegisterRoutes(group, db, validate)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
inventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory"
|
inventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory"
|
||||||
master "gitlab.com/mbugroup/lti-api.git/internal/modules/master"
|
master "gitlab.com/mbugroup/lti-api.git/internal/modules/master"
|
||||||
users "gitlab.com/mbugroup/lti-api.git/internal/modules/users"
|
users "gitlab.com/mbugroup/lti-api.git/internal/modules/users"
|
||||||
|
production "gitlab.com/mbugroup/lti-api.git/internal/modules/production"
|
||||||
// MODULE IMPORTS
|
// MODULE IMPORTS
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ func Routes(app *fiber.App, db *gorm.DB) {
|
|||||||
master.MasterModule{},
|
master.MasterModule{},
|
||||||
constants.ConstantModule{},
|
constants.ConstantModule{},
|
||||||
inventory.InventoryModule{},
|
inventory.InventoryModule{},
|
||||||
|
production.ProductionModule{},
|
||||||
// MODULE REGISTRY
|
// MODULE REGISTRY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ var allFlagTypes = func() map[FlagType]struct{} {
|
|||||||
return m
|
return m
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
||||||
func AllFlagTypes() map[FlagType]struct{} {
|
func AllFlagTypes() map[FlagType]struct{} {
|
||||||
return allFlagTypes
|
return allFlagTypes
|
||||||
}
|
}
|
||||||
@@ -75,6 +76,8 @@ const (
|
|||||||
WarehouseTypeKandang WarehouseType = "KANDANG"
|
WarehouseTypeKandang WarehouseType = "KANDANG"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// WarehouseType
|
// WarehouseType
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@@ -97,6 +100,19 @@ const (
|
|||||||
SupplierCategorySapronak SupplierCategory = "SAPRONAK"
|
SupplierCategorySapronak SupplierCategory = "SAPRONAK"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// Kandang Status
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
type KandangStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
KandangStatusNonActive KandangStatus = "NON_ACTIVE"
|
||||||
|
KandangStatusPengajuan KandangStatus = "PENGAJUAN"
|
||||||
|
KandangStatusActive KandangStatus = "ACTIVE"
|
||||||
|
)
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// Validators
|
// Validators
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@@ -191,6 +207,14 @@ func IsValidWarehouseType(v string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsValidKandangStatus(v string) bool {
|
||||||
|
switch KandangStatus(v) {
|
||||||
|
case KandangStatusNonActive, KandangStatusPengajuan, KandangStatusActive:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func IsValidCustomerSupplierType(v string) bool {
|
func IsValidCustomerSupplierType(v string) bool {
|
||||||
switch CustomerSupplierType(v) {
|
switch CustomerSupplierType(v) {
|
||||||
case CustomerSupplierTypeBisnis, CustomerSupplierTypeIndividual:
|
case CustomerSupplierTypeBisnis, CustomerSupplierTypeIndividual:
|
||||||
|
|||||||
@@ -5,16 +5,19 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestKandangIntegration(t *testing.T) {
|
func TestKandangIntegration(t *testing.T) {
|
||||||
app, _ := setupIntegrationApp(t)
|
app, db := setupIntegrationApp(t)
|
||||||
areaID := createArea(t, app, "Area Kandang")
|
areaID := createArea(t, app, "Area Kandang")
|
||||||
locationID := createLocation(t, app, "Location For Kandang", "Address", areaID)
|
locationID := createLocation(t, app, "Location For Kandang", "Address", areaID)
|
||||||
|
|
||||||
t.Run("create kandang success", func(t *testing.T) {
|
t.Run("create kandang success", func(t *testing.T) {
|
||||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
|
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
|
||||||
"name": "Kandang OK",
|
"name": "Kandang OK",
|
||||||
|
"status": "ACTIVE",
|
||||||
"location_id": locationID,
|
"location_id": locationID,
|
||||||
"pic_id": 1,
|
"pic_id": 1,
|
||||||
})
|
})
|
||||||
@@ -26,6 +29,7 @@ func TestKandangIntegration(t *testing.T) {
|
|||||||
t.Run("create kandang with unknown location fails", func(t *testing.T) {
|
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{
|
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
|
||||||
"name": "Kandang Fail",
|
"name": "Kandang Fail",
|
||||||
|
"status": "ACTIVE",
|
||||||
"location_id": 999,
|
"location_id": 999,
|
||||||
"pic_id": 1,
|
"pic_id": 1,
|
||||||
})
|
})
|
||||||
@@ -33,4 +37,47 @@ func TestKandangIntegration(t *testing.T) {
|
|||||||
t.Fatalf("expected 404, got %d: %s", resp.StatusCode, string(body))
|
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) {
|
||||||
|
categoryID := createProductCategory(t, app, "DOC Category", "DOC1")
|
||||||
|
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{
|
||||||
|
FlockId: flocID,
|
||||||
|
AreaId: areaID,
|
||||||
|
ProductCategoryId: categoryID,
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ func setupIntegrationApp(t *testing.T) (*fiber.App, *gorm.DB) {
|
|||||||
&entities.User{},
|
&entities.User{},
|
||||||
&entities.Area{},
|
&entities.Area{},
|
||||||
&entities.Location{},
|
&entities.Location{},
|
||||||
|
&entities.Flock{},
|
||||||
|
&entities.ProjectFlock{},
|
||||||
&entities.Kandang{},
|
&entities.Kandang{},
|
||||||
&entities.Warehouse{},
|
&entities.Warehouse{},
|
||||||
&entities.Uom{},
|
&entities.Uom{},
|
||||||
@@ -152,6 +154,7 @@ func createKandang(t *testing.T, app *fiber.App, name string, locationID, picID
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
|
resp, body := doJSONRequest(t, app, http.MethodPost, "/api/master-data/kandangs", map[string]any{
|
||||||
"name": name,
|
"name": name,
|
||||||
|
"status": "ACTIVE",
|
||||||
"location_id": locationID,
|
"location_id": locationID,
|
||||||
"pic_id": picID,
|
"pic_id": picID,
|
||||||
})
|
})
|
||||||
@@ -291,6 +294,17 @@ func createFcr(t *testing.T, app *fiber.App, name string, standards []map[string
|
|||||||
return parseID(t, 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 {
|
func fetchFcr(t *testing.T, db *gorm.DB, id uint) entities.Fcr {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var fcr entities.Fcr
|
var fcr entities.Fcr
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProjectFlockSummary(t *testing.T) {
|
||||||
|
app, _ := setupIntegrationApp(t)
|
||||||
|
|
||||||
|
areaID := createArea(t, app, "Area Project")
|
||||||
|
locationID := createLocation(t, app, "Location Project", "Address", areaID)
|
||||||
|
flockID := createFlock(t, app, "Flock Summary")
|
||||||
|
categoryID := createProductCategory(t, app, "DOC Summary", "DOCS")
|
||||||
|
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,
|
||||||
|
"product_category_id": categoryID,
|
||||||
|
"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"`
|
||||||
|
Flock struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"flock"`
|
||||||
|
Area struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"area"`
|
||||||
|
ProductCategory struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
} `json:"product_category"`
|
||||||
|
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.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.Period != 1 {
|
||||||
|
t.Fatalf("expected period 1 to be assigned automatically, got %d", createResp.Data.Period)
|
||||||
|
}
|
||||||
|
|
||||||
|
secondKandangID := createKandang(t, app, "Kandang Summary 2", locationID, 1)
|
||||||
|
secondPayload := map[string]any{
|
||||||
|
"flock_id": flockID,
|
||||||
|
"area_id": areaID,
|
||||||
|
"product_category_id": categoryID,
|
||||||
|
"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"`
|
||||||
|
} `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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user