diff --git a/database/entities/t_inventory_return_entity.go b/database/entities/t_inventory_return_entity.go new file mode 100644 index 0000000..b796aa7 --- /dev/null +++ b/database/entities/t_inventory_return_entity.go @@ -0,0 +1,73 @@ +package entities + +import ( + "fmt" + "strings" + "time" + + "github.com/google/uuid" + "gorm.io/gorm" +) + +type TInventoryReturnEntity 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"` + Notes string `gorm:"type:text;" json:"notes"` + Status string `gorm:"type:varchar(50);default:'draft'" json:"status"` + + InvIssueID uuid.UUID `gorm:"type:uuid;index;" json:"inv_issue_id"` + ClientID uuid.UUID `gorm:"type:uuid;index;" json:"client_id"` + + InvIssue TInventoryIssueEntity `gorm:"foreignKey:InvIssueID;references:ID"` + ReturnLines []TInventoryReturnLineEntity `gorm:"foreignKey:InvReturnID;references:ID"` + Assignment TAssignmentEntity `gorm:"-"` + Client M_Client `gorm:"foreignKey:ClientID;references:ID"` + + FullAuditTrail +} + +func (TInventoryReturnEntity) TableName() string { + return "t_inventory_returns" +} + +// GenerateDocumentNumber generates a new document number for a client +func GenerateDocumentNumberInventoryReturn(db *gorm.DB, clientId string) (string, error) { + prefix := "RTRN" + + // 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 TInventoryReturnEntity + 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/entities/t_inventory_return_line_entity.go b/database/entities/t_inventory_return_line_entity.go new file mode 100644 index 0000000..f81a835 --- /dev/null +++ b/database/entities/t_inventory_return_line_entity.go @@ -0,0 +1,25 @@ +package entities + +import ( + "github.com/google/uuid" +) + +type TInventoryReturnLineEntity struct { + ID uuid.UUID `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()" json:"id"` + Quantity float64 `gorm:"type:numeric;default:0" json:"quantity"` + Attachment string `gorm:"type:text;" json:"attachment"` + + InvReturnID uuid.UUID `gorm:"type:uuid;index;" json:"inv_return_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"` + InvReturn TInventoryReturnEntity `gorm:"foreignKey:InvReturnID;references:ID"` + Client M_Client `gorm:"foreignKey:ClientID;references:ID"` + + FullAuditTrail +} + +func (TInventoryReturnLineEntity) TableName() string { + return "t_inventory_return_lines" +} diff --git a/modules/inventory_return/controller/inventory_return_controller.go b/modules/inventory_return/controller/inventory_return_controller.go new file mode 100644 index 0000000..0034b30 --- /dev/null +++ b/modules/inventory_return/controller/inventory_return_controller.go @@ -0,0 +1,60 @@ +package controller + +import ( + "net/http" + + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_return/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_return/service" + "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +type InventoryReturnController 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 inventoryReturnController struct { + returnService service.InventoryReturnService + db *gorm.DB +} + +// DeleteLine implements InventoryReturnController. +func (c *inventoryReturnController) DeleteLine(ctx *gin.Context) { + id := ctx.Param("id") + if err := c.returnService.DeleteLine(ctx, id); err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_INVENTORY_RETURN_LINE, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_INVENTORY_RETURN_LINE, nil) + ctx.JSON(http.StatusOK, res) +} + +// UpdateLine implements InventoryReturnController. +func (c *inventoryReturnController) UpdateLine(ctx *gin.Context) { + id := ctx.Param("id") + var req dto.InventoryReturnLineUpdateRequest + 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 + } + line, err := c.returnService.UpdateLine(ctx, id, req) + if err != nil { + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_INVENTORY_RETURN_LINE, err.Error(), nil) + ctx.JSON(http.StatusInternalServerError, res) + return + } + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_INVENTORY_RETURN_LINE, line) + ctx.JSON(http.StatusOK, res) +} + +// ...implementasi fungsi lain (Create, Update, Delete, GetById, GetAll, CreateLine) dengan pola yang sama... diff --git a/modules/inventory_return/dto/inventory_return_dto.go b/modules/inventory_return/dto/inventory_return_dto.go new file mode 100644 index 0000000..f862d65 --- /dev/null +++ b/modules/inventory_return/dto/inventory_return_dto.go @@ -0,0 +1,106 @@ +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_RETURN = "failed create inventory return" + MESSAGE_FAILED_CREATE_INVENTORY_RETURN_LINE = "failed create inventory return line" + MESSAGE_SUCCESS_CREATE_INVENTORY_RETURN = "success create inventory return" + MESSAGE_SUCCESS_CREATE_INVENTORY_RETURN_LINE = "success create inventory return line" + MESSAGE_FAILED_GET_INVENTORY_RETURN = "failed get inventory return" + MESSAGE_SUCCESS_GET_INVENTORY_RETURN = "success get inventory return" + MESSAGE_FAILED_UPDATE_INVENTORY_RETURN = "failed update inventory return" + MESSAGE_FAILED_UPDATE_INVENTORY_RETURN_LINE = "failed update inventory return line" + MESSAGE_SUCCESS_UPDATE_INVENTORY_RETURN = "success update inventory return" + MESSAGE_SUCCESS_UPDATE_INVENTORY_RETURN_LINE = "success update inventory return line" + MESSAGE_FAILED_DELETE_INVENTORY_RETURN = "failed delete inventory return" + MESSAGE_FAILED_DELETE_INVENTORY_RETURN_LINE = "failed delete inventory return line" + MESSAGE_SUCCESS_DELETE_INVENTORY_RETURN = "success delete inventory return" + MESSAGE_SUCCESS_DELETE_INVENTORY_RETURN_LINE = "success delete inventory return line" + MESSAGE_FAILED_GET_DATA_FROM_BODY = "failed get data from body" +) + +type InventoryReturnCreateRequest struct { + DocumentDate string `json:"document_date"` + Notes string `json:"notes"` + Status string `json:"status"` + InvIssueID string `json:"inv_issue_id"` + ClientID string `json:"client_id" binding:"required"` + ReturnLines []InventoryReturnLineCreateRequest `json:"return_lines,omitempty" binding:"dive"` +} + +type InventoryReturnLineCreateRequest struct { + Quantity float64 `json:"quantity"` + Attachment string `json:"attachment"` + InvReturnID string `json:"inv_return_id"` + ProductID string `json:"product_id"` + ClientID string `json:"client_id"` +} + +type InventoryReturnUpdateRequest struct { + DocumentDate string `json:"document_date"` + Notes string `json:"notes"` + Status string `json:"status"` + InvIssueID string `json:"inv_issue_id"` + ClientID string `json:"client_id"` +} + +type InventoryReturnLineUpdateRequest struct { + Quantity *float64 `json:"quantity"` + Attachment *string `json:"attachment"` + InvReturnID *string `json:"inv_return_id"` + ProductID *string `json:"product_id"` + ClientID *string `json:"client_id"` +} + +type InventoryReturnResponse struct { + ID string `json:"id"` + DocumentNumber string `json:"document_number"` + DocumentDate string `json:"document_date"` + Notes string `json:"notes"` + Status string `json:"status"` + InvIssueID string `json:"inv_issue_id"` + ClientID string `json:"client_id"` + ReturnLines []InventoryReturnLineResponse `json:"return_lines,omitempty"` +} + +type InventoryReturnLineResponse struct { + ID string `json:"id"` + Quantity float64 `json:"quantity"` + Attachment string `json:"attachment"` + InvReturnID string `json:"inv_return_id"` + ProductID string `json:"product_id"` + ClientID string `json:"client_id"` +} + +// Helper untuk mapping entity ke response +func ToInventoryReturnResponse(entity entities.TInventoryReturnEntity) InventoryReturnResponse { + lines := make([]InventoryReturnLineResponse, 0, len(entity.ReturnLines)) + for _, line := range entity.ReturnLines { + lines = append(lines, ToInventoryReturnLineResponse(line)) + } + return InventoryReturnResponse{ + ID: entity.ID.String(), + DocumentNumber: entity.DocumentNumber, + DocumentDate: utils.DateTimeToString(entity.DocumentDate), + Notes: entity.Notes, + Status: entity.Status, + InvIssueID: entity.InvIssueID.String(), + ClientID: entity.ClientID.String(), + ReturnLines: lines, + } +} + +func ToInventoryReturnLineResponse(line entities.TInventoryReturnLineEntity) InventoryReturnLineResponse { + return InventoryReturnLineResponse{ + ID: line.ID.String(), + Quantity: line.Quantity, + Attachment: line.Attachment, + InvReturnID: line.InvReturnID.String(), + ProductID: line.ProductID.String(), + ClientID: line.ClientID.String(), + } +} diff --git a/modules/inventory_return/query/inventory_return_query.go b/modules/inventory_return/query/inventory_return_query.go new file mode 100644 index 0000000..fc8213a --- /dev/null +++ b/modules/inventory_return/query/inventory_return_query.go @@ -0,0 +1,22 @@ +package query + +import ( + "gorm.io/gorm" +) + +type InventoryReturnFilter struct { + ClientID string `form:"client_id"` + Source string `form:"source"` + PerPage int `form:"per_page"` + Page int `form:"page"` +} + +func ApplyInventoryReturnFilters(db *gorm.DB, filter InventoryReturnFilter) *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/inventory_return/repository/inventory_return_line_repository.go b/modules/inventory_return/repository/inventory_return_line_repository.go new file mode 100644 index 0000000..722d0ca --- /dev/null +++ b/modules/inventory_return/repository/inventory_return_line_repository.go @@ -0,0 +1,98 @@ +package repository + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "gorm.io/gorm" +) + +type InventoryReturnLineRepository interface { + Create(ctx context.Context, tx *gorm.DB, line entities.TInventoryReturnLineEntity) (entities.TInventoryReturnLineEntity, error) + GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryReturnLineEntity, error) + GetAllByReturnId(ctx context.Context, returnId string) ([]entities.TInventoryReturnLineEntity, error) + Update(ctx context.Context, tx *gorm.DB, line entities.TInventoryReturnLineEntity) (entities.TInventoryReturnLineEntity, error) + Delete(ctx context.Context, tx *gorm.DB, id string) error + BulkCreate(ctx context.Context, tx *gorm.DB, lines []entities.TInventoryReturnLineEntity) error + DeleteByReturnId(ctx context.Context, tx *gorm.DB, returnId string) error +} + +type inventoryReturnLineRepository struct { + db *gorm.DB +} + +// BulkCreate implements InventoryReturnLineRepository. +func (r *inventoryReturnLineRepository) BulkCreate(ctx context.Context, tx *gorm.DB, lines []entities.TInventoryReturnLineEntity) error { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Create(&lines).Error; err != nil { + return err + } + return nil +} + +// DeleteByReturnId implements InventoryReturnLineRepository. +func (r *inventoryReturnLineRepository) DeleteByReturnId(ctx context.Context, tx *gorm.DB, returnId string) error { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Where("inv_return_id = ?", returnId).Delete(&entities.TInventoryReturnLineEntity{}).Error; err != nil { + return err + } + return nil +} + +// GetAllByReturnId implements InventoryReturnLineRepository. +func (r *inventoryReturnLineRepository) GetAllByReturnId(ctx context.Context, returnId string) ([]entities.TInventoryReturnLineEntity, error) { + var lines []entities.TInventoryReturnLineEntity + if err := r.db.WithContext(ctx).Where("inv_return_id = ?", returnId).Find(&lines).Error; err != nil { + return nil, err + } + return lines, nil +} + +func NewInventoryReturnLineRepository(db *gorm.DB) InventoryReturnLineRepository { + return &inventoryReturnLineRepository{db: db} +} + +func (r *inventoryReturnLineRepository) Create(ctx context.Context, tx *gorm.DB, line entities.TInventoryReturnLineEntity) (entities.TInventoryReturnLineEntity, 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 *inventoryReturnLineRepository) GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryReturnLineEntity, error) { + if tx == nil { + tx = r.db + } + var line entities.TInventoryReturnLineEntity + if err := tx.WithContext(ctx).First(&line, "id = ?", id).Error; err != nil { + return line, err + } + return line, nil +} + +func (r *inventoryReturnLineRepository) Update(ctx context.Context, tx *gorm.DB, line entities.TInventoryReturnLineEntity) (entities.TInventoryReturnLineEntity, 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 *inventoryReturnLineRepository) Delete(ctx context.Context, tx *gorm.DB, id string) error { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Delete(&entities.TInventoryReturnLineEntity{}, "id = ?", id).Error; err != nil { + return err + } + return nil +} diff --git a/modules/inventory_return/repository/inventory_return_repository.go b/modules/inventory_return/repository/inventory_return_repository.go new file mode 100644 index 0000000..9220667 --- /dev/null +++ b/modules/inventory_return/repository/inventory_return_repository.go @@ -0,0 +1,97 @@ +package repository + +import ( + "context" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_return/query" + "gorm.io/gorm" +) + +type InventoryReturnRepository interface { + Create(ctx context.Context, tx *gorm.DB, ret entities.TInventoryReturnEntity) (entities.TInventoryReturnEntity, error) + GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryReturnEntity, error) + GetAll(ctx context.Context, filter query.InventoryReturnFilter) ([]entities.TInventoryReturnEntity, int64, error) + Update(ctx context.Context, tx *gorm.DB, ret entities.TInventoryReturnEntity) (entities.TInventoryReturnEntity, error) + Delete(ctx context.Context, tx *gorm.DB, id string) error +} + +type inventoryReturnRepository struct { + db *gorm.DB +} + +func NewInventoryReturnRepository(db *gorm.DB) InventoryReturnRepository { + return &inventoryReturnRepository{db: db} +} + +func (r *inventoryReturnRepository) Create(ctx context.Context, tx *gorm.DB, ret entities.TInventoryReturnEntity) (entities.TInventoryReturnEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Create(&ret).Error; err != nil { + return ret, err + } + return ret, nil +} + +func (r *inventoryReturnRepository) GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryReturnEntity, error) { + if tx == nil { + tx = r.db + } + var ret entities.TInventoryReturnEntity + if err := tx.WithContext(ctx). + Preload("Client"). + Preload("ReturnLines"). + Preload("ReturnLines.Product"). + Preload("ReturnLines.Product.Uom"). + Preload("ReturnLines.Product.DimUom"). + First(&ret, "id = ?", id).Error; err != nil { + return ret, err + } + + // Ambil assignment manual + var assignment entities.TAssignmentEntity + if err := tx.WithContext(ctx). + Preload("AssignmentUsers"). + Preload("AssignmentUsers.User"). + Preload("AssignmentUsers.Role"). + First(&assignment, "document_id = ? AND document_type = ?", ret.ID, "InventoryReturn").Error; err == nil { + ret.Assignment = assignment + } + + return ret, nil +} + +func (r *inventoryReturnRepository) GetAll(ctx context.Context, filter query.InventoryReturnFilter) ([]entities.TInventoryReturnEntity, int64, error) { + var returns []entities.TInventoryReturnEntity + var total int64 + db := query.ApplyInventoryReturnFilters(r.db, filter) + db.Model(&entities.TInventoryReturnEntity{}).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(&returns).Error; err != nil { + return returns, total, err + } + return returns, total, nil +} + +func (r *inventoryReturnRepository) Update(ctx context.Context, tx *gorm.DB, ret entities.TInventoryReturnEntity) (entities.TInventoryReturnEntity, error) { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Model(&entities.TInventoryReturnEntity{}).Where("id = ?", ret.ID).Select("*").Updates(&ret).Error; err != nil { + return ret, err + } + return ret, nil +} + +func (r *inventoryReturnRepository) Delete(ctx context.Context, tx *gorm.DB, id string) error { + if tx == nil { + tx = r.db + } + if err := tx.WithContext(ctx).Delete(&entities.TInventoryReturnEntity{}, "id = ?", id).Error; err != nil { + return err + } + return nil +} diff --git a/modules/inventory_return/routes.go b/modules/inventory_return/routes.go new file mode 100644 index 0000000..b0ded10 --- /dev/null +++ b/modules/inventory_return/routes.go @@ -0,0 +1,27 @@ +package inventoryreturn + +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_return/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) { + returnController := do.MustInvoke[controller.InventoryReturnController](injector) + jwtService := do.MustInvokeNamed[service.JWTService](injector, constants.JWTService) + + returnRoutes := server.Group("/api/v1/inventory-returns") + { + returnRoutes.POST("", middlewares.Authenticate(jwtService), returnController.Create) + returnRoutes.GET(":id", middlewares.Authenticate(jwtService), returnController.GetById) + returnRoutes.PUT(":id", middlewares.Authenticate(jwtService), returnController.Update) + returnRoutes.DELETE(":id", middlewares.Authenticate(jwtService), returnController.Delete) + returnRoutes.GET("", middlewares.Authenticate(jwtService), returnController.GetAll) + returnRoutes.POST(":id/lines", middlewares.Authenticate(jwtService), returnController.CreateLine) + returnRoutes.PUT("lines/:id", middlewares.Authenticate(jwtService), returnController.UpdateLine) + returnRoutes.DELETE("lines/:id", middlewares.Authenticate(jwtService), returnController.DeleteLine) + } +} diff --git a/modules/inventory_return/service/inventory_return_service.go b/modules/inventory_return/service/inventory_return_service.go new file mode 100644 index 0000000..2537810 --- /dev/null +++ b/modules/inventory_return/service/inventory_return_service.go @@ -0,0 +1,267 @@ +package service + +import ( + "context" + "fmt" + + "github.com/Caknoooo/go-gin-clean-starter/database/entities" + repositoryissue "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_issue/repository" + dtodomain "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_return/dto" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_return/query" + "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_return/repository" + productrepository "github.com/Caknoooo/go-gin-clean-starter/modules/product/repository" + "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" + "github.com/google/uuid" + "gorm.io/gorm" +) + +type InventoryReturnService interface { + Create(ctx context.Context, req dtodomain.InventoryReturnCreateRequest) (dtodomain.InventoryReturnResponse, error) + GetById(ctx context.Context, id string) (dtodomain.InventoryReturnResponse, error) + GetAll(ctx context.Context, filter query.InventoryReturnFilter) ([]dtodomain.InventoryReturnResponse, int64, error) + Update(ctx context.Context, req dtodomain.InventoryReturnUpdateRequest, id string) (dtodomain.InventoryReturnResponse, error) + Delete(ctx context.Context, id string) error + CreateLine(ctx context.Context, returnId string, req dtodomain.InventoryReturnLineCreateRequest) (dtodomain.InventoryReturnLineResponse, error) + UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryReturnLineUpdateRequest) (dtodomain.InventoryReturnLineResponse, error) + DeleteLine(ctx context.Context, lineId string) error +} + +type inventoryReturnService struct { + db *gorm.DB + returnRepo repository.InventoryReturnRepository + returnLineRepo repository.InventoryReturnLineRepository + issueLineRepo repositoryissue.InventoryIssueLineRepository + productRepo productrepository.ProductRepository +} + +// DeleteLine implements InventoryReturnService. +func (s *inventoryReturnService) DeleteLine(ctx context.Context, lineId string) error { + return s.returnLineRepo.Delete(ctx, nil, lineId) +} + +// UpdateLine implements InventoryReturnService. +func (s *inventoryReturnService) UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryReturnLineUpdateRequest) (dtodomain.InventoryReturnLineResponse, error) { + line, err := s.returnLineRepo.GetById(ctx, nil, lineId) + if err != nil { + return dtodomain.InventoryReturnLineResponse{}, err + } + + // Ambil product untuk cek returnable + product, err := s.productRepo.GetById(ctx, nil, line.ProductID.String()) + if err != nil { + return dtodomain.InventoryReturnLineResponse{}, err + } + + if req.Quantity != nil { + if product.IsReturnable { + return dtodomain.InventoryReturnLineResponse{}, + fmt.Errorf("product is not returnable, quantity cannot be updated") + } else { + line.Quantity = *req.Quantity + } + } + if req.Attachment != nil { + line.Attachment = *req.Attachment + } + if req.InvReturnID != nil { + line.InvReturnID = uuid.MustParse(*req.InvReturnID) + } + if req.ProductID != nil { + line.ProductID = uuid.MustParse(*req.ProductID) + } + if req.ClientID != nil { + line.ClientID = uuid.MustParse(*req.ClientID) + } + updatedLine, err := s.returnLineRepo.Update(ctx, nil, line) + if err != nil { + return dtodomain.InventoryReturnLineResponse{}, err + } + return dtodomain.ToInventoryReturnLineResponse(updatedLine), nil +} + +func (s *inventoryReturnService) Create(ctx context.Context, req dtodomain.InventoryReturnCreateRequest) (dtodomain.InventoryReturnResponse, 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.InventoryReturnResponse{}, err + } + docNum, err := entities.GenerateDocumentNumberInventoryReturn(s.db, req.ClientID) + if err != nil { + tx.Rollback() + return dtodomain.InventoryReturnResponse{}, err + } + invIssueId, err := uuid.Parse(req.InvIssueID) + if err != nil { + tx.Rollback() + return dtodomain.InventoryReturnResponse{}, err + } + entity := entities.TInventoryReturnEntity{ + DocumentNumber: docNum, + DocumentDate: utils.StringToDateTime(req.DocumentDate), + Notes: req.Notes, + Status: req.Status, + InvIssueID: invIssueId, + ClientID: clientUUID, + } + created, err := s.returnRepo.Create(ctx, tx, entity) + if err != nil { + tx.Rollback() + return dtodomain.InventoryReturnResponse{}, err + } + // Ambil semua line dari issue dan bulk insert ke return line + issueLines, err := s.issueLineRepo.GetAllByIssueId(ctx, req.InvIssueID) + if err != nil { + tx.Rollback() + return dtodomain.InventoryReturnResponse{}, err + } + var lines []entities.TInventoryReturnLineEntity + for _, issueLine := range issueLines { + // Ambil product untuk cek returnable + product, err := s.productRepo.GetById(ctx, nil, issueLine.ProductID.String()) + if err != nil { + tx.Rollback() + return dtodomain.InventoryReturnResponse{}, err + } + qty := 0.0 + if product.IsReturnable { + qty = issueLine.IssuedQuantity + } + lines = append(lines, entities.TInventoryReturnLineEntity{ + Quantity: qty, + Attachment: "", // isi sesuai kebutuhan + InvReturnID: created.ID, + ProductID: issueLine.ProductID, + ClientID: issueLine.ClientID, + }) + } + if len(lines) > 0 { + if err := s.returnLineRepo.BulkCreate(ctx, tx, lines); err != nil { + tx.Rollback() + return dtodomain.InventoryReturnResponse{}, err + } + } + + tx.Commit() + result, err := s.returnRepo.GetById(ctx, nil, created.ID.String()) + if err != nil { + return dtodomain.InventoryReturnResponse{}, err + } + return dtodomain.ToInventoryReturnResponse(result), nil +} + +func (s *inventoryReturnService) GetById(ctx context.Context, id string) (dtodomain.InventoryReturnResponse, error) { + ret, err := s.returnRepo.GetById(ctx, nil, id) + if err != nil { + return dtodomain.InventoryReturnResponse{}, err + } + return dtodomain.ToInventoryReturnResponse(ret), nil +} + +func (s *inventoryReturnService) GetAll(ctx context.Context, filter query.InventoryReturnFilter) ([]dtodomain.InventoryReturnResponse, int64, error) { + rets, total, err := s.returnRepo.GetAll(ctx, filter) + if err != nil { + return nil, total, err + } + var responses []dtodomain.InventoryReturnResponse + for _, ret := range rets { + responses = append(responses, dtodomain.ToInventoryReturnResponse(ret)) + } + if responses == nil { + responses = make([]dtodomain.InventoryReturnResponse, 0) + } + return responses, total, nil +} + +func (s *inventoryReturnService) Update(ctx context.Context, req dtodomain.InventoryReturnUpdateRequest, id string) (dtodomain.InventoryReturnResponse, error) { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + ret, err := s.returnRepo.GetById(ctx, tx, id) + if err != nil { + tx.Rollback() + return dtodomain.InventoryReturnResponse{}, err + } + ret.DocumentDate = utils.StringToDateTime(req.DocumentDate) + ret.Notes = req.Notes + ret.Status = req.Status + ret.InvIssueID = uuid.MustParse(req.InvIssueID) + ret.ClientID = uuid.MustParse(req.ClientID) + updated, err := s.returnRepo.Update(ctx, tx, ret) + if err != nil { + tx.Rollback() + return dtodomain.InventoryReturnResponse{}, err + } + tx.Commit() + result, err := s.returnRepo.GetById(ctx, nil, updated.ID.String()) + if err != nil { + return dtodomain.InventoryReturnResponse{}, err + } + return dtodomain.ToInventoryReturnResponse(result), nil +} + +func (s *inventoryReturnService) Delete(ctx context.Context, id string) error { + tx := s.db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := s.returnRepo.Delete(ctx, tx, id); err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + +func (s *inventoryReturnService) CreateLine(ctx context.Context, returnId string, req dtodomain.InventoryReturnLineCreateRequest) (dtodomain.InventoryReturnLineResponse, error) { + invReturnUUID, err := uuid.Parse(returnId) + if err != nil { + return dtodomain.InventoryReturnLineResponse{}, err + } + productUUID, err := uuid.Parse(req.ProductID) + if err != nil { + return dtodomain.InventoryReturnLineResponse{}, err + } + clientUUID, err := uuid.Parse(req.ClientID) + if err != nil { + return dtodomain.InventoryReturnLineResponse{}, err + } + line := entities.TInventoryReturnLineEntity{ + Quantity: req.Quantity, + Attachment: req.Attachment, + InvReturnID: invReturnUUID, + ProductID: productUUID, + ClientID: clientUUID, + } + created, err := s.returnLineRepo.Create(ctx, nil, line) + if err != nil { + return dtodomain.InventoryReturnLineResponse{}, err + } + return dtodomain.ToInventoryReturnLineResponse(created), nil +} + +func NewInventoryReturnService( + db *gorm.DB, + returnRepo repository.InventoryReturnRepository, + returnLineRepo repository.InventoryReturnLineRepository, + issueLineRepo repositoryissue.InventoryIssueLineRepository, + productRepo productrepository.ProductRepository, +) InventoryReturnService { + return &inventoryReturnService{ + db: db, + returnRepo: returnRepo, + returnLineRepo: returnLineRepo, + issueLineRepo: issueLineRepo, + productRepo: productRepo, + } +}