update order status
This commit is contained in:
parent
ccb0458189
commit
ee7d0e529b
@ -67,3 +67,51 @@ type InventoryAdjustmentResponse struct {
|
|||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
AdjustedAt time.Time `json:"adjusted_at"`
|
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/constants"
|
||||||
"apskel-pos-be/internal/contract"
|
"apskel-pos-be/internal/contract"
|
||||||
"apskel-pos-be/internal/logger"
|
"apskel-pos-be/internal/logger"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
"apskel-pos-be/internal/service"
|
"apskel-pos-be/internal/service"
|
||||||
"apskel-pos-be/internal/util"
|
"apskel-pos-be/internal/util"
|
||||||
"apskel-pos-be/internal/validator"
|
"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")
|
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,6 +28,8 @@ type UpdateInventoryRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type InventoryAdjustmentRequest struct {
|
type InventoryAdjustmentRequest struct {
|
||||||
|
ProductID uuid.UUID
|
||||||
|
OutletID uuid.UUID
|
||||||
Delta int
|
Delta int
|
||||||
Reason string
|
Reason string
|
||||||
}
|
}
|
||||||
@ -58,3 +60,61 @@ func (i *Inventory) AdjustQuantity(delta int) int {
|
|||||||
}
|
}
|
||||||
return newQuantity
|
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 {
|
type InventoryProcessor interface {
|
||||||
CreateInventory(ctx context.Context, req *models.CreateInventoryRequest) (*models.InventoryResponse, error)
|
Create(ctx context.Context, req *models.CreateInventoryRequest, organizationID uuid.UUID) (*models.InventoryResponse, error)
|
||||||
UpdateInventory(ctx context.Context, id uuid.UUID, req *models.UpdateInventoryRequest) (*models.InventoryResponse, error)
|
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*models.InventoryResponse, error)
|
||||||
DeleteInventory(ctx context.Context, id uuid.UUID) error
|
GetByProductAndOutlet(ctx context.Context, productID, outletID, organizationID uuid.UUID) (*models.InventoryResponse, error)
|
||||||
GetInventoryByID(ctx context.Context, id uuid.UUID) (*models.InventoryResponse, error)
|
GetByOutlet(ctx context.Context, outletID, organizationID uuid.UUID) ([]*models.InventoryResponse, error)
|
||||||
ListInventory(ctx context.Context, filters map[string]interface{}, page, limit int) ([]models.InventoryResponse, int, error)
|
GetByProduct(ctx context.Context, productID, organizationID uuid.UUID) ([]*models.InventoryResponse, error)
|
||||||
AdjustInventory(ctx context.Context, productID, outletID uuid.UUID, req *models.InventoryAdjustmentRequest) (*models.InventoryResponse, error)
|
GetLowStock(ctx context.Context, outletID, organizationID uuid.UUID) ([]*models.InventoryResponse, error)
|
||||||
GetLowStockItems(ctx context.Context, outletID uuid.UUID) ([]models.InventoryResponse, error)
|
GetZeroStock(ctx context.Context, outletID, organizationID uuid.UUID) ([]*models.InventoryResponse, error)
|
||||||
GetZeroStockItems(ctx context.Context, outletID 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 {
|
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)
|
_, err := p.productRepo.GetByID(ctx, req.ProductID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid product: %w", err)
|
return nil, fmt.Errorf("invalid product: %w", err)
|
||||||
@ -77,7 +85,8 @@ func (p *InventoryProcessorImpl) CreateInventory(ctx context.Context, req *model
|
|||||||
return response, nil
|
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
|
// Get existing inventory
|
||||||
existingInventory, err := p.inventoryRepo.GetByID(ctx, id)
|
existingInventory, err := p.inventoryRepo.GetByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -103,7 +112,8 @@ func (p *InventoryProcessorImpl) UpdateInventory(ctx context.Context, id uuid.UU
|
|||||||
return response, nil
|
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
|
// Check if inventory exists
|
||||||
_, err := p.inventoryRepo.GetByID(ctx, id)
|
_, err := p.inventoryRepo.GetByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -118,6 +128,177 @@ func (p *InventoryProcessorImpl) DeleteInventory(ctx context.Context, id uuid.UU
|
|||||||
return nil
|
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) {
|
func (p *InventoryProcessorImpl) GetInventoryByID(ctx context.Context, id uuid.UUID) (*models.InventoryResponse, error) {
|
||||||
inventoryEntity, err := p.inventoryRepo.GetWithRelations(ctx, id)
|
inventoryEntity, err := p.inventoryRepo.GetWithRelations(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -34,6 +34,7 @@ type OrderRepository interface {
|
|||||||
GetByID(ctx context.Context, id uuid.UUID) (*entities.Order, error)
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.Order, error)
|
||||||
GetWithRelations(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
|
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
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Order, int64, 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)
|
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
|
order.Status = entities.OrderStatusCompleted
|
||||||
if err := p.orderRepo.Update(ctx, order); err != nil {
|
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)
|
return nil, fmt.Errorf("failed to update order: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get updated order with relations
|
|
||||||
orderWithRelations, err := p.orderRepo.GetWithRelations(ctx, id)
|
orderWithRelations, err := p.orderRepo.GetWithRelations(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to retrieve updated order: %w", err)
|
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,
|
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)
|
return fmt.Errorf("failed to update order status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"apskel-pos-be/internal/entities"
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@ -31,6 +33,8 @@ type InventoryRepository interface {
|
|||||||
BulkUpdate(ctx context.Context, inventoryItems []*entities.Inventory) error
|
BulkUpdate(ctx context.Context, inventoryItems []*entities.Inventory) error
|
||||||
BulkAdjustQuantity(ctx context.Context, adjustments map[uuid.UUID]int, outletID uuid.UUID) error
|
BulkAdjustQuantity(ctx context.Context, adjustments map[uuid.UUID]int, outletID uuid.UUID) error
|
||||||
GetTotalValueByOutlet(ctx context.Context, outletID uuid.UUID) (float64, 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 {
|
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) {
|
func (r *InventoryRepositoryImpl) GetTotalValueByOutlet(ctx context.Context, outletID uuid.UUID) (float64, error) {
|
||||||
var totalValue float64
|
var totalValue float64
|
||||||
err := r.db.WithContext(ctx).
|
if err := r.db.WithContext(ctx).
|
||||||
Table("inventory").
|
Table("inventory").
|
||||||
Select("SUM(inventory.quantity * products.cost)").
|
Select("SUM(inventory.quantity * products.cost)").
|
||||||
Joins("JOIN products ON inventory.product_id = products.id").
|
Joins("JOIN products ON inventory.product_id = products.id").
|
||||||
Where("inventory.outlet_id = ?", outletID).
|
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
|
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 {
|
func (r *OrderRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
return r.db.WithContext(ctx).Delete(&entities.Order{}, "id = ?", id).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.POST("/adjust", r.inventoryHandler.AdjustInventory)
|
||||||
inventory.GET("/low-stock/:outlet_id", r.inventoryHandler.GetLowStockItems)
|
inventory.GET("/low-stock/:outlet_id", r.inventoryHandler.GetLowStockItems)
|
||||||
inventory.GET("/zero-stock/:outlet_id", r.inventoryHandler.GetZeroStockItems)
|
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")
|
orders := protected.Group("/orders")
|
||||||
|
|||||||
@ -2,10 +2,12 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"apskel-pos-be/internal/appcontext"
|
"apskel-pos-be/internal/appcontext"
|
||||||
"apskel-pos-be/internal/constants"
|
"apskel-pos-be/internal/constants"
|
||||||
"apskel-pos-be/internal/contract"
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
"apskel-pos-be/internal/processor"
|
"apskel-pos-be/internal/processor"
|
||||||
"apskel-pos-be/internal/transformer"
|
"apskel-pos-be/internal/transformer"
|
||||||
|
|
||||||
@ -21,6 +23,8 @@ type InventoryService interface {
|
|||||||
AdjustInventory(ctx context.Context, req *contract.AdjustInventoryRequest) *contract.Response
|
AdjustInventory(ctx context.Context, req *contract.AdjustInventoryRequest) *contract.Response
|
||||||
GetLowStockItems(ctx context.Context, outletID uuid.UUID) *contract.Response
|
GetLowStockItems(ctx context.Context, outletID uuid.UUID) *contract.Response
|
||||||
GetZeroStockItems(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 {
|
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 {
|
func (s *InventoryServiceImpl) CreateInventory(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreateInventoryRequest) *contract.Response {
|
||||||
modelReq := transformer.CreateInventoryRequestToModel(req)
|
modelReq := transformer.CreateInventoryRequestToModel(req)
|
||||||
|
|
||||||
inventoryResponse, err := s.inventoryProcessor.CreateInventory(ctx, modelReq)
|
inventoryResponse, err := s.inventoryProcessor.Create(ctx, modelReq, apctx.OrganizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
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 {
|
func (s *InventoryServiceImpl) UpdateInventory(ctx context.Context, id uuid.UUID, req *contract.UpdateInventoryRequest) *contract.Response {
|
||||||
modelReq := transformer.UpdateInventoryRequestToModel(req)
|
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 {
|
if err != nil {
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
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 {
|
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 {
|
if err != nil {
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
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 {
|
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 {
|
if err != nil {
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
@ -104,24 +108,30 @@ func (s *InventoryServiceImpl) ListInventory(ctx context.Context, req *contract.
|
|||||||
filters["search"] = req.Search
|
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 {
|
if err != nil {
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
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
|
// Convert to contract responses
|
||||||
contractResponses := transformer.InventoryToResponses(inventory)
|
contractResponses := transformer.InventoryToResponses(inventoryResponses)
|
||||||
|
|
||||||
// Calculate total pages
|
// Calculate total pages
|
||||||
totalPages := totalCount / req.Limit
|
totalPages := int(totalCount) / req.Limit
|
||||||
if totalCount%req.Limit > 0 {
|
if int(totalCount)%req.Limit > 0 {
|
||||||
totalPages++
|
totalPages++
|
||||||
}
|
}
|
||||||
|
|
||||||
listResponse := &contract.ListInventoryResponse{
|
listResponse := &contract.ListInventoryResponse{
|
||||||
Inventory: contractResponses,
|
Inventory: contractResponses,
|
||||||
TotalCount: totalCount,
|
TotalCount: int(totalCount),
|
||||||
Page: req.Page,
|
Page: req.Page,
|
||||||
Limit: req.Limit,
|
Limit: req.Limit,
|
||||||
TotalPages: totalPages,
|
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 {
|
func (s *InventoryServiceImpl) AdjustInventory(ctx context.Context, req *contract.AdjustInventoryRequest) *contract.Response {
|
||||||
modelReq := transformer.AdjustInventoryRequestToModel(req)
|
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 {
|
if err != nil {
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
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 {
|
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 {
|
if err != nil {
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
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)
|
return contract.BuildSuccessResponse(contractResponses)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InventoryServiceImpl) GetZeroStockItems(ctx context.Context, outletID uuid.UUID) *contract.Response {
|
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 {
|
if err != nil {
|
||||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.InventoryServiceEntity, err.Error())
|
||||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
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)
|
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