feat: implement inventory return management with entities, DTOs, services, and routes

This commit is contained in:
Habib Fatkhul Rohman 2025-11-18 15:23:28 +07:00
parent ca2795a493
commit 410ecde2e9
9 changed files with 775 additions and 0 deletions

View File

@ -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
}

View File

@ -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"
}

View File

@ -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...

View File

@ -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(),
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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,
}
}