feat: Add OnComplete functionality to Inventory Receipt
- Implemented OnComplete method in InventoryReceiptController to handle completion of inventory receipts. - Added success and failure messages in dto for completing inventory receipts. - Updated routes to include the new OnComplete endpoint. - Enhanced InventoryReceiptService to manage the completion logic, including updating inventory storage. - Introduced new methods in InventoryStorageRepository for bulk creation and retrieval of inventory storages. - Added methods in ProductService to fetch inventory storages and transactions by product and client. - Updated ProductController to handle new endpoints for inventory storages and transactions. - Enhanced Product DTO to include inventory storage and transaction responses. - Refactored ProductRepository to retrieve cross-references and preloaded relationships for inventory storages and transactions. - Updated core dependency injection to include new repositories and services.
This commit is contained in:
parent
e22c76abe3
commit
2ef6052a2c
|
|
@ -22,6 +22,7 @@ type InventoryReceiptController interface {
|
|||
CreateLine(ctx *gin.Context)
|
||||
UpdateLine(ctx *gin.Context)
|
||||
DeleteLine(ctx *gin.Context)
|
||||
OnComplete(ctx *gin.Context)
|
||||
}
|
||||
|
||||
type inventoryReceiptController struct {
|
||||
|
|
@ -29,6 +30,19 @@ type inventoryReceiptController struct {
|
|||
db *gorm.DB
|
||||
}
|
||||
|
||||
// OnComplete implements InventoryReceiptController.
|
||||
func (c *inventoryReceiptController) OnComplete(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
updated, err := c.receiptService.OnComplete(ctx, id)
|
||||
if err != nil {
|
||||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_COMPLETE_INVENTORY_RECEIPT, err.Error(), nil)
|
||||
ctx.JSON(http.StatusInternalServerError, res)
|
||||
return
|
||||
}
|
||||
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_COMPLETE_INVENTORY_RECEIPT, updated)
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
}
|
||||
|
||||
// DeleteLine implements InventoryReceiptController.
|
||||
func (c *inventoryReceiptController) DeleteLine(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ const (
|
|||
MESSAGE_SUCCESS_DELETE_INVENTORY_RECEIPT = "success delete inventory receipt"
|
||||
MESSAGE_SUCCESS_DELETE_INVENTORY_RECEIPT_LINE = "success delete inventory receipt line"
|
||||
MESSAGE_FAILED_GET_DATA_FROM_BODY = "failed get data from body"
|
||||
MESSAGE_SUCCESS_COMPLETE_INVENTORY_RECEIPT = "success complete inventory receipt"
|
||||
MESSAGE_FAILED_COMPLETE_INVENTORY_RECEIPT = "failed complete inventory receipt"
|
||||
)
|
||||
|
||||
type InventoryReceiptCreateRequest struct {
|
||||
|
|
|
|||
|
|
@ -23,5 +23,6 @@ func RegisterRoutes(server *gin.Engine, injector *do.Injector) {
|
|||
receiptRoutes.POST(":id/lines", middlewares.Authenticate(jwtService), receiptController.CreateLine)
|
||||
receiptRoutes.PUT("lines/:id", middlewares.Authenticate(jwtService), receiptController.UpdateLine)
|
||||
receiptRoutes.DELETE("lines/:id", middlewares.Authenticate(jwtService), receiptController.DeleteLine)
|
||||
receiptRoutes.POST(":id/complete", middlewares.Authenticate(jwtService), receiptController.OnComplete)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,16 @@ package service
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
|
||||
dtodomain "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt/dto"
|
||||
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt/query"
|
||||
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt/repository"
|
||||
invstoragerepository "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_storage/repository"
|
||||
productrepository "github.com/Caknoooo/go-gin-clean-starter/modules/product/repository"
|
||||
uomrepository "github.com/Caknoooo/go-gin-clean-starter/modules/uom/repository"
|
||||
"github.com/Caknoooo/go-gin-clean-starter/pkg/constants"
|
||||
pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto"
|
||||
"github.com/Caknoooo/go-gin-clean-starter/pkg/utils"
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -24,6 +27,7 @@ type InventoryReceiptService interface {
|
|||
CreateLine(ctx context.Context, receiptId string, req dtodomain.InventoryReceiptLineCreateRequest) (dtodomain.InventoryReceiptLineResponse, error)
|
||||
UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryReceiptLineUpdateRequest) (dtodomain.InventoryReceiptLineResponse, error)
|
||||
DeleteLine(ctx context.Context, lineId string) error
|
||||
OnComplete(ctx context.Context, id string) (dtodomain.InventoryReceiptResponse, error)
|
||||
}
|
||||
|
||||
type inventoryReceiptService struct {
|
||||
|
|
@ -32,6 +36,66 @@ type inventoryReceiptService struct {
|
|||
receiptLineRepo repository.InventoryReceiptLineRepository
|
||||
productRepo productrepository.ProductRepository
|
||||
uomRepo uomrepository.UomRepository
|
||||
invStorageRepository invstoragerepository.InventoryStorageRepository
|
||||
}
|
||||
|
||||
func (s *inventoryReceiptService) OnComplete(ctx context.Context, id string) (dtodomain.InventoryReceiptResponse, error) {
|
||||
tx := s.db.Begin()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
receipt, err := s.receiptRepo.GetById(ctx, tx, id)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return dtodomain.InventoryReceiptResponse{}, err
|
||||
}
|
||||
receipt.Status = constants.COMPLETED
|
||||
|
||||
for _, line := range receipt.ReceiptLines {
|
||||
product, err := s.productRepo.GetById(ctx, tx, line.ProductID.String())
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return dtodomain.InventoryReceiptResponse{}, err
|
||||
}
|
||||
existing, err := s.invStorageRepository.GetByProductAndClient(ctx, tx, product.ID.String(), receipt.ClientID.String())
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
tx.Rollback()
|
||||
return dtodomain.InventoryReceiptResponse{}, err
|
||||
}
|
||||
|
||||
if existing.ID != uuid.Nil {
|
||||
existing.OnHandQuantity += line.Quantity
|
||||
existing.AvailableQuantity += line.Quantity
|
||||
_, err = s.invStorageRepository.Update(ctx, tx, existing)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return dtodomain.InventoryReceiptResponse{}, err
|
||||
}
|
||||
} else {
|
||||
newStorage := entities.InventoryStorageEntity{
|
||||
ProductID: line.ProductID,
|
||||
UomID: *product.UomID,
|
||||
OnHandQuantity: line.Quantity,
|
||||
AvailableQuantity: line.Quantity,
|
||||
InvReceiptID: receipt.ID,
|
||||
ClientID: receipt.ClientID,
|
||||
}
|
||||
_, err = s.invStorageRepository.Create(ctx, tx, newStorage)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return dtodomain.InventoryReceiptResponse{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := s.receiptRepo.Update(ctx, tx, receipt); err != nil {
|
||||
tx.Rollback()
|
||||
return dtodomain.InventoryReceiptResponse{}, err
|
||||
}
|
||||
tx.Commit()
|
||||
return toInventoryReceiptResponse(receipt), nil
|
||||
}
|
||||
|
||||
// DeleteLine implements InventoryReceiptService.
|
||||
|
|
@ -235,6 +299,7 @@ func (s *inventoryReceiptService) Create(ctx context.Context, req dtodomain.Inve
|
|||
}
|
||||
// Bulk create lines
|
||||
var lines []entities.TInventoryReceiptLineEntity
|
||||
// var invStorages []entities.InventoryStorageEntity
|
||||
for _, lineReq := range req.ReceiptLines {
|
||||
var productUUID uuid.UUID
|
||||
if lineReq.ProductID != "" {
|
||||
|
|
@ -285,6 +350,21 @@ func (s *inventoryReceiptService) Create(ctx context.Context, req dtodomain.Inve
|
|||
ProductID: productUUID,
|
||||
ClientID: clientLineUUID,
|
||||
})
|
||||
|
||||
// // Prepare inventory storage entity
|
||||
// product, err := s.productRepo.GetById(ctx, tx, productUUID.String())
|
||||
// if err != nil {
|
||||
// tx.Rollback()
|
||||
// return dtodomain.InventoryReceiptResponse{}, err
|
||||
// }
|
||||
// invStorages = append(invStorages, entities.InventoryStorageEntity{
|
||||
// ProductID: productUUID,
|
||||
// UomID: *product.UomID,
|
||||
// OnHandQuantity: 0,
|
||||
// AvailableQuantity: 0,
|
||||
// InvReceiptID: created.ID,
|
||||
// ClientID: clientUUID,
|
||||
// })
|
||||
}
|
||||
if len(lines) > 0 {
|
||||
err = s.receiptLineRepo.BulkCreate(ctx, tx, lines)
|
||||
|
|
@ -293,6 +373,13 @@ func (s *inventoryReceiptService) Create(ctx context.Context, req dtodomain.Inve
|
|||
return dtodomain.InventoryReceiptResponse{}, err
|
||||
}
|
||||
}
|
||||
// if len(invStorages) > 0 {
|
||||
// err = s.invStorageRepository.BulkCreate(ctx, tx, invStorages)
|
||||
// if err != nil {
|
||||
// tx.Rollback()
|
||||
// return dtodomain.InventoryReceiptResponse{}, err
|
||||
// }
|
||||
// }
|
||||
tx.Commit()
|
||||
result, err := s.receiptRepo.GetById(ctx, nil, created.ID.String())
|
||||
if err != nil {
|
||||
|
|
@ -453,12 +540,14 @@ func NewInventoryReceiptService(db *gorm.DB,
|
|||
receiptRepo repository.InventoryReceiptRepository,
|
||||
receiptLineRepo repository.InventoryReceiptLineRepository,
|
||||
productRepo productrepository.ProductRepository,
|
||||
uomRepo uomrepository.UomRepository) InventoryReceiptService {
|
||||
uomRepo uomrepository.UomRepository,
|
||||
invStorageRepository invstoragerepository.InventoryStorageRepository) InventoryReceiptService {
|
||||
return &inventoryReceiptService{
|
||||
db: db,
|
||||
receiptRepo: receiptRepo,
|
||||
receiptLineRepo: receiptLineRepo,
|
||||
productRepo: productRepo,
|
||||
uomRepo: uomRepo,
|
||||
invStorageRepository: invStorageRepository,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ type AssignmentUserResponse struct {
|
|||
type InventoryRequestLineResponse struct {
|
||||
ID string `json:"id"`
|
||||
Quantity float64 `json:"quantity"`
|
||||
CurrentStock float64 `json:"current_stock"`
|
||||
Product InventoryRequestLineProductResponse `json:"product"`
|
||||
ClientID string `json:"client_id"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ func toInventoryRequestResponse(e entities.TInventoryRequestEntity) dtodomain.In
|
|||
lines = append(lines, dtodomain.InventoryRequestLineResponse{
|
||||
ID: line.ID.String(),
|
||||
Quantity: line.Quantity,
|
||||
// CurrentStock: line.Product.CurrentStock,
|
||||
Product: product,
|
||||
ClientID: line.ClientID.String(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ type InventoryStorageCreateRequest struct {
|
|||
ClientID string `json:"client_id" binding:"required"`
|
||||
OnHandQuantity float64 `json:"on_hand_quantity"`
|
||||
AvailableQuantity float64 `json:"available_quantity"`
|
||||
InvRequestID string `json:"inv_request_id"`
|
||||
InvReceiptID string `json:"inv_receipt_id"`
|
||||
InvRequestID *string `json:"inv_request_id"`
|
||||
InvReceiptID *string `json:"inv_receipt_id"`
|
||||
}
|
||||
|
||||
type InventoryStorageUpdateRequest struct {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
|
||||
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_storage/query"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type InventoryStorageRepository interface {
|
||||
|
|
@ -14,12 +15,66 @@ type InventoryStorageRepository interface {
|
|||
GetAll(ctx context.Context, filter query.InventoryStorageFilter) ([]entities.InventoryStorageEntity, int64, error)
|
||||
Update(ctx context.Context, tx *gorm.DB, inventoryStorage entities.InventoryStorageEntity) (entities.InventoryStorageEntity, error)
|
||||
Delete(ctx context.Context, tx *gorm.DB, inventoryStorageId string) error
|
||||
BulkCreate(ctx context.Context, tx *gorm.DB, inventoryStorages []entities.InventoryStorageEntity) error
|
||||
GetByProductAndClient(ctx context.Context, tx *gorm.DB, productId string, clientId string) (entities.InventoryStorageEntity, error)
|
||||
GetStoragesByProductAndClient(ctx context.Context, tx *gorm.DB, productId string, clientId string) ([]entities.InventoryStorageEntity, error)
|
||||
}
|
||||
|
||||
type inventoryStorageRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// GetStoragesByProductAndClient implements InventoryStorageRepository.
|
||||
func (r *inventoryStorageRepository) GetStoragesByProductAndClient(ctx context.Context, tx *gorm.DB, productId string, clientId string) ([]entities.InventoryStorageEntity, error) {
|
||||
if tx == nil {
|
||||
tx = r.db
|
||||
}
|
||||
var inventoryStorages []entities.InventoryStorageEntity
|
||||
if err := tx.WithContext(ctx).
|
||||
Preload("Product").
|
||||
Preload("Aisle").
|
||||
Preload("Uom").
|
||||
Preload("Client").
|
||||
Preload("InvRequest").
|
||||
Preload("InvReceipt").
|
||||
Where("product_id = ? AND client_id = ?", productId, clientId).
|
||||
Find(&inventoryStorages).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inventoryStorages, nil
|
||||
}
|
||||
|
||||
// GetByProductAndClient implements InventoryStorageRepository.
|
||||
func (r *inventoryStorageRepository) GetByProductAndClient(ctx context.Context, tx *gorm.DB, productId string, clientId string) (entities.InventoryStorageEntity, error) {
|
||||
if tx == nil {
|
||||
tx = r.db
|
||||
}
|
||||
var inventoryStorage entities.InventoryStorageEntity
|
||||
if err := tx.WithContext(ctx).
|
||||
Preload("Product").
|
||||
Preload("Aisle").
|
||||
Preload("Uom").
|
||||
Preload("Client").
|
||||
Preload("InvRequest").
|
||||
Preload("InvReceipt").
|
||||
Order("created_at DESC").
|
||||
First(&inventoryStorage, "product_id = ? AND client_id = ?", productId, clientId).Error; err != nil {
|
||||
return inventoryStorage, err
|
||||
}
|
||||
return inventoryStorage, nil
|
||||
}
|
||||
|
||||
// BulkCreate implements InventoryStorageRepository.
|
||||
func (r *inventoryStorageRepository) BulkCreate(ctx context.Context, tx *gorm.DB, inventoryStorages []entities.InventoryStorageEntity) error {
|
||||
if tx == nil {
|
||||
tx = r.db
|
||||
}
|
||||
if err := tx.WithContext(ctx).Clauses(clause.OnConflict{DoNothing: true}).Create(&inventoryStorages).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewInventoryStorageRepository(db *gorm.DB) InventoryStorageRepository {
|
||||
return &inventoryStorageRepository{db: db}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,16 +51,26 @@ func (s *inventoryStorageService) Create(ctx context.Context, req dtodomain.Inve
|
|||
tx.Rollback()
|
||||
return dtodomain.InventoryStorageResponse{}, err
|
||||
}
|
||||
InvRequestID, err := uuid.Parse(req.InvRequestID)
|
||||
var InvRequestID, InvReceiptID uuid.UUID
|
||||
if req.InvRequestID != nil && *req.InvRequestID != "" {
|
||||
InvRequestID, err = uuid.Parse(*req.InvRequestID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return dtodomain.InventoryStorageResponse{}, err
|
||||
}
|
||||
InvReceiptID, err := uuid.Parse(req.InvReceiptID)
|
||||
} else {
|
||||
InvRequestID = uuid.Nil
|
||||
}
|
||||
if req.InvReceiptID != nil && *req.InvReceiptID != "" {
|
||||
InvReceiptID, err = uuid.Parse(*req.InvReceiptID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return dtodomain.InventoryStorageResponse{}, err
|
||||
}
|
||||
} else {
|
||||
InvReceiptID = uuid.Nil
|
||||
}
|
||||
|
||||
inventoryStorage := entities.InventoryStorageEntity{
|
||||
ProductID: productID,
|
||||
AisleID: aisleID,
|
||||
|
|
|
|||
|
|
@ -14,12 +14,33 @@ type InventoryTransactionRepository interface {
|
|||
GetAll(ctx context.Context, filter query.InventoryTransactionFilter) ([]entities.InventoryTransactionEntity, int64, error)
|
||||
Update(ctx context.Context, tx *gorm.DB, inventoryTransaction entities.InventoryTransactionEntity) (entities.InventoryTransactionEntity, error)
|
||||
Delete(ctx context.Context, tx *gorm.DB, inventoryTransactionId string) error
|
||||
GetByProductAndClient(ctx context.Context, tx *gorm.DB, productId string, clientId string) ([]entities.InventoryTransactionEntity, error)
|
||||
}
|
||||
|
||||
type inventoryTransactionRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// GetByProductAndClient implements InventoryTransactionRepository.
|
||||
func (r *inventoryTransactionRepository) GetByProductAndClient(ctx context.Context, tx *gorm.DB, productId string, clientId string) ([]entities.InventoryTransactionEntity, error) {
|
||||
if tx == nil {
|
||||
tx = r.db
|
||||
}
|
||||
var inventoryTransactions []entities.InventoryTransactionEntity
|
||||
if err := tx.WithContext(ctx).
|
||||
Preload("Client").
|
||||
Preload("Product").
|
||||
Preload("Aisle").
|
||||
Preload("InvReceipt").
|
||||
Preload("InvIssue").
|
||||
Preload("InvMove").
|
||||
Where("product_id = ? AND client_id = ?", productId, clientId).
|
||||
Find(&inventoryTransactions).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return inventoryTransactions, nil
|
||||
}
|
||||
|
||||
func NewInventoryTransactionRepository(db *gorm.DB) InventoryTransactionRepository {
|
||||
return &inventoryTransactionRepository{db: db}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ type (
|
|||
GetAll(ctx *gin.Context)
|
||||
AssignCrossReference(ctx *gin.Context)
|
||||
RemoveCrossReference(ctx *gin.Context)
|
||||
GetInvStoragesByProductAndClient(ctx *gin.Context)
|
||||
GetInvTransactionsByProductAndClient(ctx *gin.Context)
|
||||
GetCrossReferencesByProductAndClient(ctx *gin.Context)
|
||||
}
|
||||
|
||||
productController struct {
|
||||
|
|
@ -30,6 +33,49 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
// GetCrossReferencesByProductAndClient implements ProductController.
|
||||
func (c *productController) GetCrossReferencesByProductAndClient(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
crossReferences, err := c.productService.GetCrossReferencesByProduct(ctx, id)
|
||||
if err != nil {
|
||||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_CROSS_REFERENCES, err.Error(), nil)
|
||||
ctx.JSON(http.StatusInternalServerError, res)
|
||||
return
|
||||
}
|
||||
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_CROSS_REFERENCES, crossReferences)
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
}
|
||||
|
||||
// GetInvStoragesByProductAndClient implements ProductController.
|
||||
func (c *productController) GetInvStoragesByProductAndClient(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
clientId := ctx.MustGet("client_id").(string)
|
||||
|
||||
invStorages, err := c.productService.GetInvStoragesByProductAndClient(ctx, id, clientId)
|
||||
if err != nil {
|
||||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INV_STORAGES, err.Error(), nil)
|
||||
ctx.JSON(http.StatusInternalServerError, res)
|
||||
return
|
||||
}
|
||||
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_INV_STORAGES, invStorages)
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
}
|
||||
|
||||
// GetInvTransactionsByProductAndClient implements ProductController.
|
||||
func (c *productController) GetInvTransactionsByProductAndClient(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
clientId := ctx.MustGet("client_id").(string)
|
||||
|
||||
invTransactions, err := c.productService.GetInvTransactionsByProductAndClient(ctx, id, clientId)
|
||||
if err != nil {
|
||||
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INV_TRANSACTIONS, err.Error(), nil)
|
||||
ctx.JSON(http.StatusInternalServerError, res)
|
||||
return
|
||||
}
|
||||
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_INV_TRANSACTIONS, invTransactions)
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
}
|
||||
|
||||
func NewProductController(i *do.Injector, productService service.ProductService) ProductController {
|
||||
db := do.MustInvokeNamed[*gorm.DB](i, constants.DB)
|
||||
return &productController{
|
||||
|
|
|
|||
|
|
@ -24,6 +24,12 @@ const (
|
|||
MESSAGE_SUCCESS_ASSIGN_CROSS_REF = "success assign cross reference"
|
||||
MESSAGE_FAILED_REMOVE_CROSS_REF = "failed remove cross reference"
|
||||
MESSAGE_SUCCESS_REMOVE_CROSS_REF = "success remove cross reference"
|
||||
MESSAGE_FAILED_GET_INV_STORAGES = "failed get inventory storages by product and client"
|
||||
MESSAGE_SUCCESS_GET_INV_STORAGES = "success get inventory storages by product and client"
|
||||
MESSAGE_FAILED_GET_INV_TRANSACTIONS = "failed get inventory transactions by product and client"
|
||||
MESSAGE_SUCCESS_GET_INV_TRANSACTIONS = "success get inventory transactions by product and client"
|
||||
MESSAGE_SUCCESS_GET_CROSS_REFERENCES = "success get cross references by product and client"
|
||||
MESSAGE_FAILED_GET_CROSS_REFERENCES = "failed get cross references by product and client"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -140,8 +146,9 @@ type (
|
|||
MaxStockUom pkgdto.IdNameResponse `json:"max_stock_uom"`
|
||||
LeadTimeUom pkgdto.IdNameResponse `json:"lead_time_uom"`
|
||||
UomToUom pkgdto.IdNameResponse `json:"uom_to_uom"`
|
||||
CrossReferences []ProductVendorResponse `json:"cross_references"`
|
||||
InvTransactions []ProductInventoryTransactionResponse `json:"inv_transactions"`
|
||||
// CrossReferences []ProductVendorResponse `json:"cross_references"`
|
||||
// InvTransactions []ProductInventoryTransactionResponse `json:"inv_transactions"`
|
||||
// InvStorages []ProductInventoryStorageResponse `json:"inv_storages"`
|
||||
}
|
||||
|
||||
CrossReferenceRequest struct {
|
||||
|
|
@ -167,82 +174,21 @@ type (
|
|||
InvIssueRef string `json:"inv_issue_ref,omitempty"`
|
||||
InvMoveRef string `json:"inv_move_ref,omitempty"`
|
||||
}
|
||||
|
||||
ProductInventoryStorageResponse struct {
|
||||
ID string `json:"id"`
|
||||
Locater string `json:"locater"`
|
||||
Lot string `json:"lot"`
|
||||
OnHandQuantity float64 `json:"on_hand_quantity"`
|
||||
AvailableQuantity float64 `json:"available_quantity"`
|
||||
AisleID pkgdto.IdNameResponse `json:"aisle_id"`
|
||||
Uom pkgdto.IdNameResponse `json:"uom"`
|
||||
InvReceipt pkgdto.IdNameResponse `json:"inv_receipt"`
|
||||
InvRequest pkgdto.IdNameResponse `json:"inv_request"`
|
||||
}
|
||||
)
|
||||
|
||||
func MapProductToResponse(product entities.MProductEntity) ProductResponse {
|
||||
crossRefs := make([]ProductVendorResponse, 0, len(product.CrossReferences))
|
||||
for _, v := range product.CrossReferences {
|
||||
crossRefs = append(crossRefs, ProductVendorResponse{
|
||||
ID: v.Vendor.ID.String(),
|
||||
Name: v.Vendor.Name,
|
||||
Address: v.Vendor.Address,
|
||||
ContactPerson: v.Vendor.ContactPerson,
|
||||
SearchKey: v.Vendor.SearchKey,
|
||||
})
|
||||
}
|
||||
|
||||
invTransactions := make([]ProductInventoryTransactionResponse, 0, len(product.InventoryTransactions))
|
||||
for _, it := range product.InventoryTransactions {
|
||||
var transactionQuantity float64
|
||||
var lot, locater, invReceiptRef, invIssueRef, invMoveRef string
|
||||
var transactionDate string
|
||||
valid := false
|
||||
|
||||
// Receipt
|
||||
if it.InvReceipt.ID != uuid.Nil && it.InvReceipt.Status == constants.COMPLETED {
|
||||
invReceiptRef = it.InvReceipt.ReferenceNumber
|
||||
transactionDate = utils.DateTimeToString(it.TransactionDate)
|
||||
for _, line := range it.InvReceipt.ReceiptLines {
|
||||
if line.ProductID == it.ProductID {
|
||||
transactionQuantity = line.Quantity
|
||||
lot = line.BatchNumber
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Issue
|
||||
if it.InvIssue.ID != uuid.Nil && it.InvIssue.Status == constants.COMPLETED {
|
||||
invIssueRef = it.InvIssue.DocumentNumber
|
||||
transactionDate = utils.DateTimeToString(it.TransactionDate)
|
||||
for _, line := range it.InvIssue.IssueLines {
|
||||
if line.ProductID == it.ProductID {
|
||||
transactionQuantity = line.IssuedQuantity
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move
|
||||
if it.InvMove.ID != uuid.Nil && it.InvMove.Status == constants.COMPLETED {
|
||||
invMoveRef = it.InvMove.MovementNumber
|
||||
transactionDate = utils.DateTimeToString(it.TransactionDate)
|
||||
for _, line := range it.InvMove.MovementLines {
|
||||
if line.ProductID == it.ProductID {
|
||||
transactionQuantity = line.MovedQuantity
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if valid {
|
||||
invTransactions = append(invTransactions, ProductInventoryTransactionResponse{
|
||||
ID: it.ID.String(),
|
||||
TransactionDate: transactionDate,
|
||||
TransactionType: it.TransactionType,
|
||||
TransactionQuantity: transactionQuantity,
|
||||
Lot: lot,
|
||||
Locater: locater,
|
||||
InvReceiptRef: invReceiptRef,
|
||||
InvIssueRef: invIssueRef,
|
||||
InvMoveRef: invMoveRef,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ProductResponse{
|
||||
ID: product.ID.String(),
|
||||
Name: product.Name,
|
||||
|
|
@ -307,7 +253,111 @@ func MapProductToResponse(product entities.MProductEntity) ProductResponse {
|
|||
ID: product.UomToUom.ID.String(),
|
||||
Name: product.UomToUom.Name,
|
||||
},
|
||||
CrossReferences: crossRefs,
|
||||
InvTransactions: invTransactions,
|
||||
}
|
||||
}
|
||||
|
||||
func MapInventoryTransactionsToResponses(transactions []entities.InventoryTransactionEntity) []ProductInventoryTransactionResponse {
|
||||
responses := make([]ProductInventoryTransactionResponse, 0, len(transactions))
|
||||
for _, t := range transactions {
|
||||
resp := MapInventoryTransactionToProductInventoryTransactionResponse(t)
|
||||
if resp.ID != "" { // hanya tambahkan jika mapping valid
|
||||
responses = append(responses, resp)
|
||||
}
|
||||
}
|
||||
return responses
|
||||
}
|
||||
|
||||
func MapInventoryTransactionToProductInventoryTransactionResponse(transaction entities.InventoryTransactionEntity) ProductInventoryTransactionResponse {
|
||||
var transactionQuantity float64
|
||||
var lot, locater, invReceiptRef, invIssueRef, invMoveRef string
|
||||
var transactionDate string
|
||||
valid := false
|
||||
|
||||
if transaction.InvReceipt.ID != uuid.Nil && transaction.InvReceipt.Status == constants.COMPLETED {
|
||||
invReceiptRef = transaction.InvReceipt.ReferenceNumber
|
||||
transactionDate = utils.DateTimeToString(transaction.TransactionDate)
|
||||
for _, line := range transaction.InvReceipt.ReceiptLines {
|
||||
if line.ProductID == transaction.ProductID {
|
||||
transactionQuantity = line.Quantity
|
||||
lot = line.BatchNumber
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if transaction.InvIssue.ID != uuid.Nil && transaction.InvIssue.Status == constants.COMPLETED {
|
||||
invIssueRef = transaction.InvIssue.DocumentNumber
|
||||
transactionDate = utils.DateTimeToString(transaction.TransactionDate)
|
||||
for _, line := range transaction.InvIssue.IssueLines {
|
||||
if line.ProductID == transaction.ProductID {
|
||||
transactionQuantity = line.IssuedQuantity
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if transaction.InvMove.ID != uuid.Nil && transaction.InvMove.Status == constants.COMPLETED {
|
||||
invMoveRef = transaction.InvMove.MovementNumber
|
||||
transactionDate = utils.DateTimeToString(transaction.TransactionDate)
|
||||
for _, line := range transaction.InvMove.MovementLines {
|
||||
if line.ProductID == transaction.ProductID {
|
||||
transactionQuantity = line.MovedQuantity
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !valid {
|
||||
return ProductInventoryTransactionResponse{}
|
||||
}
|
||||
|
||||
return ProductInventoryTransactionResponse{
|
||||
ID: transaction.ID.String(),
|
||||
TransactionDate: transactionDate,
|
||||
TransactionType: transaction.TransactionType,
|
||||
TransactionQuantity: transactionQuantity,
|
||||
Lot: lot,
|
||||
Locater: locater,
|
||||
InvReceiptRef: invReceiptRef,
|
||||
InvIssueRef: invIssueRef,
|
||||
InvMoveRef: invMoveRef,
|
||||
}
|
||||
}
|
||||
|
||||
func MapInventoryStorageToProductInventoryStorageResponse(storage entities.InventoryStorageEntity) ProductInventoryStorageResponse {
|
||||
return ProductInventoryStorageResponse{
|
||||
ID: storage.ID.String(),
|
||||
// Locater: storage.Locater,
|
||||
// Lot: storage.BatchNumber,
|
||||
OnHandQuantity: storage.OnHandQuantity,
|
||||
AvailableQuantity: storage.AvailableQuantity,
|
||||
AisleID: pkgdto.IdNameResponse{
|
||||
ID: storage.Aisle.ID.String(),
|
||||
Name: storage.Aisle.Name,
|
||||
},
|
||||
Uom: pkgdto.IdNameResponse{
|
||||
ID: storage.Uom.ID.String(),
|
||||
Name: storage.Uom.Name,
|
||||
},
|
||||
InvReceipt: pkgdto.IdNameResponse{
|
||||
ID: storage.InvReceipt.ID.String(),
|
||||
Name: storage.InvReceipt.ReferenceNumber,
|
||||
},
|
||||
InvRequest: pkgdto.IdNameResponse{
|
||||
ID: storage.InvRequest.ID.String(),
|
||||
Name: storage.InvRequest.ReferenceNumber,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func MapCrossReferenceToProductVendorResponse(crossRef entities.MCrossReferenceEntity) ProductVendorResponse {
|
||||
return ProductVendorResponse{
|
||||
ID: crossRef.Vendor.ID.String(),
|
||||
Name: crossRef.Vendor.Name,
|
||||
ContactPerson: crossRef.Vendor.ContactPerson,
|
||||
SearchKey: crossRef.Vendor.SearchKey,
|
||||
Address: crossRef.Vendor.Address}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,27 @@ type ProductRepository interface {
|
|||
Delete(ctx context.Context, tx *gorm.DB, productId string) error
|
||||
AssignCrossReference(ctx context.Context, tx *gorm.DB, productId string, vendorIds []string) error
|
||||
RemoveCrossReference(ctx context.Context, tx *gorm.DB, productId string, vendorIds []string) error
|
||||
GetCrossReferencesByProduct(ctx context.Context, tx *gorm.DB, productId string) ([]entities.MCrossReferenceEntity, error)
|
||||
}
|
||||
|
||||
type productRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// GetCrossReferencesByProduct implements ProductRepository.
|
||||
func (r *productRepository) GetCrossReferencesByProduct(ctx context.Context, tx *gorm.DB, productId string) ([]entities.MCrossReferenceEntity, error) {
|
||||
if tx == nil {
|
||||
tx = r.db
|
||||
}
|
||||
var crossReferences []entities.MCrossReferenceEntity
|
||||
if err := tx.WithContext(ctx).
|
||||
Where("product_id = ?", productId).
|
||||
Find(&crossReferences).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return crossReferences, nil
|
||||
}
|
||||
|
||||
// GetByCode implements ProductRepository.
|
||||
func (r *productRepository) GetByCode(ctx context.Context, tx *gorm.DB, productCode string, clientId string) (entities.MProductEntity, error) {
|
||||
if tx == nil {
|
||||
|
|
@ -156,6 +171,12 @@ func (r *productRepository) GetById(ctx context.Context, tx *gorm.DB, productId
|
|||
Preload("CrossReferences").
|
||||
Preload("CrossReferences.Vendor").
|
||||
Preload("InventoryStorages").
|
||||
Preload("InventoryStorages.Client").
|
||||
Preload("InventoryStorages.Product").
|
||||
Preload("InventoryStorages.Aisle").
|
||||
Preload("InventoryStorages.Uom").
|
||||
Preload("InventoryStorages.InvReceipt").
|
||||
Preload("InventoryStorages.InvRequest").
|
||||
Preload("InventoryTransactions").
|
||||
Preload("InventoryTransactions.Client").
|
||||
Preload("InventoryTransactions.Aisle").
|
||||
|
|
|
|||
|
|
@ -22,5 +22,8 @@ func RegisterRoutes(server *gin.Engine, injector *do.Injector) {
|
|||
productRoutes.GET("", middlewares.Authenticate(jwtService), productController.GetAll)
|
||||
productRoutes.POST("/:id/assign-cross-reference", middlewares.Authenticate(jwtService), productController.AssignCrossReference)
|
||||
productRoutes.POST("/:id/remove-cross-reference", middlewares.Authenticate(jwtService), productController.RemoveCrossReference)
|
||||
productRoutes.GET("/:id/inv-storages", middlewares.Authenticate(jwtService), productController.GetInvStoragesByProductAndClient)
|
||||
productRoutes.GET("/:id/inv-transactions", middlewares.Authenticate(jwtService), productController.GetInvTransactionsByProductAndClient)
|
||||
productRoutes.GET("/:id/cross-references", middlewares.Authenticate(jwtService), productController.GetCrossReferencesByProductAndClient)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
|
||||
invstoragerepo "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_storage/repository"
|
||||
invtransactionrepo "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_transaction/repository"
|
||||
"github.com/Caknoooo/go-gin-clean-starter/modules/product/dto"
|
||||
"github.com/Caknoooo/go-gin-clean-starter/modules/product/query"
|
||||
"github.com/Caknoooo/go-gin-clean-starter/modules/product/repository"
|
||||
|
|
@ -19,11 +21,55 @@ type ProductService interface {
|
|||
Delete(ctx context.Context, productId string) error
|
||||
AssignCrossReference(ctx context.Context, productId string, vendorIds []string) error
|
||||
RemoveCrossReference(ctx context.Context, productId string, vendorIds []string) error
|
||||
GetInvTransactionsByProductAndClient(ctx context.Context, productId string, clientId string) ([]dto.ProductInventoryTransactionResponse, error)
|
||||
GetInvStoragesByProductAndClient(ctx context.Context, productId string, clientId string) ([]dto.ProductInventoryStorageResponse, error)
|
||||
GetCrossReferencesByProduct(ctx context.Context, productId string) ([]dto.ProductVendorResponse, error)
|
||||
}
|
||||
|
||||
type productService struct {
|
||||
db *gorm.DB
|
||||
productRepo repository.ProductRepository
|
||||
inventoryTransactionRepo invtransactionrepo.InventoryTransactionRepository
|
||||
inventoryStorageRepo invstoragerepo.InventoryStorageRepository
|
||||
}
|
||||
|
||||
// GetCrossReferencesByProductAndClient implements ProductService.
|
||||
func (s *productService) GetCrossReferencesByProduct(ctx context.Context, productId string) ([]dto.ProductVendorResponse, error) {
|
||||
crossReferences, err := s.productRepo.GetCrossReferencesByProduct(ctx, nil, productId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var responses []dto.ProductVendorResponse
|
||||
for _, cr := range crossReferences {
|
||||
responses = append(responses, dto.MapCrossReferenceToProductVendorResponse(cr))
|
||||
}
|
||||
if len(responses) == 0 {
|
||||
responses = []dto.ProductVendorResponse{} // pastikan slice kosong, bukan nil
|
||||
}
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
// GetInvStoragesByProductAndClient implements ProductService.
|
||||
func (s *productService) GetInvStoragesByProductAndClient(ctx context.Context, productId string, clientId string) ([]dto.ProductInventoryStorageResponse, error) {
|
||||
invStorages, err := s.inventoryStorageRepo.GetStoragesByProductAndClient(ctx, nil, productId, clientId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var responses []dto.ProductInventoryStorageResponse
|
||||
for _, invStorage := range invStorages {
|
||||
responses = append(responses, dto.MapInventoryStorageToProductInventoryStorageResponse(invStorage))
|
||||
}
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
// GetInvTransactionsByProductAndClient implements ProductService.
|
||||
func (s *productService) GetInvTransactionsByProductAndClient(ctx context.Context, productId string, clientId string) ([]dto.ProductInventoryTransactionResponse, error) {
|
||||
invTransactions, err := s.inventoryTransactionRepo.GetByProductAndClient(ctx, nil, productId, clientId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
responses := dto.MapInventoryTransactionsToResponses(invTransactions)
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
// AssignCrossReference implements ProductService.
|
||||
|
|
@ -66,10 +112,12 @@ func (s *productService) RemoveCrossReference(ctx context.Context, productId str
|
|||
return nil
|
||||
}
|
||||
|
||||
func NewProductService(productRepo repository.ProductRepository, db *gorm.DB) ProductService {
|
||||
func NewProductService(productRepo repository.ProductRepository, db *gorm.DB, inventoryTransactionRepo invtransactionrepo.InventoryTransactionRepository, inventoryStorageRepo invstoragerepo.InventoryStorageRepository) ProductService {
|
||||
return &productService{
|
||||
productRepo: productRepo,
|
||||
db: db,
|
||||
inventoryTransactionRepo: inventoryTransactionRepo,
|
||||
inventoryStorageRepo: inventoryStorageRepo,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ func RegisterDependencies(injector *do.Injector) {
|
|||
|
||||
// Service
|
||||
userServ := userService.NewUserService(userRepository, roleRepository, warehouseRepository, clientRepository, refreshTokenRepository, jwtService, db)
|
||||
productService := productService.NewProductService(productRepository, db)
|
||||
productService := productService.NewProductService(productRepository, db, inventoryTransactionRepository, inventoryStorageRepository)
|
||||
roleServ := roleService.NewRoleService(roleRepository, refreshTokenRepository, jwtService, userServ, db)
|
||||
menuSvc := menuService.NewMenuService(menuRepository, jwtService, db)
|
||||
maintenanceGroupServ := maintGroupService.NewMaintenanceGroupService(maintenanceGroupRepository, maintenanceGroupRoleRepository, maintenanceGroupRoleUserRepository, db)
|
||||
|
|
@ -174,7 +174,7 @@ func RegisterDependencies(injector *do.Injector) {
|
|||
warehouseServ := warehouseService.NewWarehouseService(warehouseRepository, db)
|
||||
zonaServ := zonaService.NewZonaService(zonaRepository, db)
|
||||
aisleServ := aisleService.NewAisleService(aisleRepository, db)
|
||||
inventoryReceiptServ := inventoryReceiptService.NewInventoryReceiptService(db, inventoryReceiptRepository, inventoryReceiptLineRepository, productRepository, uomRepository)
|
||||
inventoryReceiptServ := inventoryReceiptService.NewInventoryReceiptService(db, inventoryReceiptRepository, inventoryReceiptLineRepository, productRepository, uomRepository, inventoryStorageRepository)
|
||||
assignmentServ := assignmentService.NewAssignmentService(db, assignmentRepository, assignmentUserRepository)
|
||||
inventoryRequestServ := inventoryRequestService.NewInventoryRequestService(db, inventoryRequestRepository, inventoryRequestLineRepository)
|
||||
inventoryIssueServ := inventoryIssueService.NewInventoryIssueService(db, inventoryIssueRepository, inventoryIssueLineRepository)
|
||||
|
|
|
|||
Loading…
Reference in New Issue