Add category table
This commit is contained in:
@@ -107,6 +107,8 @@ func (a *App) Initialize(cfg *config.Config) error {
|
||||
validators.vendorValidator,
|
||||
services.purchaseOrderService,
|
||||
validators.purchaseOrderValidator,
|
||||
services.purchaseCategoryService,
|
||||
validators.purchaseCategoryValidator,
|
||||
services.unitConverterService,
|
||||
validators.unitConverterValidator,
|
||||
services.chartOfAccountTypeService,
|
||||
@@ -214,6 +216,7 @@ type repositories struct {
|
||||
productRecipeRepo *repository.ProductRecipeRepository
|
||||
vendorRepo *repository.VendorRepositoryImpl
|
||||
purchaseOrderRepo *repository.PurchaseOrderRepositoryImpl
|
||||
purchaseCategoryRepo *repository.PurchaseCategoryRepositoryImpl
|
||||
unitConverterRepo *repository.IngredientUnitConverterRepositoryImpl
|
||||
chartOfAccountTypeRepo *repository.ChartOfAccountTypeRepositoryImpl
|
||||
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
|
||||
@@ -267,6 +270,7 @@ func (a *App) initRepositories() *repositories {
|
||||
productRecipeRepo: repository.NewProductRecipeRepository(a.db),
|
||||
vendorRepo: repository.NewVendorRepositoryImpl(a.db),
|
||||
purchaseOrderRepo: repository.NewPurchaseOrderRepositoryImpl(a.db),
|
||||
purchaseCategoryRepo: repository.NewPurchaseCategoryRepositoryImpl(a.db),
|
||||
unitConverterRepo: repository.NewIngredientUnitConverterRepositoryImpl(a.db).(*repository.IngredientUnitConverterRepositoryImpl),
|
||||
chartOfAccountTypeRepo: repository.NewChartOfAccountTypeRepositoryImpl(a.db),
|
||||
chartOfAccountRepo: repository.NewChartOfAccountRepositoryImpl(a.db),
|
||||
@@ -315,6 +319,7 @@ type processors struct {
|
||||
productRecipeProcessor *processor.ProductRecipeProcessorImpl
|
||||
vendorProcessor *processor.VendorProcessorImpl
|
||||
purchaseOrderProcessor *processor.PurchaseOrderProcessorImpl
|
||||
purchaseCategoryProcessor *processor.PurchaseCategoryProcessorImpl
|
||||
unitConverterProcessor *processor.IngredientUnitConverterProcessorImpl
|
||||
chartOfAccountTypeProcessor *processor.ChartOfAccountTypeProcessorImpl
|
||||
chartOfAccountProcessor *processor.ChartOfAccountProcessorImpl
|
||||
@@ -366,6 +371,7 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
|
||||
productRecipeProcessor: processor.NewProductRecipeProcessor(repos.productRecipeRepo, repos.productRepo, repos.ingredientRepo),
|
||||
vendorProcessor: processor.NewVendorProcessorImpl(repos.vendorRepo),
|
||||
purchaseOrderProcessor: processor.NewPurchaseOrderProcessorImpl(repos.purchaseOrderRepo, repos.vendorRepo, repos.ingredientRepo, repos.unitRepo, repos.fileRepo, inventoryMovementService, repos.unitConverterRepo),
|
||||
purchaseCategoryProcessor: processor.NewPurchaseCategoryProcessorImpl(repos.purchaseCategoryRepo),
|
||||
unitConverterProcessor: processor.NewIngredientUnitConverterProcessorImpl(repos.unitConverterRepo, repos.ingredientRepo, repos.unitRepo),
|
||||
chartOfAccountTypeProcessor: processor.NewChartOfAccountTypeProcessorImpl(repos.chartOfAccountTypeRepo),
|
||||
chartOfAccountProcessor: processor.NewChartOfAccountProcessorImpl(repos.chartOfAccountRepo, repos.chartOfAccountTypeRepo),
|
||||
@@ -414,6 +420,7 @@ type services struct {
|
||||
productRecipeService *service.ProductRecipeServiceImpl
|
||||
vendorService *service.VendorServiceImpl
|
||||
purchaseOrderService *service.PurchaseOrderServiceImpl
|
||||
purchaseCategoryService service.PurchaseCategoryService
|
||||
unitConverterService *service.IngredientUnitConverterServiceImpl
|
||||
chartOfAccountTypeService service.ChartOfAccountTypeService
|
||||
chartOfAccountService service.ChartOfAccountService
|
||||
@@ -453,6 +460,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
||||
productRecipeService := service.NewProductRecipeService(processors.productRecipeProcessor)
|
||||
vendorService := service.NewVendorService(processors.vendorProcessor)
|
||||
purchaseOrderService := service.NewPurchaseOrderService(processors.purchaseOrderProcessor)
|
||||
purchaseCategoryService := service.NewPurchaseCategoryService(processors.purchaseCategoryProcessor)
|
||||
unitConverterService := service.NewIngredientUnitConverterService(processors.unitConverterProcessor)
|
||||
chartOfAccountTypeService := service.NewChartOfAccountTypeService(processors.chartOfAccountTypeProcessor)
|
||||
chartOfAccountService := service.NewChartOfAccountService(processors.chartOfAccountProcessor)
|
||||
@@ -492,6 +500,7 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
||||
productRecipeService: productRecipeService,
|
||||
vendorService: vendorService,
|
||||
purchaseOrderService: purchaseOrderService,
|
||||
purchaseCategoryService: purchaseCategoryService,
|
||||
unitConverterService: unitConverterService,
|
||||
chartOfAccountTypeService: chartOfAccountTypeService,
|
||||
chartOfAccountService: chartOfAccountService,
|
||||
@@ -537,6 +546,7 @@ type validators struct {
|
||||
tableValidator *validator.TableValidator
|
||||
vendorValidator *validator.VendorValidatorImpl
|
||||
purchaseOrderValidator *validator.PurchaseOrderValidatorImpl
|
||||
purchaseCategoryValidator *validator.PurchaseCategoryValidatorImpl
|
||||
unitConverterValidator *validator.IngredientUnitConverterValidatorImpl
|
||||
chartOfAccountTypeValidator *validator.ChartOfAccountTypeValidatorImpl
|
||||
chartOfAccountValidator *validator.ChartOfAccountValidatorImpl
|
||||
@@ -568,6 +578,7 @@ func (a *App) initValidators() *validators {
|
||||
tableValidator: validator.NewTableValidator(),
|
||||
vendorValidator: validator.NewVendorValidator(),
|
||||
purchaseOrderValidator: validator.NewPurchaseOrderValidator(),
|
||||
purchaseCategoryValidator: validator.NewPurchaseCategoryValidator(),
|
||||
unitConverterValidator: validator.NewIngredientUnitConverterValidator().(*validator.IngredientUnitConverterValidatorImpl),
|
||||
chartOfAccountTypeValidator: validator.NewChartOfAccountTypeValidator().(*validator.ChartOfAccountTypeValidatorImpl),
|
||||
chartOfAccountValidator: validator.NewChartOfAccountValidator().(*validator.ChartOfAccountValidatorImpl),
|
||||
|
||||
@@ -40,6 +40,7 @@ const (
|
||||
OutletServiceEntity = "outlet_service"
|
||||
VendorServiceEntity = "vendor_service"
|
||||
PurchaseOrderServiceEntity = "purchase_order_service"
|
||||
PurchaseCategoryServiceEntity = "purchase_category_service"
|
||||
IngredientUnitConverterServiceEntity = "ingredient_unit_converter_service"
|
||||
IngredientCompositionServiceEntity = "ingredient_composition_service"
|
||||
TableEntity = "table"
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CreatePurchaseCategoryRequest struct {
|
||||
ParentID *uuid.UUID `json:"parent_id,omitempty"`
|
||||
Code *string `json:"code,omitempty"`
|
||||
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||
Type string `json:"type" validate:"required,oneof=raw_material non_inventory"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
}
|
||||
|
||||
type UpdatePurchaseCategoryRequest struct {
|
||||
ParentID *uuid.UUID `json:"parent_id,omitempty"`
|
||||
Code *string `json:"code,omitempty"`
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Type *string `json:"type,omitempty" validate:"omitempty,oneof=raw_material non_inventory"`
|
||||
SortOrder *int `json:"sort_order,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
}
|
||||
|
||||
type ListPurchaseCategoriesRequest struct {
|
||||
ParentID *uuid.UUID `json:"parent_id,omitempty"`
|
||||
Type string `json:"type,omitempty" validate:"omitempty,oneof=raw_material non_inventory"`
|
||||
Search string `json:"search,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
Page int `json:"page" validate:"required,min=1"`
|
||||
Limit int `json:"limit" validate:"required,min=1,max=100"`
|
||||
}
|
||||
|
||||
type PurchaseCategoryResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
PresetID *uuid.UUID `json:"preset_id"`
|
||||
ParentID *uuid.UUID `json:"parent_id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
IsSystem bool `json:"is_system"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ListPurchaseCategoriesResponse struct {
|
||||
PurchaseCategories []PurchaseCategoryResponse `json:"purchase_categories"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PurchaseCategoryType string
|
||||
|
||||
const (
|
||||
PurchaseCategoryTypeRawMaterial PurchaseCategoryType = "raw_material"
|
||||
PurchaseCategoryTypeNonInventory PurchaseCategoryType = "non_inventory"
|
||||
)
|
||||
|
||||
type PurchaseCategoryPreset struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
ParentID *uuid.UUID `gorm:"type:uuid;index" json:"parent_id"`
|
||||
Code string `gorm:"not null;unique;size:100" json:"code"`
|
||||
Name string `gorm:"not null;size:255" json:"name"`
|
||||
Type PurchaseCategoryType `gorm:"not null;size:20" json:"type"`
|
||||
SortOrder int `gorm:"not null;default:0" json:"sort_order"`
|
||||
IsActive bool `gorm:"not null;default:true" json:"is_active"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Parent *PurchaseCategoryPreset `gorm:"foreignKey:ParentID" json:"parent,omitempty"`
|
||||
}
|
||||
|
||||
func (p *PurchaseCategoryPreset) BeforeCreate(tx *gorm.DB) error {
|
||||
if p.ID == uuid.Nil {
|
||||
p.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (PurchaseCategoryPreset) TableName() string {
|
||||
return "purchase_category_presets"
|
||||
}
|
||||
|
||||
type PurchaseCategory struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index" json:"organization_id"`
|
||||
PresetID *uuid.UUID `gorm:"type:uuid;index" json:"preset_id"`
|
||||
ParentID *uuid.UUID `gorm:"type:uuid;index" json:"parent_id"`
|
||||
Code string `gorm:"not null;size:100" json:"code"`
|
||||
Name string `gorm:"not null;size:255" json:"name"`
|
||||
Type PurchaseCategoryType `gorm:"not null;size:20" json:"type"`
|
||||
SortOrder int `gorm:"not null;default:0" json:"sort_order"`
|
||||
IsSystem bool `gorm:"not null;default:false" json:"is_system"`
|
||||
IsActive bool `gorm:"not null;default:true" json:"is_active"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Organization *Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
|
||||
Preset *PurchaseCategoryPreset `gorm:"foreignKey:PresetID" json:"preset,omitempty"`
|
||||
Parent *PurchaseCategory `gorm:"foreignKey:ParentID" json:"parent,omitempty"`
|
||||
Children []PurchaseCategory `gorm:"foreignKey:ParentID" json:"children,omitempty"`
|
||||
}
|
||||
|
||||
func (c *PurchaseCategory) BeforeCreate(tx *gorm.DB) error {
|
||||
if c.ID == uuid.Nil {
|
||||
c.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (PurchaseCategory) TableName() string {
|
||||
return "purchase_categories"
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"apskel-pos-be/internal/appcontext"
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/logger"
|
||||
"apskel-pos-be/internal/service"
|
||||
"apskel-pos-be/internal/util"
|
||||
"apskel-pos-be/internal/validator"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type PurchaseCategoryHandler struct {
|
||||
purchaseCategoryService service.PurchaseCategoryService
|
||||
purchaseCategoryValidator validator.PurchaseCategoryValidator
|
||||
}
|
||||
|
||||
func NewPurchaseCategoryHandler(purchaseCategoryService service.PurchaseCategoryService, purchaseCategoryValidator validator.PurchaseCategoryValidator) *PurchaseCategoryHandler {
|
||||
return &PurchaseCategoryHandler{
|
||||
purchaseCategoryService: purchaseCategoryService,
|
||||
purchaseCategoryValidator: purchaseCategoryValidator,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PurchaseCategoryHandler) CreatePurchaseCategory(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
var req contract.CreatePurchaseCategoryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("PurchaseCategoryHandler::CreatePurchaseCategory -> request binding failed")
|
||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseCategoryHandler::CreatePurchaseCategory")
|
||||
return
|
||||
}
|
||||
|
||||
if validationError, validationErrorCode := h.purchaseCategoryValidator.ValidateCreatePurchaseCategoryRequest(&req); validationError != nil {
|
||||
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseCategoryHandler::CreatePurchaseCategory")
|
||||
return
|
||||
}
|
||||
|
||||
response := h.purchaseCategoryService.CreatePurchaseCategory(ctx, contextInfo, &req)
|
||||
util.HandleResponse(c.Writer, c.Request, response, "PurchaseCategoryHandler::CreatePurchaseCategory")
|
||||
}
|
||||
|
||||
func (h *PurchaseCategoryHandler) UpdatePurchaseCategory(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
categoryID, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid purchase category ID")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseCategoryHandler::UpdatePurchaseCategory")
|
||||
return
|
||||
}
|
||||
|
||||
var req contract.UpdatePurchaseCategoryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("PurchaseCategoryHandler::UpdatePurchaseCategory -> request binding failed")
|
||||
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseCategoryHandler::UpdatePurchaseCategory")
|
||||
return
|
||||
}
|
||||
|
||||
if validationError, validationErrorCode := h.purchaseCategoryValidator.ValidateUpdatePurchaseCategoryRequest(&req); validationError != nil {
|
||||
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseCategoryHandler::UpdatePurchaseCategory")
|
||||
return
|
||||
}
|
||||
|
||||
response := h.purchaseCategoryService.UpdatePurchaseCategory(ctx, contextInfo, categoryID, &req)
|
||||
util.HandleResponse(c.Writer, c.Request, response, "PurchaseCategoryHandler::UpdatePurchaseCategory")
|
||||
}
|
||||
|
||||
func (h *PurchaseCategoryHandler) DeletePurchaseCategory(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
categoryID, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid purchase category ID")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseCategoryHandler::DeletePurchaseCategory")
|
||||
return
|
||||
}
|
||||
|
||||
response := h.purchaseCategoryService.DeletePurchaseCategory(ctx, contextInfo, categoryID)
|
||||
util.HandleResponse(c.Writer, c.Request, response, "PurchaseCategoryHandler::DeletePurchaseCategory")
|
||||
}
|
||||
|
||||
func (h *PurchaseCategoryHandler) GetPurchaseCategory(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
categoryID, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid purchase category ID")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseCategoryHandler::GetPurchaseCategory")
|
||||
return
|
||||
}
|
||||
|
||||
response := h.purchaseCategoryService.GetPurchaseCategoryByID(ctx, contextInfo, categoryID)
|
||||
util.HandleResponse(c.Writer, c.Request, response, "PurchaseCategoryHandler::GetPurchaseCategory")
|
||||
}
|
||||
|
||||
func (h *PurchaseCategoryHandler) ListPurchaseCategories(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
req := &contract.ListPurchaseCategoriesRequest{
|
||||
Page: 1,
|
||||
Limit: 100,
|
||||
}
|
||||
|
||||
if pageStr := c.Query("page"); pageStr != "" {
|
||||
if page, err := strconv.Atoi(pageStr); err == nil {
|
||||
req.Page = page
|
||||
}
|
||||
}
|
||||
|
||||
if limitStr := c.Query("limit"); limitStr != "" {
|
||||
if limit, err := strconv.Atoi(limitStr); err == nil {
|
||||
req.Limit = limit
|
||||
}
|
||||
}
|
||||
|
||||
if parentIDStr := c.Query("parent_id"); parentIDStr != "" {
|
||||
if parentID, err := uuid.Parse(parentIDStr); err == nil {
|
||||
req.ParentID = &parentID
|
||||
}
|
||||
}
|
||||
|
||||
if categoryType := c.Query("type"); categoryType != "" {
|
||||
req.Type = categoryType
|
||||
}
|
||||
|
||||
if search := c.Query("search"); search != "" {
|
||||
req.Search = search
|
||||
}
|
||||
|
||||
if isActiveStr := c.Query("is_active"); isActiveStr != "" {
|
||||
if isActive, err := strconv.ParseBool(isActiveStr); err == nil {
|
||||
req.IsActive = &isActive
|
||||
}
|
||||
}
|
||||
|
||||
if validationError, validationErrorCode := h.purchaseCategoryValidator.ValidateListPurchaseCategoriesRequest(req); validationError != nil {
|
||||
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseCategoryHandler::ListPurchaseCategories")
|
||||
return
|
||||
}
|
||||
|
||||
response := h.purchaseCategoryService.ListPurchaseCategories(ctx, contextInfo, req)
|
||||
util.HandleResponse(c.Writer, c.Request, response, "PurchaseCategoryHandler::ListPurchaseCategories")
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package mappers
|
||||
|
||||
import (
|
||||
"apskel-pos-be/internal/entities"
|
||||
"apskel-pos-be/internal/models"
|
||||
)
|
||||
|
||||
func CreatePurchaseCategoryRequestToEntity(req *models.CreatePurchaseCategoryRequest) *entities.PurchaseCategory {
|
||||
if req == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &entities.PurchaseCategory{
|
||||
OrganizationID: req.OrganizationID,
|
||||
ParentID: req.ParentID,
|
||||
Name: req.Name,
|
||||
Type: entities.PurchaseCategoryType(req.Type),
|
||||
SortOrder: req.SortOrder,
|
||||
IsActive: req.IsActive,
|
||||
}
|
||||
}
|
||||
|
||||
func PurchaseCategoryEntityToResponse(entity *entities.PurchaseCategory) *models.PurchaseCategoryResponse {
|
||||
if entity == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &models.PurchaseCategoryResponse{
|
||||
ID: entity.ID,
|
||||
OrganizationID: entity.OrganizationID,
|
||||
PresetID: entity.PresetID,
|
||||
ParentID: entity.ParentID,
|
||||
Code: entity.Code,
|
||||
Name: entity.Name,
|
||||
Type: string(entity.Type),
|
||||
SortOrder: entity.SortOrder,
|
||||
IsSystem: entity.IsSystem,
|
||||
IsActive: entity.IsActive,
|
||||
CreatedAt: entity.CreatedAt,
|
||||
UpdatedAt: entity.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func PurchaseCategoryEntitiesToResponses(categoryEntities []*entities.PurchaseCategory) []models.PurchaseCategoryResponse {
|
||||
responses := make([]models.PurchaseCategoryResponse, len(categoryEntities))
|
||||
for i, entity := range categoryEntities {
|
||||
response := PurchaseCategoryEntityToResponse(entity)
|
||||
if response != nil {
|
||||
responses[i] = *response
|
||||
}
|
||||
}
|
||||
return responses
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type PurchaseCategoryResponse struct {
|
||||
ID uuid.UUID
|
||||
OrganizationID uuid.UUID
|
||||
PresetID *uuid.UUID
|
||||
ParentID *uuid.UUID
|
||||
Code string
|
||||
Name string
|
||||
Type string
|
||||
SortOrder int
|
||||
IsSystem bool
|
||||
IsActive bool
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type CreatePurchaseCategoryRequest struct {
|
||||
OrganizationID uuid.UUID
|
||||
ParentID *uuid.UUID
|
||||
Code *string
|
||||
Name string
|
||||
Type string
|
||||
SortOrder int
|
||||
IsActive bool
|
||||
}
|
||||
|
||||
type UpdatePurchaseCategoryRequest struct {
|
||||
ParentID *uuid.UUID
|
||||
Code *string
|
||||
Name *string
|
||||
Type *string
|
||||
SortOrder *int
|
||||
IsActive *bool
|
||||
}
|
||||
|
||||
type ListPurchaseCategoriesRequest struct {
|
||||
OrganizationID uuid.UUID
|
||||
ParentID *uuid.UUID
|
||||
Type string
|
||||
Search string
|
||||
IsActive *bool
|
||||
Page int
|
||||
Limit int
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"apskel-pos-be/internal/entities"
|
||||
"apskel-pos-be/internal/mappers"
|
||||
"apskel-pos-be/internal/models"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type PurchaseCategoryProcessor interface {
|
||||
CreatePurchaseCategory(ctx context.Context, req *models.CreatePurchaseCategoryRequest) (*models.PurchaseCategoryResponse, error)
|
||||
UpdatePurchaseCategory(ctx context.Context, id, organizationID uuid.UUID, req *models.UpdatePurchaseCategoryRequest) (*models.PurchaseCategoryResponse, error)
|
||||
DeletePurchaseCategory(ctx context.Context, id, organizationID uuid.UUID) error
|
||||
GetPurchaseCategoryByID(ctx context.Context, id, organizationID uuid.UUID) (*models.PurchaseCategoryResponse, error)
|
||||
ListPurchaseCategories(ctx context.Context, req *models.ListPurchaseCategoriesRequest) ([]models.PurchaseCategoryResponse, int, error)
|
||||
}
|
||||
|
||||
type PurchaseCategoryRepository interface {
|
||||
Create(ctx context.Context, category *entities.PurchaseCategory) error
|
||||
GetByIDAndOrganizationID(ctx context.Context, id, organizationID uuid.UUID) (*entities.PurchaseCategory, error)
|
||||
Update(ctx context.Context, category *entities.PurchaseCategory) error
|
||||
SoftDelete(ctx context.Context, id, organizationID uuid.UUID) error
|
||||
List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, limit, offset int) ([]*entities.PurchaseCategory, int64, error)
|
||||
ExistsByCode(ctx context.Context, organizationID uuid.UUID, code string, excludeID *uuid.UUID) (bool, error)
|
||||
}
|
||||
|
||||
type PurchaseCategoryProcessorImpl struct {
|
||||
purchaseCategoryRepo PurchaseCategoryRepository
|
||||
}
|
||||
|
||||
func NewPurchaseCategoryProcessorImpl(purchaseCategoryRepo PurchaseCategoryRepository) *PurchaseCategoryProcessorImpl {
|
||||
return &PurchaseCategoryProcessorImpl{purchaseCategoryRepo: purchaseCategoryRepo}
|
||||
}
|
||||
|
||||
func (p *PurchaseCategoryProcessorImpl) CreatePurchaseCategory(ctx context.Context, req *models.CreatePurchaseCategoryRequest) (*models.PurchaseCategoryResponse, error) {
|
||||
code := ""
|
||||
if req.Code != nil && strings.TrimSpace(*req.Code) != "" {
|
||||
code = normalizePurchaseCategoryCode(*req.Code)
|
||||
} else {
|
||||
code = normalizePurchaseCategoryCode(req.Name)
|
||||
}
|
||||
|
||||
if code == "" {
|
||||
return nil, fmt.Errorf("purchase category code cannot be empty")
|
||||
}
|
||||
|
||||
exists, err := p.purchaseCategoryRepo.ExistsByCode(ctx, req.OrganizationID, code, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check purchase category code uniqueness: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("purchase category with code '%s' already exists", code)
|
||||
}
|
||||
|
||||
if req.ParentID != nil {
|
||||
parent, err := p.purchaseCategoryRepo.GetByIDAndOrganizationID(ctx, *req.ParentID, req.OrganizationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parent purchase category not found: %w", err)
|
||||
}
|
||||
if string(parent.Type) != req.Type {
|
||||
return nil, fmt.Errorf("parent purchase category type must match child type")
|
||||
}
|
||||
}
|
||||
|
||||
category := mappers.CreatePurchaseCategoryRequestToEntity(req)
|
||||
category.Code = code
|
||||
|
||||
if err := p.purchaseCategoryRepo.Create(ctx, category); err != nil {
|
||||
return nil, fmt.Errorf("failed to create purchase category: %w", err)
|
||||
}
|
||||
|
||||
return mappers.PurchaseCategoryEntityToResponse(category), nil
|
||||
}
|
||||
|
||||
func (p *PurchaseCategoryProcessorImpl) UpdatePurchaseCategory(ctx context.Context, id, organizationID uuid.UUID, req *models.UpdatePurchaseCategoryRequest) (*models.PurchaseCategoryResponse, error) {
|
||||
category, err := p.purchaseCategoryRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("purchase category not found: %w", err)
|
||||
}
|
||||
|
||||
newType := string(category.Type)
|
||||
if req.Type != nil {
|
||||
newType = *req.Type
|
||||
}
|
||||
|
||||
if req.ParentID != nil {
|
||||
if *req.ParentID == id {
|
||||
return nil, fmt.Errorf("purchase category cannot be its own parent")
|
||||
}
|
||||
|
||||
parent, err := p.purchaseCategoryRepo.GetByIDAndOrganizationID(ctx, *req.ParentID, organizationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parent purchase category not found: %w", err)
|
||||
}
|
||||
if string(parent.Type) != newType {
|
||||
return nil, fmt.Errorf("parent purchase category type must match child type")
|
||||
}
|
||||
category.ParentID = req.ParentID
|
||||
}
|
||||
|
||||
if req.Code != nil {
|
||||
code := normalizePurchaseCategoryCode(*req.Code)
|
||||
if code == "" {
|
||||
return nil, fmt.Errorf("purchase category code cannot be empty")
|
||||
}
|
||||
|
||||
if code != category.Code {
|
||||
exists, err := p.purchaseCategoryRepo.ExistsByCode(ctx, organizationID, code, &id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check purchase category code uniqueness: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("purchase category with code '%s' already exists", code)
|
||||
}
|
||||
category.Code = code
|
||||
}
|
||||
}
|
||||
|
||||
if req.Name != nil {
|
||||
category.Name = strings.TrimSpace(*req.Name)
|
||||
}
|
||||
if req.Type != nil {
|
||||
category.Type = entities.PurchaseCategoryType(*req.Type)
|
||||
}
|
||||
if req.SortOrder != nil {
|
||||
category.SortOrder = *req.SortOrder
|
||||
}
|
||||
if req.IsActive != nil {
|
||||
category.IsActive = *req.IsActive
|
||||
}
|
||||
|
||||
if err := p.purchaseCategoryRepo.Update(ctx, category); err != nil {
|
||||
return nil, fmt.Errorf("failed to update purchase category: %w", err)
|
||||
}
|
||||
|
||||
return mappers.PurchaseCategoryEntityToResponse(category), nil
|
||||
}
|
||||
|
||||
func (p *PurchaseCategoryProcessorImpl) DeletePurchaseCategory(ctx context.Context, id, organizationID uuid.UUID) error {
|
||||
_, err := p.purchaseCategoryRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("purchase category not found: %w", err)
|
||||
}
|
||||
|
||||
if err := p.purchaseCategoryRepo.SoftDelete(ctx, id, organizationID); err != nil {
|
||||
return fmt.Errorf("failed to delete purchase category: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PurchaseCategoryProcessorImpl) GetPurchaseCategoryByID(ctx context.Context, id, organizationID uuid.UUID) (*models.PurchaseCategoryResponse, error) {
|
||||
category, err := p.purchaseCategoryRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("purchase category not found: %w", err)
|
||||
}
|
||||
|
||||
return mappers.PurchaseCategoryEntityToResponse(category), nil
|
||||
}
|
||||
|
||||
func (p *PurchaseCategoryProcessorImpl) ListPurchaseCategories(ctx context.Context, req *models.ListPurchaseCategoriesRequest) ([]models.PurchaseCategoryResponse, int, error) {
|
||||
filters := make(map[string]interface{})
|
||||
if req.ParentID != nil {
|
||||
filters["parent_id"] = *req.ParentID
|
||||
}
|
||||
if req.Type != "" {
|
||||
filters["type"] = req.Type
|
||||
}
|
||||
if req.Search != "" {
|
||||
filters["search"] = req.Search
|
||||
}
|
||||
if req.IsActive != nil {
|
||||
filters["is_active"] = *req.IsActive
|
||||
}
|
||||
|
||||
offset := (req.Page - 1) * req.Limit
|
||||
categories, total, err := p.purchaseCategoryRepo.List(ctx, req.OrganizationID, filters, req.Limit, offset)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to list purchase categories: %w", err)
|
||||
}
|
||||
|
||||
return mappers.PurchaseCategoryEntitiesToResponses(categories), int(total), nil
|
||||
}
|
||||
|
||||
func normalizePurchaseCategoryCode(value string) string {
|
||||
value = strings.TrimSpace(strings.ToLower(value))
|
||||
var builder strings.Builder
|
||||
lastUnderscore := false
|
||||
|
||||
for _, r := range value {
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
||||
builder.WriteRune(r)
|
||||
lastUnderscore = false
|
||||
continue
|
||||
}
|
||||
|
||||
if !lastUnderscore {
|
||||
builder.WriteRune('_')
|
||||
lastUnderscore = true
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Trim(builder.String(), "_")
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"apskel-pos-be/internal/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PurchaseCategoryRepositoryImpl struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewPurchaseCategoryRepositoryImpl(db *gorm.DB) *PurchaseCategoryRepositoryImpl {
|
||||
return &PurchaseCategoryRepositoryImpl{db: db}
|
||||
}
|
||||
|
||||
func (r *PurchaseCategoryRepositoryImpl) Create(ctx context.Context, category *entities.PurchaseCategory) error {
|
||||
return r.db.WithContext(ctx).Create(category).Error
|
||||
}
|
||||
|
||||
func (r *PurchaseCategoryRepositoryImpl) GetByIDAndOrganizationID(ctx context.Context, id, organizationID uuid.UUID) (*entities.PurchaseCategory, error) {
|
||||
var category entities.PurchaseCategory
|
||||
err := r.db.WithContext(ctx).
|
||||
First(&category, "id = ? AND organization_id = ?", id, organizationID).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &category, nil
|
||||
}
|
||||
|
||||
func (r *PurchaseCategoryRepositoryImpl) Update(ctx context.Context, category *entities.PurchaseCategory) error {
|
||||
return r.db.WithContext(ctx).Save(category).Error
|
||||
}
|
||||
|
||||
func (r *PurchaseCategoryRepositoryImpl) SoftDelete(ctx context.Context, id, organizationID uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&entities.PurchaseCategory{}).
|
||||
Where("id = ? AND organization_id = ?", id, organizationID).
|
||||
Update("is_active", false).Error
|
||||
}
|
||||
|
||||
func (r *PurchaseCategoryRepositoryImpl) List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, limit, offset int) ([]*entities.PurchaseCategory, int64, error) {
|
||||
var categories []*entities.PurchaseCategory
|
||||
var total int64
|
||||
|
||||
query := r.db.WithContext(ctx).
|
||||
Model(&entities.PurchaseCategory{}).
|
||||
Where("organization_id = ?", organizationID)
|
||||
|
||||
for key, value := range filters {
|
||||
switch key {
|
||||
case "search":
|
||||
searchValue := "%" + value.(string) + "%"
|
||||
query = query.Where("name ILIKE ? OR code ILIKE ?", searchValue, searchValue)
|
||||
case "parent_id":
|
||||
query = query.Where("parent_id = ?", value)
|
||||
case "type":
|
||||
query = query.Where("type = ?", value)
|
||||
case "is_active":
|
||||
query = query.Where("is_active = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err := query.
|
||||
Order("parent_id NULLS FIRST, sort_order ASC, name ASC").
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Find(&categories).Error
|
||||
return categories, total, err
|
||||
}
|
||||
|
||||
func (r *PurchaseCategoryRepositoryImpl) ExistsByCode(ctx context.Context, organizationID uuid.UUID, code string, excludeID *uuid.UUID) (bool, error) {
|
||||
query := r.db.WithContext(ctx).
|
||||
Model(&entities.PurchaseCategory{}).
|
||||
Where("organization_id = ? AND code = ?", organizationID, code)
|
||||
|
||||
if excludeID != nil {
|
||||
query = query.Where("id != ?", *excludeID)
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := query.Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
@@ -35,6 +35,7 @@ type Router struct {
|
||||
productRecipeHandler *handler.ProductRecipeHandler
|
||||
vendorHandler *handler.VendorHandler
|
||||
purchaseOrderHandler *handler.PurchaseOrderHandler
|
||||
purchaseCategoryHandler *handler.PurchaseCategoryHandler
|
||||
unitConverterHandler *handler.IngredientUnitConverterHandler
|
||||
chartOfAccountTypeHandler *handler.ChartOfAccountTypeHandler
|
||||
chartOfAccountHandler *handler.ChartOfAccountHandler
|
||||
@@ -55,7 +56,7 @@ type Router struct {
|
||||
customerAuthMiddleware *middleware.CustomerAuthMiddleware
|
||||
}
|
||||
|
||||
func NewRouter(cfg *config.Config, healthHandler *handler.HealthHandler, authService service.AuthService, authMiddleware *middleware.AuthMiddleware, userService *service.UserServiceImpl, userValidator *validator.UserValidatorImpl, organizationService service.OrganizationService, organizationValidator validator.OrganizationValidator, outletService service.OutletService, outletValidator validator.OutletValidator, outletSettingService service.OutletSettingService, categoryService service.CategoryService, categoryValidator validator.CategoryValidator, productService service.ProductService, productValidator validator.ProductValidator, productVariantService service.ProductVariantService, productVariantValidator validator.ProductVariantValidator, inventoryService service.InventoryService, inventoryValidator validator.InventoryValidator, orderService service.OrderService, orderValidator validator.OrderValidator, fileService service.FileService, fileValidator validator.FileValidator, customerService service.CustomerService, customerValidator validator.CustomerValidator, paymentMethodService service.PaymentMethodService, paymentMethodValidator validator.PaymentMethodValidator, analyticsService *service.AnalyticsServiceImpl, reportService service.ReportService, tableService *service.TableServiceImpl, tableValidator *validator.TableValidator, unitService handler.UnitService, ingredientService handler.IngredientService, productRecipeService service.ProductRecipeService, vendorService service.VendorService, vendorValidator validator.VendorValidator, purchaseOrderService service.PurchaseOrderService, purchaseOrderValidator validator.PurchaseOrderValidator, unitConverterService service.IngredientUnitConverterService, unitConverterValidator validator.IngredientUnitConverterValidator, chartOfAccountTypeService service.ChartOfAccountTypeService, chartOfAccountTypeValidator validator.ChartOfAccountTypeValidator, chartOfAccountService service.ChartOfAccountService, chartOfAccountValidator validator.ChartOfAccountValidator, accountService service.AccountService, accountValidator validator.AccountValidator, orderIngredientTransactionService service.OrderIngredientTransactionService, orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator, gamificationService service.GamificationService, gamificationValidator validator.GamificationValidator, rewardService service.RewardService, rewardValidator validator.RewardValidator, campaignService service.CampaignService, campaignValidator validator.CampaignValidator, customerAuthService service.CustomerAuthService, customerAuthValidator validator.CustomerAuthValidator, customerPointsService service.CustomerPointsService, spinGameService service.SpinGameService, customerAuthMiddleware *middleware.CustomerAuthMiddleware, userDeviceService service.UserDeviceService, userDeviceValidator validator.UserDeviceValidator, notificationService service.NotificationService, notificationValidator validator.NotificationValidator, productOutletPriceService service.ProductOutletPriceService, productOutletPriceValidator validator.ProductOutletPriceValidator, selfOrderHandler *handler.SelfOrderHandler, expenseService *service.ExpenseServiceImpl, expenseValidator *validator.ExpenseValidatorImpl) *Router {
|
||||
func NewRouter(cfg *config.Config, healthHandler *handler.HealthHandler, authService service.AuthService, authMiddleware *middleware.AuthMiddleware, userService *service.UserServiceImpl, userValidator *validator.UserValidatorImpl, organizationService service.OrganizationService, organizationValidator validator.OrganizationValidator, outletService service.OutletService, outletValidator validator.OutletValidator, outletSettingService service.OutletSettingService, categoryService service.CategoryService, categoryValidator validator.CategoryValidator, productService service.ProductService, productValidator validator.ProductValidator, productVariantService service.ProductVariantService, productVariantValidator validator.ProductVariantValidator, inventoryService service.InventoryService, inventoryValidator validator.InventoryValidator, orderService service.OrderService, orderValidator validator.OrderValidator, fileService service.FileService, fileValidator validator.FileValidator, customerService service.CustomerService, customerValidator validator.CustomerValidator, paymentMethodService service.PaymentMethodService, paymentMethodValidator validator.PaymentMethodValidator, analyticsService *service.AnalyticsServiceImpl, reportService service.ReportService, tableService *service.TableServiceImpl, tableValidator *validator.TableValidator, unitService handler.UnitService, ingredientService handler.IngredientService, productRecipeService service.ProductRecipeService, vendorService service.VendorService, vendorValidator validator.VendorValidator, purchaseOrderService service.PurchaseOrderService, purchaseOrderValidator validator.PurchaseOrderValidator, purchaseCategoryService service.PurchaseCategoryService, purchaseCategoryValidator validator.PurchaseCategoryValidator, unitConverterService service.IngredientUnitConverterService, unitConverterValidator validator.IngredientUnitConverterValidator, chartOfAccountTypeService service.ChartOfAccountTypeService, chartOfAccountTypeValidator validator.ChartOfAccountTypeValidator, chartOfAccountService service.ChartOfAccountService, chartOfAccountValidator validator.ChartOfAccountValidator, accountService service.AccountService, accountValidator validator.AccountValidator, orderIngredientTransactionService service.OrderIngredientTransactionService, orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator, gamificationService service.GamificationService, gamificationValidator validator.GamificationValidator, rewardService service.RewardService, rewardValidator validator.RewardValidator, campaignService service.CampaignService, campaignValidator validator.CampaignValidator, customerAuthService service.CustomerAuthService, customerAuthValidator validator.CustomerAuthValidator, customerPointsService service.CustomerPointsService, spinGameService service.SpinGameService, customerAuthMiddleware *middleware.CustomerAuthMiddleware, userDeviceService service.UserDeviceService, userDeviceValidator validator.UserDeviceValidator, notificationService service.NotificationService, notificationValidator validator.NotificationValidator, productOutletPriceService service.ProductOutletPriceService, productOutletPriceValidator validator.ProductOutletPriceValidator, selfOrderHandler *handler.SelfOrderHandler, expenseService *service.ExpenseServiceImpl, expenseValidator *validator.ExpenseValidatorImpl) *Router {
|
||||
|
||||
return &Router{
|
||||
config: cfg,
|
||||
@@ -80,6 +81,7 @@ func NewRouter(cfg *config.Config, healthHandler *handler.HealthHandler, authSer
|
||||
productRecipeHandler: handler.NewProductRecipeHandler(productRecipeService),
|
||||
vendorHandler: handler.NewVendorHandler(vendorService, vendorValidator),
|
||||
purchaseOrderHandler: handler.NewPurchaseOrderHandler(purchaseOrderService, purchaseOrderValidator),
|
||||
purchaseCategoryHandler: handler.NewPurchaseCategoryHandler(purchaseCategoryService, purchaseCategoryValidator),
|
||||
unitConverterHandler: handler.NewIngredientUnitConverterHandler(unitConverterService, unitConverterValidator),
|
||||
chartOfAccountTypeHandler: handler.NewChartOfAccountTypeHandler(chartOfAccountTypeService, chartOfAccountTypeValidator),
|
||||
chartOfAccountHandler: handler.NewChartOfAccountHandler(chartOfAccountService, chartOfAccountValidator),
|
||||
@@ -384,6 +386,16 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
||||
purchaseOrders.DELETE("/:id", r.purchaseOrderHandler.DeletePurchaseOrder)
|
||||
}
|
||||
|
||||
purchaseCategories := protected.Group("/purchase-categories")
|
||||
purchaseCategories.Use(r.authMiddleware.RequireAdminOrManager())
|
||||
{
|
||||
purchaseCategories.POST("", r.purchaseCategoryHandler.CreatePurchaseCategory)
|
||||
purchaseCategories.GET("", r.purchaseCategoryHandler.ListPurchaseCategories)
|
||||
purchaseCategories.GET("/:id", r.purchaseCategoryHandler.GetPurchaseCategory)
|
||||
purchaseCategories.PUT("/:id", r.purchaseCategoryHandler.UpdatePurchaseCategory)
|
||||
purchaseCategories.DELETE("/:id", r.purchaseCategoryHandler.DeletePurchaseCategory)
|
||||
}
|
||||
|
||||
unitConverters := protected.Group("/unit-converters")
|
||||
unitConverters.Use(r.authMiddleware.RequireAdminOrManager())
|
||||
{
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type PurchaseCategoryService interface {
|
||||
CreatePurchaseCategory(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreatePurchaseCategoryRequest) *contract.Response
|
||||
UpdatePurchaseCategory(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, req *contract.UpdatePurchaseCategoryRequest) *contract.Response
|
||||
DeletePurchaseCategory(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response
|
||||
GetPurchaseCategoryByID(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response
|
||||
ListPurchaseCategories(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ListPurchaseCategoriesRequest) *contract.Response
|
||||
}
|
||||
|
||||
type PurchaseCategoryServiceImpl struct {
|
||||
purchaseCategoryProcessor processor.PurchaseCategoryProcessor
|
||||
}
|
||||
|
||||
func NewPurchaseCategoryService(purchaseCategoryProcessor processor.PurchaseCategoryProcessor) *PurchaseCategoryServiceImpl {
|
||||
return &PurchaseCategoryServiceImpl{purchaseCategoryProcessor: purchaseCategoryProcessor}
|
||||
}
|
||||
|
||||
func (s *PurchaseCategoryServiceImpl) CreatePurchaseCategory(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreatePurchaseCategoryRequest) *contract.Response {
|
||||
modelReq := transformer.CreatePurchaseCategoryRequestToModel(apctx, req)
|
||||
|
||||
category, err := s.purchaseCategoryProcessor.CreatePurchaseCategory(ctx, modelReq)
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseCategoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(transformer.PurchaseCategoryModelResponseToResponse(category))
|
||||
}
|
||||
|
||||
func (s *PurchaseCategoryServiceImpl) UpdatePurchaseCategory(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, req *contract.UpdatePurchaseCategoryRequest) *contract.Response {
|
||||
modelReq := transformer.UpdatePurchaseCategoryRequestToModel(req)
|
||||
|
||||
category, err := s.purchaseCategoryProcessor.UpdatePurchaseCategory(ctx, id, apctx.OrganizationID, modelReq)
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseCategoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(transformer.PurchaseCategoryModelResponseToResponse(category))
|
||||
}
|
||||
|
||||
func (s *PurchaseCategoryServiceImpl) DeletePurchaseCategory(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response {
|
||||
err := s.purchaseCategoryProcessor.DeletePurchaseCategory(ctx, id, apctx.OrganizationID)
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseCategoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(map[string]interface{}{
|
||||
"message": "Purchase category deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *PurchaseCategoryServiceImpl) GetPurchaseCategoryByID(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response {
|
||||
category, err := s.purchaseCategoryProcessor.GetPurchaseCategoryByID(ctx, id, apctx.OrganizationID)
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseCategoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(transformer.PurchaseCategoryModelResponseToResponse(category))
|
||||
}
|
||||
|
||||
func (s *PurchaseCategoryServiceImpl) ListPurchaseCategories(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ListPurchaseCategoriesRequest) *contract.Response {
|
||||
modelReq := &models.ListPurchaseCategoriesRequest{
|
||||
OrganizationID: apctx.OrganizationID,
|
||||
ParentID: req.ParentID,
|
||||
Type: req.Type,
|
||||
Search: req.Search,
|
||||
IsActive: req.IsActive,
|
||||
Page: req.Page,
|
||||
Limit: req.Limit,
|
||||
}
|
||||
|
||||
categories, totalCount, err := s.purchaseCategoryProcessor.ListPurchaseCategories(ctx, modelReq)
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseCategoryServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
totalPages := totalCount / req.Limit
|
||||
if totalCount%req.Limit > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(&contract.ListPurchaseCategoriesResponse{
|
||||
PurchaseCategories: transformer.PurchaseCategoryModelResponsesToResponses(categories),
|
||||
TotalCount: totalCount,
|
||||
Page: req.Page,
|
||||
Limit: req.Limit,
|
||||
TotalPages: totalPages,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package transformer
|
||||
|
||||
import (
|
||||
"apskel-pos-be/internal/appcontext"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/models"
|
||||
)
|
||||
|
||||
func CreatePurchaseCategoryRequestToModel(apctx *appcontext.ContextInfo, req *contract.CreatePurchaseCategoryRequest) *models.CreatePurchaseCategoryRequest {
|
||||
sortOrder := 0
|
||||
if req.SortOrder != nil {
|
||||
sortOrder = *req.SortOrder
|
||||
}
|
||||
|
||||
isActive := true
|
||||
if req.IsActive != nil {
|
||||
isActive = *req.IsActive
|
||||
}
|
||||
|
||||
return &models.CreatePurchaseCategoryRequest{
|
||||
OrganizationID: apctx.OrganizationID,
|
||||
ParentID: req.ParentID,
|
||||
Code: req.Code,
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
SortOrder: sortOrder,
|
||||
IsActive: isActive,
|
||||
}
|
||||
}
|
||||
|
||||
func UpdatePurchaseCategoryRequestToModel(req *contract.UpdatePurchaseCategoryRequest) *models.UpdatePurchaseCategoryRequest {
|
||||
return &models.UpdatePurchaseCategoryRequest{
|
||||
ParentID: req.ParentID,
|
||||
Code: req.Code,
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
SortOrder: req.SortOrder,
|
||||
IsActive: req.IsActive,
|
||||
}
|
||||
}
|
||||
|
||||
func PurchaseCategoryModelResponseToResponse(category *models.PurchaseCategoryResponse) *contract.PurchaseCategoryResponse {
|
||||
if category == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &contract.PurchaseCategoryResponse{
|
||||
ID: category.ID,
|
||||
OrganizationID: category.OrganizationID,
|
||||
PresetID: category.PresetID,
|
||||
ParentID: category.ParentID,
|
||||
Code: category.Code,
|
||||
Name: category.Name,
|
||||
Type: category.Type,
|
||||
SortOrder: category.SortOrder,
|
||||
IsSystem: category.IsSystem,
|
||||
IsActive: category.IsActive,
|
||||
CreatedAt: category.CreatedAt,
|
||||
UpdatedAt: category.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func PurchaseCategoryModelResponsesToResponses(categories []models.PurchaseCategoryResponse) []contract.PurchaseCategoryResponse {
|
||||
responses := make([]contract.PurchaseCategoryResponse, len(categories))
|
||||
for i, category := range categories {
|
||||
response := PurchaseCategoryModelResponseToResponse(&category)
|
||||
if response != nil {
|
||||
responses[i] = *response
|
||||
}
|
||||
}
|
||||
return responses
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/contract"
|
||||
)
|
||||
|
||||
type PurchaseCategoryValidator interface {
|
||||
ValidateCreatePurchaseCategoryRequest(req *contract.CreatePurchaseCategoryRequest) (error, string)
|
||||
ValidateUpdatePurchaseCategoryRequest(req *contract.UpdatePurchaseCategoryRequest) (error, string)
|
||||
ValidateListPurchaseCategoriesRequest(req *contract.ListPurchaseCategoriesRequest) (error, string)
|
||||
}
|
||||
|
||||
type PurchaseCategoryValidatorImpl struct{}
|
||||
|
||||
func NewPurchaseCategoryValidator() *PurchaseCategoryValidatorImpl {
|
||||
return &PurchaseCategoryValidatorImpl{}
|
||||
}
|
||||
|
||||
func (v *PurchaseCategoryValidatorImpl) ValidateCreatePurchaseCategoryRequest(req *contract.CreatePurchaseCategoryRequest) (error, string) {
|
||||
if req == nil {
|
||||
return errors.New("request body is required"), constants.MissingFieldErrorCode
|
||||
}
|
||||
|
||||
if strings.TrimSpace(req.Name) == "" {
|
||||
return errors.New("name is required"), constants.MissingFieldErrorCode
|
||||
}
|
||||
|
||||
if len(req.Name) > 255 {
|
||||
return errors.New("name cannot exceed 255 characters"), constants.MalformedFieldErrorCode
|
||||
}
|
||||
|
||||
if !isValidPurchaseCategoryType(req.Type) {
|
||||
return errors.New("type must be raw_material or non_inventory"), constants.MalformedFieldErrorCode
|
||||
}
|
||||
|
||||
if req.Code != nil && len(strings.TrimSpace(*req.Code)) > 100 {
|
||||
return errors.New("code cannot exceed 100 characters"), constants.MalformedFieldErrorCode
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (v *PurchaseCategoryValidatorImpl) ValidateUpdatePurchaseCategoryRequest(req *contract.UpdatePurchaseCategoryRequest) (error, string) {
|
||||
if req == nil {
|
||||
return errors.New("request body is required"), constants.MissingFieldErrorCode
|
||||
}
|
||||
|
||||
if req.ParentID == nil && req.Code == nil && req.Name == nil && req.Type == nil && req.SortOrder == nil && req.IsActive == nil {
|
||||
return errors.New("at least one field must be provided for update"), constants.MissingFieldErrorCode
|
||||
}
|
||||
|
||||
if req.Name != nil {
|
||||
if strings.TrimSpace(*req.Name) == "" {
|
||||
return errors.New("name cannot be empty"), constants.MalformedFieldErrorCode
|
||||
}
|
||||
if len(*req.Name) > 255 {
|
||||
return errors.New("name cannot exceed 255 characters"), constants.MalformedFieldErrorCode
|
||||
}
|
||||
}
|
||||
|
||||
if req.Type != nil && !isValidPurchaseCategoryType(*req.Type) {
|
||||
return errors.New("type must be raw_material or non_inventory"), constants.MalformedFieldErrorCode
|
||||
}
|
||||
|
||||
if req.Code != nil && len(strings.TrimSpace(*req.Code)) > 100 {
|
||||
return errors.New("code cannot exceed 100 characters"), constants.MalformedFieldErrorCode
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (v *PurchaseCategoryValidatorImpl) ValidateListPurchaseCategoriesRequest(req *contract.ListPurchaseCategoriesRequest) (error, string) {
|
||||
if req == nil {
|
||||
return errors.New("request is required"), constants.MissingFieldErrorCode
|
||||
}
|
||||
|
||||
if req.Page < 1 {
|
||||
return errors.New("page must be at least 1"), constants.MalformedFieldErrorCode
|
||||
}
|
||||
|
||||
if req.Limit < 1 || req.Limit > 100 {
|
||||
return errors.New("limit must be between 1 and 100"), constants.MalformedFieldErrorCode
|
||||
}
|
||||
|
||||
if req.Type != "" && !isValidPurchaseCategoryType(req.Type) {
|
||||
return errors.New("type must be raw_material or non_inventory"), constants.MalformedFieldErrorCode
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func isValidPurchaseCategoryType(categoryType string) bool {
|
||||
switch categoryType {
|
||||
case "raw_material", "non_inventory":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user