diff --git a/cmd/main.go b/cmd/main.go index 7105f0c..9239d0f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -14,7 +14,8 @@ import ( "github.com/Caknoooo/go-gin-clean-starter/modules/auth" "github.com/Caknoooo/go-gin-clean-starter/modules/category" "github.com/Caknoooo/go-gin-clean-starter/modules/client" - "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt" + inventoryreceipt "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt" + inventoryrequest "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request" maintenancegroup "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group" "github.com/Caknoooo/go-gin-clean-starter/modules/menu" "github.com/Caknoooo/go-gin-clean-starter/modules/monitoring" @@ -163,8 +164,9 @@ func main() { warehouse.RegisterRoutes(server, injector) zona.RegisterRoutes(server, injector) aisle.RegisterRoutes(server, injector) - inventory_receipt.RegisterRoutes(server, injector) + inventoryreceipt.RegisterRoutes(server, injector) assignment.RegisterRoutes(server, injector) + inventoryrequest.RegisterRoutes(server, injector) // register swagger route server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) diff --git a/database/entities/t_inventory_request_entity.go b/database/entities/t_inventory_request_entity.go index 2425576..41613b0 100644 --- a/database/entities/t_inventory_request_entity.go +++ b/database/entities/t_inventory_request_entity.go @@ -1,9 +1,12 @@ package entities import ( + "fmt" + "strings" "time" "github.com/google/uuid" + "gorm.io/gorm" ) type TInventoryRequestEntity struct { @@ -13,11 +16,13 @@ type TInventoryRequestEntity struct { DueDate time.Time `gorm:"type:timestamp;" json:"due_date"` RequestType string `gorm:"type:varchar(50);" json:"request_type"` Note string `gorm:"type:text;" json:"note"` - Status string `gorm:"type:varchar(50);" json:"status"` - - ClientID uuid.UUID `gorm:"type:uuid;index;" json:"client_id"` + Status string `gorm:"type:varchar(50);default:'draft'" json:"status"` - Client M_Client `gorm:"foreignKey:ClientID;references:ID"` + ClientID uuid.UUID `gorm:"type:uuid;index;" json:"client_id"` + + RequestLines []TInventoryRequestLineEntity `gorm:"foreignKey:InvRequestID;references:ID"` + Assignment TAssignmentEntity `gorm:"foreignKey:DocumentID;references:ID"` + Client M_Client `gorm:"foreignKey:ClientID;references:ID"` FullAuditTrail } @@ -25,3 +30,44 @@ type TInventoryRequestEntity struct { func (TInventoryRequestEntity) TableName() string { return "t_inventory_requests" } + +// GenerateDocumentNumber generates a new document number for a client +func GenerateDocumentNumberInventoryRequest(db *gorm.DB, clientId string) (string, error) { + prefix := "RQST" + + // Ambil nama client berdasarkan clientId + var client struct { + Name string + } + if err := db.Table("m_clients").Select("name").Where("id = ?", clientId).First(&client).Error; err != nil { + return "", fmt.Errorf("client not found") + } + if client.Name == "" { + return "", fmt.Errorf("client name is empty") + } + words := strings.Fields(client.Name) + initials := "" + for _, w := range words { + if len(w) > 0 { + initials += strings.ToUpper(string(w[0])) + } + } + // Cari document number terakhir untuk client ini + var lastRequest TInventoryRequestEntity + err := db. + Where("client_id = ?", clientId). + Order("document_number DESC"). + First(&lastRequest).Error + + seq := 1 + if err == nil && lastRequest.DocumentNumber != "" { + parts := strings.Split(lastRequest.DocumentNumber, "-") + if len(parts) == 3 { + fmt.Sscanf(parts[2], "%d", &seq) + seq++ + } + } + + docNum := fmt.Sprintf("%s-%s-%04d", prefix, initials, seq) + return docNum, nil +} diff --git a/database/migration.go b/database/migration.go index 326ba48..30f5616 100644 --- a/database/migration.go +++ b/database/migration.go @@ -32,8 +32,8 @@ func Migrate(db *gorm.DB) error { &entities.TAssignmentUserEntity{}, &entities.TInventoryReceiptEntity{}, &entities.TInventoryReceiptLineEntity{}, - // &entities.TInventoryRequestEntity{}, - // &entities.TInventoryRequestLineEntity{}, + &entities.TInventoryRequestEntity{}, + &entities.TInventoryRequestLineEntity{}, // &entities.TInventoryIssueEntity{}, // &entities.TInventoryIssueLineEntity{}, // &entities.InventoryTransactionEntity{}, @@ -68,13 +68,13 @@ func MigrateFresh(db *gorm.DB) error { // &entities.MCrossReferenceEntity{}, // &entities.MWarehouseEntity{}, // &entities.MZonaEntity{}, - // &entities.MAisleEntity{}, - &entities.TAssignmentEntity{}, - &entities.TAssignmentUserEntity{}, - &entities.TInventoryReceiptEntity{}, - &entities.TInventoryReceiptLineEntity{}, - // &entities.TInventoryRequestEntity{}, - // &entities.TInventoryRequestLineEntity{}, + &entities.MAisleEntity{}, + // &entities.TAssignmentEntity{}, + // &entities.TAssignmentUserEntity{}, + // &entities.TInventoryReceiptEntity{}, + // &entities.TInventoryReceiptLineEntity{}, + &entities.TInventoryRequestEntity{}, + &entities.TInventoryRequestLineEntity{}, // &entities.TInventoryIssueEntity{}, // &entities.TInventoryIssueLineEntity{}, // &entities.InventoryTransactionEntity{}, diff --git a/docker/Dockerfile b/docker/Dockerfile index 2775f38..18d47e4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -8,4 +8,4 @@ COPY . . RUN go mod tidy -CMD go run cmd/main.go --migrate && air \ No newline at end of file +CMD go run cmd/main.go --fresh && air \ No newline at end of file diff --git a/modules/inventory_receipt/routes.go b/modules/inventory_receipt/routes.go index 6c1c41b..e25e733 100644 --- a/modules/inventory_receipt/routes.go +++ b/modules/inventory_receipt/routes.go @@ -1,4 +1,4 @@ -package inventory_receipt +package inventoryreceipt import ( "github.com/Caknoooo/go-gin-clean-starter/middlewares" diff --git a/modules/inventory_request/controller/inventory_request_controller.go b/modules/inventory_request/controller/inventory_request_controller.go new file mode 100644 index 0000000..6c5eba0 --- /dev/null +++ b/modules/inventory_request/controller/inventory_request_controller.go @@ -0,0 +1,179 @@ +package controller + +import ( + "net/http" + + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/query" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/service" + "github.com/Caknoooo/go-gin-clean-starter/pkg/constants" + "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" + "github.com/gin-gonic/gin" + "github.com/samber/do" + "gorm.io/gorm" +) + +type InventoryRequestController 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 inventoryRequestController struct { + requestService service.InventoryRequestService + db *gorm.DB +} + +func (c *inventoryRequestController) DeleteLine(ctx *gin.Context) { + id := ctx.Param("id") + if err := c.requestService.DeleteLine(ctx, id); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_INVENTORY_REQUEST_LINE, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_INVENTORY_REQUEST_LINE, nil) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryRequestController) UpdateLine(ctx *gin.Context) { + id := ctx.Param("id") + var req dto.InventoryRequestLineUpdateRequest + 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.requestService.UpdateLine(ctx, id, req) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_INVENTORY_REQUEST_LINE, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_INVENTORY_REQUEST_LINE, updated) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryRequestController) CreateLine(ctx *gin.Context) { + id := ctx.Param("id") + var req dto.InventoryRequestLineCreateRequest + 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.requestService.CreateLine(ctx, id, req) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_INVENTORY_REQUEST_LINE, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_INVENTORY_REQUEST_LINE, created) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryRequestController) Create(ctx *gin.Context) { + var req dto.InventoryRequestCreateRequest + 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.requestService.Create(ctx, req) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_INVENTORY_REQUEST, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_INVENTORY_REQUEST, created) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryRequestController) Update(ctx *gin.Context) { + id := ctx.Param("id") + var req dto.InventoryRequestUpdateRequest + 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.requestService.Update(ctx, req, id) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_INVENTORY_REQUEST, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_INVENTORY_REQUEST, updated) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryRequestController) Delete(ctx *gin.Context) { + id := ctx.Param("id") + if err := c.requestService.Delete(ctx, id); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_INVENTORY_REQUEST, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_INVENTORY_REQUEST, nil) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryRequestController) GetById(ctx *gin.Context) { + id := ctx.Param("id") + request, err := c.requestService.GetById(ctx, id) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INVENTORY_REQUEST, err.Error(), nil) + ctx.JSON(http.StatusNotFound, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_INVENTORY_REQUEST, request) + ctx.JSON(http.StatusOK, res) +} + +func (c *inventoryRequestController) GetAll(ctx *gin.Context) { + clientId := ctx.DefaultQuery("client_id", "") + var filter query.InventoryRequestFilter + filter.ClientID = clientId + if err := ctx.ShouldBindQuery(&filter); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INVENTORY_REQUEST, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + getAll := ctx.Query("get_all") + if getAll != "" { + requests, _, err := c.requestService.GetAll(ctx, filter) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INVENTORY_REQUEST, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) + return + } + response := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_INVENTORY_REQUEST, requests) + 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 + requests, total, err := c.requestService.GetAll(ctx, filter) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INVENTORY_REQUEST, 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_REQUEST, requests, paginationResponse) + ctx.JSON(http.StatusOK, res) +} + +func NewInventoryRequestController(i *do.Injector, requestService service.InventoryRequestService) InventoryRequestController { + db := do.MustInvokeNamed[*gorm.DB](i, constants.DB) + return &inventoryRequestController{ + requestService: requestService, + db: db, + } +} diff --git a/modules/inventory_request/dto/inventory_request_dto.go b/modules/inventory_request/dto/inventory_request_dto.go new file mode 100644 index 0000000..5825de7 --- /dev/null +++ b/modules/inventory_request/dto/inventory_request_dto.go @@ -0,0 +1,94 @@ +package dto + +import pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto" + +const ( + MESSAGE_FAILED_CREATE_INVENTORY_REQUEST = "failed create inventory request" + MESSAGE_FAILED_CREATE_INVENTORY_REQUEST_LINE = "failed create inventory request line" + MESSAGE_SUCCESS_CREATE_INVENTORY_REQUEST = "success create inventory request" + MESSAGE_SUCCESS_CREATE_INVENTORY_REQUEST_LINE = "success create inventory request line" + MESSAGE_FAILED_GET_INVENTORY_REQUEST = "failed get inventory request" + MESSAGE_SUCCESS_GET_INVENTORY_REQUEST = "success get inventory request" + MESSAGE_FAILED_UPDATE_INVENTORY_REQUEST = "failed update inventory request" + MESSAGE_FAILED_UPDATE_INVENTORY_REQUEST_LINE = "failed update inventory request line" + MESSAGE_SUCCESS_UPDATE_INVENTORY_REQUEST = "success update inventory request" + MESSAGE_SUCCESS_UPDATE_INVENTORY_REQUEST_LINE = "success update inventory request line" + MESSAGE_FAILED_DELETE_INVENTORY_REQUEST = "failed delete inventory request" + MESSAGE_FAILED_DELETE_INVENTORY_REQUEST_LINE = "failed delete inventory request line" + MESSAGE_SUCCESS_DELETE_INVENTORY_REQUEST = "success delete inventory request" + MESSAGE_SUCCESS_DELETE_INVENTORY_REQUEST_LINE = "success delete inventory request line" + MESSAGE_FAILED_GET_DATA_FROM_BODY = "failed get data from body" +) + +type InventoryRequestCreateRequest struct { + ReferenceNumber string `json:"reference_number"` + // DocumentNumber string `json:"document_number"` + DueDate string `json:"due_date"` + RequestType string `json:"request_type"` + Note string `json:"note"` + ClientID string `json:"client_id" binding:"required"` + Status string `json:"status" binding:"required"` + RequestLines []InventoryRequestLineCreateRequest `json:"request_lines,omitempty" binding:"dive"` +} + +type InventoryRequestLineCreateRequest struct { + Quantity float64 `json:"quantity"` + ProductID string `json:"product_id"` + ClientID string `json:"client_id"` +} + +type InventoryRequestUpdateRequest struct { + ReferenceNumber string `json:"reference_number"` + DocumentNumber string `json:"document_number"` + DueDate string `json:"due_date"` + RequestType string `json:"request_type"` + Note string `json:"note"` + Status string `json:"status"` +} + +type InventoryRequestResponse struct { + ID string `json:"id"` + ReferenceNumber string `json:"reference_number"` + DocumentNumber string `json:"document_number"` + DueDate string `json:"due_date"` + RequestType string `json:"request_type"` + Note string `json:"note"` + Status string `json:"status"` + Client pkgdto.IdNameResponse `json:"client"` + RequestLines []InventoryRequestLineResponse `json:"request_lines"` + LineCount int `json:"line_count"` + Assignment AssignmentResponse `json:"assignment,omitempty"` +} + +type AssignmentResponse struct { + ID string `json:"id"` + DocumentType string `json:"document_type"` + DocumentID string `json:"document_id"` + AssignmentUsers []AssignmentUserResponse `json:"assignment_users"` +} + +type AssignmentUserResponse struct { + ID string `json:"id"` + TaskType string `json:"task_type"` + User pkgdto.IdNameResponse `json:"user"` + Role pkgdto.IdNameResponse `json:"role"` +} + +type InventoryRequestLineResponse struct { + ID string `json:"id"` + Quantity float64 `json:"quantity"` + Product InventoryRequestLineProductResponse `json:"product"` + ClientID string `json:"client_id"` +} + +type InventoryRequestLineProductResponse struct { + ID string `json:"id"` + Name string `json:"name"` + RefNumber string `json:"ref_number"` + Uom pkgdto.IdNameResponse `json:"uom"` +} + +type InventoryRequestLineUpdateRequest struct { + Quantity *float64 `json:"quantity"` + ProductID *string `json:"product_id"` +} diff --git a/modules/inventory_request/query/inventory_request_query.go b/modules/inventory_request/query/inventory_request_query.go new file mode 100644 index 0000000..3c6b452 --- /dev/null +++ b/modules/inventory_request/query/inventory_request_query.go @@ -0,0 +1,22 @@ +package query + +import ( + "gorm.io/gorm" +) + +type InventoryRequestFilter struct { + ClientID string `form:"client_id"` + RequestType string `form:"request_type"` + PerPage int `form:"per_page"` + Page int `form:"page"` +} + +func ApplyInventoryRequestFilters(db *gorm.DB, filter InventoryRequestFilter) *gorm.DB { + if filter.ClientID != "" { + db = db.Where("client_id = ?", filter.ClientID) + } + if filter.RequestType != "" { + db = db.Where("request_type ILIKE ?", "%"+filter.RequestType+"%") + } + return db +} diff --git a/modules/inventory_request/repository/inventory_request_line_repository.go b/modules/inventory_request/repository/inventory_request_line_repository.go new file mode 100644 index 0000000..164e495 --- /dev/null +++ b/modules/inventory_request/repository/inventory_request_line_repository.go @@ -0,0 +1,89 @@ +package repository + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "gorm.io/gorm" +) + +type InventoryRequestLineRepository interface { + Create(ctx context.Context, tx *gorm.DB, line entities.TInventoryRequestLineEntity) (entities.TInventoryRequestLineEntity, error) + GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryRequestLineEntity, error) + GetAllByRequestId(ctx context.Context, requestId string) ([]entities.TInventoryRequestLineEntity, error) + Update(ctx context.Context, tx *gorm.DB, line entities.TInventoryRequestLineEntity) (entities.TInventoryRequestLineEntity, error) + Delete(ctx context.Context, tx *gorm.DB, id string) error + BulkCreate(ctx context.Context, tx *gorm.DB, lines []entities.TInventoryRequestLineEntity) error + DeleteByRequestId(ctx context.Context, tx *gorm.DB, requestId string) error +} + +type inventoryRequestLineRepository struct { + db *gorm.DB +} + +func NewInventoryRequestLineRepository(db *gorm.DB) InventoryRequestLineRepository { + return &inventoryRequestLineRepository{db: db} +} + +func (r *inventoryRequestLineRepository) Create(ctx context.Context, tx *gorm.DB, line entities.TInventoryRequestLineEntity) (entities.TInventoryRequestLineEntity, 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 *inventoryRequestLineRepository) GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryRequestLineEntity, error) { + if tx == nil { + tx = r.db + } + var line entities.TInventoryRequestLineEntity + if err := tx.WithContext(ctx).Preload("Product").Preload("InvRequest").Preload("Client").First(&line, "id = ?", id).Error; err != nil { + return line, err + } + return line, nil +} + +func (r *inventoryRequestLineRepository) GetAllByRequestId(ctx context.Context, requestId string) ([]entities.TInventoryRequestLineEntity, error) { + var lines []entities.TInventoryRequestLineEntity + if err := r.db.WithContext(ctx).Where("inv_request_id = ?", requestId).Preload("Product").Preload("InvRequest").Preload("Client").Find(&lines).Error; err != nil { + return lines, err + } + return lines, nil +} + +func (r *inventoryRequestLineRepository) Update(ctx context.Context, tx *gorm.DB, line entities.TInventoryRequestLineEntity) (entities.TInventoryRequestLineEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Model(&entities.TInventoryRequestLineEntity{}).Where("id = ?", line.ID).Select("*").Updates(&line).Error; err != nil { + return line, err + } + return line, nil +} + +func (r *inventoryRequestLineRepository) Delete(ctx context.Context, tx *gorm.DB, id string) error { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Delete(&entities.TInventoryRequestLineEntity{}, "id = ?", id).Error; err != nil { + return err + } + return nil +} + +func (r *inventoryRequestLineRepository) BulkCreate(ctx context.Context, tx *gorm.DB, lines []entities.TInventoryRequestLineEntity) error { + if tx == nil { + tx = r.db + } + return tx.WithContext(ctx).Create(&lines).Error +} + +func (r *inventoryRequestLineRepository) DeleteByRequestId(ctx context.Context, tx *gorm.DB, requestId string) error { + if tx == nil { + tx = r.db + } + return tx.WithContext(ctx).Where("inv_request_id = ?", requestId).Delete(&entities.TInventoryRequestLineEntity{}).Error +} diff --git a/modules/inventory_request/repository/inventory_request_repository.go b/modules/inventory_request/repository/inventory_request_repository.go new file mode 100644 index 0000000..c68712c --- /dev/null +++ b/modules/inventory_request/repository/inventory_request_repository.go @@ -0,0 +1,88 @@ +package repository + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/query" + "gorm.io/gorm" +) + +type InventoryRequestRepository interface { + Create(ctx context.Context, tx *gorm.DB, request entities.TInventoryRequestEntity) (entities.TInventoryRequestEntity, error) + GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryRequestEntity, error) + GetAll(ctx context.Context, filter query.InventoryRequestFilter) ([]entities.TInventoryRequestEntity, int64, error) + Update(ctx context.Context, tx *gorm.DB, request entities.TInventoryRequestEntity) (entities.TInventoryRequestEntity, error) + Delete(ctx context.Context, tx *gorm.DB, id string) error +} + +type inventoryRequestRepository struct { + db *gorm.DB +} + +func NewInventoryRequestRepository(db *gorm.DB) InventoryRequestRepository { + return &inventoryRequestRepository{db: db} +} + +func (r *inventoryRequestRepository) Create(ctx context.Context, tx *gorm.DB, request entities.TInventoryRequestEntity) (entities.TInventoryRequestEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Create(&request).Error; err != nil { + return request, err + } + return request, nil +} + +func (r *inventoryRequestRepository) GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryRequestEntity, error) { + if tx == nil { + tx = r.db + } + var request entities.TInventoryRequestEntity + if err := tx.WithContext(ctx). + Preload("Client"). + Preload("RequestLines"). + Preload("RequestLines.Product"). + Preload("Assignment"). + Preload("Assignment.AssignmentUsers"). + Preload("Assignment.AssignmentUsers.User"). + Preload("Assignment.AssignmentUsers.Role"). + First(&request, "id = ?", id).Error; err != nil { + return request, err + } + return request, nil +} + +func (r *inventoryRequestRepository) GetAll(ctx context.Context, filter query.InventoryRequestFilter) ([]entities.TInventoryRequestEntity, int64, error) { + var requests []entities.TInventoryRequestEntity + var total int64 + db := query.ApplyInventoryRequestFilters(r.db, filter) + db.Model(&entities.TInventoryRequestEntity{}).Count(&total) + if filter.PerPage > 0 && filter.Page > 0 { + db = db.Offset((filter.Page - 1) * filter.PerPage).Limit(filter.PerPage) + } + if err := db.Preload("Client").Find(&requests).Error; err != nil { + return requests, total, err + } + return requests, total, nil +} + +func (r *inventoryRequestRepository) Update(ctx context.Context, tx *gorm.DB, request entities.TInventoryRequestEntity) (entities.TInventoryRequestEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Model(&entities.TInventoryRequestEntity{}).Where("id = ?", request.ID).Select("*").Updates(&request).Error; err != nil { + return request, err + } + return request, nil +} + +func (r *inventoryRequestRepository) Delete(ctx context.Context, tx *gorm.DB, id string) error { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Delete(&entities.TInventoryRequestEntity{}, "id = ?", id).Error; err != nil { + return err + } + return nil +} diff --git a/modules/inventory_request/routes.go b/modules/inventory_request/routes.go new file mode 100644 index 0000000..e17ce31 --- /dev/null +++ b/modules/inventory_request/routes.go @@ -0,0 +1,27 @@ +package inventoryrequest + +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_request/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) { + requestController := do.MustInvoke[controller.InventoryRequestController](injector) + jwtService := do.MustInvokeNamed[service.JWTService](injector, constants.JWTService) + + requestRoutes := server.Group("/api/v1/inventory-requests") + { + requestRoutes.POST("", middlewares.Authenticate(jwtService), requestController.Create) + requestRoutes.GET(":id", middlewares.Authenticate(jwtService), requestController.GetById) + requestRoutes.PUT(":id", middlewares.Authenticate(jwtService), requestController.Update) + requestRoutes.DELETE(":id", middlewares.Authenticate(jwtService), requestController.Delete) + requestRoutes.GET("", middlewares.Authenticate(jwtService), requestController.GetAll) + requestRoutes.POST(":id/lines", middlewares.Authenticate(jwtService), requestController.CreateLine) + requestRoutes.PUT("lines/:id", middlewares.Authenticate(jwtService), requestController.UpdateLine) + requestRoutes.DELETE("lines/:id", middlewares.Authenticate(jwtService), requestController.DeleteLine) + } +} diff --git a/modules/inventory_request/service/inventory_request_service.go b/modules/inventory_request/service/inventory_request_service.go new file mode 100644 index 0000000..1df0c7f --- /dev/null +++ b/modules/inventory_request/service/inventory_request_service.go @@ -0,0 +1,342 @@ +package service + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + dtodomain "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/query" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/repository" + pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto" + "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" + "github.com/google/uuid" + "gorm.io/gorm" +) + +type InventoryRequestService interface { + Create(ctx context.Context, req dtodomain.InventoryRequestCreateRequest) (dtodomain.InventoryRequestResponse, error) + GetById(ctx context.Context, id string) (dtodomain.InventoryRequestResponse, error) + GetAll(ctx context.Context, filter query.InventoryRequestFilter) ([]dtodomain.InventoryRequestResponse, int64, error) + Update(ctx context.Context, req dtodomain.InventoryRequestUpdateRequest, id string) (dtodomain.InventoryRequestResponse, error) + Delete(ctx context.Context, id string) error + CreateLine(ctx context.Context, requestId string, req dtodomain.InventoryRequestLineCreateRequest) (dtodomain.InventoryRequestLineResponse, error) + UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryRequestLineUpdateRequest) (dtodomain.InventoryRequestLineResponse, error) + DeleteLine(ctx context.Context, lineId string) error +} + +type inventoryRequestService struct { + db *gorm.DB + requestRepo repository.InventoryRequestRepository + requestLineRepo repository.InventoryRequestLineRepository +} + +func (s *inventoryRequestService) DeleteLine(ctx context.Context, lineId string) error { + return s.requestLineRepo.Delete(ctx, nil, lineId) +} + +func (s *inventoryRequestService) UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryRequestLineUpdateRequest) (dtodomain.InventoryRequestLineResponse, error) { + line, err := s.requestLineRepo.GetById(ctx, nil, lineId) + if err != nil { + return dtodomain.InventoryRequestLineResponse{}, err + } + if req.Quantity != nil { + line.Quantity = *req.Quantity + } + if req.ProductID != nil { + if *req.ProductID != "" { + tmp, err := uuid.Parse(*req.ProductID) + if err != nil { + return dtodomain.InventoryRequestLineResponse{}, err + } + line.ProductID = tmp + } else { + line.ProductID = uuid.Nil + } + } + updated, err := s.requestLineRepo.Update(ctx, nil, line) + if err != nil { + return dtodomain.InventoryRequestLineResponse{}, err + } + product := dtodomain.InventoryRequestLineProductResponse{} + if updated.Product.ID != uuid.Nil { + product = dtodomain.InventoryRequestLineProductResponse{ + ID: updated.Product.ID.String(), + Name: updated.Product.Name, + RefNumber: updated.Product.RefNumber, + Uom: pkgdto.IdNameResponse{ + ID: updated.Product.Uom.ID.String(), + Name: updated.Product.Uom.Name, + }, + } + } + return dtodomain.InventoryRequestLineResponse{ + ID: updated.ID.String(), + Quantity: updated.Quantity, + Product: product, + ClientID: updated.ClientID.String(), + }, nil +} + +func toAssignmentResponse(e entities.TAssignmentEntity) dtodomain.AssignmentResponse { + users := make([]dtodomain.AssignmentUserResponse, 0) + for _, user := range e.AssignmentUsers { + userResp := dtodomain.AssignmentUserResponse{ + ID: user.ID.String(), + TaskType: user.TaskType, + User: pkgdto.IdNameResponse{ID: user.User.ID.String(), Name: user.User.Name}, + Role: pkgdto.IdNameResponse{ID: user.Role.ID.String(), Name: user.Role.Name}, + } + users = append(users, userResp) + } + return dtodomain.AssignmentResponse{ + ID: e.ID.String(), + DocumentType: e.DocumentType, + DocumentID: e.DocumentID.String(), + AssignmentUsers: users, + } +} + +func toInventoryRequestResponse(e entities.TInventoryRequestEntity) dtodomain.InventoryRequestResponse { + client := pkgdto.IdNameResponse{} + if e.Client.ID != uuid.Nil { + client = pkgdto.IdNameResponse{ + ID: e.Client.ID.String(), + Name: e.Client.Name, + } + } + lines := make([]dtodomain.InventoryRequestLineResponse, 0) + for _, line := range e.RequestLines { + product := dtodomain.InventoryRequestLineProductResponse{} + if line.Product.ID != uuid.Nil { + product = dtodomain.InventoryRequestLineProductResponse{ + ID: line.Product.ID.String(), + Name: line.Product.Name, + RefNumber: line.Product.RefNumber, + Uom: pkgdto.IdNameResponse{ + ID: line.Product.Uom.ID.String(), + Name: line.Product.Uom.Name, + }, + } + } + lines = append(lines, dtodomain.InventoryRequestLineResponse{ + ID: line.ID.String(), + Quantity: line.Quantity, + Product: product, + ClientID: line.ClientID.String(), + }) + } + assignment := dtodomain.AssignmentResponse{} + if e.Assignment.ID != uuid.Nil { + assignment = toAssignmentResponse(e.Assignment) + } + return dtodomain.InventoryRequestResponse{ + ID: e.ID.String(), + ReferenceNumber: e.ReferenceNumber, + DocumentNumber: e.DocumentNumber, + DueDate: utils.DateTimeToString(e.DueDate), + RequestType: e.RequestType, + Note: e.Note, + Status: e.Status, + Client: client, + LineCount: len(lines), + RequestLines: lines, + Assignment: assignment, + } +} + +func (s *inventoryRequestService) Create(ctx context.Context, req dtodomain.InventoryRequestCreateRequest) (dtodomain.InventoryRequestResponse, 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.InventoryRequestResponse{}, err + } + docNum, err := entities.GenerateDocumentNumberInventoryRequest(s.db, req.ClientID) + if err != nil { + tx.Rollback() + return dtodomain.InventoryRequestResponse{}, err + } + request := entities.TInventoryRequestEntity{ + ReferenceNumber: req.ReferenceNumber, + DocumentNumber: docNum, + DueDate: utils.StringToDateTime(req.DueDate), + RequestType: req.RequestType, + Note: req.Note, + ClientID: clientUUID, + Status: req.Status, + } + created, err := s.requestRepo.Create(ctx, tx, request) + if err != nil { + tx.Rollback() + return dtodomain.InventoryRequestResponse{}, err + } + var lines []entities.TInventoryRequestLineEntity + for _, lineReq := range req.RequestLines { + var productUUID uuid.UUID + if lineReq.ProductID != "" { + productUUID, err = uuid.Parse(lineReq.ProductID) + if err != nil { + tx.Rollback() + return dtodomain.InventoryRequestResponse{}, err + } + } else { + productUUID = uuid.Nil + } + clientLineUUID, err := uuid.Parse(lineReq.ClientID) + if err != nil { + tx.Rollback() + return dtodomain.InventoryRequestResponse{}, err + } + lines = append(lines, entities.TInventoryRequestLineEntity{ + Quantity: lineReq.Quantity, + InvRequestID: created.ID, + ProductID: productUUID, + ClientID: clientLineUUID, + }) + } + if len(lines) > 0 { + err = s.requestLineRepo.BulkCreate(ctx, tx, lines) + if err != nil { + tx.Rollback() + return dtodomain.InventoryRequestResponse{}, err + } + } + tx.Commit() + result, err := s.requestRepo.GetById(ctx, nil, created.ID.String()) + if err != nil { + return dtodomain.InventoryRequestResponse{}, err + } + return toInventoryRequestResponse(result), nil +} + +func (s *inventoryRequestService) GetById(ctx context.Context, id string) (dtodomain.InventoryRequestResponse, error) { + request, err := s.requestRepo.GetById(ctx, nil, id) + if err != nil { + return dtodomain.InventoryRequestResponse{}, err + } + return toInventoryRequestResponse(request), nil +} + +func (s *inventoryRequestService) GetAll(ctx context.Context, filter query.InventoryRequestFilter) ([]dtodomain.InventoryRequestResponse, int64, error) { + requests, total, err := s.requestRepo.GetAll(ctx, filter) + if err != nil { + return nil, 0, err + } + var responses []dtodomain.InventoryRequestResponse + for _, e := range requests { + responses = append(responses, toInventoryRequestResponse(e)) + } + if responses == nil { + responses = make([]dtodomain.InventoryRequestResponse, 0) + } + return responses, total, nil +} + +func (s *inventoryRequestService) Update(ctx context.Context, req dtodomain.InventoryRequestUpdateRequest, id string) (dtodomain.InventoryRequestResponse, error) { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + request, err := s.requestRepo.GetById(ctx, tx, id) + if err != nil { + tx.Rollback() + return dtodomain.InventoryRequestResponse{}, err + } + if req.ReferenceNumber != "" { + request.ReferenceNumber = req.ReferenceNumber + } + request.DocumentNumber = req.DocumentNumber + request.DueDate = utils.StringToDateTime(req.DueDate) + request.RequestType = req.RequestType + request.Note = req.Note + request.Status = req.Status + updated, err := s.requestRepo.Update(ctx, tx, request) + if err != nil { + tx.Rollback() + return dtodomain.InventoryRequestResponse{}, err + } + tx.Commit() + result, err := s.requestRepo.GetById(ctx, nil, updated.ID.String()) + if err != nil { + return dtodomain.InventoryRequestResponse{}, err + } + return toInventoryRequestResponse(result), nil +} + +func (s *inventoryRequestService) Delete(ctx context.Context, id string) error { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := s.requestRepo.Delete(ctx, tx, id); err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + +func (s *inventoryRequestService) CreateLine(ctx context.Context, requestId string, req dtodomain.InventoryRequestLineCreateRequest) (dtodomain.InventoryRequestLineResponse, error) { + requestUUID, err := uuid.Parse(requestId) + if err != nil { + return dtodomain.InventoryRequestLineResponse{}, err + } + var productUUID uuid.UUID + if req.ProductID != "" { + productUUID, err = uuid.Parse(req.ProductID) + if err != nil { + return dtodomain.InventoryRequestLineResponse{}, err + } + } else { + productUUID = uuid.Nil + } + clientLineUUID, err := uuid.Parse(req.ClientID) + if err != nil { + return dtodomain.InventoryRequestLineResponse{}, err + } + line := entities.TInventoryRequestLineEntity{ + Quantity: req.Quantity, + InvRequestID: requestUUID, + ProductID: productUUID, + ClientID: clientLineUUID, + } + created, err := s.requestLineRepo.Create(ctx, nil, line) + if err != nil { + return dtodomain.InventoryRequestLineResponse{}, err + } + product := dtodomain.InventoryRequestLineProductResponse{} + if created.Product.ID != uuid.Nil { + product = dtodomain.InventoryRequestLineProductResponse{ + ID: created.Product.ID.String(), + Name: created.Product.Name, + RefNumber: created.Product.RefNumber, + Uom: pkgdto.IdNameResponse{ + ID: created.Product.Uom.ID.String(), + Name: created.Product.Uom.Name, + }, + } + } + return dtodomain.InventoryRequestLineResponse{ + ID: created.ID.String(), + Quantity: created.Quantity, + Product: product, + ClientID: created.ClientID.String(), + }, nil +} + +func NewInventoryRequestService(db *gorm.DB, requestRepo repository.InventoryRequestRepository, requestLineRepo repository.InventoryRequestLineRepository) InventoryRequestService { + return &inventoryRequestService{ + db: db, + requestRepo: requestRepo, + requestLineRepo: requestLineRepo, + } +} diff --git a/providers/core.go b/providers/core.go index d53bf7a..60c92ee 100644 --- a/providers/core.go +++ b/providers/core.go @@ -68,6 +68,11 @@ import ( assignmentUserRepo "github.com/Caknoooo/go-gin-clean-starter/modules/assignment/repository" assignmentService "github.com/Caknoooo/go-gin-clean-starter/modules/assignment/service" + inventoryRequestController "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/controller" + inventoryRequestLineRepo "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/repository" + inventoryRequestRepo "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/repository" + inventoryRequestService "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/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" @@ -121,6 +126,8 @@ func RegisterDependencies(injector *do.Injector) { inventoryReceiptLineRepository := inventoryReceiptLineRepo.NewInventoryReceiptLineRepository(db) assignmentRepository := assignmentRepo.NewAssignmentRepository(db) assignmentUserRepository := assignmentUserRepo.NewAssignmentUserRepository(db) + inventoryRequestRepository := inventoryRequestRepo.NewInventoryRequestRepository(db) + inventoryRequestLineRepository := inventoryRequestLineRepo.NewInventoryRequestLineRepository(db) // Service userServ := userService.NewUserService(userRepository, refreshTokenRepository, jwtService, db) @@ -138,6 +145,7 @@ func RegisterDependencies(injector *do.Injector) { aisleServ := aisleService.NewAisleService(aisleRepository, db) inventoryReceiptServ := inventoryReceiptService.NewInventoryReceiptService(db, inventoryReceiptRepository, inventoryReceiptLineRepository) assignmentServ := assignmentService.NewAssignmentService(db, assignmentRepository, assignmentUserRepository) + inventoryRequestServ := inventoryRequestService.NewInventoryRequestService(db, inventoryRequestRepository, inventoryRequestLineRepository) // Controller do.Provide( @@ -225,4 +233,9 @@ func RegisterDependencies(injector *do.Injector) { return assignmentController.NewAssignmentController(i, assignmentServ), nil }, ) + do.Provide( + injector, func(i *do.Injector) (inventoryRequestController.InventoryRequestController, error) { + return inventoryRequestController.NewInventoryRequestController(i, inventoryRequestServ), nil + }, + ) }