From 79700420d49945438747148411d5e4308bd857bf Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Fri, 17 Oct 2025 12:04:19 +0700 Subject: [PATCH 01/11] fix(BE): add missing product json in transfer get all & support flag param filter in product warehouses --- .../product_warehouse.controller.go | 3 +- .../services/product_warehouse.service.go | 12 +++- .../product_warehouse.validation.go | 9 +-- .../inventory/transfers/dto/transfer.dto.go | 64 ++++++++++++++----- .../transfers/services/transfer.service.go | 2 + 5 files changed, 66 insertions(+), 24 deletions(-) diff --git a/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go b/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go index a0b72a4d..f21eef96 100644 --- a/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go +++ b/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go @@ -28,6 +28,7 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error { Limit: c.QueryInt("limit", 10), ProductId: uint(c.QueryInt("product_id", 0)), WarehouseId: uint(c.QueryInt("warehouse_id", 0)), + Flag: c.Query("flag"), } result, totalResults, err := u.ProductWarehouseService.GetAll(c, query) @@ -71,5 +72,3 @@ func (u *ProductWarehouseController) GetOne(c *fiber.Ctx) error { Data: dto.ToProductWarehouseListDTO(*result), }) } - - diff --git a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go index 9afe5707..4871dfe1 100644 --- a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go +++ b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go @@ -34,7 +34,11 @@ func NewProductWarehouseService(repo repository.ProductWarehouseRepository, vali } func (s productWarehouseService) withRelations(db *gorm.DB) *gorm.DB { - return db.Preload("Product.Flags").Preload("Product").Preload("Warehouse").Preload("CreatedUser") + return db. + Preload("Product.Flags"). + Preload("Product"). + Preload("Warehouse"). + Preload("CreatedUser") } func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProductWarehouse, int64, error) { @@ -55,6 +59,12 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) db = db.Where("warehouse_id = ?", params.WarehouseId) } + if params.Flag != "" { + db = db.Joins("JOIN products ON products.id = product_warehouses.product_id") + db = db.Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = ?", "products") + db = db.Where("flags.name = ?", params.Flag) + } + return db.Order("created_at DESC").Order("updated_at DESC") }) diff --git a/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go b/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go index 02648300..30a5bed1 100644 --- a/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go +++ b/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go @@ -13,8 +13,9 @@ type Update struct { } type Query struct { - Page int `query:"page" validate:"omitempty,number,min=1"` - Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` - ProductId uint `query:"product_id" validate:"omitempty,number,min=1"` - WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"` + Page int `query:"page" validate:"omitempty,number,min=1"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` + ProductId uint `query:"product_id" validate:"omitempty,number,min=1"` + WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"` + Flag string `query:"flag" validate:"omitempty"` } diff --git a/internal/modules/inventory/transfers/dto/transfer.dto.go b/internal/modules/inventory/transfers/dto/transfer.dto.go index 217e5038..1b08ecbb 100644 --- a/internal/modules/inventory/transfers/dto/transfer.dto.go +++ b/internal/modules/inventory/transfers/dto/transfer.dto.go @@ -57,17 +57,17 @@ type TransferDetailDTO struct { // Detail produk type TransferDetailItemDTO struct { - Id uint64 `json:"id"` - ProductId uint64 `json:"product_id"` - Quantity float64 `json:"quantity"` - BeforeQuantity float64 `json:"before_quantity"` - AfterQuantity float64 `json:"after_quantity"` + Id uint64 `json:"id"` + Product *ProductDTO `json:"product,omitempty"` + Quantity float64 `json:"quantity"` + BeforeQuantity float64 `json:"before_quantity"` + AfterQuantity float64 `json:"after_quantity"` } // Delivery ekspedisi type TransferDeliveryDTO struct { Id uint64 `json:"id"` - SupplierId uint64 `json:"supplier_id"` + Supplier *SupplierDTO `json:"supplier,omitempty"` VehiclePlate string `json:"vehicle_plate"` DriverName string `json:"driver_name"` DocumentNumber string `json:"document_number"` @@ -83,6 +83,16 @@ type TransferDeliveryItemDTO struct { Quantity float64 `json:"quantity"` } +type ProductDTO struct { + Id uint64 `json:"id"` + Name string `json:"name"` +} + +type SupplierDTO struct { + Id uint64 `json:"id"` + Name string `json:"name"` +} + // === Mapper Functions === func ToTransferBaseDTO(e entity.StockTransfer) TransferBaseDTO { @@ -114,6 +124,26 @@ func toAreaDTO(a *entity.Area) *AreaDTO { } } +func toProductDTO(p *entity.Product) *ProductDTO { + if p == nil { + return nil + } + return &ProductDTO{ + Id: uint64(p.Id), + Name: p.Name, + } +} + +func toSupplierDTO(s *entity.Supplier) *SupplierDTO { + if s == nil { + return nil + } + return &SupplierDTO{ + Id: uint64(s.Id), + Name: s.Name, + } +} + func toLocationDTO(l *entity.Location) *LocationDTO { if l == nil { return nil @@ -142,19 +172,19 @@ func ToTransferListDTO(e entity.StockTransfer) TransferListDTO { mapped := userDTO.ToUserBaseDTO(*e.CreatedUser) createdUser = &mapped } - // Map details + var details []TransferDetailItemDTO for _, d := range e.Details { details = append(details, TransferDetailItemDTO{ - Id: d.Id, - ProductId: d.ProductId, - Quantity: d.Quantity, + Id: d.Id, + Product: toProductDTO(d.Product), + Quantity: d.Quantity, }) } - // Map deliveries + var deliveries []TransferDeliveryDTO for _, del := range e.Deliveries { - // Map delivery items + var items []TransferDeliveryItemDTO for _, item := range del.Items { items = append(items, TransferDeliveryItemDTO{ @@ -165,8 +195,8 @@ func ToTransferListDTO(e entity.StockTransfer) TransferListDTO { } deliveries = append(deliveries, TransferDeliveryDTO{ Id: del.Id, - SupplierId: del.SupplierId, VehiclePlate: del.VehiclePlate, + Supplier: toSupplierDTO(del.Supplier), DriverName: del.DriverName, DocumentNumber: del.DocumentNumber, DocumentPath: del.DocumentPath, @@ -198,9 +228,9 @@ func ToTransferDetailDTO(e entity.StockTransfer) TransferDetailDTO { var details []TransferDetailItemDTO for _, d := range e.Details { details = append(details, TransferDetailItemDTO{ - Id: d.Id, - ProductId: d.ProductId, - Quantity: d.Quantity, + Id: d.Id, + Product: toProductDTO(d.Product), + Quantity: d.Quantity, }) } // Map deliveries @@ -208,7 +238,7 @@ func ToTransferDetailDTO(e entity.StockTransfer) TransferDetailDTO { for _, del := range e.Deliveries { deliveries = append(deliveries, TransferDeliveryDTO{ Id: del.Id, - SupplierId: del.SupplierId, + Supplier: toSupplierDTO(del.Supplier), VehiclePlate: del.VehiclePlate, DriverName: del.DriverName, DocumentNumber: del.DocumentNumber, diff --git a/internal/modules/inventory/transfers/services/transfer.service.go b/internal/modules/inventory/transfers/services/transfer.service.go index 7f18d257..4579d4c0 100644 --- a/internal/modules/inventory/transfers/services/transfer.service.go +++ b/internal/modules/inventory/transfers/services/transfer.service.go @@ -60,6 +60,8 @@ func (s transferService) withRelations(db *gorm.DB) *gorm.DB { Preload("ToWarehouse.Location"). Preload("ToWarehouse.Area"). Preload("Details"). + Preload("Details.Product"). + Preload("Deliveries.Supplier"). Preload("Deliveries.Items") } From a45c20d2ff14ccd3d610e5ed41911965c112e22d Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Fri, 17 Oct 2025 20:43:31 +0700 Subject: [PATCH 02/11] fix(BE): improve product and warehouse existence check in adjustment service --- .../services/adjustment.service.go | 22 +++++++++++++++++-- .../services/product_warehouse.service.go | 5 +++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index 929a5c8a..af89f442 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -83,7 +83,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product") } if !isProductExist { - return nil, fiber.NewError(fiber.StatusBadRequest, "Product not found") + return nil, fiber.NewError(fiber.StatusNotFound, "Product not found") } isWarehouseExist, err := s.WarehouseRepo.IdExists(c.Context(), uint(req.WarehouseID)) @@ -92,7 +92,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate warehouse") } if !isWarehouseExist { - return nil, fiber.NewError(fiber.StatusBadRequest, "Warehouse not found") + return nil, fiber.NewError(fiber.StatusNotFound, "Warehouse not found") } if req.Quantity <= 0 { @@ -187,6 +187,24 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu offset := (query.Page - 1) * query.Limit + isWarehousesExist, err := s.WarehouseRepo.IdExists(c.Context(), uint(query.WarehouseID)) + if err != nil { + s.Log.Errorf("Failed to check warehouse existence: %+v", err) + return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate warehouse") + } + if query.WarehouseID > 0 && !isWarehousesExist { + return nil, 0, fiber.NewError(fiber.StatusNotFound, "Warehouse not found") + } + + isProductsExist, err := s.ProductRepo.IdExists(c.Context(), uint(query.ProductID)) + if err != nil { + s.Log.Errorf("Failed to check product existence: %+v", err) + return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product") + } + if query.ProductID > 0 && !isProductsExist { + return nil, 0, fiber.NewError(fiber.StatusNotFound, "Product not found") + } + stockLogs, total, err := s.StockLogsRepository.GetAll(c.Context(), offset, query.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) diff --git a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go index 4871dfe1..0d86e073 100644 --- a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go +++ b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go @@ -72,6 +72,11 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) s.Log.Errorf("Failed to get productWarehouses: %+v", err) return nil, 0, err } + + if len(productWarehouses) == 0 { + return nil, 0, fiber.NewError(fiber.StatusNotFound, "ProductWarehouses not found") + } + return productWarehouses, total, nil } From 68a670a2bd59a8919172c8a9b19e137fe3006f2a Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Sat, 18 Oct 2025 16:30:13 +0700 Subject: [PATCH 03/11] feat(BE-116): add project chick in database schema --- ...53_create_project_chick_ins_table.down.sql | 18 +++ ...5953_create_project_chick_ins_table.up.sql | 1 + internal/entities/project_chickin.go | 24 +++ .../dto/product_warehouse.dto.go | 83 ++++++++++- .../services/product_warehouse.service.go | 6 +- .../controllers/chickin.controller.go | 140 ++++++++++++++++++ .../production/chickins/dto/chickin.dto.go | 84 +++++++++++ .../modules/production/chickins/module.go | 26 ++++ .../repositories/chickin.repository.go | 21 +++ internal/modules/production/chickins/route.go | 28 ++++ .../chickins/services/chickin.service.go | 129 ++++++++++++++++ .../validations/chickin.validation.go | 15 ++ internal/modules/production/route.go | 2 + 13 files changed, 568 insertions(+), 9 deletions(-) create mode 100644 internal/database/migrations/20251017135953_create_project_chick_ins_table.down.sql create mode 100644 internal/database/migrations/20251017135953_create_project_chick_ins_table.up.sql create mode 100644 internal/entities/project_chickin.go create mode 100644 internal/modules/production/chickins/controllers/chickin.controller.go create mode 100644 internal/modules/production/chickins/dto/chickin.dto.go create mode 100644 internal/modules/production/chickins/module.go create mode 100644 internal/modules/production/chickins/repositories/chickin.repository.go create mode 100644 internal/modules/production/chickins/route.go create mode 100644 internal/modules/production/chickins/services/chickin.service.go create mode 100644 internal/modules/production/chickins/validations/chickin.validation.go diff --git a/internal/database/migrations/20251017135953_create_project_chick_ins_table.down.sql b/internal/database/migrations/20251017135953_create_project_chick_ins_table.down.sql new file mode 100644 index 00000000..ac9a0f59 --- /dev/null +++ b/internal/database/migrations/20251017135953_create_project_chick_ins_table.down.sql @@ -0,0 +1,18 @@ +CREATE TABLE project_chick_ins ( + id BIGSERIAL PRIMARY KEY, + project_floc_id BIGINT NOT NULL REFERENCES project_flocs (id), + product_warehouse_id BIGINT NOT NULL REFERENCES product_warehouses (id), + chick_in_date DATE NOT NULL, + quantity NUMERIC(15, 3) NOT NULL CHECK (quantity > 0), + note TEXT, + created_by BIGINT NOT NULL REFERENCES users (id), + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now(), + deleted_at TIMESTAMPTZ +); + +CREATE INDEX idx_project_chick_ins_project_floc_id ON project_chick_ins (project_floc_id); + +CREATE INDEX idx_project_chick_ins_product_warehouse_id ON project_chick_ins (product_warehouse_id); + +CREATE INDEX idx_project_chick_ins_created_by ON project_chick_ins (created_by); \ No newline at end of file diff --git a/internal/database/migrations/20251017135953_create_project_chick_ins_table.up.sql b/internal/database/migrations/20251017135953_create_project_chick_ins_table.up.sql new file mode 100644 index 00000000..b1435759 --- /dev/null +++ b/internal/database/migrations/20251017135953_create_project_chick_ins_table.up.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS project_chick_ins; \ No newline at end of file diff --git a/internal/entities/project_chickin.go b/internal/entities/project_chickin.go new file mode 100644 index 00000000..a4a00596 --- /dev/null +++ b/internal/entities/project_chickin.go @@ -0,0 +1,24 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +type ProjectChickin struct { + Id uint `gorm:"primaryKey"` + ProjectFlocId uint `gorm:"not null"` + ProductWarehouseId uint `gorm:"not null"` + ChickInDate time.Time `gorm:"not null"` + Quantity float64 `gorm:"not null"` + Note string `gorm:"type:text"` + CreatedBy uint `gorm:"not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + ProjectFloc ProjectFlock `gorm:"foreignKey:ProjectFlocId;references:Id"` + ProductWarehouse ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"` + CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` +} diff --git a/internal/modules/inventory/product-warehouses/dto/product_warehouse.dto.go b/internal/modules/inventory/product-warehouses/dto/product_warehouse.dto.go index fdebb519..8c9f3846 100644 --- a/internal/modules/inventory/product-warehouses/dto/product_warehouse.dto.go +++ b/internal/modules/inventory/product-warehouses/dto/product_warehouse.dto.go @@ -4,7 +4,6 @@ import ( "time" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" - userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" ) // === DTO Structs === @@ -18,11 +17,16 @@ type ProductWarehouseBaseDTO struct { type ProductWarehouseListDTO struct { ProductWarehouseBaseDTO - Product *ProductBaseDTO `json:"product,omitempty"` - Warehouse *WarehouseBaseDTO `json:"warehouse,omitempty"` - CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + Product *ProductBaseDTO `json:"product,omitempty"` + Warehouse *WarehouseBaseDTO `json:"warehouse,omitempty"` + CreatedUser *UserBaseDTO `json:"created_user,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type UserBaseDTO struct { + Id uint `json:"id"` + Username string `json:"username"` } type ProductWarehouseDetailDTO struct { @@ -38,6 +42,24 @@ type ProductBaseDTO struct { } type WarehouseBaseDTO struct { + Id uint `json:"id"` + Name string `json:"name"` + Kandang *KandangBaseDTO `json:"kandang,omitempty"` + Location *LocationBaseDTO `json:"location,omitempty"` + Area *AreaBaseDTO `json:"area,omitempty"` +} + +type KandangBaseDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type LocationBaseDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type AreaBaseDTO struct { Id uint `json:"id"` Name string `json:"name"` } @@ -69,7 +91,6 @@ func ToProductWarehouseListDTO(e entity.ProductWarehouse) ProductWarehouseListDT if e.Product.Sku != nil { product.Sku = *e.Product.Sku } - // Map flags from Product relation if len(e.Product.Flags) > 0 { for _, f := range e.Product.Flags { product.Flags = append(product.Flags, f.Name) @@ -84,12 +105,37 @@ func ToProductWarehouseListDTO(e entity.ProductWarehouse) ProductWarehouseListDT Id: e.Warehouse.Id, Name: e.Warehouse.Name, } + // Map Kandang jika ada + if e.Warehouse.Kandang != nil && e.Warehouse.Kandang.Id != 0 { + warehouse.Kandang = &KandangBaseDTO{ + Id: e.Warehouse.Kandang.Id, + Name: e.Warehouse.Kandang.Name, + } + } + // Map Location jika ada + if e.Warehouse.Location != nil && e.Warehouse.Location.Id != 0 { + warehouse.Location = &LocationBaseDTO{ + Id: e.Warehouse.Location.Id, + Name: e.Warehouse.Location.Name, + } + } + + if &e.Warehouse.Area != nil && e.Warehouse.Area.Id != 0 { + warehouse.Area = &AreaBaseDTO{ + Id: e.Warehouse.Area.Id, + Name: e.Warehouse.Area.Name, + } + } + dto.Warehouse = &warehouse } // Map CreatedUser relation jika ada if e.CreatedUser.Id != 0 { - user := userDTO.ToUserBaseDTO(e.CreatedUser) + user := UserBaseDTO{ + Id: e.CreatedUser.Id, + Username: e.CreatedUser.Name, + } dto.CreatedUser = &user } @@ -109,3 +155,24 @@ func ToProductWarehouseDetailDTO(e entity.ProductWarehouse) ProductWarehouseDeta ProductWarehouseListDTO: ToProductWarehouseListDTO(e), } } + +func ToKandangBaseDTO(e entity.Kandang) KandangBaseDTO { + return KandangBaseDTO{ + Id: e.Id, + Name: e.Name, + } +} + +func ToLocationBaseDTO(e entity.Location) LocationBaseDTO { + return LocationBaseDTO{ + Id: e.Id, + Name: e.Name, + } +} + +func ToAreaBaseDTO(e entity.Area) AreaBaseDTO { + return AreaBaseDTO{ + Id: e.Id, + Name: e.Name, + } +} diff --git a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go index 0d86e073..a36e3621 100644 --- a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go +++ b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go @@ -35,9 +35,12 @@ func NewProductWarehouseService(repo repository.ProductWarehouseRepository, vali func (s productWarehouseService) withRelations(db *gorm.DB) *gorm.DB { return db. - Preload("Product.Flags"). Preload("Product"). + Preload("Product.Flags"). Preload("Warehouse"). + Preload("Warehouse.Location"). + Preload("Warehouse.Kandang"). + Preload("Warehouse.Area"). Preload("CreatedUser") } @@ -85,6 +88,7 @@ func (s productWarehouseService) GetOne(c *fiber.Ctx, id uint) (*entity.ProductW if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "ProductWarehouse not found") } + if err != nil { s.Log.Errorf("Failed get productWarehouse by id: %+v", err) return nil, err diff --git a/internal/modules/production/chickins/controllers/chickin.controller.go b/internal/modules/production/chickins/controllers/chickin.controller.go new file mode 100644 index 00000000..aae59ff2 --- /dev/null +++ b/internal/modules/production/chickins/controllers/chickin.controller.go @@ -0,0 +1,140 @@ +package controller + +import ( + "math" + "strconv" + + "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/dto" + service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/services" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/validations" + "gitlab.com/mbugroup/lti-api.git/internal/response" + + "github.com/gofiber/fiber/v2" +) + +type ChickinController struct { + ChickinService service.ChickinService +} + +func NewChickinController(chickinService service.ChickinService) *ChickinController { + return &ChickinController{ + ChickinService: chickinService, + } +} + +func (u *ChickinController) 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.ChickinService.GetAll(c, query) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.SuccessWithPaginate[dto.ChickinListDTO]{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get all chickins successfully", + Meta: response.Meta{ + Page: query.Page, + Limit: query.Limit, + TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), + TotalResults: totalResults, + }, + Data: dto.ToChickinListDTOs(result), + }) +} + +func (u *ChickinController) 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.ChickinService.GetOne(c, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get chickin successfully", + Data: dto.ToChickinListDTO(*result), + }) +} + +func (u *ChickinController) 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.ChickinService.CreateOne(c, req) + if err != nil { + return err + } + + return c.Status(fiber.StatusCreated). + JSON(response.Success{ + Code: fiber.StatusCreated, + Status: "success", + Message: "Create chickin successfully", + Data: dto.ToChickinListDTO(*result), + }) +} + +func (u *ChickinController) 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.ChickinService.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 chickin successfully", + Data: dto.ToChickinListDTO(*result), + }) +} + +func (u *ChickinController) 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.ChickinService.DeleteOne(c, uint(id)); err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Common{ + Code: fiber.StatusOK, + Status: "success", + Message: "Delete chickin successfully", + }) +} diff --git a/internal/modules/production/chickins/dto/chickin.dto.go b/internal/modules/production/chickins/dto/chickin.dto.go new file mode 100644 index 00000000..6e317e79 --- /dev/null +++ b/internal/modules/production/chickins/dto/chickin.dto.go @@ -0,0 +1,84 @@ +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 ChickinBaseDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type ChickinSimpleDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type ChickinListDTO struct { + ChickinBaseDTO + CreatedUser *userDTO.UserBaseDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type ChickinDetailDTO struct { + ChickinListDTO +} + +// === Mapper Functions === + +func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO { + return ChickinBaseDTO{ + Id: e.Id, + + } +} + +func ToChickinSimpleDTO(e entity.ProjectChickin) ChickinSimpleDTO { + return ChickinSimpleDTO{ + Id: e.Id, + + } +} + +func ToChickinListDTO(e entity.ProjectChickin) ChickinListDTO { + var createdUser *userDTO.UserBaseDTO + if e.CreatedUser.Id != 0 { + mapped := userDTO.ToUserBaseDTO(e.CreatedUser) + createdUser = &mapped + } + + return ChickinListDTO{ + ChickinBaseDTO: ToChickinBaseDTO(e), + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedUser: createdUser, + } +} + +func ToChickinListDTOs(e []entity.ProjectChickin) []ChickinListDTO { + result := make([]ChickinListDTO, len(e)) + for i, r := range e { + result[i] = ToChickinListDTO(r) + } + return result +} + +func ToChickinSimpleDTOs(e []entity.ProjectChickin) []ChickinSimpleDTO { + result := make([]ChickinSimpleDTO, len(e)) + for i, r := range e { + result[i] = ToChickinSimpleDTO(r) + } + return result +} + +func ToChickinDetailDTO(e entity.ProjectChickin) ChickinDetailDTO { + return ChickinDetailDTO{ + ChickinListDTO: ToChickinListDTO(e), + } +} \ No newline at end of file diff --git a/internal/modules/production/chickins/module.go b/internal/modules/production/chickins/module.go new file mode 100644 index 00000000..330bf698 --- /dev/null +++ b/internal/modules/production/chickins/module.go @@ -0,0 +1,26 @@ +package chickins + +import ( + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + + rChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" + sChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/services" + + rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" + sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" +) + +type ChickinModule struct{} + +func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + chickinRepo := rChickin.NewChickinRepository(db) + userRepo := rUser.NewUserRepository(db) + + chickinService := sChickin.NewChickinService(chickinRepo, validate) + userService := sUser.NewUserService(userRepo, validate) + + ChickinRoutes(router, userService, chickinService) +} + diff --git a/internal/modules/production/chickins/repositories/chickin.repository.go b/internal/modules/production/chickins/repositories/chickin.repository.go new file mode 100644 index 00000000..e8c3fccf --- /dev/null +++ b/internal/modules/production/chickins/repositories/chickin.repository.go @@ -0,0 +1,21 @@ +package repository + +import ( + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gorm.io/gorm" +) + +type ChickinRepository interface { + repository.BaseRepository[entity.ProjectChickin] +} + +type ChickinRepositoryImpl struct { + *repository.BaseRepositoryImpl[entity.ProjectChickin] +} + +func NewChickinRepository(db *gorm.DB) ChickinRepository { + return &ChickinRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectChickin](db), + } +} diff --git a/internal/modules/production/chickins/route.go b/internal/modules/production/chickins/route.go new file mode 100644 index 00000000..8948459e --- /dev/null +++ b/internal/modules/production/chickins/route.go @@ -0,0 +1,28 @@ +package chickins + +import ( + // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/controllers" + chickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/services" + user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" + + "github.com/gofiber/fiber/v2" +) + +func ChickinRoutes(v1 fiber.Router, u user.UserService, s chickin.ChickinService) { + ctrl := controller.NewChickinController(s) + + route := v1.Group("/chickins") + + // 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) +} diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go new file mode 100644 index 00000000..00a3012e --- /dev/null +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -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/chickins/repositories" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/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 ChickinService interface { + GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error) + GetOne(ctx *fiber.Ctx, id uint) (*entity.ProjectChickin, error) + CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectChickin, error) + UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error) + DeleteOne(ctx *fiber.Ctx, id uint) error +} + +type chickinService struct { + Log *logrus.Logger + Validate *validator.Validate + Repository repository.ChickinRepository +} + +func NewChickinService(repo repository.ChickinRepository, validate *validator.Validate) ChickinService { + return &chickinService{ + Log: utils.Log, + Validate: validate, + Repository: repo, + } +} + +func (s chickinService) withRelations(db *gorm.DB) *gorm.DB { + return db.Preload("CreatedUser") +} + +func (s chickinService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error) { + if err := s.Validate.Struct(params); err != nil { + return nil, 0, err + } + + offset := (params.Page - 1) * params.Limit + + chickins, 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 chickins: %+v", err) + return nil, 0, err + } + return chickins, total, nil +} + +func (s chickinService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectChickin, error) { + chickin, err := s.Repository.GetByID(c.Context(), id, s.withRelations) + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "Chickin not found") + } + if err != nil { + s.Log.Errorf("Failed get chickin by id: %+v", err) + return nil, err + } + return chickin, nil +} + +func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.ProjectChickin, error) { + if err := s.Validate.Struct(req); err != nil { + return nil, err + } + + createBody := &entity.ProjectChickin{ + ProjectFlocId: 1, + } + + if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { + s.Log.Errorf("Failed to create chickin: %+v", err) + return nil, err + } + + return s.GetOne(c, createBody.Id) +} + +func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, 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, "Chickin not found") + } + s.Log.Errorf("Failed to update chickin: %+v", err) + return nil, err + } + + return s.GetOne(c, id) +} + +func (s chickinService) 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, "Chickin not found") + } + s.Log.Errorf("Failed to delete chickin: %+v", err) + return err + } + return nil +} diff --git a/internal/modules/production/chickins/validations/chickin.validation.go b/internal/modules/production/chickins/validations/chickin.validation.go new file mode 100644 index 00000000..95505746 --- /dev/null +++ b/internal/modules/production/chickins/validations/chickin.validation.go @@ -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"` +} diff --git a/internal/modules/production/route.go b/internal/modules/production/route.go index 73bbe8da..597fbc62 100644 --- a/internal/modules/production/route.go +++ b/internal/modules/production/route.go @@ -9,6 +9,7 @@ import ( projectflocks "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks" recordings "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings" + chickins "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins" // MODULE IMPORTS ) @@ -18,6 +19,7 @@ func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Valida allModules := []modules.Module{ projectflocks.ProjectflockModule{}, recordings.RecordingModule{}, + chickins.ChickinModule{}, // MODULE REGISTRY } From 83c3e611136617ea63751d05b282182abd576f66 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Mon, 20 Oct 2025 06:01:16 +0700 Subject: [PATCH 04/11] feat(BE-115,116,117): implement chickin CRUD, approve logic, and stock availabilit --- ...53_create_project_chick_ins_table.down.sql | 18 -- ...5953_create_project_chick_ins_table.up.sql | 1 - ...49_create_project_chick_ins_table.down.sql | 1 + ...0649_create_project_chick_ins_table.up.sql | 21 ++ ...create_stock_availabilities_table.down.sql | 1 + ...6_create_stock_availabilities_table.up.sql | 15 ++ ...019141014_create_audit_logs_table.down.sql | 1 + ...51019141014_create_audit_logs_table.up.sql | 13 ++ internal/entities/audit_log.go | 18 ++ internal/entities/project_chickin.go | 28 +-- internal/entities/stock_availabilites.go | 26 +++ internal/entities/stock_log.go | 1 + .../modules/inventory/adjustments/module.go | 2 +- .../services/adjustment.service.go | 2 +- .../product_warehouse.controller.go | 3 +- .../services/product_warehouse.service.go | 21 +- .../product_warehouse.validation.go | 9 +- .../inventory/transfers/dto/transfer.dto.go | 64 ++---- .../modules/inventory/transfers/module.go | 2 +- .../transfers/services/transfer.service.go | 4 +- .../repositories/kandang.repository.go | 12 ++ .../repositories/warehouse.repository.go | 13 ++ .../controllers/chickin.controller.go | 23 ++- .../production/chickins/dto/chickin.dto.go | 141 +++++++++---- .../modules/production/chickins/module.go | 15 +- .../repositories/chickin.repository.go | 21 -- .../project_chickin.repository.go | 36 ++++ internal/modules/production/chickins/route.go | 1 + .../chickins/services/chickin.service.go | 195 ++++++++++++++++-- .../validations/chickin.validation.go | 5 +- internal/modules/production/route.go | 2 +- .../repositories/audit-logs.repository.go | 21 ++ .../stock-availabilites.repository.go | 21 ++ .../repositories/stock-logs.repository.go | 0 34 files changed, 558 insertions(+), 199 deletions(-) delete mode 100644 internal/database/migrations/20251017135953_create_project_chick_ins_table.down.sql delete mode 100644 internal/database/migrations/20251017135953_create_project_chick_ins_table.up.sql create mode 100644 internal/database/migrations/20251018120649_create_project_chick_ins_table.down.sql create mode 100644 internal/database/migrations/20251018120649_create_project_chick_ins_table.up.sql create mode 100644 internal/database/migrations/20251019040246_create_stock_availabilities_table.down.sql create mode 100644 internal/database/migrations/20251019040246_create_stock_availabilities_table.up.sql create mode 100644 internal/database/migrations/20251019141014_create_audit_logs_table.down.sql create mode 100644 internal/database/migrations/20251019141014_create_audit_logs_table.up.sql create mode 100644 internal/entities/audit_log.go create mode 100644 internal/entities/stock_availabilites.go delete mode 100644 internal/modules/production/chickins/repositories/chickin.repository.go create mode 100644 internal/modules/production/chickins/repositories/project_chickin.repository.go create mode 100644 internal/modules/shared/repositories/audit-logs.repository.go create mode 100644 internal/modules/shared/repositories/stock-availabilites.repository.go rename internal/modules/shared/{stock-logs => }/repositories/stock-logs.repository.go (100%) diff --git a/internal/database/migrations/20251017135953_create_project_chick_ins_table.down.sql b/internal/database/migrations/20251017135953_create_project_chick_ins_table.down.sql deleted file mode 100644 index ac9a0f59..00000000 --- a/internal/database/migrations/20251017135953_create_project_chick_ins_table.down.sql +++ /dev/null @@ -1,18 +0,0 @@ -CREATE TABLE project_chick_ins ( - id BIGSERIAL PRIMARY KEY, - project_floc_id BIGINT NOT NULL REFERENCES project_flocs (id), - product_warehouse_id BIGINT NOT NULL REFERENCES product_warehouses (id), - chick_in_date DATE NOT NULL, - quantity NUMERIC(15, 3) NOT NULL CHECK (quantity > 0), - note TEXT, - created_by BIGINT NOT NULL REFERENCES users (id), - created_at TIMESTAMPTZ DEFAULT now(), - updated_at TIMESTAMPTZ DEFAULT now(), - deleted_at TIMESTAMPTZ -); - -CREATE INDEX idx_project_chick_ins_project_floc_id ON project_chick_ins (project_floc_id); - -CREATE INDEX idx_project_chick_ins_product_warehouse_id ON project_chick_ins (product_warehouse_id); - -CREATE INDEX idx_project_chick_ins_created_by ON project_chick_ins (created_by); \ No newline at end of file diff --git a/internal/database/migrations/20251017135953_create_project_chick_ins_table.up.sql b/internal/database/migrations/20251017135953_create_project_chick_ins_table.up.sql deleted file mode 100644 index b1435759..00000000 --- a/internal/database/migrations/20251017135953_create_project_chick_ins_table.up.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS project_chick_ins; \ No newline at end of file diff --git a/internal/database/migrations/20251018120649_create_project_chick_ins_table.down.sql b/internal/database/migrations/20251018120649_create_project_chick_ins_table.down.sql new file mode 100644 index 00000000..bb8f8a2d --- /dev/null +++ b/internal/database/migrations/20251018120649_create_project_chick_ins_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS project_chickins; \ No newline at end of file diff --git a/internal/database/migrations/20251018120649_create_project_chick_ins_table.up.sql b/internal/database/migrations/20251018120649_create_project_chick_ins_table.up.sql new file mode 100644 index 00000000..a3b7dfb3 --- /dev/null +++ b/internal/database/migrations/20251018120649_create_project_chick_ins_table.up.sql @@ -0,0 +1,21 @@ +CREATE TABLE project_chickins ( + id BIGSERIAL PRIMARY KEY, + project_floc_id BIGINT NOT NULL, + chick_in_date DATE NOT NULL, + quantity NUMERIC(15, 3) NOT NULL, + note TEXT, + created_by BIGINT NOT NULL, + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now(), + deleted_at TIMESTAMPTZ +); + +CREATE INDEX idx_project_chickins_project_floc_id ON project_chickins (project_floc_id); + +CREATE INDEX idx_project_chickins_created_by ON project_chickins (created_by); + +ALTER TABLE project_chickins +ADD CONSTRAINT fk_project_floc_id FOREIGN KEY (project_floc_id) REFERENCES project_flocks (id); + +ALTER TABLE project_chickins +ADD CONSTRAINT fk_created_by FOREIGN KEY (created_by) REFERENCES users (id); \ No newline at end of file diff --git a/internal/database/migrations/20251019040246_create_stock_availabilities_table.down.sql b/internal/database/migrations/20251019040246_create_stock_availabilities_table.down.sql new file mode 100644 index 00000000..1d50d98b --- /dev/null +++ b/internal/database/migrations/20251019040246_create_stock_availabilities_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS stock_availabilities; \ No newline at end of file diff --git a/internal/database/migrations/20251019040246_create_stock_availabilities_table.up.sql b/internal/database/migrations/20251019040246_create_stock_availabilities_table.up.sql new file mode 100644 index 00000000..bce6f7e6 --- /dev/null +++ b/internal/database/migrations/20251019040246_create_stock_availabilities_table.up.sql @@ -0,0 +1,15 @@ +CREATE TABLE stock_availabilities ( + id BIGSERIAL PRIMARY KEY, + entity_type VARCHAR(50) NOT NULL, + entity_id BIGINT NOT NULL, + product_id BIGINT, + quantity NUMERIC(15, 3) NOT NULL DEFAULT 0, + reserved_quantity NUMERIC(15, 3) NOT NULL DEFAULT 0, + unit VARCHAR(20), + last_updated TIMESTAMPTZ DEFAULT now(), + created_at TIMESTAMPTZ DEFAULT now(), + deleted_at TIMESTAMPTZ +); + +ALTER TABLE stock_availabilities +ADD CONSTRAINT fk_product_id FOREIGN KEY (product_id) REFERENCES products (id); \ No newline at end of file diff --git a/internal/database/migrations/20251019141014_create_audit_logs_table.down.sql b/internal/database/migrations/20251019141014_create_audit_logs_table.down.sql new file mode 100644 index 00000000..4cf6b411 --- /dev/null +++ b/internal/database/migrations/20251019141014_create_audit_logs_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS audit_logs; \ No newline at end of file diff --git a/internal/database/migrations/20251019141014_create_audit_logs_table.up.sql b/internal/database/migrations/20251019141014_create_audit_logs_table.up.sql new file mode 100644 index 00000000..13731dcc --- /dev/null +++ b/internal/database/migrations/20251019141014_create_audit_logs_table.up.sql @@ -0,0 +1,13 @@ +CREATE TABLE audit_logs ( + id BIGSERIAL PRIMARY KEY, + table_name VARCHAR(100) NOT NULL, + record_id BIGINT NOT NULL, + action VARCHAR(30) NOT NULL, + before_data JSONB, + after_data JSONB, + changed_by BIGINT, + created_at TIMESTAMPTZ DEFAULT now() +); + +ALTER TABLE audit_logs +ADD CONSTRAINT fk_changed_by FOREIGN KEY (changed_by) REFERENCES users (id); \ No newline at end of file diff --git a/internal/entities/audit_log.go b/internal/entities/audit_log.go new file mode 100644 index 00000000..3b770125 --- /dev/null +++ b/internal/entities/audit_log.go @@ -0,0 +1,18 @@ +package entities + +import ( + "time" +) + +type AuditLog struct { + Id uint `gorm:"primaryKey"` + TableName string `gorm:"size:100;not null"` + RecordId uint `gorm:"not null"` + Action string `gorm:"size:30;not null"` + BeforeData string `gorm:"type:jsonb"` + AfterData string `gorm:"type:jsonb"` + ChangedBy uint `gorm:"not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + + User *User `gorm:"foreignKey:ChangedBy;references:Id"` +} diff --git a/internal/entities/project_chickin.go b/internal/entities/project_chickin.go index a4a00596..631c8ff3 100644 --- a/internal/entities/project_chickin.go +++ b/internal/entities/project_chickin.go @@ -6,19 +6,19 @@ import ( "gorm.io/gorm" ) -type ProjectChickin struct { - Id uint `gorm:"primaryKey"` - ProjectFlocId uint `gorm:"not null"` - ProductWarehouseId uint `gorm:"not null"` - ChickInDate time.Time `gorm:"not null"` - Quantity float64 `gorm:"not null"` - Note string `gorm:"type:text"` - CreatedBy uint `gorm:"not null"` - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +const () - ProjectFloc ProjectFlock `gorm:"foreignKey:ProjectFlocId;references:Id"` - ProductWarehouse ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"` - CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` +type ProjectChickin struct { + Id uint `gorm:"primaryKey"` + ProjectFlocId uint `gorm:"not null"` + ChickInDate time.Time `gorm:"not null"` + Quantity float64 `gorm:"not null"` + Note string `gorm:"type:text"` + CreatedBy uint `gorm:"not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlocId;references:Id"` + CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` } diff --git a/internal/entities/stock_availabilites.go b/internal/entities/stock_availabilites.go new file mode 100644 index 00000000..ec24d36b --- /dev/null +++ b/internal/entities/stock_availabilites.go @@ -0,0 +1,26 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +const ( + EntityTypeProjectFlockKandang = "PROJECT_FLOCK_KANDANG" +) + +type StockAvailability struct { + Id uint `gorm:"primaryKey"` + EntityType string `gorm:"size:50;not null"` + EntityId uint `gorm:"not null"` + ProductId uint `gorm:"not null"` + Quantity float64 `gorm:"not null;default:0"` + ReservedQuantity float64 `gorm:"not null;default:0"` + Unit string `gorm:"size:20"` + LastUpdated time.Time `gorm:"autoUpdateTime"` + CreatedAt time.Time `gorm:"autoCreateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + Product *Product `gorm:"foreignKey:ProductId;references:Id"` +} diff --git a/internal/entities/stock_log.go b/internal/entities/stock_log.go index 21e86bd4..6546e790 100644 --- a/internal/entities/stock_log.go +++ b/internal/entities/stock_log.go @@ -8,6 +8,7 @@ import ( const ( LogTypeAdjustment = "ADJUSTMENT" + LogTypeTransfer = "TRANSFER" ) const ( diff --git a/internal/modules/inventory/adjustments/module.go b/internal/modules/inventory/adjustments/module.go index cfe01118..b3e12676 100644 --- a/internal/modules/inventory/adjustments/module.go +++ b/internal/modules/inventory/adjustments/module.go @@ -9,7 +9,7 @@ import ( rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" rproduct "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" - rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/stock-logs/repositories" + rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index af89f442..69654b85 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -9,7 +9,7 @@ import ( ProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" productRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories" warehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" - stockLogsRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/stock-logs/repositories" + stockLogsRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" "gorm.io/gorm" diff --git a/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go b/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go index f21eef96..a0b72a4d 100644 --- a/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go +++ b/internal/modules/inventory/product-warehouses/controllers/product_warehouse.controller.go @@ -28,7 +28,6 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error { Limit: c.QueryInt("limit", 10), ProductId: uint(c.QueryInt("product_id", 0)), WarehouseId: uint(c.QueryInt("warehouse_id", 0)), - Flag: c.Query("flag"), } result, totalResults, err := u.ProductWarehouseService.GetAll(c, query) @@ -72,3 +71,5 @@ func (u *ProductWarehouseController) GetOne(c *fiber.Ctx) error { Data: dto.ToProductWarehouseListDTO(*result), }) } + + diff --git a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go index a36e3621..9afe5707 100644 --- a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go +++ b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go @@ -34,14 +34,7 @@ func NewProductWarehouseService(repo repository.ProductWarehouseRepository, vali } func (s productWarehouseService) withRelations(db *gorm.DB) *gorm.DB { - return db. - Preload("Product"). - Preload("Product.Flags"). - Preload("Warehouse"). - Preload("Warehouse.Location"). - Preload("Warehouse.Kandang"). - Preload("Warehouse.Area"). - Preload("CreatedUser") + return db.Preload("Product.Flags").Preload("Product").Preload("Warehouse").Preload("CreatedUser") } func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProductWarehouse, int64, error) { @@ -62,12 +55,6 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) db = db.Where("warehouse_id = ?", params.WarehouseId) } - if params.Flag != "" { - db = db.Joins("JOIN products ON products.id = product_warehouses.product_id") - db = db.Joins("JOIN flags ON flags.flagable_id = products.id AND flags.flagable_type = ?", "products") - db = db.Where("flags.name = ?", params.Flag) - } - return db.Order("created_at DESC").Order("updated_at DESC") }) @@ -75,11 +62,6 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) s.Log.Errorf("Failed to get productWarehouses: %+v", err) return nil, 0, err } - - if len(productWarehouses) == 0 { - return nil, 0, fiber.NewError(fiber.StatusNotFound, "ProductWarehouses not found") - } - return productWarehouses, total, nil } @@ -88,7 +70,6 @@ func (s productWarehouseService) GetOne(c *fiber.Ctx, id uint) (*entity.ProductW if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "ProductWarehouse not found") } - if err != nil { s.Log.Errorf("Failed get productWarehouse by id: %+v", err) return nil, err diff --git a/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go b/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go index 30a5bed1..02648300 100644 --- a/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go +++ b/internal/modules/inventory/product-warehouses/validations/product_warehouse.validation.go @@ -13,9 +13,8 @@ type Update struct { } type Query struct { - Page int `query:"page" validate:"omitempty,number,min=1"` - Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` - ProductId uint `query:"product_id" validate:"omitempty,number,min=1"` - WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"` - Flag string `query:"flag" validate:"omitempty"` + Page int `query:"page" validate:"omitempty,number,min=1"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` + ProductId uint `query:"product_id" validate:"omitempty,number,min=1"` + WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"` } diff --git a/internal/modules/inventory/transfers/dto/transfer.dto.go b/internal/modules/inventory/transfers/dto/transfer.dto.go index 1b08ecbb..217e5038 100644 --- a/internal/modules/inventory/transfers/dto/transfer.dto.go +++ b/internal/modules/inventory/transfers/dto/transfer.dto.go @@ -57,17 +57,17 @@ type TransferDetailDTO struct { // Detail produk type TransferDetailItemDTO struct { - Id uint64 `json:"id"` - Product *ProductDTO `json:"product,omitempty"` - Quantity float64 `json:"quantity"` - BeforeQuantity float64 `json:"before_quantity"` - AfterQuantity float64 `json:"after_quantity"` + Id uint64 `json:"id"` + ProductId uint64 `json:"product_id"` + Quantity float64 `json:"quantity"` + BeforeQuantity float64 `json:"before_quantity"` + AfterQuantity float64 `json:"after_quantity"` } // Delivery ekspedisi type TransferDeliveryDTO struct { Id uint64 `json:"id"` - Supplier *SupplierDTO `json:"supplier,omitempty"` + SupplierId uint64 `json:"supplier_id"` VehiclePlate string `json:"vehicle_plate"` DriverName string `json:"driver_name"` DocumentNumber string `json:"document_number"` @@ -83,16 +83,6 @@ type TransferDeliveryItemDTO struct { Quantity float64 `json:"quantity"` } -type ProductDTO struct { - Id uint64 `json:"id"` - Name string `json:"name"` -} - -type SupplierDTO struct { - Id uint64 `json:"id"` - Name string `json:"name"` -} - // === Mapper Functions === func ToTransferBaseDTO(e entity.StockTransfer) TransferBaseDTO { @@ -124,26 +114,6 @@ func toAreaDTO(a *entity.Area) *AreaDTO { } } -func toProductDTO(p *entity.Product) *ProductDTO { - if p == nil { - return nil - } - return &ProductDTO{ - Id: uint64(p.Id), - Name: p.Name, - } -} - -func toSupplierDTO(s *entity.Supplier) *SupplierDTO { - if s == nil { - return nil - } - return &SupplierDTO{ - Id: uint64(s.Id), - Name: s.Name, - } -} - func toLocationDTO(l *entity.Location) *LocationDTO { if l == nil { return nil @@ -172,19 +142,19 @@ func ToTransferListDTO(e entity.StockTransfer) TransferListDTO { mapped := userDTO.ToUserBaseDTO(*e.CreatedUser) createdUser = &mapped } - + // Map details var details []TransferDetailItemDTO for _, d := range e.Details { details = append(details, TransferDetailItemDTO{ - Id: d.Id, - Product: toProductDTO(d.Product), - Quantity: d.Quantity, + Id: d.Id, + ProductId: d.ProductId, + Quantity: d.Quantity, }) } - + // Map deliveries var deliveries []TransferDeliveryDTO for _, del := range e.Deliveries { - + // Map delivery items var items []TransferDeliveryItemDTO for _, item := range del.Items { items = append(items, TransferDeliveryItemDTO{ @@ -195,8 +165,8 @@ func ToTransferListDTO(e entity.StockTransfer) TransferListDTO { } deliveries = append(deliveries, TransferDeliveryDTO{ Id: del.Id, + SupplierId: del.SupplierId, VehiclePlate: del.VehiclePlate, - Supplier: toSupplierDTO(del.Supplier), DriverName: del.DriverName, DocumentNumber: del.DocumentNumber, DocumentPath: del.DocumentPath, @@ -228,9 +198,9 @@ func ToTransferDetailDTO(e entity.StockTransfer) TransferDetailDTO { var details []TransferDetailItemDTO for _, d := range e.Details { details = append(details, TransferDetailItemDTO{ - Id: d.Id, - Product: toProductDTO(d.Product), - Quantity: d.Quantity, + Id: d.Id, + ProductId: d.ProductId, + Quantity: d.Quantity, }) } // Map deliveries @@ -238,7 +208,7 @@ func ToTransferDetailDTO(e entity.StockTransfer) TransferDetailDTO { for _, del := range e.Deliveries { deliveries = append(deliveries, TransferDeliveryDTO{ Id: del.Id, - Supplier: toSupplierDTO(del.Supplier), + SupplierId: del.SupplierId, VehiclePlate: del.VehiclePlate, DriverName: del.DriverName, DocumentNumber: del.DocumentNumber, diff --git a/internal/modules/inventory/transfers/module.go b/internal/modules/inventory/transfers/module.go index 21f0ec89..734f0f03 100644 --- a/internal/modules/inventory/transfers/module.go +++ b/internal/modules/inventory/transfers/module.go @@ -9,7 +9,7 @@ import ( rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories" sTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/services" rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories" - rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/stock-logs/repositories" + rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" ) diff --git a/internal/modules/inventory/transfers/services/transfer.service.go b/internal/modules/inventory/transfers/services/transfer.service.go index 4579d4c0..bdc8abf6 100644 --- a/internal/modules/inventory/transfers/services/transfer.service.go +++ b/internal/modules/inventory/transfers/services/transfer.service.go @@ -10,7 +10,7 @@ import ( rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/validations" rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories" - rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/stock-logs/repositories" + rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" "github.com/go-playground/validator/v10" @@ -60,8 +60,6 @@ func (s transferService) withRelations(db *gorm.DB) *gorm.DB { Preload("ToWarehouse.Location"). Preload("ToWarehouse.Area"). Preload("Details"). - Preload("Details.Product"). - Preload("Deliveries.Supplier"). Preload("Deliveries.Items") } diff --git a/internal/modules/master/kandangs/repositories/kandang.repository.go b/internal/modules/master/kandangs/repositories/kandang.repository.go index b253fade..bcb03854 100644 --- a/internal/modules/master/kandangs/repositories/kandang.repository.go +++ b/internal/modules/master/kandangs/repositories/kandang.repository.go @@ -15,6 +15,7 @@ type KandangRepository interface { PicExists(ctx context.Context, areaId uint) (bool, error) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) ProjectFlockExists(ctx context.Context, projectFlockID uint) (bool, error) + GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.Kandang, error) HasActiveKandangForProjectFlock(ctx context.Context, projectFlockID uint, excludeID *uint) (bool, error) } @@ -69,3 +70,14 @@ func (r *KandangRepositoryImpl) HasActiveKandangForProjectFlock(ctx context.Cont } return count > 0, nil } + +func (r *KandangRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.Kandang, error) { + kandang := new(entity.Kandang) + err := r.db.WithContext(ctx). + Where("project_flock_id = ?", projectFlockID). + First(kandang).Error + if err != nil { + return nil, err + } + return kandang, nil +} diff --git a/internal/modules/master/warehouses/repositories/warehouse.repository.go b/internal/modules/master/warehouses/repositories/warehouse.repository.go index 5c791e01..956c30ef 100644 --- a/internal/modules/master/warehouses/repositories/warehouse.repository.go +++ b/internal/modules/master/warehouses/repositories/warehouse.repository.go @@ -15,6 +15,7 @@ type WarehouseRepository interface { KandangExists(ctx context.Context, kandangId uint) (bool, error) NameExists(ctx context.Context, name string, excludeID *uint) (bool, error) IdExists(ctx context.Context, id uint) (bool, error) + GetByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error) } type WarehouseRepositoryImpl struct { @@ -47,3 +48,15 @@ func (r *WarehouseRepositoryImpl) NameExists(ctx context.Context, name string, e func (r *WarehouseRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) { return repository.Exists[entity.Warehouse](ctx, r.db, id) } + +func (r *WarehouseRepositoryImpl) GetByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error) { + var warehouse entity.Warehouse + err := r.db.WithContext(ctx). + Where("kandang_id = ?", kandangId). + Where("deleted_at IS NULL"). + First(&warehouse).Error + if err != nil { + return nil, err + } + return &warehouse, nil +} diff --git a/internal/modules/production/chickins/controllers/chickin.controller.go b/internal/modules/production/chickins/controllers/chickin.controller.go index aae59ff2..6514f8c8 100644 --- a/internal/modules/production/chickins/controllers/chickin.controller.go +++ b/internal/modules/production/chickins/controllers/chickin.controller.go @@ -88,7 +88,7 @@ func (u *ChickinController) CreateOne(c *fiber.Ctx) error { Code: fiber.StatusCreated, Status: "success", Message: "Create chickin successfully", - Data: dto.ToChickinListDTO(*result), + Data: result, }) } @@ -138,3 +138,24 @@ func (u *ChickinController) DeleteOne(c *fiber.Ctx) error { Message: "Delete chickin successfully", }) } + +func (u *ChickinController) Approve(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.ChickinService.Approve(c, uint(id)); err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Approve chickin successfully", + Data: nil, + }) +} diff --git a/internal/modules/production/chickins/dto/chickin.dto.go b/internal/modules/production/chickins/dto/chickin.dto.go index 6e317e79..7a8b6773 100644 --- a/internal/modules/production/chickins/dto/chickin.dto.go +++ b/internal/modules/production/chickins/dto/chickin.dto.go @@ -1,84 +1,135 @@ package dto import ( - "time" + "time" - entity "gitlab.com/mbugroup/lti-api.git/internal/entities" - userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" ) // === DTO Structs === type ChickinBaseDTO struct { - Id uint `json:"id"` - Name string `json:"name"` + Id uint `json:"id"` + ProjectFlocId uint `json:"project_floc_id"` + ChickInDate time.Time `json:"chick_in_date"` + Quantity float64 `json:"quantity"` + Note string `json:"note"` } type ChickinSimpleDTO struct { - Id uint `json:"id"` - Name string `json:"name"` + Id uint `json:"id"` + ProjectFlocId uint `json:"project_floc_id"` + ChickInDate time.Time `json:"chick_in_date"` + Quantity float64 `json:"quantity"` + Note string `json:"note"` + CreatedBy uint `json:"created_by"` +} + +type UserBaseDTO struct { + Id uint `json:"id"` + Name string `json:"name"` } type ChickinListDTO struct { - ChickinBaseDTO - CreatedUser *userDTO.UserBaseDTO `json:"created_user"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ChickinBaseDTO + ProjectFlock *ProjectFlockDTO `json:"project_flock"` + CreatedUser *UserBaseDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type ProjectFlockDTO struct { + Id uint `json:"id"` + Period int `json:"period"` + FlockId uint `json:"flock_id"` + FlockName string `json:"flock_name"` +} + +// === Mapper Functions === + +func ToProjectFlockDTO(e entity.ProjectFlock) ProjectFlockDTO { + return ProjectFlockDTO{ + Id: e.Id, + Period: e.Period, + FlockId: e.FlockId, + FlockName: e.Flock.Name, + } } type ChickinDetailDTO struct { - ChickinListDTO + ChickinListDTO } // === Mapper Functions === func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO { - return ChickinBaseDTO{ - Id: e.Id, - - } + return ChickinBaseDTO{ + Id: e.Id, + ProjectFlocId: e.ProjectFlocId, + ChickInDate: e.ChickInDate, + Quantity: e.Quantity, + Note: e.Note, + } } func ToChickinSimpleDTO(e entity.ProjectChickin) ChickinSimpleDTO { - return ChickinSimpleDTO{ - Id: e.Id, - - } + return ChickinSimpleDTO{ + Id: e.Id, + ProjectFlocId: e.ProjectFlocId, + ChickInDate: e.ChickInDate, + Quantity: e.Quantity, + Note: e.Note, + CreatedBy: e.CreatedBy, + } } func ToChickinListDTO(e entity.ProjectChickin) ChickinListDTO { - var createdUser *userDTO.UserBaseDTO - if e.CreatedUser.Id != 0 { - mapped := userDTO.ToUserBaseDTO(e.CreatedUser) - createdUser = &mapped - } + var createdUser *UserBaseDTO + if e.CreatedUser.Id != 0 { + mapped := ToUserBaseDTO(e.CreatedUser) + createdUser = &mapped + } - return ChickinListDTO{ - ChickinBaseDTO: ToChickinBaseDTO(e), - CreatedAt: e.CreatedAt, - UpdatedAt: e.UpdatedAt, - CreatedUser: createdUser, - } + var projectFlock *ProjectFlockDTO + if e.ProjectFlock.Id != 0 { + mapped := ToProjectFlockDTO(e.ProjectFlock) + projectFlock = &mapped + } + + return ChickinListDTO{ + ChickinBaseDTO: ToChickinBaseDTO(e), + ProjectFlock: projectFlock, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedUser: createdUser, + } } func ToChickinListDTOs(e []entity.ProjectChickin) []ChickinListDTO { - result := make([]ChickinListDTO, len(e)) - for i, r := range e { - result[i] = ToChickinListDTO(r) - } - return result + result := make([]ChickinListDTO, len(e)) + for i, r := range e { + result[i] = ToChickinListDTO(r) + } + return result } func ToChickinSimpleDTOs(e []entity.ProjectChickin) []ChickinSimpleDTO { - result := make([]ChickinSimpleDTO, len(e)) - for i, r := range e { - result[i] = ToChickinSimpleDTO(r) - } - return result + result := make([]ChickinSimpleDTO, len(e)) + for i, r := range e { + result[i] = ToChickinSimpleDTO(r) + } + return result } func ToChickinDetailDTO(e entity.ProjectChickin) ChickinDetailDTO { - return ChickinDetailDTO{ - ChickinListDTO: ToChickinListDTO(e), - } -} \ No newline at end of file + return ChickinDetailDTO{ + ChickinListDTO: ToChickinListDTO(e), + } +} + +func ToUserBaseDTO(e entity.User) UserBaseDTO { + return UserBaseDTO{ + Id: e.Id, + Name: e.Name, + } +} diff --git a/internal/modules/production/chickins/module.go b/internal/modules/production/chickins/module.go index 330bf698..30146724 100644 --- a/internal/modules/production/chickins/module.go +++ b/internal/modules/production/chickins/module.go @@ -5,8 +5,14 @@ import ( "github.com/gofiber/fiber/v2" "gorm.io/gorm" + rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" + rKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories" + rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" rChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" sChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/services" + rAuditLog "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" + + rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" @@ -16,11 +22,16 @@ type ChickinModule struct{} func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { chickinRepo := rChickin.NewChickinRepository(db) + kandangRepo := rKandang.NewKandangRepository(db) + auditlogrepo := rAuditLog.NewAuditLogRepository(db) + warehouseRepo := rWarehouse.NewWarehouseRepository(db) + projectFlockRepo := rProjectFlock.NewProjectflockRepository(db) + productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) + userRepo := rUser.NewUserRepository(db) - chickinService := sChickin.NewChickinService(chickinRepo, validate) + chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, auditlogrepo, validate) userService := sUser.NewUserService(userRepo, validate) ChickinRoutes(router, userService, chickinService) } - diff --git a/internal/modules/production/chickins/repositories/chickin.repository.go b/internal/modules/production/chickins/repositories/chickin.repository.go deleted file mode 100644 index e8c3fccf..00000000 --- a/internal/modules/production/chickins/repositories/chickin.repository.go +++ /dev/null @@ -1,21 +0,0 @@ -package repository - -import ( - "gitlab.com/mbugroup/lti-api.git/internal/common/repository" - entity "gitlab.com/mbugroup/lti-api.git/internal/entities" - "gorm.io/gorm" -) - -type ChickinRepository interface { - repository.BaseRepository[entity.ProjectChickin] -} - -type ChickinRepositoryImpl struct { - *repository.BaseRepositoryImpl[entity.ProjectChickin] -} - -func NewChickinRepository(db *gorm.DB) ChickinRepository { - return &ChickinRepositoryImpl{ - BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectChickin](db), - } -} diff --git a/internal/modules/production/chickins/repositories/project_chickin.repository.go b/internal/modules/production/chickins/repositories/project_chickin.repository.go new file mode 100644 index 00000000..64e2e4b4 --- /dev/null +++ b/internal/modules/production/chickins/repositories/project_chickin.repository.go @@ -0,0 +1,36 @@ +package repository + +import ( + "context" + + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gorm.io/gorm" +) + +type ProjectChickinRepository interface { + repository.BaseRepository[entity.ProjectChickin] + GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.ProjectChickin, error) +} + +type ChickinRepositoryImpl struct { + *repository.BaseRepositoryImpl[entity.ProjectChickin] +} + +func NewChickinRepository(db *gorm.DB) ProjectChickinRepository { + return &ChickinRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectChickin](db), + } +} + +func (r *ChickinRepositoryImpl) GetFirstByProjectFlockID(ctx context.Context, projectFlockID uint) (*entity.ProjectChickin, error) { + var chickin entity.ProjectChickin + err := r.DB().WithContext(ctx). + Where("project_floc_id = ?", projectFlockID). + Where("deleted_at IS NULL"). + First(&chickin).Error + if err != nil { + return nil, err + } + return &chickin, nil +} diff --git a/internal/modules/production/chickins/route.go b/internal/modules/production/chickins/route.go index 8948459e..5fa5237a 100644 --- a/internal/modules/production/chickins/route.go +++ b/internal/modules/production/chickins/route.go @@ -25,4 +25,5 @@ func ChickinRoutes(v1 fiber.Router, u user.UserService, s chickin.ChickinService route.Get("/:id", ctrl.GetOne) route.Patch("/:id", ctrl.UpdateOne) route.Delete("/:id", ctrl.DeleteOne) + route.Post("/:id/approve", ctrl.Approve) } diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index 00a3012e..75dc0242 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -4,8 +4,14 @@ import ( "errors" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" + KandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories" + rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/validations" + rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" + AuditLogRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" "github.com/go-playground/validator/v10" @@ -20,24 +26,39 @@ type ChickinService interface { CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectChickin, error) UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error) DeleteOne(ctx *fiber.Ctx, id uint) error + Approve(ctx *fiber.Ctx, id uint) error } type chickinService struct { - Log *logrus.Logger - Validate *validator.Validate - Repository repository.ChickinRepository + Log *logrus.Logger + Validate *validator.Validate + Repository repository.ProjectChickinRepository + KandangRepo KandangRepo.KandangRepository + WarehouseRepo rWarehouse.WarehouseRepository + ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository + ProjectFlockRepo rProjectFlock.ProjectflockRepository + AuditLogRepo AuditLogRepo.AuditLogRepository } -func NewChickinService(repo repository.ChickinRepository, validate *validator.Validate) ChickinService { +func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, auditLogRepo AuditLogRepo.AuditLogRepository, validate *validator.Validate) ChickinService { return &chickinService{ - Log: utils.Log, - Validate: validate, - Repository: repo, + Log: utils.Log, + Validate: validate, + Repository: repo, + KandangRepo: kandangRepo, + WarehouseRepo: warehouseRepo, + ProductWarehouseRepo: productWarehouseRepo, + ProjectFlockRepo: projectFlockRepo, + AuditLogRepo: auditLogRepo, } } func (s chickinService) withRelations(db *gorm.DB) *gorm.DB { - return db.Preload("CreatedUser") + return db. + Preload("CreatedUser"). + Preload("ProjectFlock"). + Preload("ProjectFlock.ProductCategory") + } func (s chickinService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProjectChickin, int64, error) { @@ -79,16 +100,125 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, err } - createBody := &entity.ProjectChickin{ - ProjectFlocId: 1, + // ambil salah satu kandang dari project_floc_id dari kandang repository + kandang, err := s.KandangRepo.GetFirstByProjectFlockID(c.Context(), 1) + if err != nil { + s.Log.Errorf("Failed to get kandang: %+v", err) + return nil, err + } + // ambil warehouse dari kandangid + warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), kandang.Id) + if err != nil { + s.Log.Errorf("Failed to get warehouse: %+v", err) + return nil, err + } + // getprojectflock id with relation + projectFlock, err := s.ProjectFlockRepo.GetByID( + c.Context(), + req.ProjectFlockId, + func(db *gorm.DB) *gorm.DB { + return db.Preload("ProductCategory") + }, + ) + if err != nil { + s.Log.Errorf("Failed to get project flock: %+v", err) + return nil, fiber.NewError(fiber.StatusNotFound, "Project Flock not found") + } + // ambil quantity + var productWarehouse entity.ProductWarehouse + err = s.ProductWarehouseRepo.DB().WithContext(c.Context()). + Joins("JOIN products ON products.id = product_warehouses.product_id"). + Joins("JOIN product_categories ON product_categories.id = products.product_category_id"). + Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", projectFlock.ProductCategory.Code, warehouse.Id). + Order("created_at DESC"). + First(&productWarehouse).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse") + } + s.Log.Errorf("Failed to get product warehouse: %+v", err) + return nil, err } - if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { + if productWarehouse.Quantity < 1 { + return nil, fiber.NewError(fiber.StatusBadRequest, "Insufficient product quantity in warehouse") + } + + // masukan ke chic in + chickinDate, err := utils.ParseDateString(req.ChickInDate) + if err != nil { + s.Log.Errorf("Failed to parse chickin date: %+v", err) + return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid ChickInDate format") + } + + newChickin := &entity.ProjectChickin{ + ProjectFlocId: req.ProjectFlockId, + ChickInDate: chickinDate, + Quantity: productWarehouse.Quantity, + Note: "", + CreatedBy: 1, //todo: ganti dengan + } + + err = s.Repository.CreateOne(c.Context(), newChickin, nil) + if err != nil { s.Log.Errorf("Failed to create chickin: %+v", err) return nil, err } - return s.GetOne(c, createBody.Id) + // Kurangi quantity di product warehouse + updatedQuantity := productWarehouse.Quantity - newChickin.Quantity + if updatedQuantity < 0 { + updatedQuantity = 0 + } + err = s.ProductWarehouseRepo.PatchOne(c.Context(), productWarehouse.Id, map[string]any{ + "quantity": updatedQuantity, + }, nil) + if err != nil { + s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) + return nil, err + } + + // masukan check apakah stock availability ada, jika ada update, jika tidak buat baru + stockAvailability := &entity.StockAvailability{ + EntityType: entity.EntityTypeProjectFlockKandang, + ReservedQuantity: productWarehouse.Quantity, + EntityId: req.ProjectFlockId, //todo: nanti pakek projct flock kandang id + ProductId: productWarehouse.ProductId, + } + + var existingStockAvailability entity.StockAvailability + err = s.ProductWarehouseRepo.DB().WithContext(c.Context()). + Where("entity_type = ? AND entity_id = ? AND product_id = ?", stockAvailability.EntityType, stockAvailability.EntityId, stockAvailability.ProductId). + First(&existingStockAvailability).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + // buat baru + stockAvailability.ReservedQuantity = newChickin.Quantity + stockAvailability.Quantity = 0 + err = s.ProductWarehouseRepo.DB().WithContext(c.Context()).Create(stockAvailability).Error + if err != nil { + s.Log.Errorf("Failed to create stock availability: %+v", err) + return nil, err + } + } else { + s.Log.Errorf("Failed to get stock availability: %+v", err) + return nil, err + } + } else { + // update existing + newQuantity := existingStockAvailability.ReservedQuantity + newChickin.Quantity + err = s.ProductWarehouseRepo.DB().WithContext(c.Context()). + Model(&existingStockAvailability). + Update("reserved_quantity", newQuantity).Error + if err != nil { + s.Log.Errorf("Failed to update stock availability: %+v", err) + return nil, err + } + + } + return nil, nil } func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error) { @@ -98,10 +228,9 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) updateBody := make(map[string]any) - if req.Name != nil { - updateBody["name"] = *req.Name + if req.ChickInDate != "" { + updateBody["chick_in_date"] = req.ChickInDate } - if len(updateBody) == 0 { return s.GetOne(c, id) } @@ -125,5 +254,41 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error { s.Log.Errorf("Failed to delete chickin: %+v", err) return err } + return nil +} + +func (s *chickinService) Approve(c *fiber.Ctx, id uint) error { + + chickin, err := s.Repository.GetByID( + c.Context(), + id, + nil, + ) + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "Chickin not found") + } + if err != nil { + s.Log.Errorf("Failed get chickin by id: %+v", err) + return err + } + + //pindahkan stock dari reserved ke actual stock + // get stock avaibility untuk di update + var stockAvailability entity.StockAvailability + err = s.ProductWarehouseRepo.DB().WithContext(c.Context()). + Where("entity_type = ? AND entity_id = ? ", entity.EntityTypeProjectFlockKandang, chickin.ProjectFlocId). + First(&stockAvailability).Error + if err != nil { + s.Log.Errorf("Failed to get stock availability: %+v", err) + return err + } + + newReservedQuantity := stockAvailability.ReservedQuantity - chickin.Quantity + if newReservedQuantity < 0 { + newReservedQuantity = 0 + } + + + return nil } diff --git a/internal/modules/production/chickins/validations/chickin.validation.go b/internal/modules/production/chickins/validations/chickin.validation.go index 95505746..152b3f22 100644 --- a/internal/modules/production/chickins/validations/chickin.validation.go +++ b/internal/modules/production/chickins/validations/chickin.validation.go @@ -1,11 +1,12 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` + ProjectFlockId uint `json:"project_flock_id" validate:"required,number,min=1"` + ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"` } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty"` + ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"` } type Query struct { diff --git a/internal/modules/production/route.go b/internal/modules/production/route.go index 597fbc62..b41ef1e7 100644 --- a/internal/modules/production/route.go +++ b/internal/modules/production/route.go @@ -7,9 +7,9 @@ import ( "github.com/gofiber/fiber/v2" "gorm.io/gorm" + chickins "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins" projectflocks "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks" recordings "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings" - chickins "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins" // MODULE IMPORTS ) diff --git a/internal/modules/shared/repositories/audit-logs.repository.go b/internal/modules/shared/repositories/audit-logs.repository.go new file mode 100644 index 00000000..b247f3f2 --- /dev/null +++ b/internal/modules/shared/repositories/audit-logs.repository.go @@ -0,0 +1,21 @@ +package repository + +import ( + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gorm.io/gorm" +) + +type AuditLogRepository interface { + repository.BaseRepository[entity.AuditLog] +} + +type AuditLogRepositoryImpl struct { + *repository.BaseRepositoryImpl[entity.AuditLog] +} + +func NewAuditLogRepository(db *gorm.DB) AuditLogRepository { + return &AuditLogRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.AuditLog](db), + } +} diff --git a/internal/modules/shared/repositories/stock-availabilites.repository.go b/internal/modules/shared/repositories/stock-availabilites.repository.go new file mode 100644 index 00000000..9d3ae632 --- /dev/null +++ b/internal/modules/shared/repositories/stock-availabilites.repository.go @@ -0,0 +1,21 @@ +package repository + +import ( + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gorm.io/gorm" +) + +type StockAvailabilityRepository interface { + repository.BaseRepository[entity.StockAvailability] +} + +type StockAvailabilityRepositoryImpl struct { + *repository.BaseRepositoryImpl[entity.StockAvailability] +} + +func NewStockAvailabilityRepository(db *gorm.DB) StockAvailabilityRepository { + return &StockAvailabilityRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.StockAvailability](db), + } +} diff --git a/internal/modules/shared/stock-logs/repositories/stock-logs.repository.go b/internal/modules/shared/repositories/stock-logs.repository.go similarity index 100% rename from internal/modules/shared/stock-logs/repositories/stock-logs.repository.go rename to internal/modules/shared/repositories/stock-logs.repository.go From 5c3787886b38b066cb992fda08cc61b6c6c913fb Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Mon, 20 Oct 2025 08:45:31 +0700 Subject: [PATCH 05/11] FIX[BE]: adjust response on proudctwarehouses --- ...0649_create_project_chick_ins_table.up.sql | 31 ++- internal/entities/project_chickin.go | 22 +- .../services/product_warehouse.service.go | 9 +- .../production/chickins/dto/chickin.dto.go | 255 +++++++++++++----- .../modules/production/chickins/module.go | 3 +- .../chickins/services/chickin.service.go | 75 +++--- .../validations/chickin.validation.go | 4 +- .../projectflock_kandang.repository.go | 14 + 8 files changed, 291 insertions(+), 122 deletions(-) diff --git a/internal/database/migrations/20251018120649_create_project_chick_ins_table.up.sql b/internal/database/migrations/20251018120649_create_project_chick_ins_table.up.sql index a3b7dfb3..04475e21 100644 --- a/internal/database/migrations/20251018120649_create_project_chick_ins_table.up.sql +++ b/internal/database/migrations/20251018120649_create_project_chick_ins_table.up.sql @@ -1,6 +1,6 @@ -CREATE TABLE project_chickins ( +CREATE TABLE IF NOT EXISTS project_chickins ( id BIGSERIAL PRIMARY KEY, - project_floc_id BIGINT NOT NULL, + project_floc_kandang_id BIGINT NOT NULL, chick_in_date DATE NOT NULL, quantity NUMERIC(15, 3) NOT NULL, note TEXT, @@ -10,12 +10,27 @@ CREATE TABLE project_chickins ( deleted_at TIMESTAMPTZ ); -CREATE INDEX idx_project_chickins_project_floc_id ON project_chickins (project_floc_id); +-- FOREIGN KEYS (dijalankan setelah semua tabel parent ada) +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_flock_kandangs') THEN + ALTER TABLE project_chickins + ADD CONSTRAINT fk_project_floc_kandang_id + FOREIGN KEY (project_floc_kandang_id) + REFERENCES project_flock_kandangs(id) + ON DELETE RESTRICT ON UPDATE CASCADE; + END IF; -CREATE INDEX idx_project_chickins_created_by ON project_chickins (created_by); + IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN + ALTER TABLE project_chickins + ADD CONSTRAINT fk_created_by + FOREIGN KEY (created_by) + REFERENCES users(id) + ON DELETE RESTRICT ON UPDATE CASCADE; + END IF; +END $$; -ALTER TABLE project_chickins -ADD CONSTRAINT fk_project_floc_id FOREIGN KEY (project_floc_id) REFERENCES project_flocks (id); +-- INDEXES +CREATE INDEX IF NOT EXISTS idx_project_chickins_project_floc_kandang_id ON project_chickins (project_floc_kandang_id); -ALTER TABLE project_chickins -ADD CONSTRAINT fk_created_by FOREIGN KEY (created_by) REFERENCES users (id); \ No newline at end of file +CREATE INDEX IF NOT EXISTS idx_project_chickins_created_by ON project_chickins (created_by); \ No newline at end of file diff --git a/internal/entities/project_chickin.go b/internal/entities/project_chickin.go index 631c8ff3..07536187 100644 --- a/internal/entities/project_chickin.go +++ b/internal/entities/project_chickin.go @@ -9,16 +9,16 @@ import ( const () type ProjectChickin struct { - Id uint `gorm:"primaryKey"` - ProjectFlocId uint `gorm:"not null"` - ChickInDate time.Time `gorm:"not null"` - Quantity float64 `gorm:"not null"` - Note string `gorm:"type:text"` - CreatedBy uint `gorm:"not null"` - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + Id uint `gorm:"primaryKey"` + ProjectFlocKandangId uint `gorm:"not null"` + ChickInDate time.Time `gorm:"not null"` + Quantity float64 `gorm:"not null"` + Note string `gorm:"type:text"` + CreatedBy uint `gorm:"not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` - ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlocId;references:Id"` - CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` + ProjectFlockKandang ProjectFlockKandang `gorm:"foreignKey:ProjectFlocKandangId;references:Id"` + CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` } diff --git a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go index 9afe5707..4fad5dc5 100644 --- a/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go +++ b/internal/modules/inventory/product-warehouses/services/product_warehouse.service.go @@ -34,7 +34,14 @@ func NewProductWarehouseService(repo repository.ProductWarehouseRepository, vali } func (s productWarehouseService) withRelations(db *gorm.DB) *gorm.DB { - return db.Preload("Product.Flags").Preload("Product").Preload("Warehouse").Preload("CreatedUser") + return db. + Preload("Product.Flags"). + Preload("Product"). + Preload("Warehouse"). + Preload("Warehouse.Location"). + Preload("Warehouse.Area"). + Preload("Warehouse.Kandang"). + Preload("CreatedUser") } func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.ProductWarehouse, int64, error) { diff --git a/internal/modules/production/chickins/dto/chickin.dto.go b/internal/modules/production/chickins/dto/chickin.dto.go index 7a8b6773..c89caa0e 100644 --- a/internal/modules/production/chickins/dto/chickin.dto.go +++ b/internal/modules/production/chickins/dto/chickin.dto.go @@ -6,23 +6,52 @@ import ( entity "gitlab.com/mbugroup/lti-api.git/internal/entities" ) -// === DTO Structs === +// === DTO Structs (ordered) === -type ChickinBaseDTO struct { - Id uint `json:"id"` - ProjectFlocId uint `json:"project_floc_id"` - ChickInDate time.Time `json:"chick_in_date"` - Quantity float64 `json:"quantity"` - Note string `json:"note"` +type FlockDTO struct { + Id uint `json:"id"` + Name string `json:"name"` } -type ChickinSimpleDTO struct { - Id uint `json:"id"` - ProjectFlocId uint `json:"project_floc_id"` - ChickInDate time.Time `json:"chick_in_date"` - Quantity float64 `json:"quantity"` - Note string `json:"note"` - CreatedBy uint `json:"created_by"` +type KandangDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type ProductCategoryDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type AreaDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type FcrDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type LocationDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type ProjectFlockDTO struct { + Id uint `json:"id"` + Period int `json:"period"` + Flock *FlockDTO `json:"flock"` + ProductCategory *ProductCategoryDTO `json:"product_category"` + Area *AreaDTO `json:"area"` + Fcr *FcrDTO `json:"fcr"` + Location *LocationDTO `json:"location"` +} + +type ProjectFlockKandangDTO struct { + Id uint `json:"id"` + ProjectFlock *ProjectFlockDTO `json:"project_flock"` + Kandang *KandangDTO `json:"kandang"` } type UserBaseDTO struct { @@ -30,56 +59,159 @@ type UserBaseDTO struct { Name string `json:"name"` } +type ChickinBaseDTO struct { + Id uint `json:"id"` + ProjectFlocKandangId uint `json:"project_floc_kandang_id"` + ChickInDate time.Time `json:"chick_in_date"` + Quantity float64 `json:"quantity"` + Note string `json:"note"` +} + +type ChickinSimpleDTO struct { + Id uint `json:"id"` + ProjectFlocKandangId uint `json:"project_floc_kandang_id"` + ChickInDate time.Time `json:"chick_in_date"` + Quantity float64 `json:"quantity"` + Note string `json:"note"` + CreatedBy uint `json:"created_by"` +} + type ChickinListDTO struct { ChickinBaseDTO - ProjectFlock *ProjectFlockDTO `json:"project_flock"` - CreatedUser *UserBaseDTO `json:"created_user"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -type ProjectFlockDTO struct { - Id uint `json:"id"` - Period int `json:"period"` - FlockId uint `json:"flock_id"` - FlockName string `json:"flock_name"` -} - -// === Mapper Functions === - -func ToProjectFlockDTO(e entity.ProjectFlock) ProjectFlockDTO { - return ProjectFlockDTO{ - Id: e.Id, - Period: e.Period, - FlockId: e.FlockId, - FlockName: e.Flock.Name, - } + ProjectFlockKandang *ProjectFlockKandangDTO `json:"project_flock_kandang"` + CreatedUser *UserBaseDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } type ChickinDetailDTO struct { ChickinListDTO } -// === Mapper Functions === +// === Mapper Functions (ordered) === + +func ToFlockDTO(e entity.Flock) FlockDTO { + return FlockDTO{ + Id: e.Id, + Name: e.Name, + } +} + +func ToKandangDTO(e entity.Kandang) KandangDTO { + return KandangDTO{ + Id: e.Id, + Name: e.Name, + } +} + +func ToProductCategoryDTO(e entity.ProductCategory) ProductCategoryDTO { + return ProductCategoryDTO{ + Id: e.Id, + Name: e.Name, + } +} + +func ToAreaDTO(e entity.Area) AreaDTO { + return AreaDTO{ + Id: e.Id, + Name: e.Name, + } +} + +func ToFcrDTO(e entity.Fcr) FcrDTO { + return FcrDTO{ + Id: e.Id, + Name: e.Name, + } +} + +func ToLocationDTO(e entity.Location) LocationDTO { + return LocationDTO{ + Id: e.Id, + Name: e.Name, + } +} + +func ToProjectFlockDTO(e entity.ProjectFlock) ProjectFlockDTO { + var flock *FlockDTO + if e.Flock.Id != 0 { + mapped := ToFlockDTO(e.Flock) + flock = &mapped + } + var productCategory *ProductCategoryDTO + if e.ProductCategory.Id != 0 { + mapped := ToProductCategoryDTO(e.ProductCategory) + productCategory = &mapped + } + var area *AreaDTO + if e.Area.Id != 0 { + mapped := ToAreaDTO(e.Area) + area = &mapped + } + var fcr *FcrDTO + if e.Fcr.Id != 0 { + mapped := ToFcrDTO(e.Fcr) + fcr = &mapped + } + var location *LocationDTO + if e.Location.Id != 0 { + mapped := ToLocationDTO(e.Location) + location = &mapped + } + return ProjectFlockDTO{ + Id: e.Id, + Period: e.Period, + Flock: flock, + ProductCategory: productCategory, + Area: area, + Fcr: fcr, + Location: location, + } +} + +func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangDTO { + var pf *ProjectFlockDTO + if e.ProjectFlock.Id != 0 { + mapped := ToProjectFlockDTO(e.ProjectFlock) + pf = &mapped + } + var kandang *KandangDTO + if e.Kandang.Id != 0 { + mapped := ToKandangDTO(e.Kandang) + kandang = &mapped + } + return ProjectFlockKandangDTO{ + Id: e.Id, + ProjectFlock: pf, + Kandang: kandang, + } +} + +func ToUserBaseDTO(e entity.User) UserBaseDTO { + return UserBaseDTO{ + Id: e.Id, + Name: e.Name, + } +} func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO { return ChickinBaseDTO{ - Id: e.Id, - ProjectFlocId: e.ProjectFlocId, - ChickInDate: e.ChickInDate, - Quantity: e.Quantity, - Note: e.Note, + Id: e.Id, + ProjectFlocKandangId: e.ProjectFlocKandangId, + ChickInDate: e.ChickInDate, + Quantity: e.Quantity, + Note: e.Note, } } func ToChickinSimpleDTO(e entity.ProjectChickin) ChickinSimpleDTO { return ChickinSimpleDTO{ - Id: e.Id, - ProjectFlocId: e.ProjectFlocId, - ChickInDate: e.ChickInDate, - Quantity: e.Quantity, - Note: e.Note, - CreatedBy: e.CreatedBy, + Id: e.Id, + ProjectFlocKandangId: e.ProjectFlocKandangId, + ChickInDate: e.ChickInDate, + Quantity: e.Quantity, + Note: e.Note, + CreatedBy: e.CreatedBy, } } @@ -89,19 +221,17 @@ func ToChickinListDTO(e entity.ProjectChickin) ChickinListDTO { mapped := ToUserBaseDTO(e.CreatedUser) createdUser = &mapped } - - var projectFlock *ProjectFlockDTO - if e.ProjectFlock.Id != 0 { - mapped := ToProjectFlockDTO(e.ProjectFlock) - projectFlock = &mapped + var pfk *ProjectFlockKandangDTO + if e.ProjectFlockKandang.Id != 0 { + mapped := ToProjectFlockKandangDTO(e.ProjectFlockKandang) + pfk = &mapped } - return ChickinListDTO{ - ChickinBaseDTO: ToChickinBaseDTO(e), - ProjectFlock: projectFlock, - CreatedAt: e.CreatedAt, - UpdatedAt: e.UpdatedAt, - CreatedUser: createdUser, + ChickinBaseDTO: ToChickinBaseDTO(e), + ProjectFlockKandang: pfk, + CreatedUser: createdUser, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, } } @@ -126,10 +256,3 @@ func ToChickinDetailDTO(e entity.ProjectChickin) ChickinDetailDTO { ChickinListDTO: ToChickinListDTO(e), } } - -func ToUserBaseDTO(e entity.User) UserBaseDTO { - return UserBaseDTO{ - Id: e.Id, - Name: e.Name, - } -} diff --git a/internal/modules/production/chickins/module.go b/internal/modules/production/chickins/module.go index 30146724..abfc56ca 100644 --- a/internal/modules/production/chickins/module.go +++ b/internal/modules/production/chickins/module.go @@ -25,12 +25,13 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * kandangRepo := rKandang.NewKandangRepository(db) auditlogrepo := rAuditLog.NewAuditLogRepository(db) warehouseRepo := rWarehouse.NewWarehouseRepository(db) + projectflockkandangrepo := rProjectFlock.NewProjectFlockKandangRepository(db) projectFlockRepo := rProjectFlock.NewProjectflockRepository(db) productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) userRepo := rUser.NewUserRepository(db) - chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, auditlogrepo, validate) + chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, auditlogrepo, projectflockkandangrepo, validate) userService := sUser.NewUserService(userRepo, validate) ChickinRoutes(router, userService, chickinService) diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index 75dc0242..fbb692fa 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -8,6 +8,7 @@ import ( KandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" + rProjectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/validations" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" @@ -30,34 +31,41 @@ type ChickinService interface { } type chickinService struct { - Log *logrus.Logger - Validate *validator.Validate - Repository repository.ProjectChickinRepository - KandangRepo KandangRepo.KandangRepository - WarehouseRepo rWarehouse.WarehouseRepository - ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository - ProjectFlockRepo rProjectFlock.ProjectflockRepository - AuditLogRepo AuditLogRepo.AuditLogRepository + Log *logrus.Logger + Validate *validator.Validate + Repository repository.ProjectChickinRepository + KandangRepo KandangRepo.KandangRepository + WarehouseRepo rWarehouse.WarehouseRepository + ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository + ProjectFlockRepo rProjectFlock.ProjectflockRepository + AuditLogRepo AuditLogRepo.AuditLogRepository + ProjectflockKandangRepo rProjectFlockKandang.ProjectFlockKandangRepository } -func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, auditLogRepo AuditLogRepo.AuditLogRepository, validate *validator.Validate) ChickinService { +func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, auditLogRepo AuditLogRepo.AuditLogRepository, projectflockkandangRepo rProjectFlockKandang.ProjectFlockKandangRepository, validate *validator.Validate) ChickinService { return &chickinService{ - Log: utils.Log, - Validate: validate, - Repository: repo, - KandangRepo: kandangRepo, - WarehouseRepo: warehouseRepo, - ProductWarehouseRepo: productWarehouseRepo, - ProjectFlockRepo: projectFlockRepo, - AuditLogRepo: auditLogRepo, + Log: utils.Log, + Validate: validate, + Repository: repo, + KandangRepo: kandangRepo, + WarehouseRepo: warehouseRepo, + ProductWarehouseRepo: productWarehouseRepo, + ProjectFlockRepo: projectFlockRepo, + AuditLogRepo: auditLogRepo, + ProjectflockKandangRepo: projectflockkandangRepo, } } func (s chickinService) withRelations(db *gorm.DB) *gorm.DB { return db. Preload("CreatedUser"). - Preload("ProjectFlock"). - Preload("ProjectFlock.ProductCategory") + Preload("ProjectFlockKandang.Kandang"). + Preload("ProjectFlockKandang.ProjectFlock"). + Preload("ProjectFlockKandang.ProjectFlock.Flock"). + Preload("ProjectFlockKandang.ProjectFlock.ProductCategory"). + Preload("ProjectFlockKandang.ProjectFlock.Area"). + Preload("ProjectFlockKandang.ProjectFlock.Fcr"). + Preload("ProjectFlockKandang.ProjectFlock.Location") } @@ -101,25 +109,27 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit } // ambil salah satu kandang dari project_floc_id dari kandang repository - kandang, err := s.KandangRepo.GetFirstByProjectFlockID(c.Context(), 1) + projectflockkandang, err := s.ProjectflockKandangRepo.GetByID(c.Context(), 1) if err != nil { - s.Log.Errorf("Failed to get kandang: %+v", err) + s.Log.Errorf("Failed to get projectflock kandang: %+v", err) return nil, err } // ambil warehouse dari kandangid - warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), kandang.Id) + warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), projectflockkandang.KandangId) if err != nil { s.Log.Errorf("Failed to get warehouse: %+v", err) return nil, err } + // getprojectflock id with relation projectFlock, err := s.ProjectFlockRepo.GetByID( c.Context(), - req.ProjectFlockId, + projectflockkandang.ProjectFlockId, func(db *gorm.DB) *gorm.DB { return db.Preload("ProductCategory") }, ) + if err != nil { s.Log.Errorf("Failed to get project flock: %+v", err) return nil, fiber.NewError(fiber.StatusNotFound, "Project Flock not found") @@ -153,11 +163,11 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit } newChickin := &entity.ProjectChickin{ - ProjectFlocId: req.ProjectFlockId, - ChickInDate: chickinDate, - Quantity: productWarehouse.Quantity, - Note: "", - CreatedBy: 1, //todo: ganti dengan + ProjectFlocKandangId: projectflockkandang.ProjectFlockId, + ChickInDate: chickinDate, + Quantity: productWarehouse.Quantity, + Note: "", + CreatedBy: 1, //todo: ganti dengan } err = s.Repository.CreateOne(c.Context(), newChickin, nil) @@ -183,7 +193,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit stockAvailability := &entity.StockAvailability{ EntityType: entity.EntityTypeProjectFlockKandang, ReservedQuantity: productWarehouse.Quantity, - EntityId: req.ProjectFlockId, //todo: nanti pakek projct flock kandang id + EntityId: projectflockkandang.Id, ProductId: productWarehouse.ProductId, } @@ -218,7 +228,8 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit } } - return nil, nil + + return s.GetOne(c, newChickin.Id) } func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error) { @@ -276,7 +287,7 @@ func (s *chickinService) Approve(c *fiber.Ctx, id uint) error { // get stock avaibility untuk di update var stockAvailability entity.StockAvailability err = s.ProductWarehouseRepo.DB().WithContext(c.Context()). - Where("entity_type = ? AND entity_id = ? ", entity.EntityTypeProjectFlockKandang, chickin.ProjectFlocId). + Where("entity_type = ? AND entity_id = ? ", entity.EntityTypeProjectFlockKandang, chickin.ProjectFlocKandangId). First(&stockAvailability).Error if err != nil { s.Log.Errorf("Failed to get stock availability: %+v", err) @@ -288,7 +299,5 @@ func (s *chickinService) Approve(c *fiber.Ctx, id uint) error { newReservedQuantity = 0 } - - return nil } diff --git a/internal/modules/production/chickins/validations/chickin.validation.go b/internal/modules/production/chickins/validations/chickin.validation.go index 152b3f22..b57950b0 100644 --- a/internal/modules/production/chickins/validations/chickin.validation.go +++ b/internal/modules/production/chickins/validations/chickin.validation.go @@ -1,8 +1,8 @@ package validation type Create struct { - ProjectFlockId uint `json:"project_flock_id" validate:"required,number,min=1"` - ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"` + ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required,number,min=1"` + ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"` } type Update struct { diff --git a/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go b/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go index 9b89a399..9999e1a8 100644 --- a/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go +++ b/internal/modules/production/project_flocks/repositories/projectflock_kandang.repository.go @@ -9,6 +9,7 @@ import ( ) type ProjectFlockKandangRepository interface { + GetByID(ctx context.Context, id uint) (*entity.ProjectFlockKandang, error) CreateMany(ctx context.Context, records []*entity.ProjectFlockKandang) error MarkDetached(ctx context.Context, projectFlockID uint, kandangIDs []uint, detachedAt time.Time) error GetAll(ctx context.Context) ([]entity.ProjectFlockKandang, error) @@ -62,3 +63,16 @@ func (r *projectFlockKandangRepositoryImpl) WithTx(tx *gorm.DB) ProjectFlockKand func (r *projectFlockKandangRepositoryImpl) DB() *gorm.DB { return r.db } + +func (r *projectFlockKandangRepositoryImpl) GetByID(ctx context.Context, id uint) (*entity.ProjectFlockKandang, error) { + record := new(entity.ProjectFlockKandang) + if err := r.db.WithContext(ctx). + Preload("ProjectFlock"). + Preload("ProjectFlock.Flock"). + Preload("Kandang"). + Preload("CreatedUser"). + First(record, id).Error; err != nil { + return nil, err + } + return record, nil +} From 7b99b395293d85e56896c8ec236ef60bcb44061e Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Mon, 20 Oct 2025 11:25:42 +0700 Subject: [PATCH 06/11] feat(BE-117): implement CRUD endpoints for project --- ...0649_create_project_chick_ins_table.up.sql | 8 +- ...e_project_flock_populations_table.down.sql | 1 + ...ate_project_flock_populations_table.up.sql | 36 +++++ internal/entities/project_chickin.go | 20 +-- internal/entities/project_flock_population.go | 22 +++ .../inventory/transfers/dto/transfer.dto.go | 52 ++++--- .../transfers/services/transfer.service.go | 4 +- .../controllers/chickin.controller.go | 8 +- .../production/chickins/dto/chickin.dto.go | 26 ++-- .../modules/production/chickins/module.go | 3 +- .../chickins/services/chickin.service.go | 130 +++++++++--------- .../validations/chickin.validation.go | 6 +- .../production/project_flocks/module.go | 1 + .../project_flock_population_repository.go | 35 +++++ 14 files changed, 233 insertions(+), 119 deletions(-) create mode 100644 internal/database/migrations/20251020022357_create_project_flock_populations_table.down.sql create mode 100644 internal/database/migrations/20251020022357_create_project_flock_populations_table.up.sql create mode 100644 internal/entities/project_flock_population.go create mode 100644 internal/modules/production/project_flocks/repositories/project_flock_population_repository.go diff --git a/internal/database/migrations/20251018120649_create_project_chick_ins_table.up.sql b/internal/database/migrations/20251018120649_create_project_chick_ins_table.up.sql index 04475e21..25d3476d 100644 --- a/internal/database/migrations/20251018120649_create_project_chick_ins_table.up.sql +++ b/internal/database/migrations/20251018120649_create_project_chick_ins_table.up.sql @@ -1,6 +1,6 @@ CREATE TABLE IF NOT EXISTS project_chickins ( id BIGSERIAL PRIMARY KEY, - project_floc_kandang_id BIGINT NOT NULL, + project_flock_kandang_id BIGINT NOT NULL, chick_in_date DATE NOT NULL, quantity NUMERIC(15, 3) NOT NULL, note TEXT, @@ -15,8 +15,8 @@ DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_flock_kandangs') THEN ALTER TABLE project_chickins - ADD CONSTRAINT fk_project_floc_kandang_id - FOREIGN KEY (project_floc_kandang_id) + ADD CONSTRAINT fk_project_flock_kandang_id + FOREIGN KEY (project_flock_kandang_id) REFERENCES project_flock_kandangs(id) ON DELETE RESTRICT ON UPDATE CASCADE; END IF; @@ -31,6 +31,6 @@ BEGIN END $$; -- INDEXES -CREATE INDEX IF NOT EXISTS idx_project_chickins_project_floc_kandang_id ON project_chickins (project_floc_kandang_id); +CREATE INDEX IF NOT EXISTS idx_project_chickins_project_flock_kandang_id ON project_chickins (project_flock_kandang_id); CREATE INDEX IF NOT EXISTS idx_project_chickins_created_by ON project_chickins (created_by); \ No newline at end of file diff --git a/internal/database/migrations/20251020022357_create_project_flock_populations_table.down.sql b/internal/database/migrations/20251020022357_create_project_flock_populations_table.down.sql new file mode 100644 index 00000000..8fa11576 --- /dev/null +++ b/internal/database/migrations/20251020022357_create_project_flock_populations_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS project_flock_populations; \ No newline at end of file diff --git a/internal/database/migrations/20251020022357_create_project_flock_populations_table.up.sql b/internal/database/migrations/20251020022357_create_project_flock_populations_table.up.sql new file mode 100644 index 00000000..82b3e9a7 --- /dev/null +++ b/internal/database/migrations/20251020022357_create_project_flock_populations_table.up.sql @@ -0,0 +1,36 @@ +CREATE TABLE IF NOT EXISTS project_flock_populations ( + id BIGSERIAL PRIMARY KEY, + project_flock_kandang_id BIGINT NOT NULL, + initial_quantity NUMERIC(15, 3) NOT NULL, + current_quantity NUMERIC(15, 3) NOT NULL, + reserved_quantity NUMERIC(15, 3), + created_by BIGINT NOT NULL, + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now(), + deleted_at TIMESTAMPTZ +); + +-- FOREIGN KEYS (dijalankan setelah semua tabel parent ada) +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'project_flock_kandangs') THEN + ALTER TABLE project_flock_populations + ADD CONSTRAINT fk_project_flock_kandang_id + FOREIGN KEY (project_flock_kandang_id) + REFERENCES project_flock_kandangs(id) + ON DELETE RESTRICT ON UPDATE CASCADE; + END IF; + + IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'users') THEN + ALTER TABLE project_flock_populations + ADD CONSTRAINT fk_created_by + FOREIGN KEY (created_by) + REFERENCES users(id) + ON DELETE RESTRICT ON UPDATE CASCADE; + END IF; +END $$; + +-- INDEXES +CREATE INDEX IF NOT EXISTS idx_project_flock_populations_project_flock_kandang_id ON project_flock_populations (project_flock_kandang_id); + +CREATE INDEX IF NOT EXISTS idx_project_flock_populations_created_by ON project_flock_populations (created_by); \ No newline at end of file diff --git a/internal/entities/project_chickin.go b/internal/entities/project_chickin.go index 07536187..95a658c8 100644 --- a/internal/entities/project_chickin.go +++ b/internal/entities/project_chickin.go @@ -9,16 +9,16 @@ import ( const () type ProjectChickin struct { - Id uint `gorm:"primaryKey"` - ProjectFlocKandangId uint `gorm:"not null"` - ChickInDate time.Time `gorm:"not null"` - Quantity float64 `gorm:"not null"` - Note string `gorm:"type:text"` - CreatedBy uint `gorm:"not null"` - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + Id uint `gorm:"primaryKey"` + ProjectFlockKandangId uint `gorm:"not null"` + ChickInDate time.Time `gorm:"not null"` + Quantity float64 `gorm:"not null"` + Note string `gorm:"type:text"` + CreatedBy uint `gorm:"not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` - ProjectFlockKandang ProjectFlockKandang `gorm:"foreignKey:ProjectFlocKandangId;references:Id"` + ProjectFlockKandang ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"` CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` } diff --git a/internal/entities/project_flock_population.go b/internal/entities/project_flock_population.go new file mode 100644 index 00000000..184ace65 --- /dev/null +++ b/internal/entities/project_flock_population.go @@ -0,0 +1,22 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +type ProjectFlockPopulation struct { + Id uint `gorm:"primaryKey"` + ProjectFlockKandangId uint `gorm:"not null"` + InitialQuantity float64 `gorm:"type:numeric(15,3);not null"` + CurrentQuantity float64 `gorm:"type:numeric(15,3);not null"` + ReservedQuantity float64 `gorm:"type:numeric(15,3)"` + CreatedBy uint `gorm:"not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index"` + + ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"` + CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"` +} diff --git a/internal/modules/inventory/transfers/dto/transfer.dto.go b/internal/modules/inventory/transfers/dto/transfer.dto.go index 217e5038..82269852 100644 --- a/internal/modules/inventory/transfers/dto/transfer.dto.go +++ b/internal/modules/inventory/transfers/dto/transfer.dto.go @@ -23,6 +23,11 @@ type WarehouseSimpleDTO struct { Name string `json:"name"` } +type ProductSimpleDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + type AreaDTO struct { Id uint `json:"id"` Name string `json:"name"` @@ -33,6 +38,11 @@ type LocationDTO struct { Name string `json:"name"` } +type SuplierSimpleDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + type WarehouseDetailDTO struct { Id uint `json:"id"` Name string `json:"name"` @@ -57,17 +67,15 @@ type TransferDetailDTO struct { // Detail produk type TransferDetailItemDTO struct { - Id uint64 `json:"id"` - ProductId uint64 `json:"product_id"` - Quantity float64 `json:"quantity"` - BeforeQuantity float64 `json:"before_quantity"` - AfterQuantity float64 `json:"after_quantity"` + Id uint64 `json:"id"` + Proudct ProductSimpleDTO `json:"product"` + Quantity float64 `json:"quantity"` } // Delivery ekspedisi type TransferDeliveryDTO struct { Id uint64 `json:"id"` - SupplierId uint64 `json:"supplier_id"` + Suplier SuplierSimpleDTO `json:"suplier"` VehiclePlate string `json:"vehicle_plate"` DriverName string `json:"driver_name"` DocumentNumber string `json:"document_number"` @@ -146,9 +154,12 @@ func ToTransferListDTO(e entity.StockTransfer) TransferListDTO { var details []TransferDetailItemDTO for _, d := range e.Details { details = append(details, TransferDetailItemDTO{ - Id: d.Id, - ProductId: d.ProductId, - Quantity: d.Quantity, + Id: d.Id, + Proudct: ProductSimpleDTO{ + Id: d.Product.Id, + Name: d.Product.Name, + }, + Quantity: d.Quantity, }) } // Map deliveries @@ -164,8 +175,11 @@ func ToTransferListDTO(e entity.StockTransfer) TransferListDTO { }) } deliveries = append(deliveries, TransferDeliveryDTO{ - Id: del.Id, - SupplierId: del.SupplierId, + Id: del.Id, + Suplier: SuplierSimpleDTO{ + Id: del.Supplier.Id, + Name: del.Supplier.Name, + }, VehiclePlate: del.VehiclePlate, DriverName: del.DriverName, DocumentNumber: del.DocumentNumber, @@ -198,17 +212,23 @@ func ToTransferDetailDTO(e entity.StockTransfer) TransferDetailDTO { var details []TransferDetailItemDTO for _, d := range e.Details { details = append(details, TransferDetailItemDTO{ - Id: d.Id, - ProductId: d.ProductId, - Quantity: d.Quantity, + Id: d.Id, + Proudct: ProductSimpleDTO{ + Id: d.Product.Id, + Name: d.Product.Name, + }, + Quantity: d.Quantity, }) } // Map deliveries var deliveries []TransferDeliveryDTO for _, del := range e.Deliveries { deliveries = append(deliveries, TransferDeliveryDTO{ - Id: del.Id, - SupplierId: del.SupplierId, + Id: del.Id, + Suplier: SuplierSimpleDTO{ + Id: del.Supplier.Id, + Name: del.Supplier.Name, + }, VehiclePlate: del.VehiclePlate, DriverName: del.DriverName, DocumentNumber: del.DocumentNumber, diff --git a/internal/modules/inventory/transfers/services/transfer.service.go b/internal/modules/inventory/transfers/services/transfer.service.go index bdc8abf6..dbb4694b 100644 --- a/internal/modules/inventory/transfers/services/transfer.service.go +++ b/internal/modules/inventory/transfers/services/transfer.service.go @@ -60,7 +60,9 @@ func (s transferService) withRelations(db *gorm.DB) *gorm.DB { Preload("ToWarehouse.Location"). Preload("ToWarehouse.Area"). Preload("Details"). - Preload("Deliveries.Items") + Preload("Details.Product"). + Preload("Deliveries.Items"). + Preload("Deliveries.Supplier") } func (s transferService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.StockTransfer, int64, error) { diff --git a/internal/modules/production/chickins/controllers/chickin.controller.go b/internal/modules/production/chickins/controllers/chickin.controller.go index 6514f8c8..fadcbc3e 100644 --- a/internal/modules/production/chickins/controllers/chickin.controller.go +++ b/internal/modules/production/chickins/controllers/chickin.controller.go @@ -24,9 +24,9 @@ func NewChickinController(chickinService service.ChickinService) *ChickinControl func (u *ChickinController) GetAll(c *fiber.Ctx) error { query := &validation.Query{ - Page: c.QueryInt("page", 1), - Limit: c.QueryInt("limit", 10), - Search: c.Query("search", ""), + Page: c.QueryInt("page", 1), + Limit: c.QueryInt("limit", 10), + ProjectFlockKandangId: uint(c.QueryInt("project_flock_kandang_id", 0)), } result, totalResults, err := u.ChickinService.GetAll(c, query) @@ -88,7 +88,7 @@ func (u *ChickinController) CreateOne(c *fiber.Ctx) error { Code: fiber.StatusCreated, Status: "success", Message: "Create chickin successfully", - Data: result, + Data: dto.ToChickinListDTO(*result), }) } diff --git a/internal/modules/production/chickins/dto/chickin.dto.go b/internal/modules/production/chickins/dto/chickin.dto.go index c89caa0e..9fd29f3c 100644 --- a/internal/modules/production/chickins/dto/chickin.dto.go +++ b/internal/modules/production/chickins/dto/chickin.dto.go @@ -68,12 +68,12 @@ type ChickinBaseDTO struct { } type ChickinSimpleDTO struct { - Id uint `json:"id"` - ProjectFlocKandangId uint `json:"project_floc_kandang_id"` - ChickInDate time.Time `json:"chick_in_date"` - Quantity float64 `json:"quantity"` - Note string `json:"note"` - CreatedBy uint `json:"created_by"` + Id uint `json:"id"` + ProjectFlockKandangId uint `json:"project_flock_kandang_id"` + ChickInDate time.Time `json:"chick_in_date"` + Quantity float64 `json:"quantity"` + Note string `json:"note"` + CreatedBy uint `json:"created_by"` } type ChickinListDTO struct { @@ -197,7 +197,7 @@ func ToUserBaseDTO(e entity.User) UserBaseDTO { func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO { return ChickinBaseDTO{ Id: e.Id, - ProjectFlocKandangId: e.ProjectFlocKandangId, + ProjectFlocKandangId: e.ProjectFlockKandangId, ChickInDate: e.ChickInDate, Quantity: e.Quantity, Note: e.Note, @@ -206,12 +206,12 @@ func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO { func ToChickinSimpleDTO(e entity.ProjectChickin) ChickinSimpleDTO { return ChickinSimpleDTO{ - Id: e.Id, - ProjectFlocKandangId: e.ProjectFlocKandangId, - ChickInDate: e.ChickInDate, - Quantity: e.Quantity, - Note: e.Note, - CreatedBy: e.CreatedBy, + Id: e.Id, + ProjectFlockKandangId: e.ProjectFlockKandangId, + ChickInDate: e.ChickInDate, + Quantity: e.Quantity, + Note: e.Note, + CreatedBy: e.CreatedBy, } } diff --git a/internal/modules/production/chickins/module.go b/internal/modules/production/chickins/module.go index abfc56ca..116e2fbb 100644 --- a/internal/modules/production/chickins/module.go +++ b/internal/modules/production/chickins/module.go @@ -26,12 +26,13 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * auditlogrepo := rAuditLog.NewAuditLogRepository(db) warehouseRepo := rWarehouse.NewWarehouseRepository(db) projectflockkandangrepo := rProjectFlock.NewProjectFlockKandangRepository(db) + projectflockpopulationrepo := rProjectFlock.NewProjectFlockPopulationRepository(db) projectFlockRepo := rProjectFlock.NewProjectflockRepository(db) productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) userRepo := rUser.NewUserRepository(db) - chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, auditlogrepo, projectflockkandangrepo, validate) + chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, auditlogrepo, projectflockkandangrepo, projectflockpopulationrepo, validate) userService := sUser.NewUserService(userRepo, validate) ChickinRoutes(router, userService, chickinService) diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index fbb692fa..a11b21f7 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -31,28 +31,30 @@ type ChickinService interface { } type chickinService struct { - Log *logrus.Logger - Validate *validator.Validate - Repository repository.ProjectChickinRepository - KandangRepo KandangRepo.KandangRepository - WarehouseRepo rWarehouse.WarehouseRepository - ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository - ProjectFlockRepo rProjectFlock.ProjectflockRepository - AuditLogRepo AuditLogRepo.AuditLogRepository - ProjectflockKandangRepo rProjectFlockKandang.ProjectFlockKandangRepository + Log *logrus.Logger + Validate *validator.Validate + Repository repository.ProjectChickinRepository + KandangRepo KandangRepo.KandangRepository + WarehouseRepo rWarehouse.WarehouseRepository + ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository + ProjectFlockRepo rProjectFlock.ProjectflockRepository + AuditLogRepo AuditLogRepo.AuditLogRepository + ProjectflockKandangRepo rProjectFlockKandang.ProjectFlockKandangRepository + ProjectflockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository } -func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, auditLogRepo AuditLogRepo.AuditLogRepository, projectflockkandangRepo rProjectFlockKandang.ProjectFlockKandangRepository, validate *validator.Validate) ChickinService { +func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, auditLogRepo AuditLogRepo.AuditLogRepository, projectflockkandangRepo rProjectFlockKandang.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, validate *validator.Validate) ChickinService { return &chickinService{ - Log: utils.Log, - Validate: validate, - Repository: repo, - KandangRepo: kandangRepo, - WarehouseRepo: warehouseRepo, - ProductWarehouseRepo: productWarehouseRepo, - ProjectFlockRepo: projectFlockRepo, - AuditLogRepo: auditLogRepo, - ProjectflockKandangRepo: projectflockkandangRepo, + Log: utils.Log, + Validate: validate, + Repository: repo, + KandangRepo: kandangRepo, + WarehouseRepo: warehouseRepo, + ProductWarehouseRepo: productWarehouseRepo, + ProjectFlockRepo: projectFlockRepo, + AuditLogRepo: auditLogRepo, + ProjectflockKandangRepo: projectflockkandangRepo, + ProjectflockPopulationRepo: projectflockpopulationRepo, } } @@ -78,8 +80,9 @@ func (s chickinService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity chickins, 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+"%") + + if params.ProjectFlockKandangId != 0 { + return db.Where("project_flock_kandang_id = ?", params.ProjectFlockKandangId) } return db.Order("created_at DESC").Order("updated_at DESC") }) @@ -163,11 +166,11 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit } newChickin := &entity.ProjectChickin{ - ProjectFlocKandangId: projectflockkandang.ProjectFlockId, - ChickInDate: chickinDate, - Quantity: productWarehouse.Quantity, - Note: "", - CreatedBy: 1, //todo: ganti dengan + ProjectFlockKandangId: projectflockkandang.ProjectFlockId, + ChickInDate: chickinDate, + Quantity: productWarehouse.Quantity, + Note: "", + CreatedBy: 1, //todo: ganti dengan } err = s.Repository.CreateOne(c.Context(), newChickin, nil) @@ -188,45 +191,37 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) return nil, err } - - // masukan check apakah stock availability ada, jika ada update, jika tidak buat baru - stockAvailability := &entity.StockAvailability{ - EntityType: entity.EntityTypeProjectFlockKandang, - ReservedQuantity: productWarehouse.Quantity, - EntityId: projectflockkandang.Id, - ProductId: productWarehouse.ProductId, + // masukan data nya ke project flock population + // check apakah sudah ada + existingPopulation, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangID(c.Context(), req.ProjectFlockKandangId) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + s.Log.Errorf("Failed to get project flock population: %+v", err) + return nil, err } + if existingPopulation != nil { + // update quantity - var existingStockAvailability entity.StockAvailability - err = s.ProductWarehouseRepo.DB().WithContext(c.Context()). - Where("entity_type = ? AND entity_id = ? AND product_id = ?", stockAvailability.EntityType, stockAvailability.EntityId, stockAvailability.ProductId). - First(&existingStockAvailability).Error - - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - // buat baru - stockAvailability.ReservedQuantity = newChickin.Quantity - stockAvailability.Quantity = 0 - err = s.ProductWarehouseRepo.DB().WithContext(c.Context()).Create(stockAvailability).Error - if err != nil { - s.Log.Errorf("Failed to create stock availability: %+v", err) - return nil, err - } - } else { - s.Log.Errorf("Failed to get stock availability: %+v", err) + err = s.ProjectflockPopulationRepo.PatchOne(c.Context(), existingPopulation.Id, map[string]any{ + "reserved_quantity": newChickin.Quantity + existingPopulation.ReservedQuantity, + }, nil) + if err != nil { + s.Log.Errorf("Failed to update project flock population: %+v", err) return nil, err } } else { - // update existing - newQuantity := existingStockAvailability.ReservedQuantity + newChickin.Quantity - err = s.ProductWarehouseRepo.DB().WithContext(c.Context()). - Model(&existingStockAvailability). - Update("reserved_quantity", newQuantity).Error + // create new population + newPopulation := &entity.ProjectFlockPopulation{ + ProjectFlockKandangId: req.ProjectFlockKandangId, + InitialQuantity: 0, + CurrentQuantity: 0, + ReservedQuantity: newChickin.Quantity, + CreatedBy: 1, // todo: ganti dengan user login + } + err = s.ProjectflockPopulationRepo.CreateOne(c.Context(), newPopulation, nil) if err != nil { - s.Log.Errorf("Failed to update stock availability: %+v", err) + s.Log.Errorf("Failed to create project flock population: %+v", err) return nil, err } - } return s.GetOne(c, newChickin.Id) @@ -283,20 +278,21 @@ func (s *chickinService) Approve(c *fiber.Ctx, id uint) error { return err } - //pindahkan stock dari reserved ke actual stock - // get stock avaibility untuk di update - var stockAvailability entity.StockAvailability - err = s.ProductWarehouseRepo.DB().WithContext(c.Context()). - Where("entity_type = ? AND entity_id = ? ", entity.EntityTypeProjectFlockKandang, chickin.ProjectFlocKandangId). - First(&stockAvailability).Error + //pindahkan stock dari reserved ke actual stock pada table project flock population + population, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangID(c.Context(), chickin.ProjectFlockKandangId) if err != nil { - s.Log.Errorf("Failed to get stock availability: %+v", err) + s.Log.Errorf("Failed to get project flock population: %+v", err) return err } - newReservedQuantity := stockAvailability.ReservedQuantity - chickin.Quantity - if newReservedQuantity < 0 { - newReservedQuantity = 0 + err = s.ProjectflockPopulationRepo.PatchOne(c.Context(), population.Id, map[string]any{ + "reserved_quantity": population.ReservedQuantity - chickin.Quantity, + "initial_quantity": population.InitialQuantity + chickin.Quantity, + "current_quantity": population.CurrentQuantity + chickin.Quantity, + }, nil) + if err != nil { + s.Log.Errorf("Failed to update project flock population: %+v", err) + return err } return nil diff --git a/internal/modules/production/chickins/validations/chickin.validation.go b/internal/modules/production/chickins/validations/chickin.validation.go index b57950b0..c122c100 100644 --- a/internal/modules/production/chickins/validations/chickin.validation.go +++ b/internal/modules/production/chickins/validations/chickin.validation.go @@ -10,7 +10,7 @@ type Update struct { } 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"` + Page int `query:"page" validate:"omitempty,number,min=1"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` + ProjectFlockKandangId uint `query:"project_flock_kandang_id" validate:"omitempty,number,min=1"` } diff --git a/internal/modules/production/project_flocks/module.go b/internal/modules/production/project_flocks/module.go index 5f1afbe3..5b91ab13 100644 --- a/internal/modules/production/project_flocks/module.go +++ b/internal/modules/production/project_flocks/module.go @@ -8,6 +8,7 @@ import ( 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" diff --git a/internal/modules/production/project_flocks/repositories/project_flock_population_repository.go b/internal/modules/production/project_flocks/repositories/project_flock_population_repository.go new file mode 100644 index 00000000..cb4b0d5f --- /dev/null +++ b/internal/modules/production/project_flocks/repositories/project_flock_population_repository.go @@ -0,0 +1,35 @@ +package repository + +import ( + "context" + + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gorm.io/gorm" +) + +type ProjectFlockPopulationRepository interface { + repository.BaseRepository[entity.ProjectFlockPopulation] + GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (*entity.ProjectFlockPopulation, error) +} + +type projectFlockPopulationRepositoryImpl struct { + *repository.BaseRepositoryImpl[entity.ProjectFlockPopulation] +} + +func NewProjectFlockPopulationRepository(db *gorm.DB) ProjectFlockPopulationRepository { + return &projectFlockPopulationRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlockPopulation](db), + } +} + +func (r *projectFlockPopulationRepositoryImpl) GetByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (*entity.ProjectFlockPopulation, error) { + var record entity.ProjectFlockPopulation + err := r.DB().WithContext(ctx). + Where("project_flock_kandang_id = ?", projectFlockKandangID). + First(&record).Error + if err != nil { + return nil, err + } + return &record, nil +} From 748c959dbe53263998054b441010ff616d18a923 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Mon, 20 Oct 2025 11:36:38 +0700 Subject: [PATCH 07/11] FIX[BE]: fix json wrong json field name --- internal/modules/inventory/transfers/dto/transfer.dto.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/modules/inventory/transfers/dto/transfer.dto.go b/internal/modules/inventory/transfers/dto/transfer.dto.go index 82269852..cb85af94 100644 --- a/internal/modules/inventory/transfers/dto/transfer.dto.go +++ b/internal/modules/inventory/transfers/dto/transfer.dto.go @@ -38,7 +38,7 @@ type LocationDTO struct { Name string `json:"name"` } -type SuplierSimpleDTO struct { +type SupplierSimpleDTO struct { Id uint `json:"id"` Name string `json:"name"` } @@ -75,7 +75,7 @@ type TransferDetailItemDTO struct { // Delivery ekspedisi type TransferDeliveryDTO struct { Id uint64 `json:"id"` - Suplier SuplierSimpleDTO `json:"suplier"` + Supplier SupplierSimpleDTO `json:"supplier"` VehiclePlate string `json:"vehicle_plate"` DriverName string `json:"driver_name"` DocumentNumber string `json:"document_number"` @@ -176,7 +176,7 @@ func ToTransferListDTO(e entity.StockTransfer) TransferListDTO { } deliveries = append(deliveries, TransferDeliveryDTO{ Id: del.Id, - Suplier: SuplierSimpleDTO{ + Supplier: SupplierSimpleDTO{ Id: del.Supplier.Id, Name: del.Supplier.Name, }, @@ -225,7 +225,7 @@ func ToTransferDetailDTO(e entity.StockTransfer) TransferDetailDTO { for _, del := range e.Deliveries { deliveries = append(deliveries, TransferDeliveryDTO{ Id: del.Id, - Suplier: SuplierSimpleDTO{ + Supplier: SupplierSimpleDTO{ Id: del.Supplier.Id, Name: del.Supplier.Name, }, From a1f579f61651d2e92df7322be1e40a29d488a647 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Mon, 20 Oct 2025 12:55:19 +0700 Subject: [PATCH 08/11] feat(BE-119,135): add seeding and API documentation - Implement project data seeding logic - Add API documentation using Hoppscotch --- internal/database/seed/seeder.go | 78 +++++++++++++- .../chickins/services/chickin.service.go | 101 ++++++++++++++---- 2 files changed, 156 insertions(+), 23 deletions(-) diff --git a/internal/database/seed/seeder.go b/internal/database/seed/seeder.go index afa2a308..aa70b084 100644 --- a/internal/database/seed/seeder.go +++ b/internal/database/seed/seeder.go @@ -92,6 +92,9 @@ func Run(db *gorm.DB) error { if err := seedTransferStock(tx, adminID); err != nil { return err } + if err := seedChickin(tx, adminID); err != nil { + return err + } fmt.Println("✅ Master data seeding completed") return nil @@ -981,6 +984,7 @@ func seedProductWarehouse(tx *gorm.DB, createdBy uint) error { {ProductID: 1, WarehouseID: 1, Quantity: 100}, {ProductID: 2, WarehouseID: 2, Quantity: 200}, {ProductID: 2, WarehouseID: 1, Quantity: 300}, + {ProductID: 1, WarehouseID: 3, Quantity: 5000}, } for _, seed := range seeds { @@ -1005,8 +1009,7 @@ func seedProductWarehouse(tx *gorm.DB, createdBy uint) error { } func seedTransferStock(tx *gorm.DB, createdBy uint) error { - // Seeder Transfer Stock - // 1. Insert StockTransfer (header) + transfer := entity.StockTransfer{ FromWarehouseId: 1, ToWarehouseId: 2, @@ -1019,7 +1022,6 @@ func seedTransferStock(tx *gorm.DB, createdBy uint) error { return err } - // 2. Insert StockTransferDetail (detail) details := []entity.StockTransferDetail{ { StockTransferId: transfer.Id, @@ -1038,7 +1040,6 @@ func seedTransferStock(tx *gorm.DB, createdBy uint) error { } } - // 3. Insert StockTransferDelivery (delivery) deliveries := []entity.StockTransferDelivery{ { StockTransferId: transfer.Id, @@ -1082,6 +1083,75 @@ func seedTransferStock(tx *gorm.DB, createdBy uint) error { return nil } +func seedChickin(tx *gorm.DB, createdBy uint) error { + seeds := []struct { + ProjectFlockKandangId uint + ChickInDate string + Quantity float64 + Note string + }{ + {ProjectFlockKandangId: 1, ChickInDate: "2025-10-20", Quantity: 100, Note: "Seeder chickin 1"}, + {ProjectFlockKandangId: 2, ChickInDate: "2025-10-21", Quantity: 200, Note: "Seeder chickin 2"}, + } + + for _, seed := range seeds { + chickinDate, err := time.Parse("2006-01-02", seed.ChickInDate) + if err != nil { + return err + } + + // Insert ProjectChickin jika belum ada + var chickin entity.ProjectChickin + err = tx.Where("project_flock_kandang_id = ? AND chick_in_date = ?", seed.ProjectFlockKandangId, chickinDate). + First(&chickin).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + chickin = entity.ProjectChickin{ + ProjectFlockKandangId: seed.ProjectFlockKandangId, + ChickInDate: chickinDate, + Quantity: seed.Quantity, + Note: seed.Note, + CreatedBy: createdBy, + } + if err := tx.Create(&chickin).Error; err != nil { + return err + } + } else if err != nil { + return err + } + + // Update/Insert ProjectFlockPopulation + var population entity.ProjectFlockPopulation + err = tx.Where("project_flock_kandang_id = ?", seed.ProjectFlockKandangId).First(&population).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + population = entity.ProjectFlockPopulation{ + ProjectFlockKandangId: seed.ProjectFlockKandangId, + InitialQuantity: seed.Quantity, + CurrentQuantity: seed.Quantity, + ReservedQuantity: 0, + CreatedBy: createdBy, + } + if err := tx.Create(&population).Error; err != nil { + return err + } + } else if err != nil { + return err + } else { + // Update population quantities + if err := tx.Model(&entity.ProjectFlockPopulation{}). + Where("id = ?", population.Id). + Updates(map[string]any{ + "initial_quantity": population.InitialQuantity + seed.Quantity, + "current_quantity": population.CurrentQuantity + seed.Quantity, + "reserved_quantity": 0, + }).Error; err != nil { + return err + } + } + } + + return nil +} + func ptr[T any](v T) *T { return &v } diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index a11b21f7..f866e96d 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -77,16 +77,13 @@ func (s chickinService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity } offset := (params.Page - 1) * params.Limit - chickins, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) - if params.ProjectFlockKandangId != 0 { return db.Where("project_flock_kandang_id = ?", params.ProjectFlockKandangId) } return db.Order("created_at DESC").Order("updated_at DESC") }) - if err != nil { s.Log.Errorf("Failed to get chickins: %+v", err) return nil, 0, err @@ -95,6 +92,7 @@ func (s chickinService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity } func (s chickinService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectChickin, error) { + chickin, err := s.Repository.GetByID(c.Context(), id, s.withRelations) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Chickin not found") @@ -111,20 +109,18 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, err } - // ambil salah satu kandang dari project_floc_id dari kandang repository projectflockkandang, err := s.ProjectflockKandangRepo.GetByID(c.Context(), 1) if err != nil { s.Log.Errorf("Failed to get projectflock kandang: %+v", err) return nil, err } - // ambil warehouse dari kandangid + warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), projectflockkandang.KandangId) if err != nil { s.Log.Errorf("Failed to get warehouse: %+v", err) return nil, err } - // getprojectflock id with relation projectFlock, err := s.ProjectFlockRepo.GetByID( c.Context(), projectflockkandang.ProjectFlockId, @@ -132,20 +128,19 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return db.Preload("ProductCategory") }, ) - if err != nil { s.Log.Errorf("Failed to get project flock: %+v", err) return nil, fiber.NewError(fiber.StatusNotFound, "Project Flock not found") } - // ambil quantity + var productWarehouse entity.ProductWarehouse - err = s.ProductWarehouseRepo.DB().WithContext(c.Context()). + err = s.ProductWarehouseRepo.DB(). + WithContext(c.Context()). Joins("JOIN products ON products.id = product_warehouses.product_id"). Joins("JOIN product_categories ON product_categories.id = products.product_category_id"). Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", projectFlock.ProductCategory.Code, warehouse.Id). Order("created_at DESC"). First(&productWarehouse).Error - if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse") @@ -158,13 +153,11 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, fiber.NewError(fiber.StatusBadRequest, "Insufficient product quantity in warehouse") } - // masukan ke chic in chickinDate, err := utils.ParseDateString(req.ChickInDate) if err != nil { s.Log.Errorf("Failed to parse chickin date: %+v", err) return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid ChickInDate format") } - newChickin := &entity.ProjectChickin{ ProjectFlockKandangId: projectflockkandang.ProjectFlockId, ChickInDate: chickinDate, @@ -172,14 +165,12 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit Note: "", CreatedBy: 1, //todo: ganti dengan } - err = s.Repository.CreateOne(c.Context(), newChickin, nil) if err != nil { s.Log.Errorf("Failed to create chickin: %+v", err) return nil, err } - // Kurangi quantity di product warehouse updatedQuantity := productWarehouse.Quantity - newChickin.Quantity if updatedQuantity < 0 { updatedQuantity = 0 @@ -191,15 +182,13 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) return nil, err } - // masukan data nya ke project flock population - // check apakah sudah ada + existingPopulation, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangID(c.Context(), req.ProjectFlockKandangId) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { s.Log.Errorf("Failed to get project flock population: %+v", err) return nil, err } if existingPopulation != nil { - // update quantity err = s.ProjectflockPopulationRepo.PatchOne(c.Context(), existingPopulation.Id, map[string]any{ "reserved_quantity": newChickin.Quantity + existingPopulation.ReservedQuantity, @@ -209,7 +198,6 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, err } } else { - // create new population newPopulation := &entity.ProjectFlockPopulation{ ProjectFlockKandangId: req.ProjectFlockKandangId, InitialQuantity: 0, @@ -253,6 +241,31 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) } func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error { + // todo: cek apakah chickin sudah di approve atau belum + + chickin, err := s.Repository.GetByID(c.Context(), id, nil) + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "Chickin not found") + } + if err != nil { + s.Log.Errorf("Failed get chickin by id: %+v", err) + return err + } + + population, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangID(c.Context(), chickin.ProjectFlockKandangId) + if err != nil { + s.Log.Errorf("Failed to get project flock population: %+v", err) + return err + } + + err = s.ProjectflockPopulationRepo.PatchOne(c.Context(), population.Id, map[string]any{ + "reserved_quantity": population.ReservedQuantity - chickin.Quantity, + }, nil) + if err != nil { + s.Log.Errorf("Failed to update project flock population: %+v", err) + return err + } + if err := s.Repository.DeleteOne(c.Context(), id); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "Chickin not found") @@ -260,11 +273,62 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error { s.Log.Errorf("Failed to delete chickin: %+v", err) return err } + + projectflockkandang, err := s.ProjectflockKandangRepo.GetByID(c.Context(), population.ProjectFlockKandangId) + if err != nil { + s.Log.Errorf("Failed to get projectflock kandang: %+v", err) + return err + } + warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), projectflockkandang.KandangId) + if err != nil { + s.Log.Errorf("Failed to get warehouse: %+v", err) + return err + } + + projectFlock, err := s.ProjectFlockRepo.GetByID( + c.Context(), + projectflockkandang.ProjectFlockId, + func(db *gorm.DB) *gorm.DB { + return db.Preload("ProductCategory") + }, + ) + + if err != nil { + s.Log.Errorf("Failed to get project flock: %+v", err) + return fiber.NewError(fiber.StatusNotFound, "Project Flock not found") + } + var productWarehouse entity.ProductWarehouse + err = s.ProductWarehouseRepo.DB().WithContext(c.Context()). + Joins("JOIN products ON products.id = product_warehouses.product_id"). + Joins("JOIN product_categories ON product_categories.id = products.product_category_id"). + Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", projectFlock.ProductCategory.Code, warehouse.Id). + Order("created_at DESC"). + First(&productWarehouse).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse") + } + s.Log.Errorf("Failed to get product warehouse: %+v", err) + return err + } + + updatedQuantity := productWarehouse.Quantity + chickin.Quantity + err = s.ProductWarehouseRepo.PatchOne(c.Context(), productWarehouse.Id, map[string]any{ + "quantity": updatedQuantity, + }, nil) + if err != nil { + s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) + return err + } + return nil } func (s *chickinService) Approve(c *fiber.Ctx, id uint) error { + // todo: ini contoh akhir jika sudah approved + chickin, err := s.Repository.GetByID( c.Context(), id, @@ -278,7 +342,6 @@ func (s *chickinService) Approve(c *fiber.Ctx, id uint) error { return err } - //pindahkan stock dari reserved ke actual stock pada table project flock population population, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangID(c.Context(), chickin.ProjectFlockKandangId) if err != nil { s.Log.Errorf("Failed to get project flock population: %+v", err) From 542e5033606c74acc43688f7c0eb0b10aa5775aa Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 21 Oct 2025 09:01:02 +0700 Subject: [PATCH 09/11] fix[BE]: change dummy document path on transfer create --- .../modules/inventory/transfers/services/transfer.service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/modules/inventory/transfers/services/transfer.service.go b/internal/modules/inventory/transfers/services/transfer.service.go index dbb4694b..90642f6c 100644 --- a/internal/modules/inventory/transfers/services/transfer.service.go +++ b/internal/modules/inventory/transfers/services/transfer.service.go @@ -210,7 +210,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques SupplierId: uint64(delivery.SupplierID), VehiclePlate: delivery.VehiclePlate, DriverName: delivery.DriverName, - DocumentPath: "dummy duls", // todo: tunggu ada aws baru proses + DocumentPath: "https://tourism.gov.in/sites/default/files/2019-04/dummy-pdf_2.pdf", // todo: tunggu ada aws baru proses ShippingCostItem: delivery.DeliveryCostPerItem, ShippingCostTotal: delivery.DeliveryCost, }) From 1afbdea4ffb2a66471f167f27b7d32d9436eff50 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 21 Oct 2025 10:20:34 +0700 Subject: [PATCH 10/11] fix[BE]: fix logic pengambilan quatity untuk chick in dan penggunaan helper common --- ...create_stock_availabilities_table.down.sql | 1 - ...6_create_stock_availabilities_table.up.sql | 15 ------ ...019141014_create_audit_logs_table.down.sql | 1 - ...51019141014_create_audit_logs_table.up.sql | 13 ----- .../controllers/adjustment.controller.go | 4 +- .../services/adjustment.service.go | 28 +++-------- .../validations/adjustment.validation.go | 4 +- .../chickins/services/chickin.service.go | 49 ++++++++++--------- 8 files changed, 39 insertions(+), 76 deletions(-) delete mode 100644 internal/database/migrations/20251019040246_create_stock_availabilities_table.down.sql delete mode 100644 internal/database/migrations/20251019040246_create_stock_availabilities_table.up.sql delete mode 100644 internal/database/migrations/20251019141014_create_audit_logs_table.down.sql delete mode 100644 internal/database/migrations/20251019141014_create_audit_logs_table.up.sql diff --git a/internal/database/migrations/20251019040246_create_stock_availabilities_table.down.sql b/internal/database/migrations/20251019040246_create_stock_availabilities_table.down.sql deleted file mode 100644 index 1d50d98b..00000000 --- a/internal/database/migrations/20251019040246_create_stock_availabilities_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS stock_availabilities; \ No newline at end of file diff --git a/internal/database/migrations/20251019040246_create_stock_availabilities_table.up.sql b/internal/database/migrations/20251019040246_create_stock_availabilities_table.up.sql deleted file mode 100644 index bce6f7e6..00000000 --- a/internal/database/migrations/20251019040246_create_stock_availabilities_table.up.sql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TABLE stock_availabilities ( - id BIGSERIAL PRIMARY KEY, - entity_type VARCHAR(50) NOT NULL, - entity_id BIGINT NOT NULL, - product_id BIGINT, - quantity NUMERIC(15, 3) NOT NULL DEFAULT 0, - reserved_quantity NUMERIC(15, 3) NOT NULL DEFAULT 0, - unit VARCHAR(20), - last_updated TIMESTAMPTZ DEFAULT now(), - created_at TIMESTAMPTZ DEFAULT now(), - deleted_at TIMESTAMPTZ -); - -ALTER TABLE stock_availabilities -ADD CONSTRAINT fk_product_id FOREIGN KEY (product_id) REFERENCES products (id); \ No newline at end of file diff --git a/internal/database/migrations/20251019141014_create_audit_logs_table.down.sql b/internal/database/migrations/20251019141014_create_audit_logs_table.down.sql deleted file mode 100644 index 4cf6b411..00000000 --- a/internal/database/migrations/20251019141014_create_audit_logs_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS audit_logs; \ No newline at end of file diff --git a/internal/database/migrations/20251019141014_create_audit_logs_table.up.sql b/internal/database/migrations/20251019141014_create_audit_logs_table.up.sql deleted file mode 100644 index 13731dcc..00000000 --- a/internal/database/migrations/20251019141014_create_audit_logs_table.up.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE audit_logs ( - id BIGSERIAL PRIMARY KEY, - table_name VARCHAR(100) NOT NULL, - record_id BIGINT NOT NULL, - action VARCHAR(30) NOT NULL, - before_data JSONB, - after_data JSONB, - changed_by BIGINT, - created_at TIMESTAMPTZ DEFAULT now() -); - -ALTER TABLE audit_logs -ADD CONSTRAINT fk_changed_by FOREIGN KEY (changed_by) REFERENCES users (id); \ No newline at end of file diff --git a/internal/modules/inventory/adjustments/controllers/adjustment.controller.go b/internal/modules/inventory/adjustments/controllers/adjustment.controller.go index dc3df0a9..617a1b5f 100644 --- a/internal/modules/inventory/adjustments/controllers/adjustment.controller.go +++ b/internal/modules/inventory/adjustments/controllers/adjustment.controller.go @@ -49,8 +49,8 @@ func (u *AdjustmentController) AdjustmentHistory(c *fiber.Ctx) error { query := &validation.Query{ Page: c.QueryInt("page", 1), Limit: c.QueryInt("limit", 10), - ProductID: c.QueryInt("product_id", 0), - WarehouseID: c.QueryInt("warehouse_id", 0), + ProductID: uint(c.QueryInt("product_id", 0)), + WarehouseID: uint(c.QueryInt("warehouse_id", 0)), TransactionType: c.Query("transaction_type", ""), } diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index 69654b85..7a2d06bc 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -4,6 +4,8 @@ import ( "errors" "strings" + common "gitlab.com/mbugroup/lti-api.git/internal/common/service" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/validations" ProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" @@ -77,22 +79,11 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e } ctx := c.Context() - isProductExist, err := s.ProductRepo.IdExists(c.Context(), uint(req.ProductID)) - if err != nil { - s.Log.Errorf("Failed to check product existence: %+v", err) - return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product") - } - if !isProductExist { - return nil, fiber.NewError(fiber.StatusNotFound, "Product not found") - } - - isWarehouseExist, err := s.WarehouseRepo.IdExists(c.Context(), uint(req.WarehouseID)) - if err != nil { - s.Log.Errorf("Failed to check warehouse existence: %+v", err) - return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate warehouse") - } - if !isWarehouseExist { - return nil, fiber.NewError(fiber.StatusNotFound, "Warehouse not found") + if err := common.EnsureRelations(c.Context(), + common.RelationCheck{Name: "Product", ID: &req.ProductID, Exists: s.ProductRepo.IdExists}, + common.RelationCheck{Name: "Warehouse", ID: &req.WarehouseID, Exists: s.WarehouseRepo.IdExists}, + ); err != nil { + return nil, err } if req.Quantity <= 0 { @@ -118,6 +109,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e Quantity: 0, CreatedBy: 1, // TODO: should Get from auth middleware } + if err := s.ProductWarehouseRepo.CreateOne(ctx, newPW, nil); err != nil { s.Log.Errorf("Failed to create product warehouse: %+v", err) return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create product warehouse") @@ -126,7 +118,6 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e } err = s.StockLogsRepository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { - productWarehouse, err := s.ProductWarehouseRepo.GetProductWarehouseByProductAndWarehouseID(ctx, uint(req.ProductID), uint(req.WarehouseID)) if err != nil { s.Log.Errorf("Failed to get product warehouse: %+v", err) @@ -159,14 +150,12 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e s.Log.Errorf("Failed to create stock log: %+v", err) return err } - s.Log.Infof("Stock log created: %+v", newLog.Id) productWarehouse.Quantity = afterQuantity if err := s.ProductWarehouseRepo.WithTx(tx).UpdateOne(ctx, productWarehouse.Id, productWarehouse, nil); err != nil { s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) return err } - s.Log.Infof("Product warehouse quantity updated: %+v", productWarehouse.Id) createdLogId = newLog.Id return nil @@ -184,7 +173,6 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu if err := s.Validate.Struct(query); err != nil { return nil, 0, err } - offset := (query.Page - 1) * query.Limit isWarehousesExist, err := s.WarehouseRepo.IdExists(c.Context(), uint(query.WarehouseID)) diff --git a/internal/modules/inventory/adjustments/validations/adjustment.validation.go b/internal/modules/inventory/adjustments/validations/adjustment.validation.go index 7d2385cc..2e7259f2 100644 --- a/internal/modules/inventory/adjustments/validations/adjustment.validation.go +++ b/internal/modules/inventory/adjustments/validations/adjustment.validation.go @@ -11,7 +11,7 @@ type Create struct { type Query struct { Page int `query:"page" validate:"omitempty,min=1"` Limit int `query:"limit" validate:"omitempty,min=1,max=100"` - ProductID int `query:"product_id" validate:"omitempty,min=0"` - WarehouseID int `query:"warehouse_id" validate:"omitempty,min=0"` + ProductID uint `query:"product_id" validate:"omitempty,min=0"` + WarehouseID uint `query:"warehouse_id" validate:"omitempty,min=0"` TransactionType string `query:"transaction_type" validate:"omitempty,oneof=increase decrease"` } diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index f866e96d..64fe1e97 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -132,27 +132,33 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit s.Log.Errorf("Failed to get project flock: %+v", err) return nil, fiber.NewError(fiber.StatusNotFound, "Project Flock not found") } - - var productWarehouse entity.ProductWarehouse + var productWarehouses []entity.ProductWarehouse err = s.ProductWarehouseRepo.DB(). WithContext(c.Context()). Joins("JOIN products ON products.id = product_warehouses.product_id"). Joins("JOIN product_categories ON product_categories.id = products.product_category_id"). Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", projectFlock.ProductCategory.Code, warehouse.Id). Order("created_at DESC"). - First(&productWarehouse).Error + Find(&productWarehouses).Error if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse") - } - s.Log.Errorf("Failed to get product warehouse: %+v", err) + s.Log.Errorf("Failed to get product warehouses: %+v", err) return nil, err } - - if productWarehouse.Quantity < 1 { - return nil, fiber.NewError(fiber.StatusBadRequest, "Insufficient product quantity in warehouse") + if len(productWarehouses) == 0 { + return nil, fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse") } + // Jumlahkan semua quantity DOC + totalQuantity := 0.0 + for _, pw := range productWarehouses { + totalQuantity += pw.Quantity + } + + if totalQuantity < 1 { + return nil, fiber.NewError(fiber.StatusBadRequest, "Insufficient quantity in Product Warehouses") + } + + // Buat satu chickin dengan total quantity chickinDate, err := utils.ParseDateString(req.ChickInDate) if err != nil { s.Log.Errorf("Failed to parse chickin date: %+v", err) @@ -161,9 +167,9 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit newChickin := &entity.ProjectChickin{ ProjectFlockKandangId: projectflockkandang.ProjectFlockId, ChickInDate: chickinDate, - Quantity: productWarehouse.Quantity, + Quantity: totalQuantity, Note: "", - CreatedBy: 1, //todo: ganti dengan + CreatedBy: 1, //todo: ganti dengan user login } err = s.Repository.CreateOne(c.Context(), newChickin, nil) if err != nil { @@ -171,16 +177,15 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, err } - updatedQuantity := productWarehouse.Quantity - newChickin.Quantity - if updatedQuantity < 0 { - updatedQuantity = 0 - } - err = s.ProductWarehouseRepo.PatchOne(c.Context(), productWarehouse.Id, map[string]any{ - "quantity": updatedQuantity, - }, nil) - if err != nil { - s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) - return nil, err + // Update semua product warehouse: set quantity jadi 0 + for _, pw := range productWarehouses { + err = s.ProductWarehouseRepo.PatchOne(c.Context(), pw.Id, map[string]any{ + "quantity": 0, + }, nil) + if err != nil { + s.Log.Errorf("Failed to update product warehouse quantity: %+v", err) + return nil, err + } } existingPopulation, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangID(c.Context(), req.ProjectFlockKandangId) From b1b63d266a343aeb16094eef6020e7a228c1b480 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Tue, 21 Oct 2025 11:48:54 +0700 Subject: [PATCH 11/11] fix[BE]: menggunakan base dto dari dto utama entity ketimbang buat simple dto baru --- .../production/chickins/dto/chickin.dto.go | 178 +++++++----------- .../chickins/services/chickin.service.go | 6 +- 2 files changed, 73 insertions(+), 111 deletions(-) diff --git a/internal/modules/production/chickins/dto/chickin.dto.go b/internal/modules/production/chickins/dto/chickin.dto.go index 9fd29f3c..96115b58 100644 --- a/internal/modules/production/chickins/dto/chickin.dto.go +++ b/internal/modules/production/chickins/dto/chickin.dto.go @@ -4,68 +4,42 @@ import ( "time" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + areaBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/dto" + fcrBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto" + flockBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto" + kandangBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto" + locationBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto" + productCategoryBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/product-categories/dto" + userBaseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" ) // === DTO Structs (ordered) === -type FlockDTO struct { - Id uint `json:"id"` - Name string `json:"name"` -} - -type KandangDTO struct { - Id uint `json:"id"` - Name string `json:"name"` -} - -type ProductCategoryDTO struct { - Id uint `json:"id"` - Name string `json:"name"` -} - -type AreaDTO struct { - Id uint `json:"id"` - Name string `json:"name"` -} - -type FcrDTO struct { - Id uint `json:"id"` - Name string `json:"name"` -} - -type LocationDTO struct { - Id uint `json:"id"` - Name string `json:"name"` +type ChickinBaseDTO struct { + Id uint `json:"id"` + ProjectFlockKandang *ProjectFlockKandangDTO `json:"project_flock_kandang"` + ChickInDate time.Time `json:"chick_in_date"` + Quantity float64 `json:"quantity"` + Note string `json:"note"` } type ProjectFlockDTO struct { - Id uint `json:"id"` - Period int `json:"period"` - Flock *FlockDTO `json:"flock"` - ProductCategory *ProductCategoryDTO `json:"product_category"` - Area *AreaDTO `json:"area"` - Fcr *FcrDTO `json:"fcr"` - Location *LocationDTO `json:"location"` + Id uint `json:"id"` + Period int `json:"period"` + Flock *flockBaseDTO.FlockBaseDTO `json:"flock"` + ProductCategory *productCategoryBaseDTO.ProductCategoryBaseDTO `json:"product_category"` + Area *areaBaseDTO.AreaBaseDTO `json:"area"` + Fcr *fcrBaseDTO.FcrBaseDTO `json:"fcr"` + Location *locationBaseDTO.LocationBaseDTO `json:"location"` } type ProjectFlockKandangDTO struct { - Id uint `json:"id"` - ProjectFlock *ProjectFlockDTO `json:"project_flock"` - Kandang *KandangDTO `json:"kandang"` + Id uint `json:"id"` + ProjectFlock *ProjectFlockDTO `json:"project_flock"` + Kandang *kandangBaseDTO.KandangBaseDTO `json:"kandang"` } -type UserBaseDTO struct { - Id uint `json:"id"` - Name string `json:"name"` -} - -type ChickinBaseDTO struct { - Id uint `json:"id"` - ProjectFlocKandangId uint `json:"project_floc_kandang_id"` - ChickInDate time.Time `json:"chick_in_date"` - Quantity float64 `json:"quantity"` - Note string `json:"note"` -} +// gunakan base DTO dari package users type ChickinSimpleDTO struct { Id uint `json:"id"` @@ -78,10 +52,10 @@ type ChickinSimpleDTO struct { type ChickinListDTO struct { ChickinBaseDTO - ProjectFlockKandang *ProjectFlockKandangDTO `json:"project_flock_kandang"` - CreatedUser *UserBaseDTO `json:"created_user"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ProjectFlockKandang *ProjectFlockKandangDTO `json:"project_flock_kandang"` + CreatedUser *userBaseDTO.UserBaseDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } type ChickinDetailDTO struct { @@ -90,72 +64,58 @@ type ChickinDetailDTO struct { // === Mapper Functions (ordered) === -func ToFlockDTO(e entity.Flock) FlockDTO { - return FlockDTO{ - Id: e.Id, - Name: e.Name, - } +func ToFlockDTO(e entity.Flock) flockBaseDTO.FlockBaseDTO { + return flockBaseDTO.ToFlockBaseDTO(e) } -func ToKandangDTO(e entity.Kandang) KandangDTO { - return KandangDTO{ - Id: e.Id, - Name: e.Name, - } +func ToKandangDTO(e entity.Kandang) kandangBaseDTO.KandangBaseDTO { + return kandangBaseDTO.ToKandangBaseDTO(e) } -func ToProductCategoryDTO(e entity.ProductCategory) ProductCategoryDTO { - return ProductCategoryDTO{ - Id: e.Id, - Name: e.Name, - } +func ToProductCategoryDTO(e entity.ProductCategory) productCategoryBaseDTO.ProductCategoryBaseDTO { + return productCategoryBaseDTO.ToProductCategoryBaseDTO(e) } -func ToAreaDTO(e entity.Area) AreaDTO { - return AreaDTO{ - Id: e.Id, - Name: e.Name, - } +func ToAreaDTO(e entity.Area) areaBaseDTO.AreaBaseDTO { + return areaBaseDTO.ToAreaBaseDTO(e) } -func ToFcrDTO(e entity.Fcr) FcrDTO { - return FcrDTO{ - Id: e.Id, - Name: e.Name, - } +func ToFcrDTO(e entity.Fcr) fcrBaseDTO.FcrBaseDTO { + return fcrBaseDTO.ToFcrBaseDTO(e) } -func ToLocationDTO(e entity.Location) LocationDTO { - return LocationDTO{ - Id: e.Id, - Name: e.Name, - } +func ToLocationDTO(e entity.Location) locationBaseDTO.LocationBaseDTO { + return locationBaseDTO.ToLocationBaseDTO(e) +} + +func ToUserBaseDTO(e entity.User) userBaseDTO.UserBaseDTO { + return userBaseDTO.ToUserBaseDTO(e) } func ToProjectFlockDTO(e entity.ProjectFlock) ProjectFlockDTO { - var flock *FlockDTO + var flock *flockBaseDTO.FlockBaseDTO if e.Flock.Id != 0 { - mapped := ToFlockDTO(e.Flock) + mapped := flockBaseDTO.ToFlockBaseDTO(e.Flock) flock = &mapped } - var productCategory *ProductCategoryDTO + var productCategory *productCategoryBaseDTO.ProductCategoryBaseDTO if e.ProductCategory.Id != 0 { - mapped := ToProductCategoryDTO(e.ProductCategory) + mapped := productCategoryBaseDTO.ToProductCategoryBaseDTO(e.ProductCategory) productCategory = &mapped } - var area *AreaDTO + var area *areaBaseDTO.AreaBaseDTO if e.Area.Id != 0 { - mapped := ToAreaDTO(e.Area) + mapped := areaBaseDTO.ToAreaBaseDTO(e.Area) area = &mapped } - var fcr *FcrDTO + var fcr *fcrBaseDTO.FcrBaseDTO if e.Fcr.Id != 0 { - mapped := ToFcrDTO(e.Fcr) + mapped := fcrBaseDTO.ToFcrBaseDTO(e.Fcr) fcr = &mapped } - var location *LocationDTO + var location *locationBaseDTO.LocationBaseDTO if e.Location.Id != 0 { - mapped := ToLocationDTO(e.Location) + mapped := locationBaseDTO.ToLocationBaseDTO(e.Location) location = &mapped } return ProjectFlockDTO{ @@ -175,9 +135,9 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD mapped := ToProjectFlockDTO(e.ProjectFlock) pf = &mapped } - var kandang *KandangDTO + var kandang *kandangBaseDTO.KandangBaseDTO if e.Kandang.Id != 0 { - mapped := ToKandangDTO(e.Kandang) + mapped := kandangBaseDTO.ToKandangBaseDTO(e.Kandang) kandang = &mapped } return ProjectFlockKandangDTO{ @@ -187,20 +147,18 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD } } -func ToUserBaseDTO(e entity.User) UserBaseDTO { - return UserBaseDTO{ - Id: e.Id, - Name: e.Name, - } -} - func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO { + var pfk *ProjectFlockKandangDTO + if e.ProjectFlockKandang.Id != 0 { + mapped := ToProjectFlockKandangDTO(e.ProjectFlockKandang) + pfk = &mapped + } return ChickinBaseDTO{ - Id: e.Id, - ProjectFlocKandangId: e.ProjectFlockKandangId, - ChickInDate: e.ChickInDate, - Quantity: e.Quantity, - Note: e.Note, + Id: e.Id, + ProjectFlockKandang: pfk, + ChickInDate: e.ChickInDate, + Quantity: e.Quantity, + Note: e.Note, } } @@ -216,9 +174,9 @@ func ToChickinSimpleDTO(e entity.ProjectChickin) ChickinSimpleDTO { } func ToChickinListDTO(e entity.ProjectChickin) ChickinListDTO { - var createdUser *UserBaseDTO + var createdUser *userBaseDTO.UserBaseDTO if e.CreatedUser.Id != 0 { - mapped := ToUserBaseDTO(e.CreatedUser) + mapped := userBaseDTO.ToUserBaseDTO(e.CreatedUser) createdUser = &mapped } var pfk *ProjectFlockKandangDTO diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index 64fe1e97..46bc8069 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -62,12 +62,16 @@ func (s chickinService) withRelations(db *gorm.DB) *gorm.DB { return db. Preload("CreatedUser"). Preload("ProjectFlockKandang.Kandang"). + Preload("ProjectFlockKandang.Kandang.Location"). + Preload("ProjectFlockKandang.Kandang.Location.Area"). + Preload("ProjectFlockKandang.Kandang.Pic"). Preload("ProjectFlockKandang.ProjectFlock"). Preload("ProjectFlockKandang.ProjectFlock.Flock"). Preload("ProjectFlockKandang.ProjectFlock.ProductCategory"). Preload("ProjectFlockKandang.ProjectFlock.Area"). Preload("ProjectFlockKandang.ProjectFlock.Fcr"). - Preload("ProjectFlockKandang.ProjectFlock.Location") + Preload("ProjectFlockKandang.ProjectFlock.Location"). + Preload("ProjectFlockKandang.ProjectFlock.Location.Area") }