mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
feat[BE-127]: create available qty API and inisiate sales order and delivery order
This commit is contained in:
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package marketing
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MarketingModule struct{}
|
||||
|
||||
func (MarketingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||
RegisterRoutes(router, db, validate)
|
||||
}
|
||||
@@ -0,0 +1,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)
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
})
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
+15
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
|
||||
+49
-14
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+2
-10
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user