diff --git a/database/entities/m_product_entity.go b/database/entities/m_product_entity.go index 6252e47..cf47a36 100644 --- a/database/entities/m_product_entity.go +++ b/database/entities/m_product_entity.go @@ -1,7 +1,12 @@ package entities import ( + "fmt" + "strings" + "time" + "github.com/google/uuid" + "gorm.io/gorm" ) type MProductEntity struct { @@ -52,6 +57,7 @@ type MProductEntity struct { UomToUom MUomEntity `gorm:"foreignKey:UomToUomID;references:ID"` CrossReferences []MCrossReferenceEntity `gorm:"foreignKey:ProductID;references:ID"` InventoryTransactions []InventoryTransactionEntity `gorm:"foreignKey:ProductID;references:ID"` + InventoryStorages []InventoryStorageEntity `gorm:"foreignKey:ProductID;references:ID"` FullAuditTrail } @@ -59,3 +65,64 @@ type MProductEntity struct { func (MProductEntity) TableName() string { return "m_products" } + +// GenerateRefNumberProduct generates a new reference number for a product +func GenerateRefNumberProduct(db *gorm.DB, clientId string, categoryId string) (string, error) { + prefix := "PRD" + + // 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") + } + + // Ambil search key kategori berdasarkan categoryId + var category struct { + SearchKey string + } + if err := db.Table("m_categories").Select("search_key").Where("id = ?", categoryId).First(&category).Error; err != nil { + return "", fmt.Errorf("category not found") + } + if category.SearchKey == "" { + return "", fmt.Errorf("category search key is empty") + } + categoryInitial := "" + lettersOnly := "" + for _, r := range category.SearchKey { + if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') { + lettersOnly += string(r) + } + } + if len(lettersOnly) > 0 { + categoryInitial = strings.ToUpper(lettersOnly) + } + + // Ambil tahun dan bulan sekarang + now := time.Now() + period := now.Format("0601") // YYYYMM + + // Cari sequence terakhir untuk client dan kategori di periode ini + var lastProduct MProductEntity + prefixQuery := fmt.Sprintf("%s-%s-%s", prefix, categoryInitial, period) + err := db. + Where("client_id = ? AND category_id = ? AND LEFT(ref_number, ?) = ?", clientId, categoryId, len(prefixQuery), prefixQuery). + Order("ref_number DESC"). + First(&lastProduct).Error + + seq := 1 + if err == nil && lastProduct.RefNumber != "" { + parts := strings.Split(lastProduct.RefNumber, "-") + if len(parts) == 4 { + fmt.Sscanf(parts[3], "%d", &seq) + seq++ + } + } + + refNum := fmt.Sprintf("%s-%s-%s-%04d", prefix, categoryInitial, period, seq) + return refNum, nil +} diff --git a/modules/product/dto/product_dto.go b/modules/product/dto/product_dto.go index 9389fd7..bbb4f36 100644 --- a/modules/product/dto/product_dto.go +++ b/modules/product/dto/product_dto.go @@ -34,7 +34,7 @@ var ( type ( ProductCreateRequest struct { Name string `json:"name" binding:"required"` - RefNumber string `json:"ref_number" binding:"required"` + // RefNumber string `json:"ref_number" binding:"required"` SKU string `json:"sku" binding:"required"` Description string `json:"description"` Status string `json:"status" binding:"required"` @@ -69,7 +69,7 @@ type ( ProductUpdateRequest struct { Name *string `json:"name"` - RefNumber *string `json:"ref_number"` + // RefNumber *string `json:"ref_number"` SKU *string `json:"sku"` Description *string `json:"description"` Status *string `json:"status"` @@ -153,7 +153,14 @@ type ( } ProductInventoryTransactionResponse struct { - ID string `json:"id"` - TransactionType string `json:"transaction_type"` + ID string `json:"id"` + TransactionDate string `json:"transaction_date"` + TransactionType string `json:"transaction_type"` + TransactionQuantity float64 `json:"transaction_quantity"` + Lot string `json:"lot"` + Locater string `json:"locater"` + InvReceiptRef string `json:"inv_receipt_ref,omitempty"` + InvIssueRef string `json:"inv_issue_ref,omitempty"` + InvMoveRef string `json:"inv_move_ref,omitempty"` } ) diff --git a/modules/product/repository/product_repository.go b/modules/product/repository/product_repository.go index b25b8ee..13f8e90 100644 --- a/modules/product/repository/product_repository.go +++ b/modules/product/repository/product_repository.go @@ -116,13 +116,19 @@ func (r *productRepository) GetById(ctx context.Context, tx *gorm.DB, productId Preload("UomToUom"). Preload("CrossReferences"). Preload("CrossReferences.Vendor"). - Preload("InvTransactions"). - Preload("InvTransactions.Product"). - Preload("InvTransactions.Client"). - Preload("InvTransactions.Aisle"). - Preload("InvTransactions.InvReceipt"). - Preload("InvTransactions.InvIssue"). - Preload("InvTransactions.InvMove"). + Preload("InventoryStorages"). + Preload("InventoryTransactions"). + Preload("InventoryTransactions.Client"). + Preload("InventoryTransactions.Aisle"). + Preload("InventoryTransactions.InvReceipt"). + Preload("InventoryTransactions.InvReceipt.ReceiptLines"). + Preload("InventoryTransactions.InvReceipt.ReceiptLines.Product"). + Preload("InventoryTransactions.InvIssue"). + Preload("InventoryTransactions.InvIssue.IssueLines"). + Preload("InventoryTransactions.InvIssue.IssueLines.Product"). + Preload("InventoryTransactions.InvMove"). + Preload("InventoryTransactions.InvMove.MovementLines"). + Preload("InventoryTransactions.InvMove.MovementLines.Product"). First(&product, "id = ?", productId).Error; err != nil { return product, err } diff --git a/modules/product/service/product_service.go b/modules/product/service/product_service.go index 5554654..704fa1a 100644 --- a/modules/product/service/product_service.go +++ b/modules/product/service/product_service.go @@ -8,7 +8,9 @@ import ( "github.com/Caknoooo/go-gin-clean-starter/modules/product/query" "github.com/Caknoooo/go-gin-clean-starter/modules/product/repository" pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto" + "github.com/Caknoooo/go-gin-clean-starter/pkg/utils" "github.com/google/uuid" + "github.com/sirupsen/logrus" "gorm.io/gorm" ) @@ -81,9 +83,14 @@ func (s *productService) Create(ctx context.Context, req dto.ProductCreateReques tx.Rollback() } }() + refNumber, err := entities.GenerateRefNumberProduct(tx, req.ClientID, *req.CategoryID) + if err != nil { + tx.Rollback() + return dto.ProductResponse{}, err + } product := entities.MProductEntity{ Name: req.Name, - RefNumber: req.RefNumber, + RefNumber: refNumber, SKU: req.SKU, Description: req.Description, Status: req.Status, @@ -175,9 +182,6 @@ func (s *productService) Update(ctx context.Context, req dto.ProductUpdateReques if req.Name != nil { product.Name = *req.Name } - if req.RefNumber != nil { - product.RefNumber = *req.RefNumber - } if req.SKU != nil { product.SKU = *req.SKU } @@ -331,6 +335,70 @@ func mapProductToResponse(product entities.MProductEntity) dto.ProductResponse { }) } + logrus.Infof("Inventory Transactions Count: %d", len(product.InventoryTransactions)) + invTransactions := make([]dto.ProductInventoryTransactionResponse, 0, len(product.InventoryTransactions)) + for _, it := range product.InventoryTransactions { + var transactionQuantity float64 + var lot, locater, invReceiptRef, invIssueRef, invMoveRef string + var transactionDate string + + // Receipt + if it.InvReceipt.ID != uuid.Nil { + invReceiptRef = it.InvReceipt.ReferenceNumber + transactionDate = utils.DateTimeToString(it.TransactionDate) + // Cari line yang sesuai product + for _, line := range it.InvReceipt.ReceiptLines { + if line.ProductID == it.ProductID { + transactionQuantity = line.Quantity + lot = line.BatchNumber + // Jika ada field lokasi, isi di sini + // locater = line.Locater + break + } + } + } + + // Issue + if it.InvIssue.ID != uuid.Nil { + invIssueRef = it.InvIssue.DocumentNumber + transactionDate = utils.DateTimeToString(it.TransactionDate) + for _, line := range it.InvIssue.IssueLines { + if line.ProductID == it.ProductID { + transactionQuantity = line.IssuedQuantity + // lot = line.BatchNumber + // locater = line.Locater + break + } + } + } + + // Move + if it.InvMove.ID != uuid.Nil { + invMoveRef = it.InvMove.MovementNumber + transactionDate = utils.DateTimeToString(it.TransactionDate) + for _, line := range it.InvMove.MovementLines { + if line.ProductID == it.ProductID { + transactionQuantity = line.MovedQuantity + // lot = line.BatchNumber + // locater = line.Locater + break + } + } + } + + invTransactions = append(invTransactions, dto.ProductInventoryTransactionResponse{ + ID: it.ID.String(), + TransactionDate: transactionDate, + TransactionType: it.TransactionType, + TransactionQuantity: transactionQuantity, + Lot: lot, + Locater: locater, + InvReceiptRef: invReceiptRef, + InvIssueRef: invIssueRef, + InvMoveRef: invMoveRef, + }) + } + return dto.ProductResponse{ ID: product.ID.String(), Name: product.Name, @@ -396,5 +464,6 @@ func mapProductToResponse(product entities.MProductEntity) dto.ProductResponse { Name: product.UomToUom.Name, }, CrossReferences: crossRefs, + InvTransactions: invTransactions, } }