update order status
This commit is contained in:
parent
ccb0458189
commit
ee7d0e529b
@ -67,3 +67,51 @@ type InventoryAdjustmentResponse struct {
|
||||
Reason string `json:"reason"`
|
||||
AdjustedAt time.Time `json:"adjusted_at"`
|
||||
}
|
||||
|
||||
// Inventory Report Contracts
|
||||
type InventoryReportSummaryResponse struct {
|
||||
TotalProducts int `json:"total_products"`
|
||||
TotalIngredients int `json:"total_ingredients"`
|
||||
TotalValue float64 `json:"total_value"`
|
||||
LowStockProducts int `json:"low_stock_products"`
|
||||
LowStockIngredients int `json:"low_stock_ingredients"`
|
||||
ZeroStockProducts int `json:"zero_stock_products"`
|
||||
ZeroStockIngredients int `json:"zero_stock_ingredients"`
|
||||
OutletID string `json:"outlet_id"`
|
||||
OutletName string `json:"outlet_name"`
|
||||
GeneratedAt string `json:"generated_at"`
|
||||
}
|
||||
|
||||
type InventoryReportDetailResponse struct {
|
||||
Summary *InventoryReportSummaryResponse `json:"summary"`
|
||||
Products []*InventoryProductDetailResponse `json:"products"`
|
||||
Ingredients []*InventoryIngredientDetailResponse `json:"ingredients"`
|
||||
}
|
||||
|
||||
type InventoryProductDetailResponse struct {
|
||||
ID string `json:"id"`
|
||||
ProductID string `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
CategoryName string `json:"category_name"`
|
||||
Quantity int `json:"quantity"`
|
||||
ReorderLevel int `json:"reorder_level"`
|
||||
UnitCost float64 `json:"unit_cost"`
|
||||
TotalValue float64 `json:"total_value"`
|
||||
IsLowStock bool `json:"is_low_stock"`
|
||||
IsZeroStock bool `json:"is_zero_stock"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
type InventoryIngredientDetailResponse struct {
|
||||
ID string `json:"id"`
|
||||
IngredientID string `json:"ingredient_id"`
|
||||
IngredientName string `json:"ingredient_name"`
|
||||
UnitName string `json:"unit_name"`
|
||||
Quantity int `json:"quantity"`
|
||||
ReorderLevel int `json:"reorder_level"`
|
||||
UnitCost float64 `json:"unit_cost"`
|
||||
TotalValue float64 `json:"total_value"`
|
||||
IsLowStock bool `json:"is_low_stock"`
|
||||
IsZeroStock bool `json:"is_zero_stock"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/logger"
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/service"
|
||||
"apskel-pos-be/internal/util"
|
||||
"apskel-pos-be/internal/validator"
|
||||
@ -277,3 +278,111 @@ func (h *InventoryHandler) GetZeroStockItems(c *gin.Context) {
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, inventoryResponse, "InventoryHandler::GetZeroStockItems")
|
||||
}
|
||||
|
||||
// GetInventoryReportSummary returns summary statistics for inventory report
|
||||
func (h *InventoryHandler) GetInventoryReportSummary(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
outletIDStr := c.Param("outlet_id")
|
||||
outletID, err := uuid.Parse(outletIDStr)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetInventoryReportSummary -> Invalid outlet ID")
|
||||
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid outlet ID")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::GetInventoryReportSummary")
|
||||
return
|
||||
}
|
||||
|
||||
summary, err := h.inventoryService.GetInventoryReportSummary(ctx, outletID, contextInfo.OrganizationID)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetInventoryReportSummary -> Failed to get inventory report summary from service")
|
||||
responseError := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{responseError}), "InventoryHandler::GetInventoryReportSummary")
|
||||
return
|
||||
}
|
||||
|
||||
response := contract.BuildSuccessResponse(summary)
|
||||
util.HandleResponse(c.Writer, c.Request, response, "InventoryHandler::GetInventoryReportSummary")
|
||||
}
|
||||
|
||||
// GetInventoryReportDetails returns detailed inventory report with products and ingredients
|
||||
func (h *InventoryHandler) GetInventoryReportDetails(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
// Parse query parameters
|
||||
filter := &models.InventoryReportFilter{}
|
||||
|
||||
// Parse outlet_id (required)
|
||||
if outletIDStr := c.Query("outlet_id"); outletIDStr != "" {
|
||||
outletID, err := uuid.Parse(outletIDStr)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetInventoryReportDetails -> Invalid outlet ID")
|
||||
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid outlet ID")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::GetInventoryReportDetails")
|
||||
return
|
||||
}
|
||||
filter.OutletID = &outletID
|
||||
} else {
|
||||
logger.FromContext(ctx).Error("InventoryHandler::GetInventoryReportDetails -> Missing outlet_id parameter")
|
||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "outlet_id is required")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::GetInventoryReportDetails")
|
||||
return
|
||||
}
|
||||
|
||||
// Parse category_id (optional)
|
||||
if categoryIDStr := c.Query("category_id"); categoryIDStr != "" {
|
||||
categoryID, err := uuid.Parse(categoryIDStr)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetInventoryReportDetails -> Invalid category ID")
|
||||
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid category ID")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "InventoryHandler::GetInventoryReportDetails")
|
||||
return
|
||||
}
|
||||
filter.CategoryID = &categoryID
|
||||
}
|
||||
|
||||
// Parse show_low_stock (optional)
|
||||
if showLowStockStr := c.Query("show_low_stock"); showLowStockStr != "" {
|
||||
if showLowStock, err := strconv.ParseBool(showLowStockStr); err == nil {
|
||||
filter.ShowLowStock = &showLowStock
|
||||
}
|
||||
}
|
||||
|
||||
// Parse show_zero_stock (optional)
|
||||
if showZeroStockStr := c.Query("show_zero_stock"); showZeroStockStr != "" {
|
||||
if showZeroStock, err := strconv.ParseBool(showZeroStockStr); err == nil {
|
||||
filter.ShowZeroStock = &showZeroStock
|
||||
}
|
||||
}
|
||||
|
||||
// Parse search (optional)
|
||||
if search := c.Query("search"); search != "" {
|
||||
filter.Search = &search
|
||||
}
|
||||
|
||||
// Parse limit (optional)
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
if limit, err := strconv.Atoi(limitStr); err == nil && limit > 0 {
|
||||
filter.Limit = &limit
|
||||
}
|
||||
}
|
||||
|
||||
// Parse offset (optional)
|
||||
if offsetStr := c.Query("offset"); offsetStr != "" {
|
||||
if offset, err := strconv.Atoi(offsetStr); err == nil && offset >= 0 {
|
||||
filter.Offset = &offset
|
||||
}
|
||||
}
|
||||
|
||||
report, err := h.inventoryService.GetInventoryReportDetails(ctx, filter, contextInfo.OrganizationID)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("InventoryHandler::GetInventoryReportDetails -> Failed to get inventory report details from service")
|
||||
responseError := contract.NewResponseError(constants.InternalServerErrorCode, constants.RequestEntity, err.Error())
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{responseError}), "InventoryHandler::GetInventoryReportDetails")
|
||||
return
|
||||
}
|
||||
|
||||
response := contract.BuildSuccessResponse(report)
|
||||
util.HandleResponse(c.Writer, c.Request, response, "InventoryHandler::GetInventoryReportDetails")
|
||||
}
|
||||
|
||||
@ -28,8 +28,10 @@ type UpdateInventoryRequest struct {
|
||||
}
|
||||
|
||||
type InventoryAdjustmentRequest struct {
|
||||
Delta int
|
||||
Reason string
|
||||
ProductID uuid.UUID
|
||||
OutletID uuid.UUID
|
||||
Delta int
|
||||
Reason string
|
||||
}
|
||||
|
||||
type InventoryResponse struct {
|
||||
@ -58,3 +60,61 @@ func (i *Inventory) AdjustQuantity(delta int) int {
|
||||
}
|
||||
return newQuantity
|
||||
}
|
||||
|
||||
// Inventory Report Models
|
||||
type InventoryReportSummary struct {
|
||||
TotalProducts int `json:"total_products"`
|
||||
TotalIngredients int `json:"total_ingredients"`
|
||||
TotalValue float64 `json:"total_value"`
|
||||
LowStockProducts int `json:"low_stock_products"`
|
||||
LowStockIngredients int `json:"low_stock_ingredients"`
|
||||
ZeroStockProducts int `json:"zero_stock_products"`
|
||||
ZeroStockIngredients int `json:"zero_stock_ingredients"`
|
||||
OutletID uuid.UUID `json:"outlet_id"`
|
||||
OutletName string `json:"outlet_name"`
|
||||
GeneratedAt time.Time `json:"generated_at"`
|
||||
}
|
||||
|
||||
type InventoryReportDetail struct {
|
||||
Summary *InventoryReportSummary `json:"summary"`
|
||||
Products []*InventoryProductDetail `json:"products"`
|
||||
Ingredients []*InventoryIngredientDetail `json:"ingredients"`
|
||||
}
|
||||
|
||||
type InventoryProductDetail struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
CategoryName string `json:"category_name"`
|
||||
Quantity int `json:"quantity"`
|
||||
ReorderLevel int `json:"reorder_level"`
|
||||
UnitCost float64 `json:"unit_cost"`
|
||||
TotalValue float64 `json:"total_value"`
|
||||
IsLowStock bool `json:"is_low_stock"`
|
||||
IsZeroStock bool `json:"is_zero_stock"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type InventoryIngredientDetail struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||
IngredientName string `json:"ingredient_name"`
|
||||
UnitName string `json:"unit_name"`
|
||||
Quantity int `json:"quantity"`
|
||||
ReorderLevel int `json:"reorder_level"`
|
||||
UnitCost float64 `json:"unit_cost"`
|
||||
TotalValue float64 `json:"total_value"`
|
||||
IsLowStock bool `json:"is_low_stock"`
|
||||
IsZeroStock bool `json:"is_zero_stock"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type InventoryReportFilter struct {
|
||||
OutletID *uuid.UUID `json:"outlet_id"`
|
||||
CategoryID *uuid.UUID `json:"category_id"`
|
||||
ShowLowStock *bool `json:"show_low_stock"`
|
||||
ShowZeroStock *bool `json:"show_zero_stock"`
|
||||
Search *string `json:"search"`
|
||||
Limit *int `json:"limit"`
|
||||
Offset *int `json:"offset"`
|
||||
}
|
||||
|
||||
@ -12,14 +12,21 @@ import (
|
||||
)
|
||||
|
||||
type InventoryProcessor interface {
|
||||
CreateInventory(ctx context.Context, req *models.CreateInventoryRequest) (*models.InventoryResponse, error)
|
||||
UpdateInventory(ctx context.Context, id uuid.UUID, req *models.UpdateInventoryRequest) (*models.InventoryResponse, error)
|
||||
DeleteInventory(ctx context.Context, id uuid.UUID) error
|
||||
GetInventoryByID(ctx context.Context, id uuid.UUID) (*models.InventoryResponse, error)
|
||||
ListInventory(ctx context.Context, filters map[string]interface{}, page, limit int) ([]models.InventoryResponse, int, error)
|
||||
AdjustInventory(ctx context.Context, productID, outletID uuid.UUID, req *models.InventoryAdjustmentRequest) (*models.InventoryResponse, error)
|
||||
GetLowStockItems(ctx context.Context, outletID uuid.UUID) ([]models.InventoryResponse, error)
|
||||
GetZeroStockItems(ctx context.Context, outletID uuid.UUID) ([]models.InventoryResponse, error)
|
||||
Create(ctx context.Context, req *models.CreateInventoryRequest, organizationID uuid.UUID) (*models.InventoryResponse, error)
|
||||
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*models.InventoryResponse, error)
|
||||
GetByProductAndOutlet(ctx context.Context, productID, outletID, organizationID uuid.UUID) (*models.InventoryResponse, error)
|
||||
GetByOutlet(ctx context.Context, outletID, organizationID uuid.UUID) ([]*models.InventoryResponse, error)
|
||||
GetByProduct(ctx context.Context, productID, organizationID uuid.UUID) ([]*models.InventoryResponse, error)
|
||||
GetLowStock(ctx context.Context, outletID, organizationID uuid.UUID) ([]*models.InventoryResponse, error)
|
||||
GetZeroStock(ctx context.Context, outletID, organizationID uuid.UUID) ([]*models.InventoryResponse, error)
|
||||
Update(ctx context.Context, id uuid.UUID, req *models.UpdateInventoryRequest, organizationID uuid.UUID) (*models.InventoryResponse, error)
|
||||
Delete(ctx context.Context, id, organizationID uuid.UUID) error
|
||||
List(ctx context.Context, filters map[string]interface{}, limit, offset int, organizationID uuid.UUID) ([]*models.InventoryResponse, int64, error)
|
||||
AdjustQuantity(ctx context.Context, productID, outletID, organizationID uuid.UUID, delta int) (*models.InventoryResponse, error)
|
||||
SetQuantity(ctx context.Context, productID, outletID, organizationID uuid.UUID, quantity int) (*models.InventoryResponse, error)
|
||||
UpdateReorderLevel(ctx context.Context, id uuid.UUID, reorderLevel int, organizationID uuid.UUID) error
|
||||
GetInventoryReportSummary(ctx context.Context, outletID, organizationID uuid.UUID) (*models.InventoryReportSummary, error)
|
||||
GetInventoryReportDetails(ctx context.Context, filter *models.InventoryReportFilter, organizationID uuid.UUID) (*models.InventoryReportDetail, error)
|
||||
}
|
||||
|
||||
type InventoryProcessorImpl struct {
|
||||
@ -40,7 +47,8 @@ func NewInventoryProcessorImpl(
|
||||
}
|
||||
}
|
||||
|
||||
func (p *InventoryProcessorImpl) CreateInventory(ctx context.Context, req *models.CreateInventoryRequest) (*models.InventoryResponse, error) {
|
||||
// Create creates a new inventory record
|
||||
func (p *InventoryProcessorImpl) Create(ctx context.Context, req *models.CreateInventoryRequest, organizationID uuid.UUID) (*models.InventoryResponse, error) {
|
||||
_, err := p.productRepo.GetByID(ctx, req.ProductID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid product: %w", err)
|
||||
@ -77,7 +85,8 @@ func (p *InventoryProcessorImpl) CreateInventory(ctx context.Context, req *model
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (p *InventoryProcessorImpl) UpdateInventory(ctx context.Context, id uuid.UUID, req *models.UpdateInventoryRequest) (*models.InventoryResponse, error) {
|
||||
// Update updates an existing inventory record
|
||||
func (p *InventoryProcessorImpl) Update(ctx context.Context, id uuid.UUID, req *models.UpdateInventoryRequest, organizationID uuid.UUID) (*models.InventoryResponse, error) {
|
||||
// Get existing inventory
|
||||
existingInventory, err := p.inventoryRepo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@ -103,7 +112,8 @@ func (p *InventoryProcessorImpl) UpdateInventory(ctx context.Context, id uuid.UU
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (p *InventoryProcessorImpl) DeleteInventory(ctx context.Context, id uuid.UUID) error {
|
||||
// Delete deletes an inventory record
|
||||
func (p *InventoryProcessorImpl) Delete(ctx context.Context, id uuid.UUID, organizationID uuid.UUID) error {
|
||||
// Check if inventory exists
|
||||
_, err := p.inventoryRepo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@ -118,6 +128,177 @@ func (p *InventoryProcessorImpl) DeleteInventory(ctx context.Context, id uuid.UU
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID retrieves an inventory record by ID
|
||||
func (p *InventoryProcessorImpl) GetByID(ctx context.Context, id, organizationID uuid.UUID) (*models.InventoryResponse, error) {
|
||||
inventory, err := p.inventoryRepo.GetWithRelations(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("inventory not found: %w", err)
|
||||
}
|
||||
|
||||
response := mappers.InventoryEntityToResponse(inventory)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetByProductAndOutlet retrieves inventory by product and outlet
|
||||
func (p *InventoryProcessorImpl) GetByProductAndOutlet(ctx context.Context, productID, outletID, organizationID uuid.UUID) (*models.InventoryResponse, error) {
|
||||
inventory, err := p.inventoryRepo.GetByProductAndOutlet(ctx, productID, outletID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("inventory not found: %w", err)
|
||||
}
|
||||
|
||||
response := mappers.InventoryEntityToResponse(inventory)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetByOutlet retrieves all inventory records for a specific outlet
|
||||
func (p *InventoryProcessorImpl) GetByOutlet(ctx context.Context, outletID, organizationID uuid.UUID) ([]*models.InventoryResponse, error) {
|
||||
inventories, err := p.inventoryRepo.GetByOutlet(ctx, outletID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get inventory by outlet: %w", err)
|
||||
}
|
||||
|
||||
var responses []*models.InventoryResponse
|
||||
for _, inventory := range inventories {
|
||||
response := mappers.InventoryEntityToResponse(inventory)
|
||||
responses = append(responses, response)
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
// GetByProduct retrieves all inventory records for a specific product
|
||||
func (p *InventoryProcessorImpl) GetByProduct(ctx context.Context, productID, organizationID uuid.UUID) ([]*models.InventoryResponse, error) {
|
||||
inventories, err := p.inventoryRepo.GetByProduct(ctx, productID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get inventory by product: %w", err)
|
||||
}
|
||||
|
||||
var responses []*models.InventoryResponse
|
||||
for _, inventory := range inventories {
|
||||
response := mappers.InventoryEntityToResponse(inventory)
|
||||
responses = append(responses, response)
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
// List retrieves inventory records with filtering and pagination
|
||||
func (p *InventoryProcessorImpl) List(ctx context.Context, filters map[string]interface{}, limit, offset int, organizationID uuid.UUID) ([]*models.InventoryResponse, int64, error) {
|
||||
inventories, totalCount, err := p.inventoryRepo.List(ctx, filters, limit, offset)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to list inventory: %w", err)
|
||||
}
|
||||
|
||||
var responses []*models.InventoryResponse
|
||||
for _, inventory := range inventories {
|
||||
response := mappers.InventoryEntityToResponse(inventory)
|
||||
responses = append(responses, response)
|
||||
}
|
||||
|
||||
return responses, totalCount, nil
|
||||
}
|
||||
|
||||
// AdjustQuantity adjusts the quantity of an inventory item
|
||||
func (p *InventoryProcessorImpl) AdjustQuantity(ctx context.Context, productID, outletID, organizationID uuid.UUID, delta int) (*models.InventoryResponse, error) {
|
||||
inventory, err := p.inventoryRepo.AdjustQuantity(ctx, productID, outletID, delta)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to adjust inventory quantity: %w", err)
|
||||
}
|
||||
|
||||
response := mappers.InventoryEntityToResponse(inventory)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// SetQuantity sets the quantity of an inventory item
|
||||
func (p *InventoryProcessorImpl) SetQuantity(ctx context.Context, productID, outletID, organizationID uuid.UUID, quantity int) (*models.InventoryResponse, error) {
|
||||
inventory, err := p.inventoryRepo.SetQuantity(ctx, productID, outletID, quantity)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to set inventory quantity: %w", err)
|
||||
}
|
||||
|
||||
response := mappers.InventoryEntityToResponse(inventory)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// UpdateReorderLevel updates the reorder level of an inventory item
|
||||
func (p *InventoryProcessorImpl) UpdateReorderLevel(ctx context.Context, id uuid.UUID, reorderLevel int, organizationID uuid.UUID) error {
|
||||
return p.inventoryRepo.UpdateReorderLevel(ctx, id, reorderLevel)
|
||||
}
|
||||
|
||||
// GetLowStock retrieves low stock inventory items for a specific outlet
|
||||
func (p *InventoryProcessorImpl) GetLowStock(ctx context.Context, outletID, organizationID uuid.UUID) ([]*models.InventoryResponse, error) {
|
||||
inventories, err := p.inventoryRepo.GetLowStock(ctx, outletID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get low stock inventory: %w", err)
|
||||
}
|
||||
|
||||
var responses []*models.InventoryResponse
|
||||
for _, inventory := range inventories {
|
||||
response := mappers.InventoryEntityToResponse(inventory)
|
||||
responses = append(responses, response)
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
// GetZeroStock retrieves zero stock inventory items for a specific outlet
|
||||
func (p *InventoryProcessorImpl) GetZeroStock(ctx context.Context, outletID, organizationID uuid.UUID) ([]*models.InventoryResponse, error) {
|
||||
inventories, err := p.inventoryRepo.GetZeroStock(ctx, outletID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get zero stock inventory: %w", err)
|
||||
}
|
||||
|
||||
var responses []*models.InventoryResponse
|
||||
for _, inventory := range inventories {
|
||||
response := mappers.InventoryEntityToResponse(inventory)
|
||||
responses = append(responses, response)
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
// GetInventoryReportSummary returns summary statistics for inventory report
|
||||
func (p *InventoryProcessorImpl) GetInventoryReportSummary(ctx context.Context, outletID, organizationID uuid.UUID) (*models.InventoryReportSummary, error) {
|
||||
// Verify outlet belongs to organization
|
||||
outlet, err := p.outletRepo.GetByID(ctx, outletID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("outlet not found: %w", err)
|
||||
}
|
||||
if outlet.OrganizationID != organizationID {
|
||||
return nil, fmt.Errorf("outlet does not belong to the organization")
|
||||
}
|
||||
|
||||
summary, err := p.inventoryRepo.GetInventoryReportSummary(ctx, outletID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get inventory report summary: %w", err)
|
||||
}
|
||||
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
// GetInventoryReportDetails returns detailed inventory report with products and ingredients
|
||||
func (p *InventoryProcessorImpl) GetInventoryReportDetails(ctx context.Context, filter *models.InventoryReportFilter, organizationID uuid.UUID) (*models.InventoryReportDetail, error) {
|
||||
if filter.OutletID == nil {
|
||||
return nil, fmt.Errorf("outlet_id is required for inventory report")
|
||||
}
|
||||
|
||||
// Verify outlet belongs to organization
|
||||
outlet, err := p.outletRepo.GetByID(ctx, *filter.OutletID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("outlet not found: %w", err)
|
||||
}
|
||||
if outlet.OrganizationID != organizationID {
|
||||
return nil, fmt.Errorf("outlet does not belong to the organization")
|
||||
}
|
||||
|
||||
report, err := p.inventoryRepo.GetInventoryReportDetails(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get inventory report details: %w", err)
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (p *InventoryProcessorImpl) GetInventoryByID(ctx context.Context, id uuid.UUID) (*models.InventoryResponse, error) {
|
||||
inventoryEntity, err := p.inventoryRepo.GetWithRelations(ctx, id)
|
||||
if err != nil {
|
||||
|
||||
@ -34,6 +34,7 @@ type OrderRepository interface {
|
||||
GetByID(ctx context.Context, id uuid.UUID) (*entities.Order, error)
|
||||
GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.Order, error)
|
||||
Update(ctx context.Context, order *entities.Order) error
|
||||
UpdateStatusSuccess(ctx context.Context, id uuid.UUID, orderStatus entities.OrderStatus, paymentStatus entities.PaymentStatus) error
|
||||
Delete(ctx context.Context, id uuid.UUID) error
|
||||
List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Order, int64, error)
|
||||
GetByOrderNumber(ctx context.Context, orderNumber string) (*entities.Order, error)
|
||||
@ -458,12 +459,13 @@ func (p *OrderProcessorImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req
|
||||
}
|
||||
}
|
||||
|
||||
// Update order
|
||||
if err := p.orderRepo.Update(ctx, order); err != nil {
|
||||
order.Status = entities.OrderStatusCompleted
|
||||
order.PaymentStatus = entities.PaymentStatusCompleted
|
||||
|
||||
if err := p.orderRepo.UpdateStatusSuccess(ctx, order.ID, order.Status, order.PaymentStatus); err != nil {
|
||||
return nil, fmt.Errorf("failed to update order: %w", err)
|
||||
}
|
||||
|
||||
// Get updated order with relations
|
||||
orderWithRelations, err := p.orderRepo.GetWithRelations(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve updated order: %w", err)
|
||||
@ -858,7 +860,7 @@ func (p *OrderProcessorImpl) updateOrderStatus(ctx context.Context, orderID uuid
|
||||
PaymentStatus: entities.PaymentStatusCompleted,
|
||||
}
|
||||
|
||||
if err := p.orderRepo.Update(ctx, orderUpdate); err != nil {
|
||||
if err := p.orderRepo.UpdateStatusSuccess(ctx, orderID, orderUpdate.Status, orderUpdate.PaymentStatus); err != nil {
|
||||
return fmt.Errorf("failed to update order status: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@ -4,8 +4,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"apskel-pos-be/internal/entities"
|
||||
"apskel-pos-be/internal/models"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
@ -31,6 +33,8 @@ type InventoryRepository interface {
|
||||
BulkUpdate(ctx context.Context, inventoryItems []*entities.Inventory) error
|
||||
BulkAdjustQuantity(ctx context.Context, adjustments map[uuid.UUID]int, outletID uuid.UUID) error
|
||||
GetTotalValueByOutlet(ctx context.Context, outletID uuid.UUID) (float64, error)
|
||||
GetInventoryReportSummary(ctx context.Context, outletID uuid.UUID) (*models.InventoryReportSummary, error)
|
||||
GetInventoryReportDetails(ctx context.Context, filter *models.InventoryReportFilter) (*models.InventoryReportDetail, error)
|
||||
}
|
||||
|
||||
type InventoryRepositoryImpl struct {
|
||||
@ -326,12 +330,295 @@ func (r *InventoryRepositoryImpl) BulkAdjustQuantity(ctx context.Context, adjust
|
||||
|
||||
func (r *InventoryRepositoryImpl) GetTotalValueByOutlet(ctx context.Context, outletID uuid.UUID) (float64, error) {
|
||||
var totalValue float64
|
||||
err := r.db.WithContext(ctx).
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table("inventory").
|
||||
Select("SUM(inventory.quantity * products.cost)").
|
||||
Joins("JOIN products ON inventory.product_id = products.id").
|
||||
Where("inventory.outlet_id = ?", outletID).
|
||||
Scan(&totalValue).Error
|
||||
Scan(&totalValue).Error; err != nil {
|
||||
return 0, fmt.Errorf("failed to get total value: %w", err)
|
||||
}
|
||||
|
||||
return totalValue, err
|
||||
return totalValue, nil
|
||||
}
|
||||
|
||||
// GetInventoryReportSummary returns summary statistics for inventory report
|
||||
func (r *InventoryRepositoryImpl) GetInventoryReportSummary(ctx context.Context, outletID uuid.UUID) (*models.InventoryReportSummary, error) {
|
||||
var summary models.InventoryReportSummary
|
||||
summary.OutletID = outletID
|
||||
summary.GeneratedAt = time.Now()
|
||||
|
||||
// Get outlet name
|
||||
var outlet entities.Outlet
|
||||
if err := r.db.WithContext(ctx).Select("name").First(&outlet, "id = ?", outletID).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to get outlet name: %w", err)
|
||||
}
|
||||
summary.OutletName = outlet.Name
|
||||
|
||||
// Get total products count
|
||||
var totalProducts int64
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Inventory{}).
|
||||
Joins("JOIN products ON inventory.product_id = products.id").
|
||||
Where("inventory.outlet_id = ? AND products.has_ingredients = false", outletID).
|
||||
Count(&totalProducts).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to count total products: %w", err)
|
||||
}
|
||||
summary.TotalProducts = int(totalProducts)
|
||||
|
||||
// Get total ingredients count
|
||||
var totalIngredients int64
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Inventory{}).
|
||||
Joins("JOIN ingredients ON inventory.product_id = ingredients.id").
|
||||
Where("inventory.outlet_id = ?", outletID).
|
||||
Count(&totalIngredients).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to count total ingredients: %w", err)
|
||||
}
|
||||
summary.TotalIngredients = int(totalIngredients)
|
||||
|
||||
// Get low stock products count
|
||||
var lowStockProducts int64
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Inventory{}).
|
||||
Joins("JOIN products ON inventory.product_id = products.id").
|
||||
Where("inventory.outlet_id = ? AND products.has_ingredients = false AND inventory.quantity <= inventory.reorder_level AND inventory.quantity > 0", outletID).
|
||||
Count(&lowStockProducts).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to count low stock products: %w", err)
|
||||
}
|
||||
summary.LowStockProducts = int(lowStockProducts)
|
||||
|
||||
// Get low stock ingredients count
|
||||
var lowStockIngredients int64
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Inventory{}).
|
||||
Joins("JOIN ingredients ON inventory.product_id = ingredients.id").
|
||||
Where("inventory.outlet_id = ? AND inventory.quantity <= inventory.reorder_level AND inventory.quantity > 0", outletID).
|
||||
Count(&lowStockIngredients).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to count low stock ingredients: %w", err)
|
||||
}
|
||||
summary.LowStockIngredients = int(lowStockIngredients)
|
||||
|
||||
// Get zero stock products count
|
||||
var zeroStockProducts int64
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Inventory{}).
|
||||
Joins("JOIN products ON inventory.product_id = products.id").
|
||||
Where("inventory.outlet_id = ? AND products.has_ingredients = false AND inventory.quantity = 0", outletID).
|
||||
Count(&zeroStockProducts).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to count zero stock products: %w", err)
|
||||
}
|
||||
summary.ZeroStockProducts = int(zeroStockProducts)
|
||||
|
||||
// Get zero stock ingredients count
|
||||
var zeroStockIngredients int64
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Inventory{}).
|
||||
Joins("JOIN ingredients ON inventory.product_id = ingredients.id").
|
||||
Where("inventory.outlet_id = ? AND inventory.quantity = 0", outletID).
|
||||
Count(&zeroStockIngredients).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to count zero stock ingredients: %w", err)
|
||||
}
|
||||
summary.ZeroStockIngredients = int(zeroStockIngredients)
|
||||
|
||||
// Get total value
|
||||
totalValue, err := r.GetTotalValueByOutlet(ctx, outletID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get total value: %w", err)
|
||||
}
|
||||
summary.TotalValue = totalValue
|
||||
|
||||
return &summary, nil
|
||||
}
|
||||
|
||||
// GetInventoryReportDetails returns detailed inventory report with products and ingredients
|
||||
func (r *InventoryRepositoryImpl) GetInventoryReportDetails(ctx context.Context, filter *models.InventoryReportFilter) (*models.InventoryReportDetail, error) {
|
||||
report := &models.InventoryReportDetail{}
|
||||
|
||||
// Get summary
|
||||
if filter.OutletID != nil {
|
||||
summary, err := r.GetInventoryReportSummary(ctx, *filter.OutletID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get report summary: %w", err)
|
||||
}
|
||||
report.Summary = summary
|
||||
}
|
||||
|
||||
// Get products details
|
||||
products, err := r.getInventoryProductsDetails(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get products details: %w", err)
|
||||
}
|
||||
report.Products = products
|
||||
|
||||
// Get ingredients details
|
||||
ingredients, err := r.getInventoryIngredientsDetails(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get ingredients details: %w", err)
|
||||
}
|
||||
report.Ingredients = ingredients
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
// getInventoryProductsDetails retrieves detailed product inventory information
|
||||
func (r *InventoryRepositoryImpl) getInventoryProductsDetails(ctx context.Context, filter *models.InventoryReportFilter) ([]*models.InventoryProductDetail, error) {
|
||||
query := r.db.WithContext(ctx).Table("inventory").
|
||||
Select(`
|
||||
inventory.id,
|
||||
inventory.product_id,
|
||||
products.name as product_name,
|
||||
categories.name as category_name,
|
||||
inventory.quantity,
|
||||
inventory.reorder_level,
|
||||
COALESCE(product_variants.cost, products.cost) as unit_cost,
|
||||
(COALESCE(product_variants.cost, products.cost) * inventory.quantity) as total_value,
|
||||
inventory.updated_at
|
||||
`).
|
||||
Joins("JOIN products ON inventory.product_id = products.id").
|
||||
Joins("LEFT JOIN categories ON products.category_id = categories.id").
|
||||
Joins("LEFT JOIN product_variants ON products.id = product_variants.product_id").
|
||||
Where("inventory.outlet_id = ? AND products.has_ingredients = false", filter.OutletID)
|
||||
|
||||
// Apply filters
|
||||
if filter.CategoryID != nil {
|
||||
query = query.Where("products.category_id = ?", *filter.CategoryID)
|
||||
}
|
||||
if filter.ShowLowStock != nil && *filter.ShowLowStock {
|
||||
query = query.Where("inventory.quantity <= inventory.reorder_level AND inventory.quantity > 0")
|
||||
}
|
||||
if filter.ShowZeroStock != nil && *filter.ShowZeroStock {
|
||||
query = query.Where("inventory.quantity = 0")
|
||||
}
|
||||
if filter.Search != nil && *filter.Search != "" {
|
||||
searchTerm := "%" + *filter.Search + "%"
|
||||
query = query.Where("products.name ILIKE ? OR categories.name ILIKE ?", searchTerm, searchTerm)
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
if filter.Limit != nil {
|
||||
query = query.Limit(*filter.Limit)
|
||||
}
|
||||
if filter.Offset != nil {
|
||||
query = query.Offset(*filter.Offset)
|
||||
}
|
||||
|
||||
query = query.Order("products.name ASC")
|
||||
|
||||
var results []struct {
|
||||
ID uuid.UUID
|
||||
ProductID uuid.UUID
|
||||
ProductName string
|
||||
CategoryName *string
|
||||
Quantity int
|
||||
ReorderLevel int
|
||||
UnitCost float64
|
||||
TotalValue float64
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
if err := query.Find(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var products []*models.InventoryProductDetail
|
||||
for _, result := range results {
|
||||
categoryName := ""
|
||||
if result.CategoryName != nil {
|
||||
categoryName = *result.CategoryName
|
||||
}
|
||||
|
||||
product := &models.InventoryProductDetail{
|
||||
ID: result.ID,
|
||||
ProductID: result.ProductID,
|
||||
ProductName: result.ProductName,
|
||||
CategoryName: categoryName,
|
||||
Quantity: result.Quantity,
|
||||
ReorderLevel: result.ReorderLevel,
|
||||
UnitCost: result.UnitCost,
|
||||
TotalValue: result.TotalValue,
|
||||
IsLowStock: result.Quantity <= result.ReorderLevel && result.Quantity > 0,
|
||||
IsZeroStock: result.Quantity == 0,
|
||||
UpdatedAt: result.UpdatedAt,
|
||||
}
|
||||
products = append(products, product)
|
||||
}
|
||||
|
||||
return products, nil
|
||||
}
|
||||
|
||||
// getInventoryIngredientsDetails retrieves detailed ingredient inventory information
|
||||
func (r *InventoryRepositoryImpl) getInventoryIngredientsDetails(ctx context.Context, filter *models.InventoryReportFilter) ([]*models.InventoryIngredientDetail, error) {
|
||||
query := r.db.WithContext(ctx).Table("inventory").
|
||||
Select(`
|
||||
inventory.id,
|
||||
inventory.product_id as ingredient_id,
|
||||
ingredients.name as ingredient_name,
|
||||
units.name as unit_name,
|
||||
inventory.quantity,
|
||||
inventory.reorder_level,
|
||||
ingredients.cost as unit_cost,
|
||||
(ingredients.cost * inventory.quantity) as total_value,
|
||||
inventory.updated_at
|
||||
`).
|
||||
Joins("JOIN ingredients ON inventory.product_id = ingredients.id").
|
||||
Joins("LEFT JOIN units ON ingredients.unit_id = units.id").
|
||||
Where("inventory.outlet_id = ?", filter.OutletID)
|
||||
|
||||
// Apply filters
|
||||
if filter.ShowLowStock != nil && *filter.ShowLowStock {
|
||||
query = query.Where("inventory.quantity <= inventory.reorder_level AND inventory.quantity > 0")
|
||||
}
|
||||
if filter.ShowZeroStock != nil && *filter.ShowZeroStock {
|
||||
query = query.Where("inventory.quantity = 0")
|
||||
}
|
||||
if filter.Search != nil && *filter.Search != "" {
|
||||
searchTerm := "%" + *filter.Search + "%"
|
||||
query = query.Where("ingredients.name ILIKE ? OR units.name ILIKE ?", searchTerm, searchTerm)
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
if filter.Limit != nil {
|
||||
query = query.Limit(*filter.Limit)
|
||||
}
|
||||
if filter.Offset != nil {
|
||||
query = query.Offset(*filter.Offset)
|
||||
}
|
||||
|
||||
query = query.Order("ingredients.name ASC")
|
||||
|
||||
var results []struct {
|
||||
ID uuid.UUID
|
||||
IngredientID uuid.UUID
|
||||
IngredientName string
|
||||
UnitName *string
|
||||
Quantity int
|
||||
ReorderLevel int
|
||||
UnitCost float64
|
||||
TotalValue float64
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
if err := query.Find(&results).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ingredients []*models.InventoryIngredientDetail
|
||||
for _, result := range results {
|
||||
unitName := ""
|
||||
if result.UnitName != nil {
|
||||
unitName = *result.UnitName
|
||||
}
|
||||
|
||||
ingredient := &models.InventoryIngredientDetail{
|
||||
ID: result.ID,
|
||||
IngredientID: result.IngredientID,
|
||||
IngredientName: result.IngredientName,
|
||||
UnitName: unitName,
|
||||
Quantity: result.Quantity,
|
||||
ReorderLevel: result.ReorderLevel,
|
||||
UnitCost: result.UnitCost,
|
||||
TotalValue: result.TotalValue,
|
||||
IsLowStock: result.Quantity <= result.ReorderLevel && result.Quantity > 0,
|
||||
IsZeroStock: result.Quantity == 0,
|
||||
UpdatedAt: result.UpdatedAt,
|
||||
}
|
||||
ingredients = append(ingredients, ingredient)
|
||||
}
|
||||
|
||||
return ingredients, nil
|
||||
}
|
||||
|
||||
@ -74,6 +74,21 @@ func (r *OrderRepositoryImpl) Update(ctx context.Context, order *entities.Order)
|
||||
return r.db.WithContext(ctx).Save(order).Error
|
||||
}
|
||||
|
||||
func (r *OrderRepositoryImpl) UpdateStatusSuccess(
|
||||
ctx context.Context,
|
||||
id uuid.UUID,
|
||||
orderStatus entities.OrderStatus,
|
||||
paymentStatus entities.PaymentStatus,
|
||||
) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&entities.Order{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]interface{}{
|
||||
"order_status": orderStatus,
|
||||
"payment_status": paymentStatus,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *OrderRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.Order{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
@ -205,6 +205,8 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
||||
inventory.POST("/adjust", r.inventoryHandler.AdjustInventory)
|
||||
inventory.GET("/low-stock/:outlet_id", r.inventoryHandler.GetLowStockItems)
|
||||
inventory.GET("/zero-stock/:outlet_id", r.inventoryHandler.GetZeroStockItems)
|
||||
inventory.GET("/report/summary/:outlet_id", r.inventoryHandler.GetInventoryReportSummary)
|
||||
inventory.GET("/report/details", r.inventoryHandler.GetInventoryReportDetails)
|
||||
}
|
||||
|
||||
orders := protected.Group("/orders")
|
||||
|
||||
@ -2,10 +2,12 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"apskel-pos-be/internal/appcontext"
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/processor"
|
||||
"apskel-pos-be/internal/transformer"
|
||||
|
||||
@ -21,6 +23,8 @@ type InventoryService interface {
|
||||
AdjustInventory(ctx context.Context, req *contract.AdjustInventoryRequest) *contract.Response
|
||||
GetLowStockItems(ctx context.Context, outletID uuid.UUID) *contract.Response
|
||||
GetZeroStockItems(ctx context.Context, outletID uuid.UUID) *contract.Response
|
||||
GetInventoryReportSummary(ctx context.Context, outletID, organizationID uuid.UUID) (*contract.InventoryReportSummaryResponse, error)
|
||||
GetInventoryReportDetails(ctx context.Context, filter *models.InventoryReportFilter, organizationID uuid.UUID) (*contract.InventoryReportDetailResponse, error)
|
||||
}
|
||||
|
||||
type InventoryServiceImpl struct {
|
||||
@ -36,7 +40,7 @@ func NewInventoryService(inventoryProcessor processor.InventoryProcessor) *Inven
|
||||
func (s *InventoryServiceImpl) CreateInventory(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreateInventoryRequest) *contract.Response {
|
||||
modelReq := transformer.CreateInventoryRequestToModel(req)
|
||||
|
||||
inventoryResponse, err := s.inventoryProcessor.CreateInventory(ctx, modelReq)
|
||||
inventoryResponse, err := s.inventoryProcessor.Create(ctx, modelReq, apctx.OrganizationID)
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
@ -49,7 +53,7 @@ func (s *InventoryServiceImpl) CreateInventory(ctx context.Context, apctx *appco
|
||||
func (s *InventoryServiceImpl) UpdateInventory(ctx context.Context, id uuid.UUID, req *contract.UpdateInventoryRequest) *contract.Response {
|
||||
modelReq := transformer.UpdateInventoryRequestToModel(req)
|
||||
|
||||
inventoryResponse, err := s.inventoryProcessor.UpdateInventory(ctx, id, modelReq)
|
||||
inventoryResponse, err := s.inventoryProcessor.Update(ctx, id, modelReq, uuid.Nil) // TODO: Get organizationID from context
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
@ -60,7 +64,7 @@ func (s *InventoryServiceImpl) UpdateInventory(ctx context.Context, id uuid.UUID
|
||||
}
|
||||
|
||||
func (s *InventoryServiceImpl) DeleteInventory(ctx context.Context, id uuid.UUID) *contract.Response {
|
||||
err := s.inventoryProcessor.DeleteInventory(ctx, id)
|
||||
err := s.inventoryProcessor.Delete(ctx, id, uuid.Nil) // TODO: Get organizationID from context
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
@ -72,7 +76,7 @@ func (s *InventoryServiceImpl) DeleteInventory(ctx context.Context, id uuid.UUID
|
||||
}
|
||||
|
||||
func (s *InventoryServiceImpl) GetInventoryByID(ctx context.Context, id uuid.UUID) *contract.Response {
|
||||
inventoryResponse, err := s.inventoryProcessor.GetInventoryByID(ctx, id)
|
||||
inventoryResponse, err := s.inventoryProcessor.GetByID(ctx, id, uuid.Nil) // TODO: Get organizationID from context
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
@ -104,24 +108,30 @@ func (s *InventoryServiceImpl) ListInventory(ctx context.Context, req *contract.
|
||||
filters["search"] = req.Search
|
||||
}
|
||||
|
||||
inventory, totalCount, err := s.inventoryProcessor.ListInventory(ctx, filters, req.Page, req.Limit)
|
||||
inventory, totalCount, err := s.inventoryProcessor.List(ctx, filters, req.Limit, req.Page, uuid.Nil) // TODO: Get organizationID from context
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
// Convert from []*models.InventoryResponse to []models.InventoryResponse
|
||||
var inventoryResponses []models.InventoryResponse
|
||||
for _, inv := range inventory {
|
||||
inventoryResponses = append(inventoryResponses, *inv)
|
||||
}
|
||||
|
||||
// Convert to contract responses
|
||||
contractResponses := transformer.InventoryToResponses(inventory)
|
||||
contractResponses := transformer.InventoryToResponses(inventoryResponses)
|
||||
|
||||
// Calculate total pages
|
||||
totalPages := totalCount / req.Limit
|
||||
if totalCount%req.Limit > 0 {
|
||||
totalPages := int(totalCount) / req.Limit
|
||||
if int(totalCount)%req.Limit > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
listResponse := &contract.ListInventoryResponse{
|
||||
Inventory: contractResponses,
|
||||
TotalCount: totalCount,
|
||||
TotalCount: int(totalCount),
|
||||
Page: req.Page,
|
||||
Limit: req.Limit,
|
||||
TotalPages: totalPages,
|
||||
@ -133,7 +143,7 @@ func (s *InventoryServiceImpl) ListInventory(ctx context.Context, req *contract.
|
||||
func (s *InventoryServiceImpl) AdjustInventory(ctx context.Context, req *contract.AdjustInventoryRequest) *contract.Response {
|
||||
modelReq := transformer.AdjustInventoryRequestToModel(req)
|
||||
|
||||
inventoryResponse, err := s.inventoryProcessor.AdjustInventory(ctx, req.ProductID, req.OutletID, modelReq)
|
||||
inventoryResponse, err := s.inventoryProcessor.AdjustQuantity(ctx, modelReq.ProductID, modelReq.OutletID, uuid.Nil, modelReq.Delta) // TODO: Get organizationID from context
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
@ -144,23 +154,120 @@ func (s *InventoryServiceImpl) AdjustInventory(ctx context.Context, req *contrac
|
||||
}
|
||||
|
||||
func (s *InventoryServiceImpl) GetLowStockItems(ctx context.Context, outletID uuid.UUID) *contract.Response {
|
||||
inventory, err := s.inventoryProcessor.GetLowStockItems(ctx, outletID)
|
||||
inventory, err := s.inventoryProcessor.GetLowStock(ctx, outletID, uuid.Nil) // TODO: Get organizationID from context
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
contractResponses := transformer.InventoryToResponses(inventory)
|
||||
// Convert from []*models.InventoryResponse to []models.InventoryResponse
|
||||
var inventoryResponses []models.InventoryResponse
|
||||
for _, inv := range inventory {
|
||||
inventoryResponses = append(inventoryResponses, *inv)
|
||||
}
|
||||
|
||||
contractResponses := transformer.InventoryToResponses(inventoryResponses)
|
||||
return contract.BuildSuccessResponse(contractResponses)
|
||||
}
|
||||
|
||||
func (s *InventoryServiceImpl) GetZeroStockItems(ctx context.Context, outletID uuid.UUID) *contract.Response {
|
||||
inventory, err := s.inventoryProcessor.GetZeroStockItems(ctx, outletID)
|
||||
inventory, err := s.inventoryProcessor.GetZeroStock(ctx, outletID, uuid.Nil) // TODO: Get organizationID from context
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
contractResponses := transformer.InventoryToResponses(inventory)
|
||||
// Convert from []*models.InventoryResponse to []models.InventoryResponse
|
||||
var inventoryResponses []models.InventoryResponse
|
||||
for _, inv := range inventory {
|
||||
inventoryResponses = append(inventoryResponses, *inv)
|
||||
}
|
||||
|
||||
contractResponses := transformer.InventoryToResponses(inventoryResponses)
|
||||
return contract.BuildSuccessResponse(contractResponses)
|
||||
}
|
||||
|
||||
// GetInventoryReportSummary returns summary statistics for inventory report
|
||||
func (s *InventoryServiceImpl) GetInventoryReportSummary(ctx context.Context, outletID, organizationID uuid.UUID) (*contract.InventoryReportSummaryResponse, error) {
|
||||
summary, err := s.inventoryProcessor.GetInventoryReportSummary(ctx, outletID, organizationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &contract.InventoryReportSummaryResponse{
|
||||
TotalProducts: summary.TotalProducts,
|
||||
TotalIngredients: summary.TotalIngredients,
|
||||
TotalValue: summary.TotalValue,
|
||||
LowStockProducts: summary.LowStockProducts,
|
||||
LowStockIngredients: summary.LowStockIngredients,
|
||||
ZeroStockProducts: summary.ZeroStockProducts,
|
||||
ZeroStockIngredients: summary.ZeroStockIngredients,
|
||||
OutletID: summary.OutletID.String(),
|
||||
OutletName: summary.OutletName,
|
||||
GeneratedAt: summary.GeneratedAt.Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetInventoryReportDetails returns detailed inventory report with products and ingredients
|
||||
func (s *InventoryServiceImpl) GetInventoryReportDetails(ctx context.Context, filter *models.InventoryReportFilter, organizationID uuid.UUID) (*contract.InventoryReportDetailResponse, error) {
|
||||
report, err := s.inventoryProcessor.GetInventoryReportDetails(ctx, filter, organizationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &contract.InventoryReportDetailResponse{}
|
||||
|
||||
// Transform summary
|
||||
if report.Summary != nil {
|
||||
response.Summary = &contract.InventoryReportSummaryResponse{
|
||||
TotalProducts: report.Summary.TotalProducts,
|
||||
TotalIngredients: report.Summary.TotalIngredients,
|
||||
TotalValue: report.Summary.TotalValue,
|
||||
LowStockProducts: report.Summary.LowStockProducts,
|
||||
LowStockIngredients: report.Summary.LowStockIngredients,
|
||||
ZeroStockProducts: report.Summary.ZeroStockProducts,
|
||||
ZeroStockIngredients: report.Summary.ZeroStockIngredients,
|
||||
OutletID: report.Summary.OutletID.String(),
|
||||
OutletName: report.Summary.OutletName,
|
||||
GeneratedAt: report.Summary.GeneratedAt.Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
// Transform products
|
||||
response.Products = make([]*contract.InventoryProductDetailResponse, len(report.Products))
|
||||
for i, product := range report.Products {
|
||||
response.Products[i] = &contract.InventoryProductDetailResponse{
|
||||
ID: product.ID.String(),
|
||||
ProductID: product.ProductID.String(),
|
||||
ProductName: product.ProductName,
|
||||
CategoryName: product.CategoryName,
|
||||
Quantity: product.Quantity,
|
||||
ReorderLevel: product.ReorderLevel,
|
||||
UnitCost: product.UnitCost,
|
||||
TotalValue: product.TotalValue,
|
||||
IsLowStock: product.IsLowStock,
|
||||
IsZeroStock: product.IsZeroStock,
|
||||
UpdatedAt: product.UpdatedAt.Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
// Transform ingredients
|
||||
response.Ingredients = make([]*contract.InventoryIngredientDetailResponse, len(report.Ingredients))
|
||||
for i, ingredient := range report.Ingredients {
|
||||
response.Ingredients[i] = &contract.InventoryIngredientDetailResponse{
|
||||
ID: ingredient.ID.String(),
|
||||
IngredientID: ingredient.IngredientID.String(),
|
||||
IngredientName: ingredient.IngredientName,
|
||||
UnitName: ingredient.UnitName,
|
||||
Quantity: ingredient.Quantity,
|
||||
ReorderLevel: ingredient.ReorderLevel,
|
||||
UnitCost: ingredient.UnitCost,
|
||||
TotalValue: ingredient.TotalValue,
|
||||
IsLowStock: ingredient.IsLowStock,
|
||||
IsZeroStock: ingredient.IsZeroStock,
|
||||
UpdatedAt: ingredient.UpdatedAt.Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user