diff --git a/cmd/main.go b/cmd/main.go index 54e0b3f..3ab2ded 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -27,6 +27,7 @@ import ( "github.com/Caknoooo/go-gin-clean-starter/modules/mvendor" "github.com/Caknoooo/go-gin-clean-starter/modules/permissions" "github.com/Caknoooo/go-gin-clean-starter/modules/product" + "github.com/Caknoooo/go-gin-clean-starter/modules/quarantine" "github.com/Caknoooo/go-gin-clean-starter/modules/role" "github.com/Caknoooo/go-gin-clean-starter/modules/uom" "github.com/Caknoooo/go-gin-clean-starter/modules/warehouse" @@ -177,6 +178,7 @@ func main() { inventorymovement.RegisterRoutes(server, injector) inventorystorage.RegisterRoutes(server, injector) inventorytransaction.RegisterRoutes(server, injector) + quarantine.RegisterRoutes(server, injector) // register swagger route server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) diff --git a/database/entities/t_inventory_quarantine_entity.go b/database/entities/t_inventory_quarantine_entity.go new file mode 100644 index 0000000..ea78869 --- /dev/null +++ b/database/entities/t_inventory_quarantine_entity.go @@ -0,0 +1,74 @@ +package entities + +import ( + "fmt" + "strings" + "time" + + "github.com/google/uuid" + "gorm.io/gorm" +) + +type TInventoryQuarantineEntity struct { + ID uuid.UUID `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"` + DocumentNumber string `gorm:"type:varchar(100);" json:"document_number"` + DocumentDate time.Time `gorm:"type:timestamp;" json:"document_date"` + Status string `gorm:"type:varchar(50);default:'draft'" json:"status"` + + WarehouseID uuid.UUID `gorm:"type:uuid;index;" json:"warehouse_id"` + ZonaID uuid.UUID `gorm:"type:uuid;index;" json:"zona_id"` + ClientID uuid.UUID `gorm:"type:uuid;index;" json:"client_id"` + + Warehouse MWarehouseEntity `gorm:"foreignKey:WarehouseID;references:ID"` + Zona MZonaEntity `gorm:"foreignKey:ZonaID;references:ID"` + Client M_Client `gorm:"foreignKey:ClientID;references:ID"` + QuarantineLines []TInventoryQuarantineLineEntity `gorm:"foreignKey:QuarantineID;references:ID"` + Assignment TAssignmentEntity `gorm:"-"` + + FullAuditTrail +} + +func (TInventoryQuarantineEntity) TableName() string { + return "t_inventory_quarantines" +} + +// GenerateDocumentNumber generates a new document number for a client +func GenerateDocumentNumberInQuarantine(db *gorm.DB, clientId string) (string, error) { + prefix := "QRTN" + + // 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 lastReceipt TInventoryIssueEntity + err := db. + Where("client_id = ?", clientId). + Order("document_number DESC"). + First(&lastReceipt).Error + + seq := 1 + if err == nil && lastReceipt.DocumentNumber != "" { + parts := strings.Split(lastReceipt.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/entities/t_inventory_quarantine_line_entity.go b/database/entities/t_inventory_quarantine_line_entity.go new file mode 100644 index 0000000..a7bd5ef --- /dev/null +++ b/database/entities/t_inventory_quarantine_line_entity.go @@ -0,0 +1,25 @@ +package entities + +import ( + "github.com/google/uuid" +) + +type TInventoryQuarantineLineEntity struct { + ID uuid.UUID `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"` + + QuarantineID uuid.UUID `gorm:"type:uuid;index;" json:"quarantine_id"` + StorageID uuid.UUID `gorm:"type:uuid;index;" json:"storage_id"` + ProductID uuid.UUID `gorm:"type:uuid;index;" json:"product_id"` + ClientID uuid.UUID `gorm:"type:uuid;index;" json:"client_id"` + + Product MProductEntity `gorm:"foreignKey:ProductID;references:ID"` + Storage InventoryStorageEntity `gorm:"foreignKey:StorageID;references:ID"` + Quarantine TInventoryQuarantineEntity `gorm:"foreignKey:QuarantineID;references:ID"` + Client M_Client `gorm:"foreignKey:ClientID;references:ID"` + + FullAuditTrail +} + +func (TInventoryQuarantineLineEntity) TableName() string { + return "t_inventory_quarantine_lines" +} diff --git a/database/migration.go b/database/migration.go index 6a896eb..ea2d7c4 100644 --- a/database/migration.go +++ b/database/migration.go @@ -43,6 +43,8 @@ func Migrate(db *gorm.DB) error { &entities.InventoryStorageEntity{}, &entities.TInventoryMovementEntity{}, &entities.TInventoryMovementLineEntity{}, + &entities.TInventoryQuarantineEntity{}, + &entities.TInventoryQuarantineLineEntity{}, ); err != nil { return err } @@ -53,42 +55,44 @@ func Migrate(db *gorm.DB) error { func MigrateFresh(db *gorm.DB) error { // Drop tables if err := db.Migrator().DropTable( - // &entities.M_Client{}, - // &entities.M_User{}, - // &entities.RefreshToken{}, - // &entities.M_Menu{}, - // &entities.M_Role{}, - // &entities.M_Permissions{}, - // &entities.M_User_Role{}, - // &entities.M_Role_Menu{}, - // &entities.M_Menu_Client{}, - // &entities.M_Role_Permission{}, - // &entities.M_MaintenanceGroup{}, - // &entities.M_MaintenanceGroupRole{}, - // &entities.M_MaintenanceGroupRoleUser{}, - // &entities.MCategoryEntity{}, - // &entities.MProductEntity{}, - // &entities.MUomEntity{}, - // &entities.MVendorEntity{}, - // &entities.MCrossReferenceEntity{}, - // &entities.MWarehouseEntity{}, - // &entities.MZonaEntity{}, - // &entities.MAisleEntity{}, - // &entities.MUserWarehouseEntity{}, - // &entities.TAssignmentEntity{}, - // &entities.TAssignmentUserEntity{}, - // &entities.TInventoryReceiptEntity{}, - // &entities.TInventoryReceiptLineEntity{}, - // &entities.TInventoryRequestEntity{}, - // &entities.TInventoryRequestLineEntity{}, - // &entities.TInventoryIssueEntity{}, - // &entities.TInventoryIssueLineEntity{}, - // &entities.TInventoryReturnEntity{}, - // &entities.TInventoryReturnLineEntity{}, - // &entities.InventoryTransactionEntity{}, - // &entities.InventoryStorageEntity{}, - // &entities.TInventoryMovementEntity{}, - // &entities.TInventoryMovementLineEntity{}, + // &entities.M_Client{}, + // &entities.M_User{}, + // &entities.RefreshToken{}, + // &entities.M_Menu{}, + // &entities.M_Role{}, + // &entities.M_Permissions{}, + // &entities.M_User_Role{}, + // &entities.M_Role_Menu{}, + // &entities.M_Menu_Client{}, + // &entities.M_Role_Permission{}, + // &entities.M_MaintenanceGroup{}, + // &entities.M_MaintenanceGroupRole{}, + // &entities.M_MaintenanceGroupRoleUser{}, + // &entities.MCategoryEntity{}, + // &entities.MProductEntity{}, + // &entities.MUomEntity{}, + // &entities.MVendorEntity{}, + // &entities.MCrossReferenceEntity{}, + // &entities.MWarehouseEntity{}, + // &entities.MZonaEntity{}, + // &entities.MAisleEntity{}, + // &entities.MUserWarehouseEntity{}, + // &entities.TAssignmentEntity{}, + // &entities.TAssignmentUserEntity{}, + // &entities.TInventoryReceiptEntity{}, + // &entities.TInventoryReceiptLineEntity{}, + // &entities.TInventoryRequestEntity{}, + // &entities.TInventoryRequestLineEntity{}, + // &entities.TInventoryIssueEntity{}, + // &entities.TInventoryIssueLineEntity{}, + // &entities.TInventoryReturnEntity{}, + // &entities.TInventoryReturnLineEntity{}, + // &entities.InventoryTransactionEntity{}, + // &entities.InventoryStorageEntity{}, + // &entities.TInventoryMovementEntity{}, + // &entities.TInventoryMovementLineEntity{}, + &entities.TInventoryQuarantineEntity{}, + &entities.TInventoryQuarantineLineEntity{}, ); err != nil { return err } diff --git a/modules/quarantine/controller/quarantine_controller.go b/modules/quarantine/controller/quarantine_controller.go new file mode 100644 index 0000000..cdd9abd --- /dev/null +++ b/modules/quarantine/controller/quarantine_controller.go @@ -0,0 +1,189 @@ +package controller + +import ( + "github.com/Caknoooo/go-gin-clean-starter/modules/quarantine/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/quarantine/query" + "github.com/Caknoooo/go-gin-clean-starter/modules/quarantine/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 QuarantineController 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) + OnComplete(ctx *gin.Context) +} + +type quarantineController struct { + quarantineService service.QuarantineService + db *gorm.DB +} + +func NewQuarantineController(i *do.Injector, quarantineService service.QuarantineService) QuarantineController { + return &quarantineController{ + quarantineService: quarantineService, + db: do.MustInvokeNamed[*gorm.DB](i, constants.DB), + } +} + +func (c *quarantineController) Create(ctx *gin.Context) { + var req dto.QuarantineCreateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) + ctx.JSON(400, res) + return + } + created, err := c.quarantineService.Create(ctx, req) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_QUARANTINE, err.Error(), nil) + ctx.JSON(500, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_QUARANTINE, created) + ctx.JSON(200, res) +} + +func (c *quarantineController) GetById(ctx *gin.Context) { + id := ctx.Param("id") + quarantine, err := c.quarantineService.GetById(ctx, id) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_QUARANTINE, err.Error(), nil) + ctx.JSON(404, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_QUARANTINE, quarantine) + ctx.JSON(200, res) +} + +func (c *quarantineController) GetAll(ctx *gin.Context) { + clientId := ctx.DefaultQuery("client_id", "") + var filter query.QuarantineFilter + filter.ClientID = clientId + if err := ctx.ShouldBindQuery(&filter); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_QUARANTINE, err.Error(), nil) + ctx.JSON(400, res) + return + } + getAll := ctx.Query("get_all") + if getAll != "" { + quarantines, _, err := c.quarantineService.GetAll(ctx, filter) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_QUARANTINE, err.Error(), nil) + ctx.JSON(400, res) + return + } + response := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_QUARANTINE, quarantines) + ctx.JSON(200, 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 + quarantines, total, err := c.quarantineService.GetAll(ctx, filter) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_QUARANTINE, err.Error(), nil) + ctx.JSON(500, res) + return + } + paginationResponse := utils.BuildPaginationResponse(perPage, page, total) + res := utils.BuildResponseSuccessWithPagination(200, dto.MESSAGE_SUCCESS_GET_QUARANTINE, quarantines, paginationResponse) + ctx.JSON(200, res) +} + +func (c *quarantineController) Update(ctx *gin.Context) { + id := ctx.Param("id") + var req dto.QuarantineUpdateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) + ctx.JSON(400, res) + return + } + updated, err := c.quarantineService.Update(ctx, req, id) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_QUARANTINE, err.Error(), nil) + ctx.JSON(500, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_QUARANTINE, updated) + ctx.JSON(200, res) +} + +func (c *quarantineController) Delete(ctx *gin.Context) { + id := ctx.Param("id") + if err := c.quarantineService.Delete(ctx, id); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_QUARANTINE, err.Error(), nil) + ctx.JSON(500, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_QUARANTINE, nil) + ctx.JSON(200, res) +} + +func (c *quarantineController) CreateLine(ctx *gin.Context) { + id := ctx.Param("id") + var req dto.QuarantineLineCreateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) + ctx.JSON(400, res) + return + } + created, err := c.quarantineService.CreateLine(ctx, id, req) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_QUARANTINE_LINE, err.Error(), nil) + ctx.JSON(500, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_QUARANTINE_LINE, created) + ctx.JSON(200, res) +} + +func (c *quarantineController) UpdateLine(ctx *gin.Context) { + id := ctx.Param("id") + var req dto.QuarantineLineUpdateRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil) + ctx.JSON(400, res) + return + } + updated, err := c.quarantineService.UpdateLine(ctx, id, req) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_QUARANTINE_LINE, err.Error(), nil) + ctx.JSON(500, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_QUARANTINE_LINE, updated) + ctx.JSON(200, res) +} + +func (c *quarantineController) DeleteLine(ctx *gin.Context) { + id := ctx.Param("id") + if err := c.quarantineService.DeleteLine(ctx, id); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_QUARANTINE_LINE, err.Error(), nil) + ctx.JSON(500, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_QUARANTINE_LINE, nil) + ctx.JSON(200, res) +} + +func (c *quarantineController) OnComplete(ctx *gin.Context) { + id := ctx.Param("id") + updated, err := c.quarantineService.OnComplete(ctx, id) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_COMPLETE_QUARANTINE, err.Error(), nil) + ctx.JSON(500, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_COMPLETE_QUARANTINE, updated) + ctx.JSON(200, res) +} diff --git a/modules/quarantine/dto/quarantine_dto.go b/modules/quarantine/dto/quarantine_dto.go new file mode 100644 index 0000000..316b195 --- /dev/null +++ b/modules/quarantine/dto/quarantine_dto.go @@ -0,0 +1,87 @@ +package dto + +const ( + MESSAGE_FAILED_CREATE_QUARANTINE = "failed create quarantine" + MESSAGE_FAILED_CREATE_QUARANTINE_LINE = "failed create quarantine line" + MESSAGE_SUCCESS_CREATE_QUARANTINE = "success create quarantine" + MESSAGE_SUCCESS_CREATE_QUARANTINE_LINE = "success create quarantine line" + MESSAGE_FAILED_GET_QUARANTINE = "failed get quarantine" + MESSAGE_SUCCESS_GET_QUARANTINE = "success get quarantine" + MESSAGE_FAILED_UPDATE_QUARANTINE = "failed update quarantine" + MESSAGE_FAILED_UPDATE_QUARANTINE_LINE = "failed update quarantine line" + MESSAGE_SUCCESS_UPDATE_QUARANTINE = "success update quarantine" + MESSAGE_SUCCESS_UPDATE_QUARANTINE_LINE = "success update quarantine line" + MESSAGE_FAILED_DELETE_QUARANTINE = "failed delete quarantine" + MESSAGE_FAILED_DELETE_QUARANTINE_LINE = "failed delete quarantine line" + MESSAGE_SUCCESS_DELETE_QUARANTINE = "success delete quarantine" + MESSAGE_SUCCESS_DELETE_QUARANTINE_LINE = "success delete quarantine line" + MESSAGE_FAILED_GET_DATA_FROM_BODY = "failed get data from body" + MESSAGE_SUCCESS_COMPLETE_QUARANTINE = "success complete quarantine" + MESSAGE_FAILED_COMPLETE_QUARANTINE = "failed complete quarantine" +) + +type QuarantineCreateRequest struct { + ReferenceNumber string `json:"reference_number"` + DocumentDate string `json:"document_date"` + Source string `json:"source"` + QrCodeFile string `json:"qr_code_file"` + ClientID string `json:"client_id" binding:"required"` + Status string `json:"status"` + QuarantineLines []QuarantineLineCreateRequest `json:"quarantine_lines,omitempty" binding:"dive"` +} + +type QuarantineLineCreateRequest struct { + Quantity float64 `json:"quantity"` + BatchNumber string `json:"batch_number"` + RepackingSuggestion string `json:"repacking_suggestion"` + RepackUomID string `json:"repack_uom_id"` + RepackUomCode string `json:"repack_uom_code"` + ProductID string `json:"product_id"` + ProductCode string `json:"product_code"` + ClientID string `json:"client_id"` +} + +type QuarantineUpdateRequest struct { + ReferenceNumber string `json:"reference_number"` + DocumentDate string `json:"document_date"` + Source string `json:"source"` + QrCodeFile string `json:"qr_code_file"` + Status string `json:"status"` +} + +type QuarantineLineUpdateRequest struct { + Quantity float64 `json:"quantity"` + BatchNumber string `json:"batch_number"` + RepackingSuggestion string `json:"repacking_suggestion"` + RepackUomID string `json:"repack_uom_id"` + RepackUomCode string `json:"repack_uom_code"` + ProductID string `json:"product_id"` + ProductCode string `json:"product_code"` + ClientID string `json:"client_id"` +} + +type QuarantineResponse struct { + ID string `json:"id"` + DocumentNumber string `json:"document_number"` + DocumentDate string `json:"document_date"` + Status string `json:"status"` + WarehouseID string `json:"warehouse_id"` + ZonaID string `json:"zona_id"` + ClientID string `json:"client_id"` + Warehouse string `json:"warehouse"` + Zona string `json:"zona"` + Client string `json:"client"` + QuarantineLines []QuarantineLineResponse `json:"quarantine_lines,omitempty"` +} + +type QuarantineLineResponse struct { + ID string `json:"id"` + QuarantineID string `json:"quarantine_id"` + StorageID string `json:"storage_id"` + ProductID string `json:"product_id"` + ClientID string `json:"client_id"` + Product string `json:"product"` + Storage string `json:"storage"` + Quarantine string `json:"quarantine"` + Client string `json:"client"` +} diff --git a/modules/quarantine/query/quarantine_query.go b/modules/quarantine/query/quarantine_query.go new file mode 100644 index 0000000..739cc7f --- /dev/null +++ b/modules/quarantine/query/quarantine_query.go @@ -0,0 +1,22 @@ +package query + +import ( + "gorm.io/gorm" +) + +type QuarantineFilter struct { + ClientID string `form:"client_id"` + Source string `form:"source"` + PerPage int `form:"per_page"` + Page int `form:"page"` +} + +func ApplyQuarantineFilters(db *gorm.DB, filter QuarantineFilter) *gorm.DB { + if filter.ClientID != "" { + db = db.Where("client_id = ?", filter.ClientID) + } + if filter.Source != "" { + db = db.Where("source ILIKE ?", "%"+filter.Source+"%") + } + return db +} diff --git a/modules/quarantine/repository/quarantine_line_repository.go b/modules/quarantine/repository/quarantine_line_repository.go new file mode 100644 index 0000000..38fb8b6 --- /dev/null +++ b/modules/quarantine/repository/quarantine_line_repository.go @@ -0,0 +1,89 @@ +package repository + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "gorm.io/gorm" +) + +type QuarantineLineRepository interface { + Create(ctx context.Context, tx *gorm.DB, line entities.TInventoryQuarantineLineEntity) (entities.TInventoryQuarantineLineEntity, error) + GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryQuarantineLineEntity, error) + GetAllByQuarantineId(ctx context.Context, quarantineId string) ([]entities.TInventoryQuarantineLineEntity, error) + Update(ctx context.Context, tx *gorm.DB, line entities.TInventoryQuarantineLineEntity) (entities.TInventoryQuarantineLineEntity, error) + Delete(ctx context.Context, tx *gorm.DB, id string) error + BulkCreate(ctx context.Context, tx *gorm.DB, lines []entities.TInventoryQuarantineLineEntity) error + DeleteByQuarantineId(ctx context.Context, tx *gorm.DB, quarantineId string) error +} + +type quarantineLineRepository struct { + db *gorm.DB +} + +func NewQuarantineLineRepository(db *gorm.DB) QuarantineLineRepository { + return &quarantineLineRepository{db: db} +} + +func (r *quarantineLineRepository) Create(ctx context.Context, tx *gorm.DB, line entities.TInventoryQuarantineLineEntity) (entities.TInventoryQuarantineLineEntity, 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 *quarantineLineRepository) GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryQuarantineLineEntity, error) { + if tx == nil { + tx = r.db + } + var line entities.TInventoryQuarantineLineEntity + if err := tx.WithContext(ctx).Preload("Product").Preload("Storage").Preload("Quarantine").Preload("Client").First(&line, "id = ?", id).Error; err != nil { + return line, err + } + return line, nil +} + +func (r *quarantineLineRepository) GetAllByQuarantineId(ctx context.Context, quarantineId string) ([]entities.TInventoryQuarantineLineEntity, error) { + var lines []entities.TInventoryQuarantineLineEntity + if err := r.db.WithContext(ctx).Where("quarantine_id = ?", quarantineId).Preload("Product").Preload("Storage").Preload("Quarantine").Preload("Client").Find(&lines).Error; err != nil { + return lines, err + } + return lines, nil +} + +func (r *quarantineLineRepository) Update(ctx context.Context, tx *gorm.DB, line entities.TInventoryQuarantineLineEntity) (entities.TInventoryQuarantineLineEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Model(&entities.TInventoryQuarantineLineEntity{}).Where("id = ?", line.ID).Select("*").Updates(&line).Error; err != nil { + return line, err + } + return line, nil +} + +func (r *quarantineLineRepository) Delete(ctx context.Context, tx *gorm.DB, id string) error { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Delete(&entities.TInventoryQuarantineLineEntity{}, "id = ?", id).Error; err != nil { + return err + } + return nil +} + +func (r *quarantineLineRepository) BulkCreate(ctx context.Context, tx *gorm.DB, lines []entities.TInventoryQuarantineLineEntity) error { + if tx == nil { + tx = r.db + } + return tx.WithContext(ctx).Create(&lines).Error +} + +func (r *quarantineLineRepository) DeleteByQuarantineId(ctx context.Context, tx *gorm.DB, quarantineId string) error { + if tx == nil { + tx = r.db + } + return tx.WithContext(ctx).Where("quarantine_id = ?", quarantineId).Delete(&entities.TInventoryQuarantineLineEntity{}).Error +} diff --git a/modules/quarantine/repository/quarantine_repository.go b/modules/quarantine/repository/quarantine_repository.go new file mode 100644 index 0000000..9b1d286 --- /dev/null +++ b/modules/quarantine/repository/quarantine_repository.go @@ -0,0 +1,94 @@ +package repository + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "github.com/Caknoooo/go-gin-clean-starter/modules/quarantine/query" + "gorm.io/gorm" +) + +type QuarantineRepository interface { + Create(ctx context.Context, tx *gorm.DB, quarantine entities.TInventoryQuarantineEntity) (entities.TInventoryQuarantineEntity, error) + GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryQuarantineEntity, error) + GetAll(ctx context.Context, filter query.QuarantineFilter) ([]entities.TInventoryQuarantineEntity, int64, error) + Update(ctx context.Context, tx *gorm.DB, quarantine entities.TInventoryQuarantineEntity) (entities.TInventoryQuarantineEntity, error) + Delete(ctx context.Context, tx *gorm.DB, id string) error +} + +type quarantineRepository struct { + db *gorm.DB +} + +func NewQuarantineRepository(db *gorm.DB) QuarantineRepository { + return &quarantineRepository{db: db} +} + +func (r *quarantineRepository) Create(ctx context.Context, tx *gorm.DB, quarantine entities.TInventoryQuarantineEntity) (entities.TInventoryQuarantineEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Create(&quarantine).Error; err != nil { + return quarantine, err + } + return quarantine, nil +} + +func (r *quarantineRepository) GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryQuarantineEntity, error) { + if tx == nil { + tx = r.db + } + var quarantine entities.TInventoryQuarantineEntity + if err := tx.WithContext(ctx). + Preload("Client"). + Preload("QuarantineLines"). + Preload("QuarantineLines.Product"). + Preload("QuarantineLines.Storage"). + First(&quarantine, "id = ?", id).Error; err != nil { + return quarantine, err + } + // Assignment manual jika ada + var assignment entities.TAssignmentEntity + if err := tx.WithContext(ctx). + Preload("AssignmentUsers"). + Preload("AssignmentUsers.User"). + Preload("AssignmentUsers.Role"). + First(&assignment, "document_id = ? AND document_type = ?", quarantine.ID, "Quarantine").Error; err == nil { + quarantine.Assignment = assignment + } + return quarantine, nil +} + +func (r *quarantineRepository) GetAll(ctx context.Context, filter query.QuarantineFilter) ([]entities.TInventoryQuarantineEntity, int64, error) { + var quarantines []entities.TInventoryQuarantineEntity + var total int64 + db := query.ApplyQuarantineFilters(r.db, filter) + db.Model(&entities.TInventoryQuarantineEntity{}).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(&quarantines).Error; err != nil { + return quarantines, total, err + } + return quarantines, total, nil +} + +func (r *quarantineRepository) Update(ctx context.Context, tx *gorm.DB, quarantine entities.TInventoryQuarantineEntity) (entities.TInventoryQuarantineEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Model(&entities.TInventoryQuarantineEntity{}).Where("id = ?", quarantine.ID).Select("*").Updates(&quarantine).Error; err != nil { + return quarantine, err + } + return quarantine, nil +} + +func (r *quarantineRepository) Delete(ctx context.Context, tx *gorm.DB, id string) error { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Delete(&entities.TInventoryQuarantineEntity{}, "id = ?", id).Error; err != nil { + return err + } + return nil +} diff --git a/modules/quarantine/routes.go b/modules/quarantine/routes.go new file mode 100644 index 0000000..79fba21 --- /dev/null +++ b/modules/quarantine/routes.go @@ -0,0 +1,28 @@ +package quarantine + +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/quarantine/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) { + quarantineController := do.MustInvoke[controller.QuarantineController](injector) + jwtService := do.MustInvokeNamed[service.JWTService](injector, constants.JWTService) + + quarantineRoutes := server.Group("/api/v1/quarantines") + { + quarantineRoutes.POST("", middlewares.Authenticate(jwtService), quarantineController.Create) + quarantineRoutes.GET(":id", middlewares.Authenticate(jwtService), quarantineController.GetById) + quarantineRoutes.PUT(":id", middlewares.Authenticate(jwtService), quarantineController.Update) + quarantineRoutes.DELETE(":id", middlewares.Authenticate(jwtService), quarantineController.Delete) + quarantineRoutes.GET("", middlewares.Authenticate(jwtService), quarantineController.GetAll) + quarantineRoutes.POST(":id/lines", middlewares.Authenticate(jwtService), quarantineController.CreateLine) + quarantineRoutes.PUT("lines/:id", middlewares.Authenticate(jwtService), quarantineController.UpdateLine) + quarantineRoutes.DELETE("lines/:id", middlewares.Authenticate(jwtService), quarantineController.DeleteLine) + quarantineRoutes.POST(":id/complete", middlewares.Authenticate(jwtService), quarantineController.OnComplete) + } +} diff --git a/modules/quarantine/service/quarantine_service.go b/modules/quarantine/service/quarantine_service.go new file mode 100644 index 0000000..0f08b04 --- /dev/null +++ b/modules/quarantine/service/quarantine_service.go @@ -0,0 +1,236 @@ +package service + +import ( + "context" + "time" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + invstoragerepository "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_storage/repository" + productrepository "github.com/Caknoooo/go-gin-clean-starter/modules/product/repository" + dtodomain "github.com/Caknoooo/go-gin-clean-starter/modules/quarantine/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/quarantine/query" + "github.com/Caknoooo/go-gin-clean-starter/modules/quarantine/repository" + uomrepository "github.com/Caknoooo/go-gin-clean-starter/modules/uom/repository" + "github.com/google/uuid" + "gorm.io/gorm" +) + +type QuarantineService interface { + Create(ctx context.Context, req dtodomain.QuarantineCreateRequest) (dtodomain.QuarantineResponse, error) + GetById(ctx context.Context, id string) (dtodomain.QuarantineResponse, error) + GetAll(ctx context.Context, filter query.QuarantineFilter) ([]dtodomain.QuarantineResponse, int64, error) + Update(ctx context.Context, req dtodomain.QuarantineUpdateRequest, id string) (dtodomain.QuarantineResponse, error) + Delete(ctx context.Context, id string) error + CreateLine(ctx context.Context, quarantineId string, req dtodomain.QuarantineLineCreateRequest) (dtodomain.QuarantineLineResponse, error) + UpdateLine(ctx context.Context, lineId string, req dtodomain.QuarantineLineUpdateRequest) (dtodomain.QuarantineLineResponse, error) + DeleteLine(ctx context.Context, lineId string) error + OnComplete(ctx context.Context, id string) (dtodomain.QuarantineResponse, error) +} + +type quarantineService struct { + db *gorm.DB + quarantineRepo repository.QuarantineRepository + quarantineLineRepo repository.QuarantineLineRepository + productRepo productrepository.ProductRepository +} + +func NewQuarantineService(db *gorm.DB, quarantineRepo repository.QuarantineRepository, quarantineLineRepo repository.QuarantineLineRepository, productRepo productrepository.ProductRepository, uomRepo uomrepository.UomRepository, invStorageRepository invstoragerepository.InventoryStorageRepository) QuarantineService { + return &quarantineService{ + db: db, + quarantineRepo: quarantineRepo, + quarantineLineRepo: quarantineLineRepo, + productRepo: productRepo, + } +} + +func (s *quarantineService) Create(ctx context.Context, req dtodomain.QuarantineCreateRequest) (dtodomain.QuarantineResponse, error) { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + entity := entities.TInventoryQuarantineEntity{ + DocumentNumber: req.ReferenceNumber, + DocumentDate: parseDate(req.DocumentDate), + Status: req.Status, + ClientID: parseUUID(req.ClientID), + } + created, err := s.quarantineRepo.Create(ctx, tx, entity) + if err != nil { + tx.Rollback() + return dtodomain.QuarantineResponse{}, err + } + var lines []entities.TInventoryQuarantineLineEntity + for _, lineReq := range req.QuarantineLines { + line := entities.TInventoryQuarantineLineEntity{ + QuarantineID: created.ID, + ProductID: parseUUID(lineReq.ProductID), + ClientID: parseUUID(lineReq.ClientID), + } + lines = append(lines, line) + } + if len(lines) > 0 { + err = s.quarantineLineRepo.BulkCreate(ctx, tx, lines) + if err != nil { + tx.Rollback() + return dtodomain.QuarantineResponse{}, err + } + } + tx.Commit() + return mapQuarantineToResponse(created, lines), nil +} + +func (s *quarantineService) GetById(ctx context.Context, id string) (dtodomain.QuarantineResponse, error) { + entity, err := s.quarantineRepo.GetById(ctx, nil, id) + if err != nil { + return dtodomain.QuarantineResponse{}, err + } + lines, _ := s.quarantineLineRepo.GetAllByQuarantineId(ctx, id) + return mapQuarantineToResponse(entity, lines), nil +} + +func (s *quarantineService) GetAll(ctx context.Context, filter query.QuarantineFilter) ([]dtodomain.QuarantineResponse, int64, error) { + entities, total, err := s.quarantineRepo.GetAll(ctx, filter) + if err != nil { + return nil, 0, err + } + var responses []dtodomain.QuarantineResponse + for _, entity := range entities { + lines, _ := s.quarantineLineRepo.GetAllByQuarantineId(ctx, entity.ID.String()) + responses = append(responses, mapQuarantineToResponse(entity, lines)) + } + return responses, total, nil +} + +func (s *quarantineService) Update(ctx context.Context, req dtodomain.QuarantineUpdateRequest, id string) (dtodomain.QuarantineResponse, error) { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + entity, err := s.quarantineRepo.GetById(ctx, tx, id) + if err != nil { + tx.Rollback() + return dtodomain.QuarantineResponse{}, err + } + entity.DocumentNumber = req.ReferenceNumber + entity.DocumentDate = parseDate(req.DocumentDate) + entity.Status = req.Status + updated, err := s.quarantineRepo.Update(ctx, tx, entity) + if err != nil { + tx.Rollback() + return dtodomain.QuarantineResponse{}, err + } + tx.Commit() + lines, _ := s.quarantineLineRepo.GetAllByQuarantineId(ctx, id) + return mapQuarantineToResponse(updated, lines), nil +} + +func (s *quarantineService) Delete(ctx context.Context, id string) error { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := s.quarantineLineRepo.DeleteByQuarantineId(ctx, tx, id); err != nil { + tx.Rollback() + return err + } + if err := s.quarantineRepo.Delete(ctx, tx, id); err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + +func (s *quarantineService) CreateLine(ctx context.Context, quarantineId string, req dtodomain.QuarantineLineCreateRequest) (dtodomain.QuarantineLineResponse, error) { + line := entities.TInventoryQuarantineLineEntity{ + QuarantineID: parseUUID(quarantineId), + ProductID: parseUUID(req.ProductID), + ClientID: parseUUID(req.ClientID), + } + created, err := s.quarantineLineRepo.Create(ctx, nil, line) + if err != nil { + return dtodomain.QuarantineLineResponse{}, err + } + return mapQuarantineLineToResponse(created), nil +} + +func (s *quarantineService) UpdateLine(ctx context.Context, lineId string, req dtodomain.QuarantineLineUpdateRequest) (dtodomain.QuarantineLineResponse, error) { + line, err := s.quarantineLineRepo.GetById(ctx, nil, lineId) + if err != nil { + return dtodomain.QuarantineLineResponse{}, err + } + line.ProductID = parseUUID(req.ProductID) + line.ClientID = parseUUID(req.ClientID) + updated, err := s.quarantineLineRepo.Update(ctx, nil, line) + if err != nil { + return dtodomain.QuarantineLineResponse{}, err + } + return mapQuarantineLineToResponse(updated), nil +} + +func (s *quarantineService) DeleteLine(ctx context.Context, lineId string) error { + return s.quarantineLineRepo.Delete(ctx, nil, lineId) +} + +func (s *quarantineService) OnComplete(ctx context.Context, id string) (dtodomain.QuarantineResponse, error) { + entity, err := s.quarantineRepo.GetById(ctx, nil, id) + if err != nil { + return dtodomain.QuarantineResponse{}, err + } + entity.Status = "completed" + updated, err := s.quarantineRepo.Update(ctx, nil, entity) + if err != nil { + return dtodomain.QuarantineResponse{}, err + } + lines, _ := s.quarantineLineRepo.GetAllByQuarantineId(ctx, id) + return mapQuarantineToResponse(updated, lines), nil +} + +func parseUUID(id string) uuid.UUID { + uid, _ := uuid.Parse(id) + return uid +} + +func parseDate(dateStr string) time.Time { + t, _ := time.Parse("2006-01-02", dateStr) + return t +} + +func mapQuarantineToResponse(entity entities.TInventoryQuarantineEntity, lines []entities.TInventoryQuarantineLineEntity) dtodomain.QuarantineResponse { + resp := dtodomain.QuarantineResponse{ + ID: entity.ID.String(), + DocumentNumber: entity.DocumentNumber, + DocumentDate: entity.DocumentDate.Format("2006-01-02"), + Status: entity.Status, + WarehouseID: entity.WarehouseID.String(), + ZonaID: entity.ZonaID.String(), + ClientID: entity.ClientID.String(), + Warehouse: entity.Warehouse.Name, + Zona: entity.Zona.Name, + Client: entity.Client.Name, + } + for _, line := range lines { + resp.QuarantineLines = append(resp.QuarantineLines, mapQuarantineLineToResponse(line)) + } + return resp +} + +func mapQuarantineLineToResponse(line entities.TInventoryQuarantineLineEntity) dtodomain.QuarantineLineResponse { + return dtodomain.QuarantineLineResponse{ + ID: line.ID.String(), + QuarantineID: line.QuarantineID.String(), + StorageID: line.StorageID.String(), + ProductID: line.ProductID.String(), + ClientID: line.ClientID.String(), + Product: line.Product.Name, + Storage: line.StorageID.String(), // gunakan StorageID, karena field Name tidak ada + Quarantine: line.Quarantine.DocumentNumber, + Client: line.Client.Name, + } +} diff --git a/providers/core.go b/providers/core.go index c231b5a..7ebfe75 100644 --- a/providers/core.go +++ b/providers/core.go @@ -96,6 +96,11 @@ import ( inventoryTransactionRepo "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_transaction/repository" inventoryTransactionService "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_transaction/service" + quarantineController "github.com/Caknoooo/go-gin-clean-starter/modules/quarantine/controller" + quarantineLineRepo "github.com/Caknoooo/go-gin-clean-starter/modules/quarantine/repository" + quarantineRepo "github.com/Caknoooo/go-gin-clean-starter/modules/quarantine/repository" + quarantineService "github.com/Caknoooo/go-gin-clean-starter/modules/quarantine/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" @@ -159,6 +164,8 @@ func RegisterDependencies(injector *do.Injector) { inventoryMovementLineRepository := inventoryMovementLineRepo.NewInventoryMovementLineRepository(db) inventoryStorageRepository := inventoryStorageRepo.NewInventoryStorageRepository(db) inventoryTransactionRepository := inventoryTransactionRepo.NewInventoryTransactionRepository(db) + quarantineRepository := quarantineRepo.NewQuarantineRepository(db) + quarantineLineRepository := quarantineLineRepo.NewQuarantineLineRepository(db) // Service userServ := userService.NewUserService(userRepository, roleRepository, warehouseRepository, clientRepository, refreshTokenRepository, jwtService, db) @@ -182,6 +189,7 @@ func RegisterDependencies(injector *do.Injector) { inventoryMovementServ := inventoryMovementService.NewInventoryMovementService(db, inventoryMovementRepository, inventoryMovementLineRepository) inventoryStorageService := inventoryStorageService.NewInventoryStorageService(db, inventoryStorageRepository) inventoryTransactionServ := inventoryTransactionService.NewInventoryTransactionService(db, inventoryTransactionRepository) + quarantineServ := quarantineService.NewQuarantineService(db, quarantineRepository, quarantineLineRepository, productRepository, uomRepository, inventoryStorageRepository) // Controller do.Provide( @@ -299,4 +307,9 @@ func RegisterDependencies(injector *do.Injector) { return inventoryTransactionController.NewInventoryTransactionController(i, inventoryTransactionServ), nil }, ) + do.Provide( + injector, func(i *do.Injector) (quarantineController.QuarantineController, error) { + return quarantineController.NewQuarantineController(i, quarantineServ), nil + }, + ) }