diff --git a/internal/entities/marketing.go b/internal/entities/marketing.go new file mode 100644 index 00000000..1ae4d8c3 --- /dev/null +++ b/internal/entities/marketing.go @@ -0,0 +1,26 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +type Marketing struct { + Id uint `gorm:"primaryKey;autoIncrement"` + SoNumber string `gorm:"uniqueIndex;not null"` + CustomerId uint `gorm:"not null"` + SoDocs string `gorm:"type:varchar(20)"` + SoDate time.Time `gorm:"type:date;not null"` + SalesPersonId uint `gorm:"not null"` + Notes string `gorm:"type:text"` + CreatedBy uint `gorm:"not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + Customer Customer `gorm:"foreignKey:CustomerId;references:Id"` + SalesPerson User `gorm:"foreignKey:SalesPersonId;references:Id"` + CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` + Products []MarketingProduct `gorm:"foreignKey:MarketingId;references:Id"` +} diff --git a/internal/entities/marketing_delivery_product.go b/internal/entities/marketing_delivery_product.go new file mode 100644 index 00000000..85b4591a --- /dev/null +++ b/internal/entities/marketing_delivery_product.go @@ -0,0 +1,24 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +type MarketingDeliveryProduct struct { + Id uint `gorm:"primaryKey;autoIncrement"` + MarketingProductId uint `gorm:"uniqueIndex;not null"` + Qty float64 `gorm:"type:numeric(15,3);not null"` + UnitPrice float64 `gorm:"type:numeric(15,3);not null"` + TotalWeight float64 `gorm:"type:numeric(15,3);not null"` + AvgWeight float64 `gorm:"type:numeric(15,3);not null"` + TotalPrice float64 `gorm:"type:numeric(15,3);not null"` + DeliveryDate *time.Time `gorm:"type:timestamptz"` + VehicleNumber string `gorm:"type:varchar(50)"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + MarketingProduct MarketingProduct `gorm:"foreignKey:MarketingProductId;references:Id"` +} diff --git a/internal/entities/marketing_product.go b/internal/entities/marketing_product.go new file mode 100644 index 00000000..2e6aef58 --- /dev/null +++ b/internal/entities/marketing_product.go @@ -0,0 +1,24 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +type MarketingProduct struct { + Id uint `gorm:"primaryKey;autoIncrement"` + MarketingId uint `gorm:"not null"` + ProductWarehouseId uint `gorm:"not null"` + Qty float64 `gorm:"type:numeric(15,3);not null"` + UnitPrice float64 `gorm:"type:numeric(15,3);not null"` + AvgWeight float64 `gorm:"type:numeric(15,3);not null"` + TotalWeight float64 `gorm:"type:numeric(15,3);not null"` + TotalPrice float64 `gorm:"type:numeric(15,3);not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + Marketing Marketing `gorm:"foreignKey:MarketingId;references:Id"` + ProductWarehouse ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"` +} diff --git a/internal/entities/sales-orders.go b/internal/entities/sales-orders.go new file mode 100644 index 00000000..48615607 --- /dev/null +++ b/internal/entities/sales-orders.go @@ -0,0 +1,18 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +type SalesOrders struct { + Id uint `gorm:"primaryKey"` + Name string `gorm:"not null;uniqueIndex:idx_name,where:deleted_at IS NULL"` + CreatedBy uint `gorm:"not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` +} diff --git a/internal/modules/marketing/module.go b/internal/modules/marketing/module.go new file mode 100644 index 00000000..9bf4f018 --- /dev/null +++ b/internal/modules/marketing/module.go @@ -0,0 +1,13 @@ +package marketing + +import ( + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +type MarketingModule struct{} + +func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + RegisterRoutes(router, db, validate) +} diff --git a/internal/modules/marketing/route.go b/internal/modules/marketing/route.go new file mode 100644 index 00000000..1b72b8cb --- /dev/null +++ b/internal/modules/marketing/route.go @@ -0,0 +1,25 @@ +package marketing + +import ( + "gitlab.com/mbugroup/lti-api.git/internal/modules" + + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + + salesOrderss "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders" + // MODULE IMPORTS +) + +func RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + group := router.Group("/marketing") + + allModules := []modules.Module{ + salesOrderss.SalesOrdersModule{}, + // MODULE REGISTRY + } + + for _, m := range allModules { + m.RegisterRoutes(group, db, validate) + } +} diff --git a/internal/modules/marketing/sales-orders/controllers/sales-orders.controller.go b/internal/modules/marketing/sales-orders/controllers/sales-orders.controller.go new file mode 100644 index 00000000..837a8ee0 --- /dev/null +++ b/internal/modules/marketing/sales-orders/controllers/sales-orders.controller.go @@ -0,0 +1,144 @@ +package controller + +import ( + "math" + "strconv" + + "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/dto" + service "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/validations" + "gitlab.com/mbugroup/lti-api.git/internal/response" + + "github.com/gofiber/fiber/v2" +) + +type SalesOrdersController struct { + SalesOrdersService service.SalesOrdersService +} + +func NewSalesOrdersController(salesOrdersService service.SalesOrdersService) *SalesOrdersController { + return &SalesOrdersController{ + SalesOrdersService: salesOrdersService, + } +} + +func (u *SalesOrdersController) GetAll(c *fiber.Ctx) error { + query := &validation.Query{ + Page: c.QueryInt("page", 1), + Limit: c.QueryInt("limit", 10), + Search: c.Query("search", ""), + } + + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") + } + + result, totalResults, err := u.SalesOrdersService.GetAll(c, query) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.SuccessWithPaginate[dto.SalesOrdersListDTO]{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get all salesOrderss successfully", + Meta: response.Meta{ + Page: query.Page, + Limit: query.Limit, + TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), + TotalResults: totalResults, + }, + Data: dto.ToSalesOrdersListDTOs(result), + }) +} + +func (u *SalesOrdersController) 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.SalesOrdersService.GetOne(c, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get salesOrders successfully", + Data: dto.ToSalesOrdersListDTO(*result), + }) +} + +func (u *SalesOrdersController) CreateOne(c *fiber.Ctx) error { + req := new(validation.Create) + + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + // result, err := u.SalesOrdersService.CreateOne(c, req) + // if err != nil { + // return err + // } + + return c.Status(fiber.StatusCreated). + JSON(response.Success{ + Code: fiber.StatusCreated, + Status: "success", + Message: "Create salesOrders successfully", + Data: req, + }) +} + +func (u *SalesOrdersController) UpdateOne(c *fiber.Ctx) error { + req := new(validation.Update) + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + if err := c.BodyParser(req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + } + + result, err := u.SalesOrdersService.UpdateOne(c, req, uint(id)) + if err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Update salesOrders successfully", + Data: dto.ToSalesOrdersListDTO(*result), + }) +} + +func (u *SalesOrdersController) DeleteOne(c *fiber.Ctx) error { + param := c.Params("id") + + id, err := strconv.Atoi(param) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") + } + + if err := u.SalesOrdersService.DeleteOne(c, uint(id)); err != nil { + return err + } + + return c.Status(fiber.StatusOK). + JSON(response.Common{ + Code: fiber.StatusOK, + Status: "success", + Message: "Delete salesOrders successfully", + }) +} diff --git a/internal/modules/marketing/sales-orders/dto/sales-orders.dto.go b/internal/modules/marketing/sales-orders/dto/sales-orders.dto.go new file mode 100644 index 00000000..6dd7c8e3 --- /dev/null +++ b/internal/modules/marketing/sales-orders/dto/sales-orders.dto.go @@ -0,0 +1,64 @@ +package dto + +import ( + "time" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" +) + +// === DTO Structs === + +type SalesOrdersBaseDTO struct { + Id uint `json:"id"` + Name string `json:"name"` +} + +type SalesOrdersListDTO struct { + SalesOrdersBaseDTO + CreatedUser *userDTO.UserBaseDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type SalesOrdersDetailDTO struct { + SalesOrdersListDTO +} + +// === Mapper Functions === + +func ToSalesOrdersBaseDTO(e entity.SalesOrders) SalesOrdersBaseDTO { + return SalesOrdersBaseDTO{ + Id: e.Id, + Name: e.Name, + } +} + +func ToSalesOrdersListDTO(e entity.SalesOrders) SalesOrdersListDTO { + var createdUser *userDTO.UserBaseDTO + if e.CreatedUser.Id != 0 { + mapped := userDTO.ToUserBaseDTO(e.CreatedUser) + createdUser = &mapped + } + + return SalesOrdersListDTO{ + SalesOrdersBaseDTO: ToSalesOrdersBaseDTO(e), + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedUser: createdUser, + } +} + +func ToSalesOrdersListDTOs(e []entity.SalesOrders) []SalesOrdersListDTO { + result := make([]SalesOrdersListDTO, len(e)) + for i, r := range e { + result[i] = ToSalesOrdersListDTO(r) + } + return result +} + +func ToSalesOrdersDetailDTO(e entity.SalesOrders) SalesOrdersDetailDTO { + return SalesOrdersDetailDTO{ + SalesOrdersListDTO: ToSalesOrdersListDTO(e), + } +} diff --git a/internal/modules/marketing/sales-orders/module.go b/internal/modules/marketing/sales-orders/module.go new file mode 100644 index 00000000..12310354 --- /dev/null +++ b/internal/modules/marketing/sales-orders/module.go @@ -0,0 +1,26 @@ +package sales_orders + +import ( + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" + + rSalesOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories" + sSalesOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services" + + rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories" + sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" +) + +type SalesOrdersModule struct{} + +func (SalesOrdersModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { + salesOrdersRepo := rSalesOrders.NewSalesOrdersRepository(db) + userRepo := rUser.NewUserRepository(db) + + salesOrdersService := sSalesOrders.NewSalesOrdersService(salesOrdersRepo, validate) + userService := sUser.NewUserService(userRepo, validate) + + SalesOrdersRoutes(router, userService, salesOrdersService) +} + diff --git a/internal/modules/marketing/sales-orders/repositories/sales-orders.repository.go b/internal/modules/marketing/sales-orders/repositories/sales-orders.repository.go new file mode 100644 index 00000000..5f8cfe79 --- /dev/null +++ b/internal/modules/marketing/sales-orders/repositories/sales-orders.repository.go @@ -0,0 +1,21 @@ +package repository + +import ( + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + "gorm.io/gorm" +) + +type SalesOrdersRepository interface { + repository.BaseRepository[entity.SalesOrders] +} + +type SalesOrdersRepositoryImpl struct { + *repository.BaseRepositoryImpl[entity.SalesOrders] +} + +func NewSalesOrdersRepository(db *gorm.DB) SalesOrdersRepository { + return &SalesOrdersRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.SalesOrders](db), + } +} diff --git a/internal/modules/marketing/sales-orders/route.go b/internal/modules/marketing/sales-orders/route.go new file mode 100644 index 00000000..455977fb --- /dev/null +++ b/internal/modules/marketing/sales-orders/route.go @@ -0,0 +1,28 @@ +package sales_orders + +import ( + // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + controller "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/controllers" + salesOrders "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/services" + user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" + + "github.com/gofiber/fiber/v2" +) + +func SalesOrdersRoutes(v1 fiber.Router, u user.UserService, s salesOrders.SalesOrdersService) { + ctrl := controller.NewSalesOrdersController(s) + + route := v1.Group("/sales-orders") + + // route.Get("/", m.Auth(u), ctrl.GetAll) + // route.Post("/", m.Auth(u), ctrl.CreateOne) + // route.Get("/:id", m.Auth(u), ctrl.GetOne) + // route.Patch("/:id", m.Auth(u), ctrl.UpdateOne) + // route.Delete("/:id", m.Auth(u), ctrl.DeleteOne) + + route.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/marketing/sales-orders/services/sales-orders.service.go b/internal/modules/marketing/sales-orders/services/sales-orders.service.go new file mode 100644 index 00000000..33c93f6d --- /dev/null +++ b/internal/modules/marketing/sales-orders/services/sales-orders.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/marketing/sales-orders/repositories" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/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 SalesOrdersService interface { + GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.SalesOrders, int64, error) + GetOne(ctx *fiber.Ctx, id uint) (*entity.SalesOrders, error) + CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.SalesOrders, error) + UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.SalesOrders, error) + DeleteOne(ctx *fiber.Ctx, id uint) error +} + +type salesOrdersService struct { + Log *logrus.Logger + Validate *validator.Validate + Repository repository.SalesOrdersRepository +} + +func NewSalesOrdersService(repo repository.SalesOrdersRepository, validate *validator.Validate) SalesOrdersService { + return &salesOrdersService{ + Log: utils.Log, + Validate: validate, + Repository: repo, + } +} + +func (s salesOrdersService) withRelations(db *gorm.DB) *gorm.DB { + return db.Preload("CreatedUser") +} + +func (s salesOrdersService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.SalesOrders, int64, error) { + if err := s.Validate.Struct(params); err != nil { + return nil, 0, err + } + + offset := (params.Page - 1) * params.Limit + + salesOrderss, 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 salesOrderss: %+v", err) + return nil, 0, err + } + return salesOrderss, total, nil +} + +func (s salesOrdersService) GetOne(c *fiber.Ctx, id uint) (*entity.SalesOrders, error) { + salesOrders, err := s.Repository.GetByID(c.Context(), id, s.withRelations) + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "SalesOrders not found") + } + if err != nil { + s.Log.Errorf("Failed get salesOrders by id: %+v", err) + return nil, err + } + return salesOrders, nil +} + +func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.SalesOrders, error) { + if err := s.Validate.Struct(req); err != nil { + return nil, err + } + + createBody := &entity.SalesOrders{ + Name: req.Name, + } + + if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { + s.Log.Errorf("Failed to create salesOrders: %+v", err) + return nil, err + } + + return s.GetOne(c, createBody.Id) +} + +func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.SalesOrders, 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, "SalesOrders not found") + } + s.Log.Errorf("Failed to update salesOrders: %+v", err) + return nil, err + } + + return s.GetOne(c, id) +} + +func (s salesOrdersService) 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, "SalesOrders not found") + } + s.Log.Errorf("Failed to delete salesOrders: %+v", err) + return err + } + return nil +} diff --git a/internal/modules/marketing/sales-orders/validations/sales-orders.validation.go b/internal/modules/marketing/sales-orders/validations/sales-orders.validation.go new file mode 100644 index 00000000..7d16d3ee --- /dev/null +++ b/internal/modules/marketing/sales-orders/validations/sales-orders.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,gt=0"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` + Search string `query:"search" validate:"omitempty,max=50"` +} diff --git a/internal/modules/production/project_flocks/controllers/projectflock.controller.go b/internal/modules/production/project_flocks/controllers/projectflock.controller.go index d3b0061c..cf2dff05 100644 --- a/internal/modules/production/project_flocks/controllers/projectflock.controller.go +++ b/internal/modules/production/project_flocks/controllers/projectflock.controller.go @@ -71,6 +71,10 @@ func (u *ProjectflockController) GetAll(c *fiber.Ctx) error { if period := c.QueryInt("period", 0); period > 0 { query.Period = period } + if category := c.Query("category", ""); category != "" { + query.Category = category + + } if kandangRaw := c.Query("kandang_id", c.Query("kandang_ids", "")); kandangRaw != "" { ids, err := parseUintList(kandangRaw) 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 index d914b128..a2b56dce 100644 --- a/internal/modules/production/project_flocks/repositories/project_flock_population_repository.go +++ b/internal/modules/production/project_flocks/repositories/project_flock_population_repository.go @@ -14,6 +14,7 @@ type ProjectFlockPopulationRepository interface { ExistsByProjectChickinID(ctx context.Context, projectChickinID uint) (bool, error) GetByProjectChickinIDAndProductWarehouseID(ctx context.Context, projectChickinID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error) GetByProjectFlockKandangIDAndProductWarehouseID(ctx context.Context, projectFlockKandangID uint, productWarehouseID uint) ([]entity.ProjectFlockPopulation, error) + GetTotalQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error) // subset of base repository methods used by services CreateOne(ctx context.Context, entity *entity.ProjectFlockPopulation, modifier func(*gorm.DB) *gorm.DB) error @@ -91,3 +92,17 @@ func (r *projectFlockPopulationRepositoryImpl) GetByProjectFlockKandangIDAndProd } return records, nil } + +func (r *projectFlockPopulationRepositoryImpl) GetTotalQtyByProjectFlockKandangID(ctx context.Context, projectFlockKandangID uint) (float64, error) { + var total float64 + err := r.DB().WithContext(ctx). + Table("project_flock_populations"). + Select("COALESCE(SUM(total_qty), 0) AS total_qty"). + Joins("JOIN project_chickins ON project_chickins.id = project_flock_populations.project_chickin_id"). + Where("project_chickins.project_flock_kandang_id = ?", projectFlockKandangID). + Scan(&total).Error + if err != nil { + return 0, err + } + return total, nil +} diff --git a/internal/modules/production/project_flocks/repositories/projectflock.repository.go b/internal/modules/production/project_flocks/repositories/projectflock.repository.go index a8fab919..41cbde12 100644 --- a/internal/modules/production/project_flocks/repositories/projectflock.repository.go +++ b/internal/modules/production/project_flocks/repositories/projectflock.repository.go @@ -127,6 +127,9 @@ func (r *ProjectflockRepositoryImpl) applyQueryFilters(db *gorm.DB, params *vali return db } + if params.Category != "" { + db = db.Where("project_flocks.category = ?", params.Category) + } if params.AreaId > 0 { db = db.Where("project_flocks.area_id = ?", params.AreaId) } diff --git a/internal/modules/production/project_flocks/route.go b/internal/modules/production/project_flocks/route.go index 70a22bad..a8d4ca6d 100644 --- a/internal/modules/production/project_flocks/route.go +++ b/internal/modules/production/project_flocks/route.go @@ -28,5 +28,5 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj route.Get("/kandangs/lookup", ctrl.LookupProjectFlockKandang) route.Post("/approvals", ctrl.Approval) route.Get("/kandangs/:project-flock_kandang-id/periods", ctrl.GetFlockPeriodSummary) - + } diff --git a/internal/modules/production/project_flocks/validations/projectflock.validation.go b/internal/modules/production/project_flocks/validations/projectflock.validation.go index 7932e07e..33f20725 100644 --- a/internal/modules/production/project_flocks/validations/projectflock.validation.go +++ b/internal/modules/production/project_flocks/validations/projectflock.validation.go @@ -27,6 +27,7 @@ type Query struct { AreaId uint `query:"area_id" validate:"omitempty,number,gt=0"` LocationId uint `query:"location_id" validate:"omitempty,number,gt=0"` Period int `query:"period" validate:"omitempty,number,gt=0"` + Category string `query:"category" validate:"omitempty"` KandangIds []uint `query:"kandang_id" validate:"omitempty,dive,gt=0"` } diff --git a/internal/modules/production/transfer_layings/controllers/transfer_laying.controller.go b/internal/modules/production/transfer_layings/controllers/transfer_laying.controller.go index 62d64127..c69f4ff5 100644 --- a/internal/modules/production/transfer_layings/controllers/transfer_laying.controller.go +++ b/internal/modules/production/transfer_layings/controllers/transfer_laying.controller.go @@ -1,6 +1,7 @@ package controller import ( + "fmt" "math" "strconv" @@ -24,16 +25,8 @@ func NewTransferLayingController(transferLayingService service.TransferLayingSer func (u *TransferLayingController) GetAll(c *fiber.Ctx) error { query := &validation.Query{ - Page: c.QueryInt("page", 1), - Limit: c.QueryInt("limit", 10), - SourceProjectFlockId: uint(c.QueryInt("source_project_flock_id", 0)), - TargetProjectFlockId: uint(c.QueryInt("target_project_flock_id", 0)), - TransferDateFrom: c.Query("transfer_date_from", ""), - TransferDateTo: c.Query("transfer_date_to", ""), - ApprovalStatus: c.Query("approval_status", ""), - TransferNumber: c.Query("transfer_number", ""), - Sort: c.Query("sort", "created_at"), - Order: c.Query("order", "desc"), + Page: c.QueryInt("page", 1), + Limit: c.QueryInt("limit", 10), } if query.Page < 1 || query.Limit < 1 { @@ -45,8 +38,13 @@ func (u *TransferLayingController) GetAll(c *fiber.Ctx) error { return err } + data := make([]dto.TransferLayingDetailDTO, len(result)) + for i, transfer := range result { + data[i] = dto.ToTransferLayingDetailDTOWithSingleApproval(transfer, transfer.LatestApproval) + } + return c.Status(fiber.StatusOK). - JSON(response.SuccessWithPaginate[dto.TransferLayingListDTO]{ + JSON(response.SuccessWithPaginate[dto.TransferLayingDetailDTO]{ Code: fiber.StatusOK, Status: "success", Message: "Get all transferLayings successfully", @@ -56,7 +54,7 @@ func (u *TransferLayingController) GetAll(c *fiber.Ctx) error { TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), TotalResults: totalResults, }, - Data: dto.ToTransferLayingListDTOs(result), + Data: data, }) } @@ -113,7 +111,7 @@ func (u *TransferLayingController) UpdateOne(c *fiber.Ctx) error { } if err := c.BodyParser(req); err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid request body: %s", err.Error())) } result, err := u.TransferLayingService.UpdateOne(c, req, uint(id)) @@ -126,7 +124,7 @@ func (u *TransferLayingController) UpdateOne(c *fiber.Ctx) error { Code: fiber.StatusOK, Status: "success", Message: "Update transferLaying successfully", - Data: dto.ToTransferLayingListDTO(*result), + Data: dto.ToTransferLayingDetailDTOWithSingleApproval(*result, result.LatestApproval), }) } @@ -180,3 +178,40 @@ func (u *TransferLayingController) Approval(c *fiber.Ctx) error { Data: data, }) } + + +func (u *TransferLayingController) GetAvailableQtyPerKandang(c *fiber.Ctx) error { + projectFlockID, err := strconv.ParseUint(c.Params("project_flock_id"), 10, 32) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid project_flock_id") + } + + pf, kandangQtyMap, err := u.TransferLayingService.GetAvailableQtyPerKandang(c, uint(projectFlockID)) + if err != nil { + return err + } + + // Build kandang list response + kandangs := make([]dto.KandangAvailableQtyDTO, 0, len(kandangQtyMap)) + for kandangPFID, qty := range kandangQtyMap { + kandangs = append(kandangs, dto.KandangAvailableQtyDTO{ + ProjectFlockKandangId: kandangPFID, + AvailableQty: qty, + }) + } + + resp := dto.AvailableQtyForTransferDTO{ + ProjectFlockId: pf.Id, + ProjectFlockCode: pf.FlockName, + Category: pf.Category, + Kandangs: kandangs, + } + + return c.Status(fiber.StatusOK). + JSON(response.Success{ + Code: fiber.StatusOK, + Status: "success", + Message: "Get available quantity successfully", + Data: resp, + }) +} diff --git a/internal/modules/production/transfer_layings/dto/transfer_laying.dto.go b/internal/modules/production/transfer_layings/dto/transfer_laying.dto.go index 719e458a..087f146a 100644 --- a/internal/modules/production/transfer_layings/dto/transfer_laying.dto.go +++ b/internal/modules/production/transfer_layings/dto/transfer_laying.dto.go @@ -77,6 +77,20 @@ type TransferLayingDetailDTO struct { Approval *approvalDTO.ApprovalBaseDTO `json:"approval,omitempty"` } +// === Available Quantity DTOs === + +type KandangAvailableQtyDTO struct { + ProjectFlockKandangId uint `json:"project_flock_kandang_id"` + AvailableQty float64 `json:"available_qty"` +} + +type AvailableQtyForTransferDTO struct { + ProjectFlockId uint `json:"project_flock_id"` + ProjectFlockCode string `json:"project_flock_code"` + Category string `json:"category"` + Kandangs []KandangAvailableQtyDTO `json:"kandangs"` +} + // === Mapper Functions === func ToProjectFlockSummaryDTO(pf *entity.ProjectFlock) *ProjectFlockSummaryDTO { @@ -207,7 +221,6 @@ func ToTransferLayingListDTO(e entity.LayingTransfer) TransferLayingListDTO { func ToTransferLayingDetailDTO(e entity.LayingTransfer, approvals []entity.Approval) TransferLayingDetailDTO { var latestApproval *approvalDTO.ApprovalBaseDTO - // Use LatestApproval from entity if available if e.LatestApproval != nil { mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval) latestApproval = &mapped diff --git a/internal/modules/production/transfer_layings/route.go b/internal/modules/production/transfer_layings/route.go index 13a0bc8f..ad0cb9e1 100644 --- a/internal/modules/production/transfer_layings/route.go +++ b/internal/modules/production/transfer_layings/route.go @@ -27,4 +27,5 @@ func TransferLayingRoutes(v1 fiber.Router, u user.UserService, s transferLaying. route.Patch("/:id", ctrl.UpdateOne) route.Delete("/:id", ctrl.DeleteOne) route.Post("/approvals", ctrl.Approval) + route.Get("/project-flocks/:project_flock_id/available-qty", ctrl.GetAvailableQtyPerKandang) } diff --git a/internal/modules/production/transfer_layings/services/transfer_laying.service.go b/internal/modules/production/transfer_layings/services/transfer_laying.service.go index 06c297bc..e7591aef 100644 --- a/internal/modules/production/transfer_layings/services/transfer_laying.service.go +++ b/internal/modules/production/transfer_layings/services/transfer_laying.service.go @@ -31,6 +31,7 @@ type TransferLayingService interface { UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.LayingTransfer, error) DeleteOne(ctx *fiber.Ctx, id uint) error Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.LayingTransfer, error) + GetAvailableQtyPerKandang(ctx *fiber.Ctx, projectFlockID uint) (*entity.ProjectFlock, map[uint]float64, error) } type transferLayingService struct { @@ -92,32 +93,7 @@ func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([ transferLayings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) - if params.SourceProjectFlockId != 0 { - db = db.Where("from_project_flock_id = ?", params.SourceProjectFlockId) - } - if params.TargetProjectFlockId != 0 { - db = db.Where("to_project_flock_id = ?", params.TargetProjectFlockId) - } - if params.TransferDateFrom != "" { - db = db.Where("transfer_date >= ?", params.TransferDateFrom) - } - if params.TransferDateTo != "" { - db = db.Where("transfer_date <= ?", params.TransferDateTo) - } - if params.TransferNumber != "" { - db = db.Where("transfer_number ILIKE ?", "%"+params.TransferNumber+"%") - } - - sortField := "created_at" - if params.Sort != "" { - sortField = params.Sort - } - sortOrder := "DESC" - if params.Order == "asc" { - sortOrder = "ASC" - } - db = db.Order(fmt.Sprintf("%s %s", sortField, sortOrder)) - + db = db.Order("created_at DESC") return db }) @@ -126,19 +102,14 @@ func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([ return nil, 0, err } - if params.ApprovalStatus != "" { - var filtered []entity.LayingTransfer - approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB()) - - for _, transfer := range transferLayings { - latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), transfer.Id, nil) - if err == nil && latestApproval != nil && latestApproval.Action != nil { - if string(*latestApproval.Action) == params.ApprovalStatus { - filtered = append(filtered, transfer) - } - } + approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB()) + for i, transfer := range transferLayings { + latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), transfer.Id, func(db *gorm.DB) *gorm.DB { + return db.Preload("ActionUser") + }) + if err == nil && latestApproval != nil { + transferLayings[i].LatestApproval = latestApproval } - transferLayings = filtered } return transferLayings, total, nil @@ -155,7 +126,9 @@ func (s transferLayingService) GetOne(c *fiber.Ctx, id uint) (*entity.LayingTran } approvalRepo := commonRepo.NewApprovalRepository(s.Repository.DB()) - latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), transferLaying.Id, nil) + latestApproval, err := approvalRepo.LatestByTarget(c.Context(), string(utils.ApprovalWorkflowTransferToLaying), transferLaying.Id, func(db *gorm.DB) *gorm.DB { + return db.Preload("ActionUser") + }) if err == nil && latestApproval != nil { transferLaying.LatestApproval = latestApproval } @@ -547,7 +520,6 @@ func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error { return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Cannot delete transfer laying with status %s", action)) } } - err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { productWarehouseRepoTx := s.ProductWarehouseRepo.WithTx(dbTransaction) @@ -851,8 +823,6 @@ func (s *transferLayingService) restoreProjectFlockPopulation(ctx context.Contex return fiber.NewError(fiber.StatusBadRequest, "No populations found for restoration") } - // Restore in LIFO order (from newest to oldest) - // Add all quantity back to the last (newest) population if len(populations) > 0 { lastPop := populations[len(populations)-1] newQty := lastPop.TotalQty + quantityToRestore @@ -863,3 +833,37 @@ func (s *transferLayingService) restoreProjectFlockPopulation(ctx context.Contex return nil } + +func (s transferLayingService) GetAvailableQtyPerKandang(ctx *fiber.Ctx, projectFlockID uint) (*entity.ProjectFlock, map[uint]float64, error) { + + pf, err := s.ProjectFlockRepo.GetByID(ctx.Context(), projectFlockID, func(db *gorm.DB) *gorm.DB { + return db + }) + if err != nil { + s.Log.Errorf("Failed to get project flock %d: %+v", projectFlockID, err) + return nil, nil, fiber.NewError(fiber.StatusNotFound, "Project flock not found") + } + + kandangs, _, err := s.ProjectFlockKandangRepo.GetAll(ctx.Context(), 0, 1000, func(db *gorm.DB) *gorm.DB { + return db.Where("project_flock_id = ?", projectFlockID).Order("kandang_id ASC") + }) + if err != nil { + s.Log.Errorf("Failed to get kandangs for project flock %d: %+v", projectFlockID, err) + return nil, nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch kandangs") + } + + kandangAvailableQty := make(map[uint]float64) + for _, kandang := range kandangs { + + totalQty, err := s.ProjectFlockPopulationRepo.GetTotalQtyByProjectFlockKandangID(ctx.Context(), kandang.Id) + if err != nil { + s.Log.Warnf("Failed to get total qty for kandang %d: %+v", kandang.Id, err) + kandangAvailableQty[kandang.Id] = 0 + continue + } + + kandangAvailableQty[kandang.Id] = totalQty + } + + return pf, kandangAvailableQty, nil +} diff --git a/internal/modules/production/transfer_layings/validations/transfer_laying.validation.go b/internal/modules/production/transfer_layings/validations/transfer_laying.validation.go index bd117bd9..45a73e48 100644 --- a/internal/modules/production/transfer_layings/validations/transfer_laying.validation.go +++ b/internal/modules/production/transfer_layings/validations/transfer_laying.validation.go @@ -29,16 +29,8 @@ type Update struct { } type Query struct { - Page int `query:"page" validate:"omitempty,number,min=1,gt=0"` - Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` - SourceProjectFlockId uint `query:"source_project_flock_id" validate:"omitempty"` - TargetProjectFlockId uint `query:"target_project_flock_id" validate:"omitempty"` - TransferDateFrom string `query:"transfer_date_from" validate:"omitempty,datetime=2006-01-02"` - TransferDateTo string `query:"transfer_date_to" validate:"omitempty,datetime=2006-01-02"` - ApprovalStatus string `query:"approval_status" validate:"omitempty,oneof=PENDING APPROVED REJECTED"` // Filter by latest approval status - TransferNumber string `query:"transfer_number" validate:"omitempty"` // Search by transfer number - Sort string `query:"sort" validate:"omitempty,oneof=created_at transfer_date"` // Sort by field - Order string `query:"order" validate:"omitempty,oneof=asc desc"` // Sort order + Page int `query:"page" validate:"omitempty,number,min=1,gt=0"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` } type Approve struct { diff --git a/internal/route/route.go b/internal/route/route.go index d6277549..d66828c6 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -15,6 +15,7 @@ import ( production "gitlab.com/mbugroup/lti-api.git/internal/modules/production" purchases "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases" users "gitlab.com/mbugroup/lti-api.git/internal/modules/users" + marketing "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing" // MODULE IMPORTS ) @@ -32,6 +33,7 @@ func Routes(app *fiber.App, db *gorm.DB) { production.ProductionModule{}, approvals.ApprovalModule{}, purchases.PurchaseModule{}, + marketing.MarketingModule{}, // MODULE REGISTRY }