diff --git a/cmd/main.go b/cmd/main.go index f846125..e4d2909 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -15,6 +15,7 @@ import ( "github.com/Caknoooo/go-gin-clean-starter/modules/category" "github.com/Caknoooo/go-gin-clean-starter/modules/client" inventoryissue "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_issue" + inventorymovement "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement" inventoryreceipt "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt" inventoryrequest "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request" inventoryreturn "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_return" @@ -171,6 +172,7 @@ func main() { inventoryrequest.RegisterRoutes(server, injector) inventoryissue.RegisterRoutes(server, injector) inventoryreturn.RegisterRoutes(server, injector) + inventorymovement.RegisterRoutes(server, injector) // register swagger route server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) diff --git a/database/entities/t_inventory_movement_entity.go b/database/entities/t_inventory_movement_entity.go index f3c464b..8893b0e 100644 --- a/database/entities/t_inventory_movement_entity.go +++ b/database/entities/t_inventory_movement_entity.go @@ -20,7 +20,10 @@ type TInventoryMovementEntity struct { SourceLocationID uuid.UUID `gorm:"type:uuid;index;" json:"source_location_id"` DestinationLocationID uuid.UUID `gorm:"type:uuid;index;" json:"destination_location_id"` - Client M_Client `gorm:"foreignKey:ClientID;references:ID"` + MovementLines []TInventoryMovementLineEntity `gorm:"foreignKey:InventoryMovementID;references:ID"` + SourceLocation MWarehouseEntity `gorm:"foreignKey:SourceLocationID;references:ID"` + DestinationLocation MWarehouseEntity `gorm:"foreignKey:DestinationLocationID;references:ID"` + Client M_Client `gorm:"foreignKey:ClientID;references:ID"` FullAuditTrail } diff --git a/database/migration.go b/database/migration.go index 7c6a91e..9aaf785 100644 --- a/database/migration.go +++ b/database/migration.go @@ -74,7 +74,7 @@ func MigrateFresh(db *gorm.DB) error { // &entities.MWarehouseEntity{}, // &entities.MZonaEntity{}, // &entities.MAisleEntity{}, - &entities.MUserWarehouseEntity{}, + // &entities.MUserWarehouseEntity{}, // &entities.TAssignmentEntity{}, // &entities.TAssignmentUserEntity{}, // &entities.TInventoryReceiptEntity{}, diff --git a/modules/inventory_movement/controller/inventory_movement_controller.go b/modules/inventory_movement/controller/inventory_movement_controller.go new file mode 100644 index 0000000..f70c1f3 --- /dev/null +++ b/modules/inventory_movement/controller/inventory_movement_controller.go @@ -0,0 +1,174 @@ +package controller + +import ( + "net/http" + + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement/query" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement/service" + "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" + "github.com/gin-gonic/gin" + "github.com/samber/do" +) + +type InventoryMovementController interface { + Create(ctx *gin.Context) + Update(ctx *gin.Context) + Delete(ctx *gin.Context) + GetById(ctx *gin.Context) + GetAll(ctx *gin.Context) + CreateLine(ctx *gin.Context) + UpdateLine(ctx *gin.Context) + DeleteLine(ctx *gin.Context) +} + +type inventoryMovementController struct { + movementService service.InventoryMovementService +} + +func (c *inventoryMovementController) Create(ctx *gin.Context) { + var req dto.InventoryMovementCreateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + result, err := c.movementService.Create(ctx, req) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_INVENTORY_MOVEMENT, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_INVENTORY_MOVEMENT, result) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryMovementController) Update(ctx *gin.Context) { + id := ctx.Param("id") + var req dto.InventoryMovementUpdateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + result, err := c.movementService.Update(ctx, req, id) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_INVENTORY_MOVEMENT, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_INVENTORY_MOVEMENT, result) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryMovementController) Delete(ctx *gin.Context) { + id := ctx.Param("id") + if err := c.movementService.Delete(ctx, id); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_INVENTORY_MOVEMENT, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_INVENTORY_MOVEMENT, nil) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryMovementController) GetById(ctx *gin.Context) { + id := ctx.Param("id") + result, err := c.movementService.GetById(ctx, id) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INVENTORY_MOVEMENT, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_INVENTORY_MOVEMENT, result) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryMovementController) GetAll(ctx *gin.Context) { + clientId := ctx.DefaultQuery("client_id", "") + var filter query.InventoryMovementFilter + filter.ClientID = clientId + if err := ctx.ShouldBindQuery(&filter); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INVENTORY_MOVEMENT, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + getAll := ctx.Query("get_all") + if getAll != "" { + movements, _, err := c.movementService.GetAll(ctx, filter) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INVENTORY_MOVEMENT, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + response := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_INVENTORY_MOVEMENT, movements) + ctx.JSON(http.StatusOK, response) + return + } + perPage := utils.ParseInt(ctx.DefaultQuery("per_page", "10")) + page := utils.ParseInt(ctx.DefaultQuery("page", "1")) + filter.PerPage = perPage + filter.Page = (page - 1) * perPage + movements, total, err := c.movementService.GetAll(ctx, filter) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INVENTORY_MOVEMENT, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + paginationResponse := utils.BuildPaginationResponse(perPage, page, total) + res := utils.BuildResponseSuccessWithPagination(http.StatusOK, dto.MESSAGE_SUCCESS_GET_INVENTORY_MOVEMENT, movements, paginationResponse) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryMovementController) CreateLine(ctx *gin.Context) { + id := ctx.Param("id") + var req dto.InventoryMovementLineCreateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + created, err := c.movementService.CreateLine(ctx, id, req) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_INVENTORY_MOVEMENT_LINE, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_INVENTORY_MOVEMENT_LINE, created) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryMovementController) UpdateLine(ctx *gin.Context) { + id := ctx.Param("id") + var req dto.InventoryMovementLineUpdateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + updated, err := c.movementService.UpdateLine(ctx, id, req) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_INVENTORY_MOVEMENT_LINE, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_INVENTORY_MOVEMENT_LINE, updated) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryMovementController) DeleteLine(ctx *gin.Context) { + id := ctx.Param("id") + if err := c.movementService.DeleteLine(ctx, id); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_INVENTORY_MOVEMENT_LINE, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_INVENTORY_MOVEMENT_LINE, nil) + ctx.JSON(http.StatusOK, res) +} + +func NewInventoryMovementController(i *do.Injector, movementService service.InventoryMovementService) InventoryMovementController { + return &inventoryMovementController{ + movementService: movementService, + } +} diff --git a/modules/inventory_movement/dto/inventory_movement_dto.go b/modules/inventory_movement/dto/inventory_movement_dto.go new file mode 100644 index 0000000..3b6622e --- /dev/null +++ b/modules/inventory_movement/dto/inventory_movement_dto.go @@ -0,0 +1,99 @@ +package dto + +import ( + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" +) + +const ( + MESSAGE_FAILED_CREATE_INVENTORY_MOVEMENT = "failed create inventory movement" + MESSAGE_FAILED_CREATE_INVENTORY_MOVEMENT_LINE = "failed create inventory movement line" + MESSAGE_SUCCESS_CREATE_INVENTORY_MOVEMENT = "success create inventory movement" + MESSAGE_SUCCESS_CREATE_INVENTORY_MOVEMENT_LINE = "success create inventory movement line" + MESSAGE_FAILED_GET_INVENTORY_MOVEMENT = "failed get inventory movement" + MESSAGE_SUCCESS_GET_INVENTORY_MOVEMENT = "success get inventory movement" + MESSAGE_FAILED_UPDATE_INVENTORY_MOVEMENT = "failed update inventory movement" + MESSAGE_FAILED_UPDATE_INVENTORY_MOVEMENT_LINE = "failed update inventory movement line" + MESSAGE_SUCCESS_UPDATE_INVENTORY_MOVEMENT = "success update inventory movement" + MESSAGE_SUCCESS_UPDATE_INVENTORY_MOVEMENT_LINE = "success update inventory movement line" + MESSAGE_FAILED_DELETE_INVENTORY_MOVEMENT = "failed delete inventory movement" + MESSAGE_FAILED_DELETE_INVENTORY_MOVEMENT_LINE = "failed delete inventory movement line" + MESSAGE_SUCCESS_DELETE_INVENTORY_MOVEMENT = "success delete inventory movement" + MESSAGE_SUCCESS_DELETE_INVENTORY_MOVEMENT_LINE = "success delete inventory movement line" + MESSAGE_FAILED_GET_DATA_FROM_BODY = "failed get data from body" +) + +type InventoryMovementCreateRequest struct { + // MovementNumber string `json:"movement_number"` + MovementDate string `json:"movement_date"` + MovementType string `json:"movement_type"` + ClientID string `json:"client_id" binding:"required"` + Status string `json:"status" binding:"required"` + SourceLocationID string `json:"source_location_id"` + DestinationLocationID string `json:"destination_location_id"` + MovementLines []InventoryMovementLineCreateRequest `json:"movement_lines,omitempty" binding:"dive"` +} + +type InventoryMovementLineCreateRequest struct { + MovedQuantity float64 `json:"moved_quantity"` + ProductID string `json:"product_id"` + StorageID string `json:"storage_id"` + ClientID string `json:"client_id"` + Status string `json:"status"` +} + +type InventoryMovementUpdateRequest struct { + // MovementNumber string `json:"movement_number"` + MovementDate string `json:"movement_date"` + MovementType string `json:"movement_type"` + Status string `json:"status"` +} + +type InventoryMovementLineUpdateRequest struct { + MovedQuantity *float64 `json:"moved_quantity"` + Status *string `json:"status"` +} + +type InventoryMovementResponse struct { + ID string `json:"id"` + MovementNumber string `json:"movement_number"` + MovementDate string `json:"movement_date"` + MovementType string `json:"movement_type"` + Status string `json:"status"` + ClientID string `json:"client_id"` + SourceLocationID string `json:"source_location_id"` + DestinationLocationID string `json:"destination_location_id"` +} + +type InventoryMovementLineResponse struct { + ID string `json:"id"` + MovedQuantity float64 `json:"moved_quantity"` + ProductID string `json:"product_id"` + StorageID string `json:"storage_id"` + ClientID string `json:"client_id"` + Status string `json:"status"` +} + +func ToInventoryMovementResponse(e entities.TInventoryMovementEntity) InventoryMovementResponse { + return InventoryMovementResponse{ + ID: e.ID.String(), + MovementNumber: e.MovementNumber, + MovementDate: utils.DateTimeToString(e.MovementDate), + MovementType: e.MovementType, + Status: e.Status, + ClientID: e.ClientID.String(), + SourceLocationID: e.SourceLocationID.String(), + DestinationLocationID: e.DestinationLocationID.String(), + } +} + +func ToInventoryMovementLineResponse(e entities.TInventoryMovementLineEntity) InventoryMovementLineResponse { + return InventoryMovementLineResponse{ + ID: e.ID.String(), + MovedQuantity: e.MovedQuantity, + ProductID: e.ProductID.String(), + StorageID: e.StorageID.String(), + ClientID: e.ClientID.String(), + Status: e.Status, + } +} diff --git a/modules/inventory_movement/query/inventory_movement_query.go b/modules/inventory_movement/query/inventory_movement_query.go new file mode 100644 index 0000000..cab7b6b --- /dev/null +++ b/modules/inventory_movement/query/inventory_movement_query.go @@ -0,0 +1,26 @@ +package query + +import ( + "gorm.io/gorm" +) + +type InventoryMovementFilter struct { + ClientID string `form:"client_id"` + SourceLocation string `form:"source_location"` + DestinationLocation string `form:"destination_location"` + PerPage int `form:"per_page"` + Page int `form:"page"` +} + +func ApplyInventoryMovementFilters(db *gorm.DB, filter InventoryMovementFilter) *gorm.DB { + if filter.ClientID != "" { + db = db.Where("client_id = ?", filter.ClientID) + } + if filter.SourceLocation != "" { + db = db.Where("source_location_id = ?", filter.SourceLocation) + } + if filter.DestinationLocation != "" { + db = db.Where("destination_location_id = ?", filter.DestinationLocation) + } + return db +} diff --git a/modules/inventory_movement/repository/inventory_movement_line_repository.go b/modules/inventory_movement/repository/inventory_movement_line_repository.go new file mode 100644 index 0000000..84bae4e --- /dev/null +++ b/modules/inventory_movement/repository/inventory_movement_line_repository.go @@ -0,0 +1,89 @@ +package repository + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "gorm.io/gorm" +) + +type InventoryMovementLineRepository interface { + Create(ctx context.Context, tx *gorm.DB, line entities.TInventoryMovementLineEntity) (entities.TInventoryMovementLineEntity, error) + GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryMovementLineEntity, error) + Update(ctx context.Context, tx *gorm.DB, line entities.TInventoryMovementLineEntity) (entities.TInventoryMovementLineEntity, error) + Delete(ctx context.Context, tx *gorm.DB, id string) error + GetAllByMovementId(ctx context.Context, movementId string) ([]entities.TInventoryMovementLineEntity, error) + BulkCreate(ctx context.Context, tx *gorm.DB, lines []entities.TInventoryMovementLineEntity) error + DeleteByMovementId(ctx context.Context, tx *gorm.DB, movementId string) error +} + +type inventoryMovementLineRepository struct { + db *gorm.DB +} + +func NewInventoryMovementLineRepository(db *gorm.DB) InventoryMovementLineRepository { + return &inventoryMovementLineRepository{db: db} +} + +func (r *inventoryMovementLineRepository) Create(ctx context.Context, tx *gorm.DB, line entities.TInventoryMovementLineEntity) (entities.TInventoryMovementLineEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Create(&line).Error; err != nil { + return line, err + } + return line, nil +} + +func (r *inventoryMovementLineRepository) GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryMovementLineEntity, error) { + if tx == nil { + tx = r.db + } + var line entities.TInventoryMovementLineEntity + if err := tx.WithContext(ctx).First(&line, "id = ?", id).Error; err != nil { + return line, err + } + return line, nil +} + +func (r *inventoryMovementLineRepository) Update(ctx context.Context, tx *gorm.DB, line entities.TInventoryMovementLineEntity) (entities.TInventoryMovementLineEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Save(&line).Error; err != nil { + return line, err + } + return line, nil +} + +func (r *inventoryMovementLineRepository) Delete(ctx context.Context, tx *gorm.DB, id string) error { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Delete(&entities.TInventoryMovementLineEntity{}, "id = ?", id).Error; err != nil { + return err + } + return nil +} + +func (r *inventoryMovementLineRepository) GetAllByMovementId(ctx context.Context, movementId string) ([]entities.TInventoryMovementLineEntity, error) { + var lines []entities.TInventoryMovementLineEntity + if err := r.db.WithContext(ctx).Where("inv_movement_id = ?", movementId).Preload("Product").Preload("InvMovement").Preload("InvStorage").Preload("Client").Find(&lines).Error; err != nil { + return lines, err + } + return lines, nil +} + +func (r *inventoryMovementLineRepository) BulkCreate(ctx context.Context, tx *gorm.DB, lines []entities.TInventoryMovementLineEntity) error { + if tx == nil { + tx = r.db + } + return tx.WithContext(ctx).Create(&lines).Error +} + +func (r *inventoryMovementLineRepository) DeleteByMovementId(ctx context.Context, tx *gorm.DB, movementId string) error { + if tx == nil { + tx = r.db + } + return tx.WithContext(ctx).Where("inv_movement_id = ?", movementId).Delete(&entities.TInventoryMovementLineEntity{}).Error +} diff --git a/modules/inventory_movement/repository/inventory_movement_repository.go b/modules/inventory_movement/repository/inventory_movement_repository.go new file mode 100644 index 0000000..37c8193 --- /dev/null +++ b/modules/inventory_movement/repository/inventory_movement_repository.go @@ -0,0 +1,79 @@ +package repository + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement/query" + "gorm.io/gorm" +) + +type InventoryMovementRepository interface { + Create(ctx context.Context, tx *gorm.DB, movement entities.TInventoryMovementEntity) (entities.TInventoryMovementEntity, error) + GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryMovementEntity, error) + GetAll(ctx context.Context, filter query.InventoryMovementFilter) ([]entities.TInventoryMovementEntity, int64, error) + Update(ctx context.Context, tx *gorm.DB, movement entities.TInventoryMovementEntity) (entities.TInventoryMovementEntity, error) + Delete(ctx context.Context, tx *gorm.DB, id string) error +} + +type inventoryMovementRepository struct { + db *gorm.DB +} + +func NewInventoryMovementRepository(db *gorm.DB) InventoryMovementRepository { + return &inventoryMovementRepository{db: db} +} + +func (r *inventoryMovementRepository) Create(ctx context.Context, tx *gorm.DB, movement entities.TInventoryMovementEntity) (entities.TInventoryMovementEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Create(&movement).Error; err != nil { + return movement, err + } + return movement, nil +} + +func (r *inventoryMovementRepository) GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryMovementEntity, error) { + if tx == nil { + tx = r.db + } + var movement entities.TInventoryMovementEntity + if err := tx.WithContext(ctx). + Preload("Client"). + First(&movement, "id = ?", id).Error; err != nil { + return movement, err + } + return movement, nil +} + +func (r *inventoryMovementRepository) GetAll(ctx context.Context, filter query.InventoryMovementFilter) ([]entities.TInventoryMovementEntity, int64, error) { + db := r.db.WithContext(ctx) + db = query.ApplyInventoryMovementFilters(db, filter) + var movements []entities.TInventoryMovementEntity + var total int64 + if err := db.Find(&movements).Count(&total).Error; err != nil { + return nil, 0, err + } + return movements, total, nil +} + +func (r *inventoryMovementRepository) Update(ctx context.Context, tx *gorm.DB, movement entities.TInventoryMovementEntity) (entities.TInventoryMovementEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Save(&movement).Error; err != nil { + return movement, err + } + return movement, nil +} + +func (r *inventoryMovementRepository) Delete(ctx context.Context, tx *gorm.DB, id string) error { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Delete(&entities.TInventoryMovementEntity{}, "id = ?", id).Error; err != nil { + return err + } + return nil +} diff --git a/modules/inventory_movement/routes.go b/modules/inventory_movement/routes.go new file mode 100644 index 0000000..42cbb75 --- /dev/null +++ b/modules/inventory_movement/routes.go @@ -0,0 +1,27 @@ +package inventorymovement + +import ( + "github.com/Caknoooo/go-gin-clean-starter/middlewares" + "github.com/Caknoooo/go-gin-clean-starter/modules/auth/service" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement/controller" + "github.com/Caknoooo/go-gin-clean-starter/pkg/constants" + "github.com/gin-gonic/gin" + "github.com/samber/do" +) + +func RegisterRoutes(server *gin.Engine, injector *do.Injector) { + movementController := do.MustInvoke[controller.InventoryMovementController](injector) + jwtService := do.MustInvokeNamed[service.JWTService](injector, constants.JWTService) + + movementRoutes := server.Group("/api/v1/inventory-movements") + { + movementRoutes.POST("", middlewares.Authenticate(jwtService), movementController.Create) + movementRoutes.GET(":id", middlewares.Authenticate(jwtService), movementController.GetById) + movementRoutes.PUT(":id", middlewares.Authenticate(jwtService), movementController.Update) + movementRoutes.DELETE(":id", middlewares.Authenticate(jwtService), movementController.Delete) + movementRoutes.GET("", middlewares.Authenticate(jwtService), movementController.GetAll) + movementRoutes.POST(":id/lines", middlewares.Authenticate(jwtService), movementController.CreateLine) + movementRoutes.PUT("lines/:id", middlewares.Authenticate(jwtService), movementController.UpdateLine) + movementRoutes.DELETE("lines/:id", middlewares.Authenticate(jwtService), movementController.DeleteLine) + } +} diff --git a/modules/inventory_movement/service/inventory_movement_service.go b/modules/inventory_movement/service/inventory_movement_service.go new file mode 100644 index 0000000..a2b4ead --- /dev/null +++ b/modules/inventory_movement/service/inventory_movement_service.go @@ -0,0 +1,231 @@ +package service + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + dtodomain "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement/query" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement/repository" + "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" + "github.com/google/uuid" + "gorm.io/gorm" +) + +type InventoryMovementService interface { + Create(ctx context.Context, req dtodomain.InventoryMovementCreateRequest) (dtodomain.InventoryMovementResponse, error) + GetById(ctx context.Context, id string) (dtodomain.InventoryMovementResponse, error) + GetAll(ctx context.Context, filter query.InventoryMovementFilter) ([]dtodomain.InventoryMovementResponse, int64, error) + Update(ctx context.Context, req dtodomain.InventoryMovementUpdateRequest, id string) (dtodomain.InventoryMovementResponse, error) + Delete(ctx context.Context, id string) error + CreateLine(ctx context.Context, movementId string, req dtodomain.InventoryMovementLineCreateRequest) (dtodomain.InventoryMovementLineResponse, error) + UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryMovementLineUpdateRequest) (dtodomain.InventoryMovementLineResponse, error) + DeleteLine(ctx context.Context, lineId string) error +} + +type inventoryMovementService struct { + db *gorm.DB + movementRepo repository.InventoryMovementRepository + movementLineRepo repository.InventoryMovementLineRepository +} + +func (s *inventoryMovementService) Create(ctx context.Context, req dtodomain.InventoryMovementCreateRequest) (dtodomain.InventoryMovementResponse, error) { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + clientUUID, err := uuid.Parse(req.ClientID) + if err != nil { + tx.Rollback() + return dtodomain.InventoryMovementResponse{}, err + } + movNumber, err := entities.GenerateDocumentNumberInvMovement(s.db, req.ClientID) + if err != nil { + tx.Rollback() + return dtodomain.InventoryMovementResponse{}, err + } + movement := entities.TInventoryMovementEntity{ + MovementNumber: movNumber, + MovementDate: utils.StringToDateTime(req.MovementDate), + MovementType: req.MovementType, + ClientID: clientUUID, + Status: req.Status, + SourceLocationID: uuid.MustParse(req.SourceLocationID), + DestinationLocationID: uuid.MustParse(req.DestinationLocationID), + } + created, err := s.movementRepo.Create(ctx, tx, movement) + if err != nil { + tx.Rollback() + return dtodomain.InventoryMovementResponse{}, err + } + // Bulk create lines + var lines []entities.TInventoryMovementLineEntity + for _, lineReq := range req.MovementLines { + productUUID := uuid.Nil + if lineReq.ProductID != "" { + productUUID, err = uuid.Parse(lineReq.ProductID) + if err != nil { + tx.Rollback() + return dtodomain.InventoryMovementResponse{}, err + } + } + clientLineUUID, err := uuid.Parse(lineReq.ClientID) + if err != nil { + tx.Rollback() + return dtodomain.InventoryMovementResponse{}, err + } + lines = append(lines, entities.TInventoryMovementLineEntity{ + MovedQuantity: lineReq.MovedQuantity, + InvMovementID: created.ID, + ProductID: productUUID, + StorageID: uuid.MustParse(lineReq.StorageID), + ClientID: clientLineUUID, + Status: lineReq.Status, + }) + } + if len(lines) > 0 { + err = s.movementLineRepo.BulkCreate(ctx, tx, lines) + if err != nil { + tx.Rollback() + return dtodomain.InventoryMovementResponse{}, err + } + } + tx.Commit() + result, err := s.movementRepo.GetById(ctx, nil, created.ID.String()) + if err != nil { + return dtodomain.InventoryMovementResponse{}, err + } + return dtodomain.ToInventoryMovementResponse(result), nil +} + +func (s *inventoryMovementService) GetById(ctx context.Context, id string) (dtodomain.InventoryMovementResponse, error) { + movement, err := s.movementRepo.GetById(ctx, nil, id) + if err != nil { + return dtodomain.InventoryMovementResponse{}, err + } + return dtodomain.ToInventoryMovementResponse(movement), nil +} + +func (s *inventoryMovementService) GetAll(ctx context.Context, filter query.InventoryMovementFilter) ([]dtodomain.InventoryMovementResponse, int64, error) { + movements, total, err := s.movementRepo.GetAll(ctx, filter) + if err != nil { + return nil, 0, err + } + var responses []dtodomain.InventoryMovementResponse + for _, e := range movements { + responses = append(responses, dtodomain.ToInventoryMovementResponse(e)) + } + if responses == nil { + responses = make([]dtodomain.InventoryMovementResponse, 0) + } + return responses, total, nil +} + +func (s *inventoryMovementService) Update(ctx context.Context, req dtodomain.InventoryMovementUpdateRequest, id string) (dtodomain.InventoryMovementResponse, error) { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + movement, err := s.movementRepo.GetById(ctx, tx, id) + if err != nil { + tx.Rollback() + return dtodomain.InventoryMovementResponse{}, err + } + // if req.MovementNumber != "" { + // movement.MovementNumber = req.MovementNumber + // } + movement.MovementDate = utils.StringToDateTime(req.MovementDate) + movement.MovementType = req.MovementType + movement.Status = req.Status + updated, err := s.movementRepo.Update(ctx, tx, movement) + if err != nil { + tx.Rollback() + return dtodomain.InventoryMovementResponse{}, err + } + tx.Commit() + result, err := s.movementRepo.GetById(ctx, nil, updated.ID.String()) + if err != nil { + return dtodomain.InventoryMovementResponse{}, err + } + return dtodomain.ToInventoryMovementResponse(result), nil +} + +func (s *inventoryMovementService) Delete(ctx context.Context, id string) error { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := s.movementRepo.Delete(ctx, tx, id); err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + +func (s *inventoryMovementService) CreateLine(ctx context.Context, movementId string, req dtodomain.InventoryMovementLineCreateRequest) (dtodomain.InventoryMovementLineResponse, error) { + movementUUID, err := uuid.Parse(movementId) + if err != nil { + return dtodomain.InventoryMovementLineResponse{}, err + } + productUUID := uuid.Nil + if req.ProductID != "" { + productUUID, err = uuid.Parse(req.ProductID) + if err != nil { + return dtodomain.InventoryMovementLineResponse{}, err + } + } + clientLineUUID, err := uuid.Parse(req.ClientID) + if err != nil { + return dtodomain.InventoryMovementLineResponse{}, err + } + line := entities.TInventoryMovementLineEntity{ + MovedQuantity: req.MovedQuantity, + InvMovementID: movementUUID, + ProductID: productUUID, + StorageID: uuid.MustParse(req.StorageID), + ClientID: clientLineUUID, + Status: req.Status, + } + created, err := s.movementLineRepo.Create(ctx, nil, line) + if err != nil { + return dtodomain.InventoryMovementLineResponse{}, err + } + return dtodomain.ToInventoryMovementLineResponse(created), nil +} + +func (s *inventoryMovementService) UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryMovementLineUpdateRequest) (dtodomain.InventoryMovementLineResponse, error) { + line, err := s.movementLineRepo.GetById(ctx, nil, lineId) + if err != nil { + return dtodomain.InventoryMovementLineResponse{}, err + } + if req.MovedQuantity != nil { + line.MovedQuantity = *req.MovedQuantity + } + if req.Status != nil { + line.Status = *req.Status + } + updated, err := s.movementLineRepo.Update(ctx, nil, line) + if err != nil { + return dtodomain.InventoryMovementLineResponse{}, err + } + return dtodomain.ToInventoryMovementLineResponse(updated), nil +} + +func (s *inventoryMovementService) DeleteLine(ctx context.Context, lineId string) error { + return s.movementLineRepo.Delete(ctx, nil, lineId) +} + +func NewInventoryMovementService(db *gorm.DB, movementRepo repository.InventoryMovementRepository, movementLineRepo repository.InventoryMovementLineRepository) InventoryMovementService { + return &inventoryMovementService{ + db: db, + movementRepo: movementRepo, + movementLineRepo: movementLineRepo, + } +} diff --git a/providers/core.go b/providers/core.go index 8946910..ec32428 100644 --- a/providers/core.go +++ b/providers/core.go @@ -83,6 +83,11 @@ import ( inventoryReturnRepo "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_return/repository" inventoryReturnService "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_return/service" + inventoryMovementController "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement/controller" + inventoryMovementLineRepo "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement/repository" + inventoryMovementRepo "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement/repository" + inventoryMovementService "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_movement/service" + "github.com/Caknoooo/go-gin-clean-starter/modules/user/controller" "github.com/Caknoooo/go-gin-clean-starter/modules/user/repository" userService "github.com/Caknoooo/go-gin-clean-starter/modules/user/service" @@ -142,6 +147,8 @@ func RegisterDependencies(injector *do.Injector) { inventoryIssueLineRepository := inventoryIssueLineRepo.NewInventoryIssueLineRepository(db) inventoryReturnRepository := inventoryReturnRepo.NewInventoryReturnRepository(db) inventoryReturnLineRepository := inventoryReturnLineRepo.NewInventoryReturnLineRepository(db) + inventoryMovementRepository := inventoryMovementRepo.NewInventoryMovementRepository(db) + inventoryMovementLineRepository := inventoryMovementLineRepo.NewInventoryMovementLineRepository(db) // Service userServ := userService.NewUserService(userRepository, roleRepository, warehouseRepository, refreshTokenRepository, jwtService, db) @@ -162,6 +169,7 @@ func RegisterDependencies(injector *do.Injector) { inventoryRequestServ := inventoryRequestService.NewInventoryRequestService(db, inventoryRequestRepository, inventoryRequestLineRepository) inventoryIssueServ := inventoryIssueService.NewInventoryIssueService(db, inventoryIssueRepository, inventoryIssueLineRepository) inventoryReturnServ := inventoryReturnService.NewInventoryReturnService(db, inventoryReturnRepository, inventoryReturnLineRepository, inventoryIssueLineRepository, productRepository) + inventoryMovementServ := inventoryMovementService.NewInventoryMovementService(db, inventoryMovementRepository, inventoryMovementLineRepository) // Controller do.Provide( @@ -264,4 +272,9 @@ func RegisterDependencies(injector *do.Injector) { return inventoryReturnController.NewInventoryReturnController(i, inventoryReturnServ), nil }, ) + do.Provide( + injector, func(i *do.Injector) (inventoryMovementController.InventoryMovementController, error) { + return inventoryMovementController.NewInventoryMovementController(i, inventoryMovementServ), nil + }, + ) }