init
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
# Internal Architecture
|
||||
|
||||
This document describes the clean architecture implementation for the POS backend with complete separation of concerns between database entities, business models, and constants.
|
||||
|
||||
## 📁 Package Structure
|
||||
|
||||
### `/constants` - Business Constants
|
||||
- **Purpose**: All business logic constants, enums, and validation helpers
|
||||
- **Usage**: Used by models, services, and validation layers
|
||||
- **Features**:
|
||||
- Type-safe enums (UserRole, OrderStatus, PaymentStatus, etc.)
|
||||
- Business validation functions (IsValidUserRole, etc.)
|
||||
- Default values and limits
|
||||
- No dependencies on database or frameworks
|
||||
|
||||
### `/entities` - Database Models
|
||||
- **Purpose**: Database-specific models with GORM tags and hooks
|
||||
- **Usage**: **ONLY** used by repository layer for database operations
|
||||
- **Features**:
|
||||
- GORM annotations (`gorm:` tags)
|
||||
- Database relationships and constraints
|
||||
- BeforeCreate/AfterCreate hooks
|
||||
- Table name specifications
|
||||
- SQL-specific data types
|
||||
- **Never used in business logic**
|
||||
|
||||
### `/models` - Business Models
|
||||
- **Purpose**: **Pure** business domain models without any framework dependencies
|
||||
- **Usage**: Used by services, handlers, and business logic
|
||||
- **Features**:
|
||||
- Clean JSON serialization (`json:` tags)
|
||||
- Validation rules (`validate:` tags)
|
||||
- Request/Response DTOs
|
||||
- **Zero GORM dependencies**
|
||||
- **Zero database annotations**
|
||||
- Uses constants package for type safety
|
||||
- Pure business logic methods
|
||||
|
||||
### `/mappers` - Data Transformation
|
||||
- **Purpose**: Convert between entities and business models
|
||||
- **Usage**: Bridge between repository and service layers
|
||||
- **Features**:
|
||||
- Entity ↔ Model conversion functions
|
||||
- Request DTO → Entity conversion
|
||||
- Entity → Response DTO conversion
|
||||
- Null-safe conversions
|
||||
- Slice/collection conversions
|
||||
- Type conversions between constants and entities
|
||||
|
||||
### `/repository` - Data Access Layer
|
||||
- **Purpose**: Database operations using entities exclusively
|
||||
- **Usage**: Only works with database entities
|
||||
- **Features**:
|
||||
- CRUD operations with entities
|
||||
- Query methods with entities
|
||||
- **Private repository implementations**
|
||||
- Interface-based contracts
|
||||
- **Never references business models**
|
||||
|
||||
## 🔄 Data Flow
|
||||
|
||||
```
|
||||
API Request (JSON)
|
||||
↓
|
||||
Request DTO (models)
|
||||
↓
|
||||
Business Logic (services with models + constants)
|
||||
↓
|
||||
Entity (via mapper)
|
||||
↓
|
||||
Repository Layer (entities only)
|
||||
↓
|
||||
Database
|
||||
↓
|
||||
Entity (from database)
|
||||
↓
|
||||
Business Model (via mapper)
|
||||
↓
|
||||
Response DTO (models)
|
||||
↓
|
||||
API Response (JSON)
|
||||
```
|
||||
|
||||
## 🎯 Key Design Principles
|
||||
|
||||
### ✅ **Clean Business Models**
|
||||
```go
|
||||
|
||||
type User struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Role constants.UserRole `json:"role"`
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
|
||||
type User struct {
|
||||
ID uuid.UUID `gorm:"primaryKey" json:"id"`
|
||||
Role string `gorm:"size:50" json:"role"`
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ **Type-Safe Constants**
|
||||
```go
|
||||
|
||||
type UserRole string
|
||||
const (
|
||||
RoleAdmin UserRole = "admin"
|
||||
)
|
||||
func IsValidUserRole(role UserRole) bool { /* ... */ }
|
||||
```
|
||||
|
||||
```go
|
||||
|
||||
const AdminRole = "admin" ```
|
||||
|
||||
### ✅ **Repository Isolation**
|
||||
```go
|
||||
|
||||
func (r *userRepository) Create(ctx context.Context, user *entities.User) error {
|
||||
return r.db.Create(user).Error
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
|
||||
func (r *userRepository) Create(ctx context.Context, user *models.User) error {
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Example Usage
|
||||
|
||||
### Service Layer (Business Logic)
|
||||
```go
|
||||
func (s *userService) CreateUser(req *models.UserCreateRequest) (*models.UserResponse, error) {
|
||||
if !constants.IsValidUserRole(req.Role) {
|
||||
return nil, errors.New("invalid role")
|
||||
}
|
||||
|
||||
entity := mappers.UserCreateRequestToEntity(req, hashedPassword)
|
||||
|
||||
err := s.userRepo.Create(ctx, entity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mappers.UserEntityToResponse(entity), nil
|
||||
}
|
||||
```
|
||||
|
||||
### Repository Layer (Data Access)
|
||||
```go
|
||||
func (r *userRepository) Create(ctx context.Context, user *entities.User) error {
|
||||
return r.db.WithContext(ctx).Create(user).Error
|
||||
}
|
||||
```
|
||||
|
||||
### Handler Layer (API)
|
||||
```go
|
||||
func (h *userHandler) CreateUser(c *gin.Context) {
|
||||
var req models.UserCreateRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.userService.CreateUser(&req)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(201, resp)
|
||||
}
|
||||
```
|
||||
|
||||
## 🏗️ Architecture Benefits
|
||||
|
||||
1. **🎯 Single Responsibility**: Each package has one clear purpose
|
||||
2. **🔒 Zero Database Leakage**: Business logic never sees database concerns
|
||||
3. **🧪 Testability**: Easy to mock interfaces and test business logic
|
||||
4. **🔧 Maintainability**: Changes to database don't affect business models
|
||||
5. **🚀 Flexibility**: Can change ORM without touching business logic
|
||||
6. **📜 API Stability**: Business models provide stable contracts
|
||||
7. **🛡️ Type Safety**: Constants package prevents invalid states
|
||||
8. **🧹 Clean Code**: No mixed concerns anywhere in the codebase
|
||||
|
||||
## 📋 Development Guidelines
|
||||
|
||||
### Constants Package (`/constants`)
|
||||
- ✅ Define all business enums and constants
|
||||
- ✅ Provide validation helper functions
|
||||
- ✅ Include default values and limits
|
||||
- ❌ Never import database or framework packages
|
||||
- ❌ No business logic, only constants and validation
|
||||
|
||||
### Models Package (`/models`)
|
||||
- ✅ Pure business structs with JSON tags only
|
||||
- ✅ Use constants package for type safety
|
||||
- ✅ Include validation tags for input validation
|
||||
- ✅ Separate Request/Response DTOs
|
||||
- ✅ Add business logic methods (validation, calculations)
|
||||
- ❌ **NEVER** include GORM tags or database annotations
|
||||
- ❌ **NEVER** import database packages
|
||||
- ❌ No database relationships or foreign keys
|
||||
|
||||
### Entities Package (`/entities`)
|
||||
- ✅ Include GORM tags and database constraints
|
||||
- ✅ Define relationships and foreign keys
|
||||
- ✅ Add database hooks (BeforeCreate, etc.)
|
||||
- ✅ Use database-specific types
|
||||
- ❌ **NEVER** use in business logic or handlers
|
||||
- ❌ **NEVER** add business validation rules
|
||||
|
||||
### Mappers Package (`/mappers`)
|
||||
- ✅ Always check for nil inputs
|
||||
- ✅ Handle type conversions between constants and strings
|
||||
- ✅ Provide slice conversion helpers
|
||||
- ✅ Keep conversions simple and direct
|
||||
- ❌ No business logic in mappers
|
||||
- ❌ No database operations
|
||||
|
||||
### Repository Package (`/repository`)
|
||||
- ✅ Work exclusively with entities
|
||||
- ✅ Use private repository implementations
|
||||
- ✅ Provide clean interface contracts
|
||||
- ❌ **NEVER** reference business models
|
||||
- ❌ **NEVER** import models package
|
||||
|
||||
## 🚀 Migration Complete
|
||||
|
||||
**All packages have been successfully reorganized:**
|
||||
|
||||
- ✅ **4 Constants files** - All business constants moved to type-safe enums
|
||||
- ✅ **10 Clean Model files** - Zero GORM dependencies, pure business logic
|
||||
- ✅ **11 Entity files** - Database-only models with GORM tags
|
||||
- ✅ **11 Repository files** - Updated to use entities exclusively
|
||||
- ✅ **2 Mapper files** - Handle conversions between layers
|
||||
- ✅ **Complete separation** - No cross-layer dependencies
|
||||
|
||||
**The codebase now follows strict clean architecture principles with complete separation of database concerns from business logic!** 🎉
|
||||
@@ -0,0 +1,284 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"apskel-pos-be/internal/client"
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"apskel-pos-be/config"
|
||||
"apskel-pos-be/internal/handler"
|
||||
"apskel-pos-be/internal/middleware"
|
||||
"apskel-pos-be/internal/processor"
|
||||
"apskel-pos-be/internal/repository"
|
||||
"apskel-pos-be/internal/router"
|
||||
"apskel-pos-be/internal/service"
|
||||
"apskel-pos-be/internal/validator"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
server *http.Server
|
||||
db *gorm.DB
|
||||
router *router.Router
|
||||
shutdown chan os.Signal
|
||||
}
|
||||
|
||||
func NewApp(db *gorm.DB) *App {
|
||||
return &App{
|
||||
db: db,
|
||||
shutdown: make(chan os.Signal, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) Initialize(cfg *config.Config) error {
|
||||
repos := a.initRepositories()
|
||||
processors := a.initProcessors(cfg, repos)
|
||||
services := a.initServices(processors, cfg)
|
||||
validators := a.initValidators()
|
||||
middleware := a.initMiddleware(services)
|
||||
healthHandler := handler.NewHealthHandler()
|
||||
|
||||
a.router = router.NewRouter(
|
||||
cfg,
|
||||
healthHandler,
|
||||
services.authService,
|
||||
middleware.authMiddleware,
|
||||
services.userService,
|
||||
validators.userValidator,
|
||||
services.organizationService,
|
||||
validators.organizationValidator,
|
||||
services.outletService,
|
||||
validators.outletValidator,
|
||||
services.outletSettingService,
|
||||
services.categoryService,
|
||||
validators.categoryValidator,
|
||||
services.productService,
|
||||
validators.productValidator,
|
||||
services.productVariantService,
|
||||
validators.productVariantValidator,
|
||||
services.inventoryService,
|
||||
validators.inventoryValidator,
|
||||
services.orderService,
|
||||
validators.orderValidator,
|
||||
services.fileService,
|
||||
validators.fileValidator,
|
||||
services.customerService,
|
||||
validators.customerValidator,
|
||||
services.paymentMethodService,
|
||||
validators.paymentMethodValidator,
|
||||
services.analyticsService,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) Start(port string) error {
|
||||
engine := a.router.Init()
|
||||
|
||||
a.server = &http.Server{
|
||||
Addr: ":" + port,
|
||||
Handler: engine,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
IdleTimeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
signal.Notify(a.shutdown, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
log.Printf("Server starting on port %s", port)
|
||||
if err := a.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
<-a.shutdown
|
||||
log.Println("Shutting down server...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := a.server.Shutdown(ctx); err != nil {
|
||||
log.Printf("Server forced to shutdown: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("Server exited gracefully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) Shutdown() {
|
||||
close(a.shutdown)
|
||||
}
|
||||
|
||||
type repositories struct {
|
||||
userRepo *repository.UserRepositoryImpl
|
||||
organizationRepo *repository.OrganizationRepositoryImpl
|
||||
outletRepo *repository.OutletRepositoryImpl
|
||||
outletSettingRepo *repository.OutletSettingRepositoryImpl
|
||||
categoryRepo *repository.CategoryRepositoryImpl
|
||||
productRepo *repository.ProductRepositoryImpl
|
||||
productVariantRepo *repository.ProductVariantRepositoryImpl
|
||||
inventoryRepo *repository.InventoryRepositoryImpl
|
||||
orderRepo *repository.OrderRepositoryImpl
|
||||
orderItemRepo *repository.OrderItemRepositoryImpl
|
||||
paymentRepo *repository.PaymentRepositoryImpl
|
||||
paymentMethodRepo *repository.PaymentMethodRepositoryImpl
|
||||
fileRepo *repository.FileRepositoryImpl
|
||||
customerRepo *repository.CustomerRepository
|
||||
analyticsRepo *repository.AnalyticsRepositoryImpl
|
||||
}
|
||||
|
||||
func (a *App) initRepositories() *repositories {
|
||||
return &repositories{
|
||||
userRepo: repository.NewUserRepository(a.db),
|
||||
organizationRepo: repository.NewOrganizationRepositoryImpl(a.db),
|
||||
outletRepo: repository.NewOutletRepositoryImpl(a.db),
|
||||
outletSettingRepo: repository.NewOutletSettingRepositoryImpl(a.db),
|
||||
categoryRepo: repository.NewCategoryRepositoryImpl(a.db),
|
||||
productRepo: repository.NewProductRepositoryImpl(a.db),
|
||||
productVariantRepo: repository.NewProductVariantRepositoryImpl(a.db),
|
||||
inventoryRepo: repository.NewInventoryRepositoryImpl(a.db),
|
||||
orderRepo: repository.NewOrderRepositoryImpl(a.db),
|
||||
orderItemRepo: repository.NewOrderItemRepositoryImpl(a.db),
|
||||
paymentRepo: repository.NewPaymentRepositoryImpl(a.db),
|
||||
paymentMethodRepo: repository.NewPaymentMethodRepositoryImpl(a.db),
|
||||
fileRepo: repository.NewFileRepositoryImpl(a.db),
|
||||
customerRepo: repository.NewCustomerRepository(a.db),
|
||||
analyticsRepo: repository.NewAnalyticsRepositoryImpl(a.db),
|
||||
}
|
||||
}
|
||||
|
||||
type processors struct {
|
||||
userProcessor *processor.UserProcessorImpl
|
||||
organizationProcessor processor.OrganizationProcessor
|
||||
outletProcessor processor.OutletProcessor
|
||||
outletSettingProcessor *processor.OutletSettingProcessorImpl
|
||||
categoryProcessor processor.CategoryProcessor
|
||||
productProcessor processor.ProductProcessor
|
||||
productVariantProcessor processor.ProductVariantProcessor
|
||||
inventoryProcessor processor.InventoryProcessor
|
||||
orderProcessor processor.OrderProcessor
|
||||
paymentMethodProcessor processor.PaymentMethodProcessor
|
||||
fileProcessor processor.FileProcessor
|
||||
customerProcessor *processor.CustomerProcessor
|
||||
analyticsProcessor *processor.AnalyticsProcessorImpl
|
||||
}
|
||||
|
||||
func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors {
|
||||
fileClient := client.NewFileClient(cfg.S3Config)
|
||||
|
||||
return &processors{
|
||||
userProcessor: processor.NewUserProcessor(repos.userRepo, repos.organizationRepo, repos.outletRepo),
|
||||
organizationProcessor: processor.NewOrganizationProcessorImpl(repos.organizationRepo, repos.outletRepo, repos.userRepo),
|
||||
outletProcessor: processor.NewOutletProcessorImpl(repos.outletRepo),
|
||||
outletSettingProcessor: processor.NewOutletSettingProcessorImpl(repos.outletSettingRepo, repos.outletRepo),
|
||||
categoryProcessor: processor.NewCategoryProcessorImpl(repos.categoryRepo),
|
||||
productProcessor: processor.NewProductProcessorImpl(repos.productRepo, repos.categoryRepo, repos.productVariantRepo, repos.inventoryRepo, repos.outletRepo),
|
||||
productVariantProcessor: processor.NewProductVariantProcessorImpl(repos.productVariantRepo, repos.productRepo),
|
||||
inventoryProcessor: processor.NewInventoryProcessorImpl(repos.inventoryRepo, repos.productRepo, repos.outletRepo),
|
||||
orderProcessor: processor.NewOrderProcessorImpl(repos.orderRepo, repos.orderItemRepo, repos.paymentRepo, repos.productRepo, repos.paymentMethodRepo, repos.inventoryRepo, repos.productVariantRepo, repos.outletRepo, repos.customerRepo),
|
||||
paymentMethodProcessor: processor.NewPaymentMethodProcessorImpl(repos.paymentMethodRepo),
|
||||
fileProcessor: processor.NewFileProcessorImpl(repos.fileRepo, fileClient),
|
||||
customerProcessor: processor.NewCustomerProcessor(repos.customerRepo),
|
||||
analyticsProcessor: processor.NewAnalyticsProcessorImpl(repos.analyticsRepo),
|
||||
}
|
||||
}
|
||||
|
||||
type services struct {
|
||||
userService *service.UserServiceImpl
|
||||
authService service.AuthService
|
||||
organizationService service.OrganizationService
|
||||
outletService service.OutletService
|
||||
outletSettingService service.OutletSettingService
|
||||
categoryService service.CategoryService
|
||||
productService service.ProductService
|
||||
productVariantService service.ProductVariantService
|
||||
inventoryService service.InventoryService
|
||||
orderService service.OrderService
|
||||
paymentMethodService service.PaymentMethodService
|
||||
fileService service.FileService
|
||||
customerService service.CustomerService
|
||||
analyticsService *service.AnalyticsServiceImpl
|
||||
}
|
||||
|
||||
func (a *App) initServices(processors *processors, cfg *config.Config) *services {
|
||||
authConfig := cfg.Auth()
|
||||
jwtSecret := authConfig.AccessTokenSecret()
|
||||
authService := service.NewAuthService(processors.userProcessor, jwtSecret)
|
||||
organizationService := service.NewOrganizationService(processors.organizationProcessor)
|
||||
outletService := service.NewOutletService(processors.outletProcessor)
|
||||
outletSettingService := service.NewOutletSettingService(processors.outletSettingProcessor)
|
||||
categoryService := service.NewCategoryService(processors.categoryProcessor)
|
||||
productService := service.NewProductService(processors.productProcessor)
|
||||
productVariantService := service.NewProductVariantService(processors.productVariantProcessor)
|
||||
inventoryService := service.NewInventoryService(processors.inventoryProcessor)
|
||||
orderService := service.NewOrderServiceImpl(processors.orderProcessor)
|
||||
paymentMethodService := service.NewPaymentMethodService(processors.paymentMethodProcessor)
|
||||
fileService := service.NewFileServiceImpl(processors.fileProcessor)
|
||||
var customerService service.CustomerService = service.NewCustomerService(processors.customerProcessor)
|
||||
analyticsService := service.NewAnalyticsServiceImpl(processors.analyticsProcessor)
|
||||
|
||||
return &services{
|
||||
userService: service.NewUserService(processors.userProcessor),
|
||||
authService: authService,
|
||||
organizationService: organizationService,
|
||||
outletService: outletService,
|
||||
outletSettingService: outletSettingService,
|
||||
categoryService: categoryService,
|
||||
productService: productService,
|
||||
productVariantService: productVariantService,
|
||||
inventoryService: inventoryService,
|
||||
orderService: orderService,
|
||||
paymentMethodService: paymentMethodService,
|
||||
fileService: fileService,
|
||||
customerService: customerService,
|
||||
analyticsService: analyticsService,
|
||||
}
|
||||
}
|
||||
|
||||
type middlewares struct {
|
||||
authMiddleware *middleware.AuthMiddleware
|
||||
}
|
||||
|
||||
func (a *App) initMiddleware(services *services) *middlewares {
|
||||
return &middlewares{
|
||||
authMiddleware: middleware.NewAuthMiddleware(services.authService),
|
||||
}
|
||||
}
|
||||
|
||||
type validators struct {
|
||||
userValidator *validator.UserValidatorImpl
|
||||
organizationValidator validator.OrganizationValidator
|
||||
outletValidator validator.OutletValidator
|
||||
categoryValidator validator.CategoryValidator
|
||||
productValidator validator.ProductValidator
|
||||
productVariantValidator validator.ProductVariantValidator
|
||||
inventoryValidator validator.InventoryValidator
|
||||
orderValidator validator.OrderValidator
|
||||
paymentMethodValidator validator.PaymentMethodValidator
|
||||
fileValidator validator.FileValidator
|
||||
customerValidator validator.CustomerValidator
|
||||
}
|
||||
|
||||
func (a *App) initValidators() *validators {
|
||||
return &validators{
|
||||
userValidator: validator.NewUserValidator(),
|
||||
organizationValidator: validator.NewOrganizationValidator(),
|
||||
outletValidator: validator.NewOutletValidator(),
|
||||
categoryValidator: validator.NewCategoryValidator(),
|
||||
productValidator: validator.NewProductValidator(),
|
||||
productVariantValidator: validator.NewProductVariantValidator(),
|
||||
inventoryValidator: validator.NewInventoryValidator(),
|
||||
orderValidator: validator.NewOrderValidator(),
|
||||
paymentMethodValidator: validator.NewPaymentMethodValidator(),
|
||||
fileValidator: validator.NewFileValidatorImpl(),
|
||||
customerValidator: validator.NewCustomerValidator(),
|
||||
}
|
||||
}
|
||||
+3
-31
@@ -1,46 +1,18 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/config"
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gofrs/uuid"
|
||||
|
||||
"enaklo-pos-be/internal/middlewares"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func NewServer(cfg *config.Config) *Server {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
engine := gin.New()
|
||||
engine.RedirectTrailingSlash = true
|
||||
engine.RedirectFixedPath = true
|
||||
|
||||
server := &Server{
|
||||
engine,
|
||||
}
|
||||
|
||||
server.Use(middlewares.Cors())
|
||||
server.Use(middlewares.LogCorsError())
|
||||
server.Use(middlewares.Trace())
|
||||
server.Use(middlewares.Logger(&cfg.FeatureToggle))
|
||||
server.Use(middlewares.RequestMiddleware(&cfg.FeatureToggle))
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
*gin.Engine
|
||||
}
|
||||
|
||||
func (*Server) GenerateUUID() (string, error) {
|
||||
id, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return id.String(), nil
|
||||
func generateServerID() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
func (s Server) Listen(address string) error {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package appcontext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type key string
|
||||
|
||||
const (
|
||||
CorrelationIDKey = key("CorrelationID")
|
||||
OrganizationIDKey = key("OrganizationIDKey")
|
||||
UserIDKey = key("UserID")
|
||||
OutletIDKey = key("OutletID")
|
||||
RoleIDKey = key("RoleID")
|
||||
AppVersionKey = key("AppVersion")
|
||||
AppIDKey = key("AppID")
|
||||
AppTypeKey = key("AppType")
|
||||
PlatformKey = key("platform")
|
||||
DeviceOSKey = key("deviceOS")
|
||||
UserLocaleKey = key("userLocale")
|
||||
UserRoleKey = key("userRole")
|
||||
)
|
||||
|
||||
func LogFields(ctx interface{}) map[string]interface{} {
|
||||
fields := make(map[string]interface{})
|
||||
fields[string(CorrelationIDKey)] = value(ctx, CorrelationIDKey)
|
||||
fields[string(OrganizationIDKey)] = value(ctx, OrganizationIDKey)
|
||||
fields[string(OutletIDKey)] = value(ctx, OutletIDKey)
|
||||
fields[string(AppVersionKey)] = value(ctx, AppVersionKey)
|
||||
fields[string(AppIDKey)] = value(ctx, AppIDKey)
|
||||
fields[string(AppTypeKey)] = value(ctx, AppTypeKey)
|
||||
fields[string(UserIDKey)] = value(ctx, UserIDKey)
|
||||
fields[string(PlatformKey)] = value(ctx, PlatformKey)
|
||||
fields[string(DeviceOSKey)] = value(ctx, DeviceOSKey)
|
||||
fields[string(UserLocaleKey)] = value(ctx, UserLocaleKey)
|
||||
return fields
|
||||
}
|
||||
|
||||
func value(ctx interface{}, key key) string {
|
||||
switch c := ctx.(type) {
|
||||
case *gin.Context:
|
||||
return getFromGinContext(c, key)
|
||||
case context.Context:
|
||||
return getFromGoContext(c, key)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func uuidValue(ctx interface{}, key key) uuid.UUID {
|
||||
switch c := ctx.(type) {
|
||||
case *gin.Context:
|
||||
val, _ := uuid.Parse(getFromGinContext(c, key))
|
||||
return val
|
||||
case context.Context:
|
||||
val, _ := uuid.Parse(getFromGoContext(c, key))
|
||||
return val
|
||||
default:
|
||||
return uuid.New()
|
||||
}
|
||||
}
|
||||
|
||||
func getFromGinContext(c *gin.Context, key key) string {
|
||||
keyStr := string(key)
|
||||
if val, exists := c.Get(keyStr); exists {
|
||||
if str, ok := val.(string); ok {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return getFromGoContext(c.Request.Context(), key)
|
||||
}
|
||||
|
||||
func getFromGoContext(ctx context.Context, key key) string {
|
||||
if val, ok := ctx.Value(key).(string); ok {
|
||||
return val
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package appcontext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type logCtxKeyType struct{}
|
||||
|
||||
var logCtxKey = logCtxKeyType(struct{}{})
|
||||
|
||||
type Logger struct {
|
||||
*logrus.Logger
|
||||
}
|
||||
|
||||
var log *Logger
|
||||
|
||||
type ContextInfo struct {
|
||||
CorrelationID string
|
||||
UserID uuid.UUID
|
||||
OrganizationID uuid.UUID
|
||||
OutletID string
|
||||
AppVersion string
|
||||
AppID string
|
||||
AppType string
|
||||
Platform string
|
||||
DeviceOS string
|
||||
UserLocale string
|
||||
UserRole string
|
||||
}
|
||||
|
||||
type ctxKeyType struct{}
|
||||
|
||||
var ctxKey = ctxKeyType(struct{}{})
|
||||
|
||||
func NewAppContext(ctx context.Context, info *ContextInfo) context.Context {
|
||||
ctx = NewContext(ctx, map[string]interface{}{
|
||||
"correlation_id": info.CorrelationID,
|
||||
"user_id": info.UserID,
|
||||
"app_version": info.AppVersion,
|
||||
"app_id": info.AppID,
|
||||
"app_type": info.AppType,
|
||||
"platform": info.Platform,
|
||||
"device_os": info.DeviceOS,
|
||||
"user_locale": info.UserLocale,
|
||||
})
|
||||
return context.WithValue(ctx, ctxKey, info)
|
||||
}
|
||||
|
||||
func NewContext(ctx context.Context, baseFields map[string]interface{}) context.Context {
|
||||
entry, ok := ctx.Value(logCtxKey).(*logrus.Entry)
|
||||
if !ok {
|
||||
entry = log.WithFields(map[string]interface{}{})
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, logCtxKey, entry.WithFields(baseFields))
|
||||
}
|
||||
|
||||
func FromGinContext(ctx context.Context) *ContextInfo {
|
||||
return &ContextInfo{
|
||||
CorrelationID: value(ctx, CorrelationIDKey),
|
||||
UserID: uuidValue(ctx, UserIDKey),
|
||||
OutletID: value(ctx, OutletIDKey),
|
||||
OrganizationID: uuidValue(ctx, OrganizationIDKey),
|
||||
AppVersion: value(ctx, AppVersionKey),
|
||||
AppID: value(ctx, AppIDKey),
|
||||
AppType: value(ctx, AppTypeKey),
|
||||
Platform: value(ctx, PlatformKey),
|
||||
DeviceOS: value(ctx, DeviceOSKey),
|
||||
UserLocale: value(ctx, UserLocaleKey),
|
||||
UserRole: value(ctx, UserRoleKey),
|
||||
}
|
||||
}
|
||||
|
||||
func FromContext(ctx context.Context) *ContextInfo {
|
||||
if info, ok := ctx.Value(ctxKey).(*ContextInfo); ok {
|
||||
return info
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package oss
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
)
|
||||
|
||||
type OSSConfig interface {
|
||||
type FileConfig interface {
|
||||
GetAccessKeyID() string
|
||||
GetAccessKeySecret() string
|
||||
GetEndpoint() string
|
||||
@@ -22,17 +22,17 @@ type OSSConfig interface {
|
||||
const _awsRegion = "us-east-1"
|
||||
const _s3ACL = "public-read"
|
||||
|
||||
type OssRepositoryImpl struct {
|
||||
type S3FileClientImpl struct {
|
||||
s3 *s3.S3
|
||||
cfg OSSConfig
|
||||
cfg FileConfig
|
||||
}
|
||||
|
||||
func NewOssRepositoryImpl(ossCfg OSSConfig) *OssRepositoryImpl {
|
||||
func NewFileClient(fileCfg FileConfig) *S3FileClientImpl {
|
||||
sess, err := session.NewSession(&aws.Config{
|
||||
S3ForcePathStyle: aws.Bool(true),
|
||||
Endpoint: aws.String(ossCfg.GetEndpoint()),
|
||||
Endpoint: aws.String(fileCfg.GetEndpoint()),
|
||||
Region: aws.String(_awsRegion),
|
||||
Credentials: credentials.NewStaticCredentials(ossCfg.GetAccessKeyID(), ossCfg.GetAccessKeySecret(), ""),
|
||||
Credentials: credentials.NewStaticCredentials(fileCfg.GetAccessKeyID(), fileCfg.GetAccessKeySecret(), ""),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -40,13 +40,13 @@ func NewOssRepositoryImpl(ossCfg OSSConfig) *OssRepositoryImpl {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &OssRepositoryImpl{
|
||||
return &S3FileClientImpl{
|
||||
s3: s3.New(sess),
|
||||
cfg: ossCfg,
|
||||
cfg: fileCfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *OssRepositoryImpl) UploadFile(ctx context.Context, fileName string, fileContent []byte) (fileUrl string, err error) {
|
||||
func (r *S3FileClientImpl) UploadFile(ctx context.Context, fileName string, fileContent []byte) (fileUrl string, err error) {
|
||||
reader := bytes.NewReader(fileContent)
|
||||
|
||||
_, err = r.s3.PutObject(&s3.PutObjectInput{
|
||||
@@ -59,7 +59,7 @@ func (r *OssRepositoryImpl) UploadFile(ctx context.Context, fileName string, fil
|
||||
return r.GetPublicURL(fileName), err
|
||||
}
|
||||
|
||||
func (r *OssRepositoryImpl) GetPublicURL(fileName string) string {
|
||||
func (r *S3FileClientImpl) GetPublicURL(fileName string) string {
|
||||
if fileName == "" {
|
||||
return ""
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
package database
|
||||
|
||||
type Config interface {
|
||||
ConnString() string
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package errors
|
||||
|
||||
import "net/http"
|
||||
|
||||
const (
|
||||
Success Code = "20000"
|
||||
ServerError Code = "50000"
|
||||
BadRequest Code = "40000"
|
||||
InvalidRequest Code = "40001"
|
||||
Unauthorized Code = "40100"
|
||||
CheckinInvalid Code = "40002"
|
||||
Forbidden Code = "40300"
|
||||
Timeout Code = "50400"
|
||||
)
|
||||
|
||||
type Code string
|
||||
|
||||
var (
|
||||
codeMap = map[Code]string{
|
||||
Success: "Success",
|
||||
BadRequest: "Bad or invalid request",
|
||||
Unauthorized: "Unauthorized Token",
|
||||
Timeout: "Gateway Timeout",
|
||||
ServerError: "Internal Server Error",
|
||||
Forbidden: "Forbidden",
|
||||
InvalidRequest: "Invalid Request",
|
||||
CheckinInvalid: "Ticket Already Used or Expired",
|
||||
}
|
||||
|
||||
codeHTTPMap = map[Code]int{
|
||||
Success: http.StatusOK,
|
||||
BadRequest: http.StatusBadRequest,
|
||||
Unauthorized: http.StatusUnauthorized,
|
||||
Timeout: http.StatusGatewayTimeout,
|
||||
ServerError: http.StatusInternalServerError,
|
||||
Forbidden: http.StatusForbidden,
|
||||
InvalidRequest: http.StatusUnprocessableEntity,
|
||||
CheckinInvalid: http.StatusBadRequest,
|
||||
}
|
||||
)
|
||||
|
||||
func (c Code) GetMessage() string {
|
||||
return codeMap[c]
|
||||
}
|
||||
|
||||
func (c Code) GetHTTPCode() int {
|
||||
return codeHTTPMap[c]
|
||||
}
|
||||
|
||||
func (c Code) GetCode() string {
|
||||
return string(c)
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package errors
|
||||
|
||||
import "net/http"
|
||||
|
||||
type ErrType string
|
||||
|
||||
const (
|
||||
errRequestTimeOut ErrType = "Request Timeout to 3rd Party"
|
||||
errConnectTimeOut ErrType = "Connect Timeout to 3rd Party"
|
||||
errFailedExternalCall ErrType = "Failed response from 3rd Party call"
|
||||
errExternalCall ErrType = "error on 3rd Party call"
|
||||
errInvalidRequest ErrType = "Invalid Request"
|
||||
errBadRequest ErrType = "Bad Request"
|
||||
errOrderNotFound ErrType = "Astria order is not found"
|
||||
errCheckoutIDNotDefined ErrType = "Checkout client id not found"
|
||||
errInternalServer ErrType = "Internal Server error"
|
||||
errExternalServer ErrType = "External Server error"
|
||||
errUserIsNotFound ErrType = "User is not found"
|
||||
errInvalidLogin ErrType = "User email or password is invalid"
|
||||
errUnauthorized ErrType = "Unauthorized"
|
||||
errInsufficientBalance ErrType = "Insufficient Balance"
|
||||
errInactivePartner ErrType = "Partner's license is invalid or has expired. Please contact Admin Support."
|
||||
errTicketAlreadyUsed ErrType = "Ticket Already Used."
|
||||
errProductIsRequired ErrType = "Product"
|
||||
errEmailAndPhoneNumberRequired ErrType = "Email or Phone is required"
|
||||
errEmailAlreadyRegistered ErrType = "Email is already registered"
|
||||
errPhoneNumberAlreadyRegistered ErrType = "Phone is already registered"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrorBadRequest = NewServiceException(errBadRequest)
|
||||
ErrorInvalidRequest = NewServiceException(errInvalidRequest)
|
||||
ErrorExternalRequest = NewServiceException(errExternalServer)
|
||||
ErrorUnauthorized = NewServiceException(errUnauthorized)
|
||||
ErrorOrderNotFound = NewServiceException(errOrderNotFound)
|
||||
ErrorClientIDNotDefined = NewServiceException(errCheckoutIDNotDefined)
|
||||
ErrorRequestTimeout = NewServiceException(errRequestTimeOut)
|
||||
ErrorExternalCall = NewServiceException(errExternalCall)
|
||||
ErrorFailedExternalCall = NewServiceException(errFailedExternalCall)
|
||||
ErrorConnectionTimeOut = NewServiceException(errConnectTimeOut)
|
||||
ErrorInternalServer = NewServiceException(errInternalServer)
|
||||
ErrorUserIsNotFound = NewServiceException(errUserIsNotFound)
|
||||
ErrorUserInvalidLogin = NewServiceException(errInvalidLogin)
|
||||
ErrorInsufficientBalance = NewServiceException(errInsufficientBalance)
|
||||
ErrorInvalidLicense = NewServiceException(errInactivePartner)
|
||||
ErrorTicketInvalidOrAlreadyUsed = NewServiceException(errTicketAlreadyUsed)
|
||||
ErrorPhoneNumberEmailIsRequired = NewServiceException(errEmailAndPhoneNumberRequired)
|
||||
ErrorPhoneNumberIsAlreadyRegistered = NewServiceException(errPhoneNumberAlreadyRegistered)
|
||||
ErrorEmailIsAlreadyRegistered = NewServiceException(errEmailAlreadyRegistered)
|
||||
)
|
||||
|
||||
type Error interface {
|
||||
ErrorType() ErrType
|
||||
MapErrorsToHTTPCode() int
|
||||
MapErrorsToCode() Code
|
||||
error
|
||||
}
|
||||
|
||||
type ServiceException struct {
|
||||
errorType ErrType
|
||||
message string
|
||||
}
|
||||
|
||||
func NewServiceException(errType ErrType) *ServiceException {
|
||||
return &ServiceException{
|
||||
errorType: errType,
|
||||
message: string(errType),
|
||||
}
|
||||
}
|
||||
|
||||
func NewError(errType ErrType, message string) *ServiceException {
|
||||
return &ServiceException{
|
||||
errorType: errType,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
func NewErrorMessage(errType *ServiceException, message string) *ServiceException {
|
||||
return &ServiceException{
|
||||
errorType: errType.ErrorType(),
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServiceException) ErrorType() ErrType {
|
||||
return s.errorType
|
||||
}
|
||||
|
||||
func (s *ServiceException) Error() string {
|
||||
return s.message
|
||||
}
|
||||
|
||||
func (s *ServiceException) MapErrorsToHTTPCode() int {
|
||||
switch s.ErrorType() {
|
||||
case errBadRequest:
|
||||
return http.StatusBadRequest
|
||||
|
||||
case errInvalidRequest:
|
||||
return http.StatusBadRequest
|
||||
|
||||
case errInvalidLogin:
|
||||
return http.StatusBadRequest
|
||||
|
||||
case errUserIsNotFound:
|
||||
return http.StatusBadRequest
|
||||
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServiceException) MapErrorsToCode() Code {
|
||||
switch s.ErrorType() {
|
||||
|
||||
case errUnauthorized:
|
||||
return Unauthorized
|
||||
|
||||
case errConnectTimeOut:
|
||||
return Timeout
|
||||
|
||||
case errBadRequest:
|
||||
return BadRequest
|
||||
|
||||
case errUserIsNotFound:
|
||||
return BadRequest
|
||||
|
||||
case errInvalidLogin:
|
||||
return BadRequest
|
||||
|
||||
case errInsufficientBalance:
|
||||
return BadRequest
|
||||
|
||||
case errInactivePartner:
|
||||
return BadRequest
|
||||
|
||||
case errTicketAlreadyUsed:
|
||||
return CheckinInvalid
|
||||
|
||||
default:
|
||||
return BadRequest
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"enaklo-pos-be/internal/common/logger"
|
||||
)
|
||||
|
||||
type HttpClient struct {
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
func NewHttpClient() *HttpClient {
|
||||
return &HttpClient{
|
||||
Client: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *HttpClient) Do(ctx context.Context, req *http.Request) (int, []byte, error) {
|
||||
start := time.Now()
|
||||
logger.ContextLogger(ctx).Info(fmt.Sprintf("Sending request: %v %v", req.Method, req.URL))
|
||||
resp, err := c.Client.Do(req)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error(" Failed to send request:", zap.Error(err))
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
logger.ContextLogger(ctx).Info(fmt.Sprintf("Received Response: : %v", resp.StatusCode))
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error(" Failed to read response:", zap.Error(err))
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
logger.ContextLogger(ctx).Info(fmt.Sprintf("Latency : %v", time.Since(start)))
|
||||
|
||||
return resp.StatusCode, body, nil
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"enaklo-pos-be/internal/constants"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var mainLogger *zap.Logger = nil
|
||||
|
||||
var mainLoggerInit sync.Once
|
||||
|
||||
func NewMainLoggerSingleton() *zap.Logger {
|
||||
mainLoggerInit.Do(func() {
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
logger.Error("logger initialization failed", zap.Any("error", err))
|
||||
panic(fmt.Sprintf("logger initialization failed %v", err))
|
||||
}
|
||||
logger.Info("logger started")
|
||||
mainLogger = logger
|
||||
})
|
||||
|
||||
return mainLogger
|
||||
}
|
||||
|
||||
func NewMainNoOpLoggerSingleton() *zap.Logger {
|
||||
mainLoggerInit.Do(func() {
|
||||
logger := zap.NewNop()
|
||||
logger.Info("logger started")
|
||||
mainLogger = logger
|
||||
})
|
||||
|
||||
return mainLogger
|
||||
}
|
||||
|
||||
func NewNoOp() *zap.Logger {
|
||||
return zap.NewNop()
|
||||
}
|
||||
|
||||
func GetLogger() *zap.Logger {
|
||||
return mainLogger
|
||||
}
|
||||
|
||||
func ContextLogger(ctx context.Context) *zap.Logger {
|
||||
logger := GetLogger()
|
||||
|
||||
if ctxRqID, ok := ctx.Value(constants.ContextRequestID).(string); ok {
|
||||
return logger.With(zap.String(constants.ContextRequestID, ctxRqID))
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package mycontext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"enaklo-pos-be/internal/constants/role"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
)
|
||||
|
||||
type ContextKey string
|
||||
|
||||
type Context interface {
|
||||
context.Context
|
||||
|
||||
RequestedBy() int64
|
||||
IsSuperAdmin() bool
|
||||
IsAdmin() bool
|
||||
IsPartnerAdmin() bool
|
||||
IsCasheer() bool
|
||||
GetPartnerID() *int64
|
||||
GetSiteID() *int64
|
||||
GetName() string
|
||||
}
|
||||
|
||||
type MyContextImpl struct {
|
||||
context.Context
|
||||
|
||||
requestedBy int64
|
||||
requestID string
|
||||
partnerID int64
|
||||
roleID int
|
||||
siteID int64
|
||||
name string
|
||||
}
|
||||
|
||||
func (m *MyContextImpl) RequestedBy() int64 {
|
||||
return m.requestedBy
|
||||
}
|
||||
|
||||
func (m *MyContextImpl) IsSuperAdmin() bool {
|
||||
return m.roleID == int(role.SuperAdmin)
|
||||
}
|
||||
|
||||
func (m *MyContextImpl) IsAdmin() bool {
|
||||
return m.roleID == int(role.SuperAdmin) || m.roleID == int(role.Admin)
|
||||
}
|
||||
|
||||
func (m *MyContextImpl) IsPartnerAdmin() bool {
|
||||
return m.roleID == int(role.PartnerAdmin)
|
||||
}
|
||||
|
||||
func (m *MyContextImpl) IsCasheer() bool {
|
||||
return m.roleID == int(role.Casheer)
|
||||
}
|
||||
|
||||
func (m *MyContextImpl) GetPartnerID() *int64 {
|
||||
if m.partnerID != 0 {
|
||||
return &m.partnerID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MyContextImpl) GetSiteID() *int64 {
|
||||
if m.siteID != 0 {
|
||||
return &m.siteID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MyContextImpl) GetName() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
func NewMyContext(parent context.Context, claims *entity.JWTAuthClaims) (*MyContextImpl, error) {
|
||||
return &MyContextImpl{
|
||||
Context: parent,
|
||||
requestedBy: claims.UserID,
|
||||
partnerID: claims.PartnerID,
|
||||
roleID: claims.Role,
|
||||
siteID: claims.SiteID,
|
||||
name: claims.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewMyContextCustomer(parent context.Context, claims *entity.JWTAuthClaimsCustomer) (*MyContextImpl, error) {
|
||||
return &MyContextImpl{
|
||||
Context: parent,
|
||||
requestedBy: claims.UserID,
|
||||
name: claims.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewContext(parent context.Context) *MyContextImpl {
|
||||
return &MyContextImpl{
|
||||
Context: parent,
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
ReqInfoKey reqInfoKeyType = "request-info"
|
||||
)
|
||||
|
||||
func SetTraceId(c *gin.Context, traceId string) {
|
||||
info, exists := c.Get(ReqInfoKey)
|
||||
if exists {
|
||||
parsedInfo := info.(RequestInfo)
|
||||
parsedInfo.TraceId = traceId
|
||||
|
||||
c.Set(ReqInfoKey, parsedInfo)
|
||||
|
||||
return
|
||||
}
|
||||
c.Set(ReqInfoKey, RequestInfo{TraceId: traceId})
|
||||
}
|
||||
|
||||
func SetUserId(c *gin.Context, userId int64) {
|
||||
info, exists := c.Get(ReqInfoKey)
|
||||
if exists {
|
||||
parsedInfo := info.(RequestInfo)
|
||||
parsedInfo.UserId = userId
|
||||
|
||||
c.Set(ReqInfoKey, parsedInfo)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
c.Set(ReqInfoKey, RequestInfo{UserId: userId})
|
||||
}
|
||||
|
||||
func SetUserContext(c *gin.Context, payload map[string]interface{}) {
|
||||
c.Set(ReqInfoKey, RequestInfo{
|
||||
UserId: int64(payload["userId"].(float64)),
|
||||
Role: payload["role"].(string),
|
||||
})
|
||||
}
|
||||
|
||||
func ContextWithReqInfo(c *gin.Context) context.Context {
|
||||
info, ok := c.Get(ReqInfoKey)
|
||||
if ok {
|
||||
return WithRequestInfo(c, info.(RequestInfo))
|
||||
}
|
||||
|
||||
return WithRequestInfo(c, RequestInfo{})
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package request
|
||||
|
||||
import "context"
|
||||
|
||||
type requestInfoKey int
|
||||
|
||||
const (
|
||||
key requestInfoKey = iota
|
||||
)
|
||||
|
||||
type RequestInfo struct {
|
||||
UserId int64
|
||||
TraceId string
|
||||
Permissions map[string]bool
|
||||
Role string
|
||||
}
|
||||
|
||||
func WithRequestInfo(ctx context.Context, info RequestInfo) context.Context {
|
||||
return context.WithValue(ctx, key, info)
|
||||
}
|
||||
|
||||
func GetRequestInfo(ctx context.Context) (requestInfo RequestInfo, ok bool) {
|
||||
requestInfo, ok = ctx.Value(key).(RequestInfo)
|
||||
return
|
||||
}
|
||||
|
||||
type reqInfoKeyType = string
|
||||
|
||||
const (
|
||||
reqInfoKey reqInfoKeyType = "request-info"
|
||||
)
|
||||
|
||||
func GetReqInfo(c context.Context) RequestInfo {
|
||||
info := c.Value(reqInfoKey)
|
||||
if info != nil {
|
||||
return info.(RequestInfo)
|
||||
}
|
||||
|
||||
return RequestInfo{}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
@@ -1,12 +0,0 @@
|
||||
package branch
|
||||
|
||||
type BranchStatus string
|
||||
|
||||
const (
|
||||
Active BranchStatus = "Active"
|
||||
Inactive BranchStatus = "Inactive"
|
||||
)
|
||||
|
||||
func (b BranchStatus) toString() string {
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package constants
|
||||
|
||||
type BusinessType string
|
||||
|
||||
const (
|
||||
BusinessTypeRestaurant BusinessType = "restaurant"
|
||||
BusinessTypeRetail BusinessType = "retail"
|
||||
BusinessTypeCafe BusinessType = "cafe"
|
||||
BusinessTypeBar BusinessType = "bar"
|
||||
)
|
||||
|
||||
type Currency string
|
||||
|
||||
const (
|
||||
CurrencyUSD Currency = "USD"
|
||||
CurrencyEUR Currency = "EUR"
|
||||
CurrencyGBP Currency = "GBP"
|
||||
CurrencyIDR Currency = "IDR"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultBusinessType = BusinessTypeRestaurant
|
||||
DefaultCurrency = CurrencyUSD
|
||||
DefaultTaxRate = 0.0
|
||||
MaxNameLength = 255
|
||||
MaxDescriptionLength = 1000
|
||||
)
|
||||
|
||||
func GetAllBusinessTypes() []BusinessType {
|
||||
return []BusinessType{
|
||||
BusinessTypeRestaurant,
|
||||
BusinessTypeRetail,
|
||||
BusinessTypeCafe,
|
||||
BusinessTypeBar,
|
||||
}
|
||||
}
|
||||
|
||||
func GetAllCurrencies() []Currency {
|
||||
return []Currency{
|
||||
CurrencyUSD,
|
||||
CurrencyEUR,
|
||||
CurrencyGBP,
|
||||
CurrencyIDR,
|
||||
}
|
||||
}
|
||||
|
||||
func IsValidBusinessType(businessType BusinessType) bool {
|
||||
for _, validType := range GetAllBusinessTypes() {
|
||||
if businessType == validType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsValidCurrency(currency Currency) bool {
|
||||
for _, validCurrency := range GetAllCurrencies() {
|
||||
if currency == validCurrency {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
RequestMethod = "RequestMethod"
|
||||
RequestPath = "RequestPath"
|
||||
RequestURLQueryParam = "RequestURLQueryParam"
|
||||
ResponseStatusCode = "ResponseStatusCode"
|
||||
ResponseStatusText = "ResponseStatusText"
|
||||
ResponseTimeTaken = "ResponseTimeTaken"
|
||||
)
|
||||
|
||||
var ValidCountryCodeMap = map[string]bool{
|
||||
"ID": true,
|
||||
"VI": true,
|
||||
"SG": true,
|
||||
"TH": true,
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ContextRequestID string = "requestId"
|
||||
)
|
||||
|
||||
type UserType string
|
||||
|
||||
func (u UserType) toString() string {
|
||||
return string(u)
|
||||
}
|
||||
|
||||
const (
|
||||
StatusPending = "PENDING"
|
||||
StatusPaid = "PAID"
|
||||
StatusCanceled = "CANCELED"
|
||||
StatusExpired = "EXPIRED"
|
||||
StatusExecuted = "EXECUTED"
|
||||
)
|
||||
|
||||
const (
|
||||
PaymentCash = "CASH"
|
||||
PaymentCreditCard = "CREDIT_CARD"
|
||||
PaymentDebitCard = "DEBIT_CARD"
|
||||
PaymentEWallet = "E_WALLET"
|
||||
)
|
||||
|
||||
const (
|
||||
SourcePOS = "POS"
|
||||
SourceMobile = "MOBILE"
|
||||
SourceWeb = "WEB"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultInquiryExpiryDuration = 30 * time.Minute
|
||||
)
|
||||
|
||||
func GenerateUUID() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
func GenerateRefID() string {
|
||||
now := time.Now()
|
||||
return now.Format("20060102") + "-" + uuid.New().String()[:8]
|
||||
}
|
||||
|
||||
var TimeNow = func() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
type RegistrationStatus string
|
||||
|
||||
const (
|
||||
RegistrationSuccess RegistrationStatus = "SUCCESS"
|
||||
RegistrationPending RegistrationStatus = "PENDING"
|
||||
RegistrationFailed RegistrationStatus = "FAILED"
|
||||
)
|
||||
|
||||
func (u RegistrationStatus) String() string {
|
||||
return string(u)
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package device
|
||||
|
||||
type DeviceStatus string
|
||||
|
||||
const (
|
||||
On DeviceStatus = "On"
|
||||
Off DeviceStatus = "Off"
|
||||
)
|
||||
|
||||
func (b DeviceStatus) toString() string {
|
||||
return string(b)
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package device
|
||||
|
||||
type DeviceConnectionStatus string
|
||||
|
||||
const (
|
||||
Connected DeviceConnectionStatus = "Connected"
|
||||
Disconnected DeviceConnectionStatus = "Disconnected"
|
||||
)
|
||||
|
||||
func (b DeviceConnectionStatus) toString() string {
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
InternalServerErrorCode = "900"
|
||||
MissingFieldErrorCode = "303"
|
||||
MalformedFieldErrorCode = "310"
|
||||
ValidationErrorCode = "304"
|
||||
InvalidFieldErrorCode = "305"
|
||||
)
|
||||
|
||||
const (
|
||||
RequestEntity = "request"
|
||||
UserServiceEntity = "user_service"
|
||||
OrganizationServiceEntity = "organization_service"
|
||||
CategoryServiceEntity = "category_service"
|
||||
ProductServiceEntity = "product_service"
|
||||
ProductVariantServiceEntity = "product_variant_service"
|
||||
InventoryServiceEntity = "inventory_service"
|
||||
OrderServiceEntity = "order_service"
|
||||
CustomerServiceEntity = "customer_service"
|
||||
UserValidatorEntity = "user_validator"
|
||||
AuthHandlerEntity = "auth_handler"
|
||||
UserHandlerEntity = "user_handler"
|
||||
CategoryHandlerEntity = "category_handler"
|
||||
ProductHandlerEntity = "product_handler"
|
||||
ProductVariantHandlerEntity = "product_variant_handler"
|
||||
InventoryHandlerEntity = "inventory_handler"
|
||||
OrderValidatorEntity = "order_validator"
|
||||
OrderHandlerEntity = "order_handler"
|
||||
OrganizationValidatorEntity = "organization_validator"
|
||||
OrgHandlerEntity = "organization_handler"
|
||||
PaymentMethodValidatorEntity = "payment_method_validator"
|
||||
PaymentMethodHandlerEntity = "payment_method_handler"
|
||||
OutletServiceEntity = "outlet_service"
|
||||
)
|
||||
|
||||
var HttpErrorMap = map[string]int{
|
||||
InternalServerErrorCode: http.StatusInternalServerError,
|
||||
MissingFieldErrorCode: http.StatusBadRequest,
|
||||
MalformedFieldErrorCode: http.StatusBadRequest,
|
||||
ValidationErrorCode: http.StatusBadRequest,
|
||||
InvalidFieldErrorCode: http.StatusBadRequest,
|
||||
}
|
||||
|
||||
// Error messages
|
||||
var (
|
||||
ErrPaymentMethodNameRequired = fmt.Errorf("payment method name is required")
|
||||
ErrPaymentMethodTypeRequired = fmt.Errorf("payment method type is required")
|
||||
ErrInvalidPaymentMethodType = fmt.Errorf("invalid payment method type")
|
||||
ErrInvalidPageNumber = fmt.Errorf("page number must be greater than 0")
|
||||
ErrInvalidLimit = fmt.Errorf("limit must be between 1 and 100")
|
||||
)
|
||||
@@ -0,0 +1,80 @@
|
||||
package constants
|
||||
|
||||
type FileType string
|
||||
|
||||
const (
|
||||
FileTypeImage FileType = "image"
|
||||
FileTypeDocument FileType = "document"
|
||||
FileTypeVideo FileType = "video"
|
||||
FileTypeAudio FileType = "audio"
|
||||
FileTypeArchive FileType = "archive"
|
||||
FileTypeOther FileType = "other"
|
||||
)
|
||||
|
||||
func GetAllFileTypes() []FileType {
|
||||
return []FileType{
|
||||
FileTypeImage,
|
||||
FileTypeDocument,
|
||||
FileTypeVideo,
|
||||
FileTypeAudio,
|
||||
FileTypeArchive,
|
||||
FileTypeOther,
|
||||
}
|
||||
}
|
||||
|
||||
func IsValidFileType(fileType FileType) bool {
|
||||
for _, validType := range GetAllFileTypes() {
|
||||
if fileType == validType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MIME type mappings
|
||||
var MimeTypeToFileType = map[string]FileType{
|
||||
// Images
|
||||
"image/jpeg": FileTypeImage,
|
||||
"image/jpg": FileTypeImage,
|
||||
"image/png": FileTypeImage,
|
||||
"image/gif": FileTypeImage,
|
||||
"image/webp": FileTypeImage,
|
||||
"image/svg+xml": FileTypeImage,
|
||||
|
||||
// Documents
|
||||
"application/pdf": FileTypeDocument,
|
||||
"application/msword": FileTypeDocument,
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": FileTypeDocument,
|
||||
"application/vnd.ms-excel": FileTypeDocument,
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": FileTypeDocument,
|
||||
"text/plain": FileTypeDocument,
|
||||
"text/csv": FileTypeDocument,
|
||||
|
||||
// Videos
|
||||
"video/mp4": FileTypeVideo,
|
||||
"video/avi": FileTypeVideo,
|
||||
"video/mov": FileTypeVideo,
|
||||
"video/wmv": FileTypeVideo,
|
||||
"video/flv": FileTypeVideo,
|
||||
"video/webm": FileTypeVideo,
|
||||
|
||||
// Audio
|
||||
"audio/mpeg": FileTypeAudio,
|
||||
"audio/mp3": FileTypeAudio,
|
||||
"audio/wav": FileTypeAudio,
|
||||
"audio/ogg": FileTypeAudio,
|
||||
"audio/aac": FileTypeAudio,
|
||||
|
||||
// Archives
|
||||
"application/zip": FileTypeArchive,
|
||||
"application/x-rar-compressed": FileTypeArchive,
|
||||
"application/x-7z-compressed": FileTypeArchive,
|
||||
"application/gzip": FileTypeArchive,
|
||||
}
|
||||
|
||||
func GetFileTypeFromMimeType(mimeType string) FileType {
|
||||
if fileType, exists := MimeTypeToFileType[mimeType]; exists {
|
||||
return fileType
|
||||
}
|
||||
return FileTypeOther
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
CorrelationIDHeader = "debug-id"
|
||||
XAppVersionHeader = "x-appversion"
|
||||
XDeviceOSHeader = "X-DeviceOS"
|
||||
XPlatformHeader = "X-Platform"
|
||||
XAppTypeHeader = "X-AppType"
|
||||
XAppIDHeader = "x-appid"
|
||||
XPhoneModelHeader = "X-PhoneModel"
|
||||
OrganizationID = "x_organization_id"
|
||||
OutletID = "x_owner_id"
|
||||
CountryCodeHeader = "country-code"
|
||||
AcceptedLanguageHeader = "accept-language"
|
||||
XUserLocaleHeader = "x-user-locale"
|
||||
LocaleHeader = "locale"
|
||||
GojekTimezoneHeader = "Gojek-Timezone"
|
||||
UserTypeHeader = "User-Type"
|
||||
AccountIDHeader = "Account-Id"
|
||||
GopayUserType = "gopay"
|
||||
XCorrelationIDHeader = "X-Correlation-Id"
|
||||
XRequestIDHeader = "X-Request-Id"
|
||||
XCountryCodeHeader = "X-Country-Code"
|
||||
XAppVersionHeaderPOP = "X-App-Version"
|
||||
XOwnerIDHeader = "X-Owner-Id"
|
||||
XAppIDHeaderPOP = "X-App-Id"
|
||||
)
|
||||
@@ -0,0 +1,87 @@
|
||||
package constants
|
||||
|
||||
type OrderType string
|
||||
|
||||
const (
|
||||
OrderTypeDineIn OrderType = "dine_in"
|
||||
OrderTypeTakeout OrderType = "takeout"
|
||||
OrderTypeDelivery OrderType = "delivery"
|
||||
)
|
||||
|
||||
type OrderStatus string
|
||||
|
||||
const (
|
||||
OrderStatusPending OrderStatus = "pending"
|
||||
OrderStatusPreparing OrderStatus = "preparing"
|
||||
OrderStatusReady OrderStatus = "ready"
|
||||
OrderStatusCompleted OrderStatus = "completed"
|
||||
OrderStatusCancelled OrderStatus = "cancelled"
|
||||
OrderStatusPaid OrderStatus = "paid"
|
||||
)
|
||||
|
||||
type OrderItemStatus string
|
||||
|
||||
const (
|
||||
OrderItemStatusPending OrderItemStatus = "pending"
|
||||
OrderItemStatusPreparing OrderItemStatus = "preparing"
|
||||
OrderItemStatusReady OrderItemStatus = "ready"
|
||||
OrderItemStatusServed OrderItemStatus = "served"
|
||||
OrderItemStatusCancelled OrderItemStatus = "cancelled"
|
||||
OrderItemStatusCompleted OrderItemStatus = "completed"
|
||||
)
|
||||
|
||||
func GetAllOrderTypes() []OrderType {
|
||||
return []OrderType{
|
||||
OrderTypeDineIn,
|
||||
OrderTypeTakeout,
|
||||
OrderTypeDelivery,
|
||||
}
|
||||
}
|
||||
|
||||
func GetAllOrderStatuses() []OrderStatus {
|
||||
return []OrderStatus{
|
||||
OrderStatusPending,
|
||||
OrderStatusPreparing,
|
||||
OrderStatusReady,
|
||||
OrderStatusCompleted,
|
||||
OrderStatusCancelled,
|
||||
OrderStatusPaid,
|
||||
}
|
||||
}
|
||||
|
||||
func GetAllOrderItemStatuses() []OrderItemStatus {
|
||||
return []OrderItemStatus{
|
||||
OrderItemStatusPending,
|
||||
OrderItemStatusPreparing,
|
||||
OrderItemStatusReady,
|
||||
OrderItemStatusServed,
|
||||
OrderItemStatusCancelled,
|
||||
}
|
||||
}
|
||||
|
||||
func (o OrderType) IsValidOrderType() bool {
|
||||
for _, validType := range GetAllOrderTypes() {
|
||||
if o == validType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsValidOrderStatus(status OrderStatus) bool {
|
||||
for _, validStatus := range GetAllOrderStatuses() {
|
||||
if status == validStatus {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsValidOrderItemStatus(status OrderItemStatus) bool {
|
||||
for _, validStatus := range GetAllOrderItemStatuses() {
|
||||
if status == validStatus {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package order
|
||||
|
||||
type OrderStatus string
|
||||
|
||||
const (
|
||||
New OrderStatus = "NEW"
|
||||
Paid OrderStatus = "PAID"
|
||||
Cancel OrderStatus = "CANCEL"
|
||||
Pending OrderStatus = "PENDING"
|
||||
Refunded OrderStatus = "REFUNDED"
|
||||
Voided OrderStatus = "VOIDED"
|
||||
Partial OrderStatus = "PARTIAL"
|
||||
)
|
||||
|
||||
func (b OrderStatus) toString() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (i *OrderStatus) IsNew() bool {
|
||||
if i == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if *i == New {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (i OrderStatus) String() string {
|
||||
return string(i)
|
||||
}
|
||||
|
||||
type ItemType string
|
||||
|
||||
const (
|
||||
Product ItemType = "PRODUCT"
|
||||
Studio ItemType = "STUDIO"
|
||||
)
|
||||
|
||||
func (b ItemType) toString() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (i *ItemType) IsProduct() bool {
|
||||
if i == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if *i == Product {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (i *ItemType) IsStudio() bool {
|
||||
if i == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if *i == Studio {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type OrderSearchStatus string
|
||||
|
||||
const (
|
||||
Active OrderSearchStatus = "ACTIVE"
|
||||
Inactive OrderSearchStatus = "INACTIVE"
|
||||
)
|
||||
|
||||
func (i *OrderSearchStatus) IsActive() bool {
|
||||
if i == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if *i == Active {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package constants
|
||||
|
||||
type PlanType string
|
||||
|
||||
const (
|
||||
PlanBasic PlanType = "basic"
|
||||
PlanPremium PlanType = "premium"
|
||||
PlanEnterprise PlanType = "enterprise"
|
||||
)
|
||||
|
||||
func GetAllPlanTypes() []PlanType {
|
||||
return []PlanType{
|
||||
PlanBasic,
|
||||
PlanPremium,
|
||||
PlanEnterprise,
|
||||
}
|
||||
}
|
||||
|
||||
func IsValidPlanType(planType PlanType) bool {
|
||||
for _, validType := range GetAllPlanTypes() {
|
||||
if planType == validType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
OssLogLevelLogOff = "LogOff"
|
||||
OssLogLevelDebug = "Debug"
|
||||
OssLogLevelError = "Error"
|
||||
OssLogLevelWarn = "Warn"
|
||||
OssLogLevelInfo = "Info"
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
package constants
|
||||
|
||||
// Outlet printer setting keys
|
||||
const (
|
||||
PRINTER_OUTLET_NAME = "printer_outlet_name"
|
||||
PRINTER_ADDRESS = "printer_address"
|
||||
PRINTER_PHONE_NUMBER = "printer_phone_number"
|
||||
PRINTER_PAPER_SIZE = "printer_paper_size"
|
||||
PRINTER_FOOTER = "printer_footer"
|
||||
PRINTER_FOOTER_HASHTAG = "printer_footer_hashtag"
|
||||
)
|
||||
|
||||
// Default printer settings
|
||||
const (
|
||||
DEFAULT_PAPER_SIZE = "80mm"
|
||||
DEFAULT_FOOTER = "Thank you for your purchase!"
|
||||
DEFAULT_FOOTER_HASHTAG = "#ThankYou"
|
||||
)
|
||||
|
||||
// Valid paper sizes
|
||||
var ValidPaperSizes = []string{
|
||||
"58mm",
|
||||
"80mm",
|
||||
"A4",
|
||||
"A5",
|
||||
"Letter",
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package constants
|
||||
|
||||
type PaymentMethodType string
|
||||
|
||||
const (
|
||||
PaymentMethodTypeCash PaymentMethodType = "cash"
|
||||
PaymentMethodTypeCard PaymentMethodType = "card"
|
||||
PaymentMethodTypeDigitalWallet PaymentMethodType = "digital_wallet"
|
||||
PaymentMethodTypeQR PaymentMethodType = "qr"
|
||||
PaymentMethodTypeEDC PaymentMethodType = "edc"
|
||||
)
|
||||
|
||||
type PaymentStatus string
|
||||
|
||||
const (
|
||||
PaymentStatusPending PaymentStatus = "pending"
|
||||
PaymentStatusCompleted PaymentStatus = "completed"
|
||||
PaymentStatusFailed PaymentStatus = "failed"
|
||||
PaymentStatusRefunded PaymentStatus = "refunded"
|
||||
)
|
||||
|
||||
type PaymentTransactionStatus string
|
||||
|
||||
const (
|
||||
PaymentTransactionStatusPending PaymentTransactionStatus = "pending"
|
||||
PaymentTransactionStatusCompleted PaymentTransactionStatus = "completed"
|
||||
PaymentTransactionStatusFailed PaymentTransactionStatus = "failed"
|
||||
PaymentTransactionStatusRefunded PaymentTransactionStatus = "refunded"
|
||||
)
|
||||
|
||||
func GetAllPaymentMethodTypes() []PaymentMethodType {
|
||||
return []PaymentMethodType{
|
||||
PaymentMethodTypeCash,
|
||||
PaymentMethodTypeCard,
|
||||
PaymentMethodTypeDigitalWallet,
|
||||
}
|
||||
}
|
||||
|
||||
func GetAllPaymentStatuses() []PaymentStatus {
|
||||
return []PaymentStatus{
|
||||
PaymentStatusPending,
|
||||
PaymentStatusCompleted,
|
||||
PaymentStatusFailed,
|
||||
PaymentStatusRefunded,
|
||||
}
|
||||
}
|
||||
|
||||
func GetAllPaymentTransactionStatuses() []PaymentTransactionStatus {
|
||||
return []PaymentTransactionStatus{
|
||||
PaymentTransactionStatusPending,
|
||||
PaymentTransactionStatusCompleted,
|
||||
PaymentTransactionStatusFailed,
|
||||
PaymentTransactionStatusRefunded,
|
||||
}
|
||||
}
|
||||
|
||||
func IsValidPaymentMethodType(methodType PaymentMethodType) bool {
|
||||
for _, validType := range GetAllPaymentMethodTypes() {
|
||||
if methodType == validType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsValidPaymentStatus(status PaymentStatus) bool {
|
||||
for _, validStatus := range GetAllPaymentStatuses() {
|
||||
if status == validStatus {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsValidPaymentTransactionStatus(status PaymentTransactionStatus) bool {
|
||||
for _, validStatus := range GetAllPaymentTransactionStatuses() {
|
||||
if status == validStatus {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package product
|
||||
|
||||
type ProductStatus string
|
||||
|
||||
const (
|
||||
Active ProductStatus = "Active"
|
||||
Inactive ProductStatus = "Inactive"
|
||||
)
|
||||
|
||||
func (b ProductStatus) toString() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
type ProductType string
|
||||
|
||||
const (
|
||||
Food ProductType = "FOOD"
|
||||
Beverage ProductType = "BEVERAGE"
|
||||
)
|
||||
|
||||
func (b ProductType) toString() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
type ProductStock string
|
||||
|
||||
const (
|
||||
Available ProductStock = "AVAILABLE"
|
||||
Unavailable ProductStock = "UNAVAILABLE"
|
||||
)
|
||||
|
||||
func (b ProductStock) toString() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (i *ProductStock) IsAvailable() bool {
|
||||
if i == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if *i == Available {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (i *ProductStock) IsUnavailable() bool {
|
||||
if i == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if *i == Unavailable {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package role
|
||||
|
||||
type Role int64
|
||||
|
||||
const (
|
||||
SuperAdmin Role = 1
|
||||
Admin Role = 2
|
||||
PartnerAdmin Role = 3
|
||||
SiteAdmin Role = 4
|
||||
Casheer Role = 5
|
||||
Customer Role = 6
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
package studio
|
||||
|
||||
type StudioStatus string
|
||||
|
||||
const (
|
||||
Active StudioStatus = "active"
|
||||
Inactive StudioStatus = "inactive"
|
||||
)
|
||||
|
||||
func (b StudioStatus) toString() string {
|
||||
return string(b)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package transaction
|
||||
|
||||
type PaymentStatus string
|
||||
|
||||
const (
|
||||
New PaymentStatus = "NEW"
|
||||
Paid PaymentStatus = "PAID"
|
||||
Cancel PaymentStatus = "CANCEL"
|
||||
Refund PaymentStatus = "REFUND"
|
||||
)
|
||||
|
||||
func (b PaymentStatus) toString() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
type PaymentMethod string
|
||||
|
||||
const (
|
||||
Cash PaymentMethod = "CASH"
|
||||
Debit PaymentMethod = "DEBIT"
|
||||
Transfer PaymentMethod = "TRANSFER"
|
||||
QRIS PaymentMethod = "QRIS"
|
||||
Online PaymentMethod = "ONLINE"
|
||||
VA PaymentMethod = "VA"
|
||||
)
|
||||
|
||||
func (b PaymentMethod) toString() string {
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package constants
|
||||
|
||||
type UserRole string
|
||||
|
||||
const (
|
||||
RoleAdmin UserRole = "admin"
|
||||
RoleManager UserRole = "manager"
|
||||
RoleCashier UserRole = "cashier"
|
||||
RoleWaiter UserRole = "waiter"
|
||||
)
|
||||
|
||||
func GetAllUserRoles() []UserRole {
|
||||
return []UserRole{
|
||||
RoleAdmin,
|
||||
RoleManager,
|
||||
RoleCashier,
|
||||
RoleWaiter,
|
||||
}
|
||||
}
|
||||
|
||||
func IsValidUserRole(role UserRole) bool {
|
||||
for _, validRole := range GetAllUserRoles() {
|
||||
if role == validRole {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package userstatus
|
||||
|
||||
type UserStatus string
|
||||
|
||||
const (
|
||||
Active UserStatus = "Active"
|
||||
Inactive UserStatus = "Inactive"
|
||||
)
|
||||
|
||||
func (u UserStatus) toString() string {
|
||||
return string(u)
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type PaymentMethodAnalyticsRequest struct {
|
||||
OrganizationID uuid.UUID `form:"organization_id"`
|
||||
OutletID *uuid.UUID `form:"outlet_id,omitempty"`
|
||||
DateFrom string `form:"date_from" validate:"required"`
|
||||
DateTo string `form:"date_to" validate:"required"`
|
||||
GroupBy string `form:"group_by,default=day" validate:"omitempty,oneof=day hour week month"`
|
||||
}
|
||||
|
||||
// PaymentMethodAnalyticsResponse represents the response for payment method analytics
|
||||
type PaymentMethodAnalyticsResponse struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||
DateFrom time.Time `json:"date_from"`
|
||||
DateTo time.Time `json:"date_to"`
|
||||
GroupBy string `json:"group_by"`
|
||||
Summary PaymentMethodSummary `json:"summary"`
|
||||
Data []PaymentMethodAnalyticsData `json:"data"`
|
||||
}
|
||||
|
||||
// PaymentMethodSummary represents the summary of payment method analytics
|
||||
type PaymentMethodSummary struct {
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
TotalPayments int64 `json:"total_payments"`
|
||||
AverageOrderValue float64 `json:"average_order_value"`
|
||||
}
|
||||
|
||||
type PaymentMethodAnalyticsData struct {
|
||||
PaymentMethodID uuid.UUID `json:"payment_method_id"`
|
||||
PaymentMethodName string `json:"payment_method_name"`
|
||||
PaymentMethodType string `json:"payment_method_type"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
PaymentCount int64 `json:"payment_count"`
|
||||
Percentage float64 `json:"percentage"`
|
||||
}
|
||||
|
||||
type SalesAnalyticsRequest struct {
|
||||
OrganizationID uuid.UUID
|
||||
OutletID *uuid.UUID `form:"outlet_id,omitempty"`
|
||||
DateFrom string `form:"date_from" validate:"required"`
|
||||
DateTo string `form:"date_to" validate:"required"`
|
||||
GroupBy string `form:"group_by,default=day" validate:"omitempty,oneof=day hour week month"`
|
||||
}
|
||||
|
||||
type SalesAnalyticsResponse struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||
DateFrom time.Time `json:"date_from"`
|
||||
DateTo time.Time `json:"date_to"`
|
||||
GroupBy string `json:"group_by"`
|
||||
Summary SalesSummary `json:"summary"`
|
||||
Data []SalesAnalyticsData `json:"data"`
|
||||
}
|
||||
|
||||
// SalesSummary represents the summary of sales analytics
|
||||
type SalesSummary struct {
|
||||
TotalSales float64 `json:"total_sales"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
TotalItems int64 `json:"total_items"`
|
||||
AverageOrderValue float64 `json:"average_order_value"`
|
||||
TotalTax float64 `json:"total_tax"`
|
||||
TotalDiscount float64 `json:"total_discount"`
|
||||
NetSales float64 `json:"net_sales"`
|
||||
}
|
||||
|
||||
// SalesAnalyticsData represents individual sales analytics data point
|
||||
type SalesAnalyticsData struct {
|
||||
Date time.Time `json:"date"`
|
||||
Sales float64 `json:"sales"`
|
||||
Orders int64 `json:"orders"`
|
||||
Items int64 `json:"items"`
|
||||
Tax float64 `json:"tax"`
|
||||
Discount float64 `json:"discount"`
|
||||
NetSales float64 `json:"net_sales"`
|
||||
}
|
||||
|
||||
// ProductAnalyticsRequest represents the request for product analytics
|
||||
type ProductAnalyticsRequest struct {
|
||||
OrganizationID uuid.UUID
|
||||
OutletID *uuid.UUID `form:"outlet_id,omitempty"`
|
||||
DateFrom string `form:"date_from" validate:"required"`
|
||||
DateTo string `form:"date_to" validate:"required"`
|
||||
Limit int `form:"limit,default=10" validate:"min=1,max=100"`
|
||||
}
|
||||
|
||||
// ProductAnalyticsResponse represents the response for product analytics
|
||||
type ProductAnalyticsResponse struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||
DateFrom time.Time `json:"date_from"`
|
||||
DateTo time.Time `json:"date_to"`
|
||||
Data []ProductAnalyticsData `json:"data"`
|
||||
}
|
||||
|
||||
// ProductAnalyticsData represents individual product analytics data
|
||||
type ProductAnalyticsData struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
QuantitySold int64 `json:"quantity_sold"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
}
|
||||
|
||||
// DashboardAnalyticsRequest represents the request for dashboard analytics
|
||||
type DashboardAnalyticsRequest struct {
|
||||
OrganizationID uuid.UUID
|
||||
OutletID *uuid.UUID `form:"outlet_id,omitempty"`
|
||||
DateFrom string `form:"date_from" validate:"required"`
|
||||
DateTo string `form:"date_to" validate:"required"`
|
||||
}
|
||||
|
||||
// DashboardAnalyticsResponse represents the response for dashboard analytics
|
||||
type DashboardAnalyticsResponse struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||
DateFrom time.Time `json:"date_from"`
|
||||
DateTo time.Time `json:"date_to"`
|
||||
Overview DashboardOverview `json:"overview"`
|
||||
TopProducts []ProductAnalyticsData `json:"top_products"`
|
||||
PaymentMethods []PaymentMethodAnalyticsData `json:"payment_methods"`
|
||||
RecentSales []SalesAnalyticsData `json:"recent_sales"`
|
||||
}
|
||||
|
||||
// DashboardOverview represents the overview data for dashboard
|
||||
type DashboardOverview struct {
|
||||
TotalSales float64 `json:"total_sales"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
AverageOrderValue float64 `json:"average_order_value"`
|
||||
TotalCustomers int64 `json:"total_customers"`
|
||||
VoidedOrders int64 `json:"voided_orders"`
|
||||
RefundedOrders int64 `json:"refunded_orders"`
|
||||
}
|
||||
|
||||
// ProfitLossAnalyticsRequest represents the request for profit and loss analytics
|
||||
type ProfitLossAnalyticsRequest struct {
|
||||
OrganizationID uuid.UUID
|
||||
OutletID *uuid.UUID `form:"outlet_id,omitempty"`
|
||||
DateFrom string `form:"date_from" validate:"required"`
|
||||
DateTo string `form:"date_to" validate:"required"`
|
||||
GroupBy string `form:"group_by,default=day" validate:"omitempty,oneof=day hour week month"`
|
||||
}
|
||||
|
||||
// ProfitLossAnalyticsResponse represents the response for profit and loss analytics
|
||||
type ProfitLossAnalyticsResponse struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||
DateFrom time.Time `json:"date_from"`
|
||||
DateTo time.Time `json:"date_to"`
|
||||
GroupBy string `json:"group_by"`
|
||||
Summary ProfitLossSummary `json:"summary"`
|
||||
Data []ProfitLossData `json:"data"`
|
||||
ProductData []ProductProfitData `json:"product_data"`
|
||||
}
|
||||
|
||||
// ProfitLossSummary represents the summary of profit and loss analytics
|
||||
type ProfitLossSummary struct {
|
||||
TotalRevenue float64 `json:"total_revenue"`
|
||||
TotalCost float64 `json:"total_cost"`
|
||||
GrossProfit float64 `json:"gross_profit"`
|
||||
GrossProfitMargin float64 `json:"gross_profit_margin"`
|
||||
TotalTax float64 `json:"total_tax"`
|
||||
TotalDiscount float64 `json:"total_discount"`
|
||||
NetProfit float64 `json:"net_profit"`
|
||||
NetProfitMargin float64 `json:"net_profit_margin"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
AverageProfit float64 `json:"average_profit"`
|
||||
ProfitabilityRatio float64 `json:"profitability_ratio"`
|
||||
}
|
||||
|
||||
// ProfitLossData represents individual profit and loss data point by time period
|
||||
type ProfitLossData struct {
|
||||
Date time.Time `json:"date"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
Cost float64 `json:"cost"`
|
||||
GrossProfit float64 `json:"gross_profit"`
|
||||
GrossProfitMargin float64 `json:"gross_profit_margin"`
|
||||
Tax float64 `json:"tax"`
|
||||
Discount float64 `json:"discount"`
|
||||
NetProfit float64 `json:"net_profit"`
|
||||
NetProfitMargin float64 `json:"net_profit_margin"`
|
||||
Orders int64 `json:"orders"`
|
||||
}
|
||||
|
||||
// ProductProfitData represents profit data for individual products
|
||||
type ProductProfitData struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
QuantitySold int64 `json:"quantity_sold"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
Cost float64 `json:"cost"`
|
||||
GrossProfit float64 `json:"gross_profit"`
|
||||
GrossProfitMargin float64 `json:"gross_profit_margin"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
AverageCost float64 `json:"average_cost"`
|
||||
ProfitPerUnit float64 `json:"profit_per_unit"`
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CreateCategoryRequest struct {
|
||||
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
BusinessType *string `json:"business_type,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateCategoryRequest struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
BusinessType *string `json:"business_type,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type ListCategoriesRequest struct {
|
||||
OrganizationID *uuid.UUID `json:"organization_id,omitempty"`
|
||||
BusinessType string `json:"business_type,omitempty"`
|
||||
Search string `json:"search,omitempty"`
|
||||
Page int `json:"page" validate:"required,min=1"`
|
||||
Limit int `json:"limit" validate:"required,min=1,max=100"`
|
||||
}
|
||||
|
||||
// Category Response DTOs
|
||||
type CategoryResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
BusinessType string `json:"business_type"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ListCategoriesResponse struct {
|
||||
Categories []CategoryResponse `json:"categories"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package contract
|
||||
|
||||
import "time"
|
||||
|
||||
type ErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Details map[string]interface{} `json:"details,omitempty"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
type ValidationErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Details map[string]string `json:"details"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
type SuccessResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type PaginationRequest struct {
|
||||
Page int `json:"page" validate:"min=1"`
|
||||
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||
}
|
||||
|
||||
type PaginationResponse struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
type SearchRequest struct {
|
||||
Query string `json:"query,omitempty"`
|
||||
}
|
||||
|
||||
type DateRangeRequest struct {
|
||||
From *time.Time `json:"from,omitempty"`
|
||||
To *time.Time `json:"to,omitempty"`
|
||||
}
|
||||
|
||||
type HealthResponse struct {
|
||||
Status string `json:"status"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CreateCustomerRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Email *string `json:"email,omitempty" validate:"omitempty,email"`
|
||||
Phone *string `json:"phone,omitempty"`
|
||||
Address *string `json:"address,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateCustomerRequest struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,required"`
|
||||
Email *string `json:"email,omitempty" validate:"omitempty,email"`
|
||||
Phone *string `json:"phone,omitempty"`
|
||||
Address *string `json:"address,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
}
|
||||
|
||||
type CustomerResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
Name string `json:"name"`
|
||||
Email *string `json:"email,omitempty"`
|
||||
Phone *string `json:"phone,omitempty"`
|
||||
Address *string `json:"address,omitempty"`
|
||||
IsDefault bool `json:"is_default"`
|
||||
IsActive bool `json:"is_active"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ListCustomersRequest struct {
|
||||
Page int `json:"page" validate:"min=1"`
|
||||
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||
Search string `json:"search"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
IsDefault *bool `json:"is_default"`
|
||||
SortBy string `json:"sort_by" validate:"omitempty,oneof=name email created_at updated_at"`
|
||||
SortOrder string `json:"sort_order" validate:"omitempty,oneof=asc desc"`
|
||||
}
|
||||
|
||||
type SetDefaultCustomerRequest struct {
|
||||
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
|
||||
}
|
||||
|
||||
type PaginatedCustomerResponse struct {
|
||||
Data []CustomerResponse `json:"data"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ListFilesQuery struct {
|
||||
OrganizationID string `form:"organization_id"`
|
||||
UserID string `form:"user_id"`
|
||||
FileType string `form:"file_type"`
|
||||
IsPublic string `form:"is_public"`
|
||||
DateFrom string `form:"date_from"`
|
||||
DateTo string `form:"date_to"`
|
||||
Search string `form:"search"`
|
||||
Page int `form:"page,default=1"`
|
||||
Limit int `form:"limit,default=10"`
|
||||
}
|
||||
|
||||
// Request DTOs
|
||||
type UploadFileRequest struct {
|
||||
FileType string `json:"file_type" validate:"required"`
|
||||
IsPublic *bool `json:"is_public,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateFileRequest struct {
|
||||
IsPublic *bool `json:"is_public,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type ListFilesRequest struct {
|
||||
OrganizationID *uuid.UUID `json:"organization_id,omitempty"`
|
||||
UserID *uuid.UUID `json:"user_id,omitempty"`
|
||||
FileType *string `json:"file_type,omitempty"`
|
||||
IsPublic *bool `json:"is_public,omitempty"`
|
||||
DateFrom *time.Time `json:"date_from,omitempty"`
|
||||
DateTo *time.Time `json:"date_to,omitempty"`
|
||||
Search string `json:"search,omitempty"`
|
||||
Page int `json:"page" validate:"required,min=1"`
|
||||
Limit int `json:"limit" validate:"required,min=1,max=100"`
|
||||
}
|
||||
|
||||
// Response DTOs
|
||||
type FileResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
UserID uuid.UUID `json:"user_id"`
|
||||
FileName string `json:"file_name"`
|
||||
OriginalName string `json:"original_name"`
|
||||
FileURL string `json:"file_url"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
MimeType string `json:"mime_type"`
|
||||
FileType string `json:"file_type"`
|
||||
UploadPath string `json:"upload_path"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ListFilesResponse struct {
|
||||
Files []*FileResponse `json:"files"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
type UploadFileResponse struct {
|
||||
File FileResponse `json:"file"`
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Inventory Request DTOs
|
||||
type CreateInventoryRequest struct {
|
||||
OutletID uuid.UUID `json:"outlet_id" validate:"required"`
|
||||
ProductID uuid.UUID `json:"product_id" validate:"required"`
|
||||
Quantity int `json:"quantity" validate:"min=0"`
|
||||
ReorderLevel int `json:"reorder_level" validate:"min=0"`
|
||||
}
|
||||
|
||||
type UpdateInventoryRequest struct {
|
||||
Quantity *int `json:"quantity,omitempty" validate:"omitempty,min=0"`
|
||||
ReorderLevel *int `json:"reorder_level,omitempty" validate:"omitempty,min=0"`
|
||||
}
|
||||
|
||||
type AdjustInventoryRequest struct {
|
||||
ProductID uuid.UUID `json:"product_id" validate:"required"`
|
||||
OutletID uuid.UUID `json:"outlet_id" validate:"required"`
|
||||
Delta int `json:"delta" validate:"required"` // Can be positive or negative
|
||||
Reason string `json:"reason" validate:"required,min=1,max=255"`
|
||||
}
|
||||
|
||||
type ListInventoryRequest struct {
|
||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||
ProductID *uuid.UUID `json:"product_id,omitempty"`
|
||||
CategoryID *uuid.UUID `json:"category_id,omitempty"`
|
||||
LowStockOnly *bool `json:"low_stock_only,omitempty"`
|
||||
ZeroStockOnly *bool `json:"zero_stock_only,omitempty"`
|
||||
Search string `json:"search,omitempty"`
|
||||
Page int `json:"page" validate:"required,min=1"`
|
||||
Limit int `json:"limit" validate:"required,min=1,max=100"`
|
||||
}
|
||||
|
||||
// Inventory Response DTOs
|
||||
type InventoryResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OutletID uuid.UUID `json:"outlet_id"`
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
Quantity int `json:"quantity"`
|
||||
ReorderLevel int `json:"reorder_level"`
|
||||
IsLowStock bool `json:"is_low_stock"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// Related data (optional)
|
||||
Product *ProductResponse `json:"product,omitempty"`
|
||||
Outlet *OutletResponse `json:"outlet,omitempty"`
|
||||
}
|
||||
|
||||
type ListInventoryResponse struct {
|
||||
Inventory []InventoryResponse `json:"inventory"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
type InventoryAdjustmentResponse struct {
|
||||
InventoryID uuid.UUID `json:"inventory_id"`
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
OutletID uuid.UUID `json:"outlet_id"`
|
||||
PreviousQty int `json:"previous_quantity"`
|
||||
NewQty int `json:"new_quantity"`
|
||||
Delta int `json:"delta"`
|
||||
Reason string `json:"reason"`
|
||||
AdjustedAt time.Time `json:"adjusted_at"`
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CreateOrderRequest struct {
|
||||
OutletID uuid.UUID `json:"outlet_id" validate:"required"`
|
||||
UserID uuid.UUID `json:"user_id" validate:"required"`
|
||||
TableNumber *string `json:"table_number,omitempty" validate:"omitempty,max=50"`
|
||||
OrderType string `json:"order_type" validate:"required,oneof=dine_in takeaway delivery"`
|
||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=1000"`
|
||||
OrderItems []CreateOrderItemRequest `json:"order_items" validate:"required,min=1,dive"`
|
||||
CustomerName *string `json:"customer_name,omitempty" validate:"omitempty,max=255"`
|
||||
}
|
||||
|
||||
type AddToOrderRequest struct {
|
||||
OrderItems []CreateOrderItemRequest `json:"order_items" validate:"required,min=1,dive"`
|
||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=1000"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type AddToOrderResponse struct {
|
||||
OrderID uuid.UUID `json:"order_id"`
|
||||
OrderNumber string `json:"order_number"`
|
||||
AddedItems []OrderItemResponse `json:"added_items"`
|
||||
UpdatedOrder OrderResponse `json:"updated_order"`
|
||||
}
|
||||
|
||||
type UpdateOrderRequest struct {
|
||||
TableNumber *string `json:"table_number,omitempty" validate:"omitempty,max=50"`
|
||||
Status *string `json:"status,omitempty" validate:"omitempty,oneof=pending preparing ready completed cancelled"`
|
||||
DiscountAmount *float64 `json:"discount_amount,omitempty" validate:"omitempty,min=0"`
|
||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=1000"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type CreateOrderItemRequest struct {
|
||||
ProductID uuid.UUID `json:"product_id" validate:"required"`
|
||||
ProductVariantID *uuid.UUID `json:"product_variant_id,omitempty"`
|
||||
Quantity int `json:"quantity" validate:"required,min=1"`
|
||||
UnitPrice *float64 `json:"unit_price,omitempty" validate:"omitempty,min=0"` // Optional, will use database price if not provided
|
||||
Modifiers []map[string]interface{} `json:"modifiers,omitempty"`
|
||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateOrderItemRequest struct {
|
||||
Quantity *int `json:"quantity,omitempty" validate:"omitempty,min=1"`
|
||||
UnitPrice *float64 `json:"unit_price,omitempty" validate:"omitempty,min=0"`
|
||||
Modifiers []map[string]interface{} `json:"modifiers,omitempty"`
|
||||
Status *string `json:"status,omitempty" validate:"omitempty,oneof=pending preparing completed cancelled"`
|
||||
}
|
||||
|
||||
type OrderResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrderNumber string `json:"order_number"`
|
||||
OutletID uuid.UUID `json:"outlet_id"`
|
||||
UserID uuid.UUID `json:"user_id"`
|
||||
TableNumber *string `json:"table_number"`
|
||||
OrderType string `json:"order_type"`
|
||||
Status string `json:"status"`
|
||||
Subtotal float64 `json:"subtotal"`
|
||||
TaxAmount float64 `json:"tax_amount"`
|
||||
DiscountAmount float64 `json:"discount_amount"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
Notes *string `json:"notes"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
OrderItems []OrderItemResponse `json:"order_items,omitempty"`
|
||||
}
|
||||
|
||||
type OrderItemResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrderID uuid.UUID `json:"order_id"`
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
ProductVariantID *uuid.UUID `json:"product_variant_id"`
|
||||
ProductVariantName *string `json:"product_variant_name,omitempty"`
|
||||
Quantity int `json:"quantity"`
|
||||
UnitPrice float64 `json:"unit_price"`
|
||||
TotalPrice float64 `json:"total_price"`
|
||||
Modifiers []map[string]interface{} `json:"modifiers"`
|
||||
Notes *string `json:"notes,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ListOrdersQuery struct {
|
||||
OrganizationID string `form:"organization_id"`
|
||||
OutletID string `form:"outlet_id"`
|
||||
UserID string `form:"user_id"`
|
||||
CustomerID string `form:"customer_id"`
|
||||
OrderType string `form:"order_type"`
|
||||
Status string `form:"status"`
|
||||
PaymentStatus string `form:"payment_status"`
|
||||
IsVoid string `form:"is_void"`
|
||||
IsRefund string `form:"is_refund"`
|
||||
DateFrom string `form:"date_from"`
|
||||
DateTo string `form:"date_to"`
|
||||
Search string `form:"search"`
|
||||
Page int `form:"page,default=1"`
|
||||
Limit int `form:"limit,default=10"`
|
||||
}
|
||||
|
||||
type ListOrdersRequest struct {
|
||||
Page int `json:"page" validate:"min=1"`
|
||||
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||
UserID *uuid.UUID `json:"user_id,omitempty"`
|
||||
Status *string `json:"status,omitempty" validate:"omitempty,oneof=pending preparing ready completed cancelled"`
|
||||
OrderType *string `json:"order_type,omitempty" validate:"omitempty,oneof=dine_in takeaway delivery"`
|
||||
DateFrom *time.Time `json:"date_from,omitempty"`
|
||||
DateTo *time.Time `json:"date_to,omitempty"`
|
||||
}
|
||||
|
||||
type ListOrdersResponse struct {
|
||||
Orders []OrderResponse `json:"orders"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
type VoidOrderRequest struct {
|
||||
OrderID uuid.UUID `json:"order_id" validate:"required"`
|
||||
Reason string `json:"reason" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=ALL ITEM"`
|
||||
Items []VoidItemRequest `json:"items,omitempty" validate:"required_if=Type ITEM,dive"`
|
||||
}
|
||||
|
||||
type VoidItemRequest struct {
|
||||
OrderItemID uuid.UUID `json:"order_item_id" validate:"required"`
|
||||
Quantity int `json:"quantity" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
type SetOrderCustomerRequest struct {
|
||||
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
|
||||
}
|
||||
|
||||
type SetOrderCustomerResponse struct {
|
||||
OrderID uuid.UUID `json:"order_id"`
|
||||
CustomerID uuid.UUID `json:"customer_id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Payment-related contracts
|
||||
type CreatePaymentRequest struct {
|
||||
OrderID uuid.UUID `json:"order_id" validate:"required"`
|
||||
PaymentMethodID uuid.UUID `json:"payment_method_id" validate:"required"`
|
||||
Amount float64 `json:"amount" validate:"required,min=0"`
|
||||
TransactionID *string `json:"transaction_id,omitempty" validate:"omitempty"`
|
||||
SplitNumber int `json:"split_number,omitempty" validate:"omitempty,min=1"`
|
||||
SplitTotal int `json:"split_total,omitempty" validate:"omitempty,min=1"`
|
||||
SplitDescription *string `json:"split_description,omitempty" validate:"omitempty,max=255"`
|
||||
PaymentOrderItems []CreatePaymentOrderItemRequest `json:"payment_order_items,omitempty" validate:"omitempty,dive"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type CreatePaymentOrderItemRequest struct {
|
||||
OrderItemID uuid.UUID `json:"order_item_id" validate:"required"`
|
||||
Amount float64 `json:"amount" validate:"required,min=0"`
|
||||
}
|
||||
|
||||
type PaymentResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrderID uuid.UUID `json:"order_id"`
|
||||
PaymentMethodID uuid.UUID `json:"payment_method_id"`
|
||||
Amount float64 `json:"amount"`
|
||||
Status string `json:"status"`
|
||||
TransactionID *string `json:"transaction_id,omitempty"`
|
||||
SplitNumber int `json:"split_number"`
|
||||
SplitTotal int `json:"split_total"`
|
||||
SplitDescription *string `json:"split_description,omitempty"`
|
||||
RefundAmount float64 `json:"refund_amount"`
|
||||
RefundReason *string `json:"refund_reason,omitempty"`
|
||||
RefundedAt *time.Time `json:"refunded_at,omitempty"`
|
||||
RefundedBy *uuid.UUID `json:"refunded_by,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
PaymentOrderItems []PaymentOrderItemResponse `json:"payment_order_items,omitempty"`
|
||||
}
|
||||
|
||||
type PaymentOrderItemResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
PaymentID uuid.UUID `json:"payment_id"`
|
||||
OrderItemID uuid.UUID `json:"order_item_id"`
|
||||
Amount float64 `json:"amount"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type RefundOrderRequest struct {
|
||||
Reason *string `json:"reason,omitempty" validate:"omitempty,max=255"`
|
||||
RefundAmount *float64 `json:"refund_amount,omitempty" validate:"omitempty,min=0"`
|
||||
OrderItems []RefundOrderItemRequest `json:"order_items,omitempty" validate:"omitempty,dive"`
|
||||
}
|
||||
|
||||
type RefundOrderItemRequest struct {
|
||||
OrderItemID uuid.UUID `json:"order_item_id" validate:"required"`
|
||||
RefundQuantity int `json:"refund_quantity,omitempty" validate:"omitempty,min=1"`
|
||||
RefundAmount *float64 `json:"refund_amount,omitempty" validate:"omitempty,min=0"`
|
||||
Reason *string `json:"reason,omitempty" validate:"omitempty,max=255"`
|
||||
}
|
||||
|
||||
type RefundPaymentRequest struct {
|
||||
RefundAmount float64 `json:"refund_amount" validate:"required,min=0"`
|
||||
Reason string `json:"reason" validate:"omitempty,max=255"`
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CreateOrganizationRequest struct {
|
||||
OrganizationName string `json:"organization_name" validate:"required,min=1,max=255"`
|
||||
OrganizationEmail *string `json:"organization_email,omitempty" validate:"omitempty,email"`
|
||||
OrganizationPhoneNumber *string `json:"organization_phone_number,omitempty"`
|
||||
PlanType string `json:"plan_type" validate:"required,oneof=basic premium enterprise"`
|
||||
|
||||
AdminName string `json:"admin_name" validate:"required,min=1,max=255"`
|
||||
AdminEmail string `json:"admin_email" validate:"required,email"`
|
||||
AdminPassword string `json:"admin_password" validate:"required,min=6"`
|
||||
|
||||
OutletName string `json:"outlet_name" validate:"required,min=1,max=255"`
|
||||
OutletAddress *string `json:"outlet_address,omitempty"`
|
||||
OutletTimezone *string `json:"outlet_timezone,omitempty"`
|
||||
OutletCurrency string `json:"outlet_currency" validate:"required,len=3"`
|
||||
}
|
||||
|
||||
type UpdateOrganizationRequest struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Email *string `json:"email,omitempty" validate:"omitempty,email"`
|
||||
PhoneNumber *string `json:"phone_number,omitempty"`
|
||||
PlanType *string `json:"plan_type,omitempty" validate:"omitempty,oneof=basic premium enterprise"`
|
||||
}
|
||||
|
||||
type OrganizationResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email *string `json:"email"`
|
||||
PhoneNumber *string `json:"phone_number"`
|
||||
PlanType string `json:"plan_type"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CreateOrganizationResponse struct {
|
||||
Organization OrganizationResponse `json:"organization"`
|
||||
AdminUser UserResponse `json:"admin_user"`
|
||||
DefaultOutlet OutletResponse `json:"default_outlet"`
|
||||
}
|
||||
|
||||
type ListOrganizationsRequest struct {
|
||||
Page int `json:"page" validate:"min=1"`
|
||||
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||
Search string `json:"search,omitempty"`
|
||||
PlanType string `json:"plan_type,omitempty" validate:"omitempty,oneof=basic premium enterprise"`
|
||||
}
|
||||
|
||||
type ListOrganizationsResponse struct {
|
||||
Organizations []OrganizationResponse `json:"organizations"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CreateOutletRequest struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id" validate:"required"`
|
||||
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||
Address string `json:"address" validate:"required,min=1,max=500"`
|
||||
PhoneNumber *string `json:"phone_number,omitempty" validate:"omitempty,e164"`
|
||||
BusinessType string `json:"business_type" validate:"required,oneof=restaurant cafe bar fastfood retail"`
|
||||
Currency string `json:"currency" validate:"required,len=3"`
|
||||
TaxRate float64 `json:"tax_rate" validate:"min=0,max=1"`
|
||||
}
|
||||
|
||||
type UpdateOutletRequest struct {
|
||||
OrganizationID uuid.UUID
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Address *string `json:"address,omitempty" validate:"omitempty,min=1,max=500"`
|
||||
PhoneNumber *string `json:"phone_number,omitempty" validate:"omitempty,e164"`
|
||||
TaxRate *float64 `json:"tax_rate,omitempty" validate:"omitempty,min=0,max=1"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
}
|
||||
|
||||
type OutletResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
Name string `json:"name"`
|
||||
Address string `json:"address"`
|
||||
PhoneNumber *string `json:"phone_number"`
|
||||
BusinessType string `json:"business_type"`
|
||||
Currency string `json:"currency"`
|
||||
TaxRate float64 `json:"tax_rate"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ListOutletsRequest struct {
|
||||
Page int `json:"page" validate:"min=1"`
|
||||
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||
Search string `json:"search,omitempty"`
|
||||
OrganizationID uuid.UUID `json:"organization_id,omitempty"`
|
||||
BusinessType *string `json:"business_type,omitempty" validate:"omitempty,oneof=restaurant cafe bar fastfood retail"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
}
|
||||
|
||||
type ListOutletsResponse struct {
|
||||
Outlets []OutletResponse `json:"outlets"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CreatePaymentMethodRequest struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id" validate:"required"`
|
||||
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||
Type string `json:"type" validate:"required,oneof=cash card digital_wallet qr edc"`
|
||||
Processor *string `json:"processor,omitempty" validate:"omitempty,max=100"`
|
||||
Configuration map[string]interface{} `json:"configuration,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
}
|
||||
|
||||
type UpdatePaymentMethodRequest struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=100"`
|
||||
Type *string `json:"type,omitempty" validate:"omitempty,oneof=cash card digital_wallet qr edc"`
|
||||
Processor *string `json:"processor,omitempty" validate:"omitempty,max=100"`
|
||||
Configuration map[string]interface{} `json:"configuration,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
}
|
||||
|
||||
type PaymentMethodResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Processor *string `json:"processor,omitempty"`
|
||||
Configuration map[string]interface{} `json:"configuration,omitempty"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ListPaymentMethodsRequest struct {
|
||||
OrganizationID *uuid.UUID `json:"organization_id,omitempty"`
|
||||
Type *string `json:"type,omitempty" validate:"omitempty,oneof=cash card digital_wallet qr edc"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
Search string `json:"search,omitempty"`
|
||||
Page int `json:"page" validate:"min=1"`
|
||||
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||
}
|
||||
|
||||
type ListPaymentMethodsResponse struct {
|
||||
PaymentMethods []PaymentMethodResponse `json:"payment_methods"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CreateProductRequest struct {
|
||||
CategoryID uuid.UUID `json:"category_id" validate:"required"`
|
||||
SKU *string `json:"sku,omitempty"`
|
||||
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Price float64 `json:"price" validate:"required,min=0"`
|
||||
Cost *float64 `json:"cost,omitempty" validate:"omitempty,min=0"`
|
||||
BusinessType *string `json:"business_type,omitempty"`
|
||||
Image *string `json:"image,omitempty"` // Will be stored in metadata["image"]
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
Variants []CreateProductVariantRequest `json:"variants,omitempty"`
|
||||
InitialStock *int `json:"initial_stock,omitempty" validate:"omitempty,min=0"` // Initial stock quantity for all outlets
|
||||
ReorderLevel *int `json:"reorder_level,omitempty" validate:"omitempty,min=0"` // Reorder level for all outlets
|
||||
CreateInventory bool `json:"create_inventory,omitempty"` // Whether to create inventory records for all outlets
|
||||
}
|
||||
|
||||
type UpdateProductRequest struct {
|
||||
CategoryID *uuid.UUID `json:"category_id,omitempty"`
|
||||
SKU *string `json:"sku,omitempty"`
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Price *float64 `json:"price,omitempty" validate:"omitempty,min=0"`
|
||||
Cost *float64 `json:"cost,omitempty" validate:"omitempty,min=0"`
|
||||
BusinessType *string `json:"business_type,omitempty"`
|
||||
Image *string `json:"image,omitempty"` // Will be stored in metadata["image"]
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
// Stock management fields
|
||||
ReorderLevel *int `json:"reorder_level,omitempty" validate:"omitempty,min=0"` // Update reorder level for all existing inventory records
|
||||
}
|
||||
|
||||
type CreateProductVariantRequest struct {
|
||||
ProductID uuid.UUID `json:"product_id" validate:"required"`
|
||||
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||
PriceModifier float64 `json:"price_modifier" validate:"required"`
|
||||
Cost float64 `json:"cost" validate:"min=0"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateProductVariantRequest struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
PriceModifier *float64 `json:"price_modifier,omitempty"`
|
||||
Cost *float64 `json:"cost,omitempty" validate:"omitempty,min=0"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type ProductResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
SKU *string `json:"sku"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Price float64 `json:"price"`
|
||||
Cost float64 `json:"cost"`
|
||||
BusinessType string `json:"business_type"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Category *CategoryResponse `json:"category,omitempty"`
|
||||
Variants []ProductVariantResponse `json:"variants,omitempty"`
|
||||
}
|
||||
|
||||
type ProductVariantResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
Name string `json:"name"`
|
||||
PriceModifier float64 `json:"price_modifier"`
|
||||
Cost float64 `json:"cost"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ListProductsRequest struct {
|
||||
OrganizationID *uuid.UUID `json:"organization_id,omitempty"`
|
||||
CategoryID *uuid.UUID `json:"category_id,omitempty"`
|
||||
BusinessType string `json:"business_type,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
Search string `json:"search,omitempty"`
|
||||
MinPrice *float64 `json:"min_price,omitempty" validate:"omitempty,min=0"`
|
||||
MaxPrice *float64 `json:"max_price,omitempty" validate:"omitempty,min=0"`
|
||||
Page int `json:"page" validate:"required,min=1"`
|
||||
Limit int `json:"limit" validate:"required,min=1,max=100"`
|
||||
}
|
||||
|
||||
type ListProductsResponse struct {
|
||||
Products []ProductResponse `json:"products"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package contract
|
||||
|
||||
type Response struct {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data"`
|
||||
Errors []*ResponseError `json:"errors"`
|
||||
}
|
||||
|
||||
func (r *Response) GetSuccess() bool {
|
||||
return r.Success
|
||||
}
|
||||
|
||||
func (r *Response) GetData() interface{} {
|
||||
return r.Data
|
||||
}
|
||||
|
||||
func (r *Response) GetErrors() []*ResponseError {
|
||||
return r.Errors
|
||||
}
|
||||
|
||||
func BuildSuccessResponse(data interface{}) *Response {
|
||||
return &Response{
|
||||
Success: true,
|
||||
Data: data,
|
||||
Errors: []*ResponseError(nil),
|
||||
}
|
||||
}
|
||||
|
||||
func BuildErrorResponse(errorList []*ResponseError) *Response {
|
||||
return &Response{
|
||||
Success: false,
|
||||
Data: nil,
|
||||
Errors: errorList,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) HasErrors() bool {
|
||||
return r.GetErrors() != nil && len(r.GetErrors()) > 0
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package contract
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ResponseError struct {
|
||||
Code string `json:"code"`
|
||||
Entity string `json:"entity"`
|
||||
Cause string `json:"cause"`
|
||||
}
|
||||
|
||||
func NewResponseError(code, entity, cause string) *ResponseError {
|
||||
return &ResponseError{
|
||||
Code: code,
|
||||
Cause: cause,
|
||||
Entity: entity,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ResponseError) GetCode() string {
|
||||
return e.Code
|
||||
}
|
||||
|
||||
func (e *ResponseError) GetEntity() string {
|
||||
return e.Entity
|
||||
}
|
||||
|
||||
func (e *ResponseError) GetCause() string {
|
||||
return e.Cause
|
||||
}
|
||||
|
||||
func (e *ResponseError) Error() string {
|
||||
return fmt.Sprintf("%s: %s: %s", e.GetCode(), e.GetEntity(), e.GetCause())
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package contract
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CreateUserRequest struct {
|
||||
OrganizationID uuid.UUID `json:"organization_id" validate:"required"`
|
||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Password string `json:"password" validate:"required,min=6"`
|
||||
Role string `json:"role" validate:"required,oneof=admin manager cashier waiter"`
|
||||
Permissions map[string]interface{} `json:"permissions,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateUserRequest struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Email *string `json:"email,omitempty" validate:"omitempty,email"`
|
||||
Role *string `json:"role,omitempty" validate:"omitempty,oneof=admin manager cashier waiter"`
|
||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
Permissions *map[string]interface{} `json:"permissions,omitempty"`
|
||||
}
|
||||
|
||||
type ChangePasswordRequest struct {
|
||||
CurrentPassword string `json:"current_password" validate:"required"`
|
||||
NewPassword string `json:"new_password" validate:"required,min=6"`
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
User UserResponse `json:"user"`
|
||||
}
|
||||
|
||||
type UserResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
OutletID *uuid.UUID `json:"outlet_id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Role string `json:"role"`
|
||||
Permissions map[string]interface{} `json:"permissions"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ListUsersRequest struct {
|
||||
Page int `json:"page" validate:"min=1"`
|
||||
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||
Role *string `json:"role,omitempty"`
|
||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
||||
IsActive *bool `json:"is_active,omitempty"`
|
||||
OrganizationID *uuid.UUID `json:"organization_id,omitempty"`
|
||||
}
|
||||
|
||||
type ListUsersResponse struct {
|
||||
Users []UserResponse `json:"users"`
|
||||
Pagination PaginationResponse `json:"pagination"`
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"apskel-pos-be/config"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"go.uber.org/zap"
|
||||
_ "gopkg.in/yaml.v3"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"enaklo-pos-be/config"
|
||||
)
|
||||
|
||||
func NewPostgres(c config.Database) (*gorm.DB, error) {
|
||||
@@ -19,21 +17,14 @@ func NewPostgres(c config.Database) (*gorm.DB, error) {
|
||||
|
||||
db, err := gorm.Open(dialector, &gorm.Config{})
|
||||
|
||||
//db, err := gorm.Open(dialector, &gorm.Config{
|
||||
// Logger: logger.Default.LogMode(logger.Info), // Enable GORM logging
|
||||
//})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zapCfg := zap.NewProductionConfig()
|
||||
zapCfg.Level = zap.NewAtomicLevelAt(zap.ErrorLevel) // whatever minimum level
|
||||
zapCfg.Level = zap.NewAtomicLevelAt(zap.ErrorLevel)
|
||||
zapCfg.DisableCaller = false
|
||||
// logger, _ := zapCfg.Build()
|
||||
// db = gorm.Open(sqldblogger.New(logger), db)
|
||||
|
||||
// ping the database to test the connection
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -0,0 +1,102 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// PaymentMethodAnalytics represents payment method analytics data
|
||||
type PaymentMethodAnalytics struct {
|
||||
PaymentMethodID uuid.UUID `json:"payment_method_id"`
|
||||
PaymentMethodName string `json:"payment_method_name"`
|
||||
PaymentMethodType string `json:"payment_method_type"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
PaymentCount int64 `json:"payment_count"`
|
||||
}
|
||||
|
||||
// SalesAnalytics represents sales analytics data
|
||||
type SalesAnalytics struct {
|
||||
Date time.Time `json:"date"`
|
||||
Sales float64 `json:"sales"`
|
||||
Orders int64 `json:"orders"`
|
||||
Items int64 `json:"items"`
|
||||
Tax float64 `json:"tax"`
|
||||
Discount float64 `json:"discount"`
|
||||
NetSales float64 `json:"net_sales"`
|
||||
}
|
||||
|
||||
// ProductAnalytics represents product analytics data
|
||||
type ProductAnalytics struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
QuantitySold int64 `json:"quantity_sold"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
}
|
||||
|
||||
// DashboardOverview represents dashboard overview data
|
||||
type DashboardOverview struct {
|
||||
TotalSales float64 `json:"total_sales"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
AverageOrderValue float64 `json:"average_order_value"`
|
||||
TotalCustomers int64 `json:"total_customers"`
|
||||
VoidedOrders int64 `json:"voided_orders"`
|
||||
RefundedOrders int64 `json:"refunded_orders"`
|
||||
}
|
||||
|
||||
// ProfitLossAnalytics represents profit and loss analytics data
|
||||
type ProfitLossAnalytics struct {
|
||||
Summary ProfitLossSummary `json:"summary"`
|
||||
Data []ProfitLossData `json:"data"`
|
||||
ProductData []ProductProfitData `json:"product_data"`
|
||||
}
|
||||
|
||||
// ProfitLossSummary represents profit and loss summary data
|
||||
type ProfitLossSummary struct {
|
||||
TotalRevenue float64 `json:"total_revenue"`
|
||||
TotalCost float64 `json:"total_cost"`
|
||||
GrossProfit float64 `json:"gross_profit"`
|
||||
GrossProfitMargin float64 `json:"gross_profit_margin"`
|
||||
TotalTax float64 `json:"total_tax"`
|
||||
TotalDiscount float64 `json:"total_discount"`
|
||||
NetProfit float64 `json:"net_profit"`
|
||||
NetProfitMargin float64 `json:"net_profit_margin"`
|
||||
TotalOrders int64 `json:"total_orders"`
|
||||
AverageProfit float64 `json:"average_profit"`
|
||||
ProfitabilityRatio float64 `json:"profitability_ratio"`
|
||||
}
|
||||
|
||||
// ProfitLossData represents profit and loss data by time period
|
||||
type ProfitLossData struct {
|
||||
Date time.Time `json:"date"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
Cost float64 `json:"cost"`
|
||||
GrossProfit float64 `json:"gross_profit"`
|
||||
GrossProfitMargin float64 `json:"gross_profit_margin"`
|
||||
Tax float64 `json:"tax"`
|
||||
Discount float64 `json:"discount"`
|
||||
NetProfit float64 `json:"net_profit"`
|
||||
NetProfitMargin float64 `json:"net_profit_margin"`
|
||||
Orders int64 `json:"orders"`
|
||||
}
|
||||
|
||||
// ProductProfitData represents profit data for individual products
|
||||
type ProductProfitData struct {
|
||||
ProductID uuid.UUID `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
CategoryID uuid.UUID `json:"category_id"`
|
||||
CategoryName string `json:"category_name"`
|
||||
QuantitySold int64 `json:"quantity_sold"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
Cost float64 `json:"cost"`
|
||||
GrossProfit float64 `json:"gross_profit"`
|
||||
GrossProfitMargin float64 `json:"gross_profit_margin"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
AverageCost float64 `json:"average_cost"`
|
||||
ProfitPerUnit float64 `json:"profit_per_unit"`
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Metadata map[string]interface{}
|
||||
|
||||
func (m Metadata) Value() (driver.Value, error) {
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
func (m *Metadata) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*m = make(Metadata)
|
||||
return nil
|
||||
}
|
||||
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
return errors.New("type assertion to []byte failed")
|
||||
}
|
||||
|
||||
return json.Unmarshal(bytes, m)
|
||||
}
|
||||
|
||||
type Category 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" validate:"required"`
|
||||
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
|
||||
Description *string `gorm:"type:text" json:"description"`
|
||||
BusinessType string `gorm:"size:50;default:'restaurant'" json:"business_type"`
|
||||
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
|
||||
Products []Product `gorm:"foreignKey:CategoryID" json:"products,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Category) BeforeCreate(tx *gorm.DB) error {
|
||||
if c.ID == uuid.Nil {
|
||||
c.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Category) TableName() string {
|
||||
return "categories"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Customer 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" validate:"required"`
|
||||
Name string `gorm:"not null;size:255" json:"name" validate:"required"`
|
||||
Email *string `gorm:"size:255;uniqueIndex" json:"email,omitempty"`
|
||||
Phone *string `gorm:"size:20" json:"phone,omitempty"`
|
||||
Address *string `gorm:"size:500" json:"address,omitempty"`
|
||||
IsDefault bool `gorm:"default:false" json:"is_default"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
|
||||
Orders []Order `gorm:"foreignKey:CustomerID" json:"orders,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Customer) BeforeCreate(tx *gorm.DB) error {
|
||||
if c.ID == uuid.Nil {
|
||||
c.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Customer) TableName() string {
|
||||
return "customers"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package entities
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
func GetAllEntities() []interface{} {
|
||||
return []interface{}{
|
||||
&Organization{},
|
||||
&Outlet{},
|
||||
&OutletSetting{},
|
||||
&User{},
|
||||
&Category{},
|
||||
&Product{},
|
||||
&ProductVariant{},
|
||||
&Inventory{},
|
||||
&Order{},
|
||||
&OrderItem{},
|
||||
&PaymentMethod{},
|
||||
&Payment{},
|
||||
&Customer{},
|
||||
// Analytics entities are not database tables, they are query results
|
||||
}
|
||||
}
|
||||
|
||||
func AutoMigrate(db *gorm.DB) error {
|
||||
return db.AutoMigrate(GetAllEntities()...)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
OrganizationID uuid.UUID `gorm:"type:uuid;not null" json:"organization_id"`
|
||||
UserID uuid.UUID `gorm:"type:uuid;not null" json:"user_id"`
|
||||
FileName string `gorm:"size:255;not null" json:"file_name"`
|
||||
OriginalName string `gorm:"size:255;not null" json:"original_name"`
|
||||
FileURL string `gorm:"size:500;not null" json:"file_url"`
|
||||
FileSize int64 `gorm:"not null" json:"file_size"`
|
||||
MimeType string `gorm:"size:100;not null" json:"mime_type"`
|
||||
FileType string `gorm:"size:50;not null" json:"file_type"` // image, document, video, etc.
|
||||
UploadPath string `gorm:"size:500;not null" json:"upload_path"`
|
||||
IsPublic bool `gorm:"default:true" json:"is_public"`
|
||||
Metadata Metadata `gorm:"type:jsonb" json:"metadata"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (File) TableName() string {
|
||||
return "files"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Inventory struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
OutletID uuid.UUID `gorm:"type:uuid;not null;index" json:"outlet_id" validate:"required"`
|
||||
ProductID uuid.UUID `gorm:"type:uuid;not null;index" json:"product_id" validate:"required"`
|
||||
Quantity int `gorm:"not null;default:0" json:"quantity" validate:"min=0"`
|
||||
ReorderLevel int `gorm:"default:0" json:"reorder_level" validate:"min=0"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Outlet Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`
|
||||
Product Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
|
||||
}
|
||||
|
||||
func (i *Inventory) BeforeCreate(tx *gorm.DB) error {
|
||||
if i.ID == uuid.Nil {
|
||||
i.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Inventory) TableName() string {
|
||||
return "inventory"
|
||||
}
|
||||
|
||||
func (i *Inventory) IsLowStock() bool {
|
||||
return i.Quantity <= i.ReorderLevel
|
||||
}
|
||||
|
||||
func (i *Inventory) UpdateQuantity(delta int) {
|
||||
i.Quantity += delta
|
||||
if i.Quantity < 0 {
|
||||
i.Quantity = 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type OrderType string
|
||||
type OrderStatus string
|
||||
type PaymentStatus string
|
||||
|
||||
const (
|
||||
OrderTypeDineIn OrderType = "dine_in"
|
||||
OrderTypeTakeout OrderType = "takeout"
|
||||
OrderTypeDelivery OrderType = "delivery"
|
||||
)
|
||||
|
||||
const (
|
||||
OrderStatusPending OrderStatus = "pending"
|
||||
OrderStatusPreparing OrderStatus = "preparing"
|
||||
OrderStatusReady OrderStatus = "ready"
|
||||
OrderStatusCompleted OrderStatus = "completed"
|
||||
OrderStatusCancelled OrderStatus = "cancelled"
|
||||
)
|
||||
|
||||
const (
|
||||
PaymentStatusPending PaymentStatus = "pending"
|
||||
PaymentStatusCompleted PaymentStatus = "completed"
|
||||
PaymentStatusFailed PaymentStatus = "failed"
|
||||
PaymentStatusRefunded PaymentStatus = "refunded"
|
||||
PaymentStatusPartiallyRefunded PaymentStatus = "partial-refunded"
|
||||
)
|
||||
|
||||
type Order 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" validate:"required"`
|
||||
OutletID uuid.UUID `gorm:"type:uuid;not null;index" json:"outlet_id" validate:"required"`
|
||||
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id" validate:"required"`
|
||||
CustomerID *uuid.UUID `gorm:"type:uuid;index" json:"customer_id"`
|
||||
OrderNumber string `gorm:"uniqueIndex;not null;size:50" json:"order_number" validate:"required"`
|
||||
TableNumber *string `gorm:"size:20" json:"table_number"`
|
||||
OrderType OrderType `gorm:"not null;size:50" json:"order_type" validate:"required,oneof=dine_in takeout delivery"`
|
||||
Status OrderStatus `gorm:"default:'pending';size:50" json:"status"`
|
||||
Subtotal float64 `gorm:"type:decimal(10,2);not null" json:"subtotal" validate:"required,min=0"`
|
||||
TaxAmount float64 `gorm:"type:decimal(10,2);not null" json:"tax_amount" validate:"required,min=0"`
|
||||
DiscountAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"discount_amount" validate:"min=0"`
|
||||
TotalAmount float64 `gorm:"type:decimal(10,2);not null" json:"total_amount" validate:"required,min=0"`
|
||||
TotalCost float64 `gorm:"type:decimal(10,2);default:0.00" json:"total_cost"`
|
||||
PaymentStatus PaymentStatus `gorm:"default:'pending';size:50" json:"payment_status"`
|
||||
RefundAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"refund_amount"`
|
||||
IsVoid bool `gorm:"default:false" json:"is_void"`
|
||||
IsRefund bool `gorm:"default:false" json:"is_refund"`
|
||||
VoidReason *string `gorm:"size:255" json:"void_reason,omitempty"`
|
||||
VoidedAt *time.Time `gorm:"" json:"voided_at,omitempty"`
|
||||
VoidedBy *uuid.UUID `gorm:"type:uuid" json:"voided_by,omitempty"`
|
||||
RefundReason *string `gorm:"size:255" json:"refund_reason,omitempty"`
|
||||
RefundedAt *time.Time `gorm:"" json:"refunded_at,omitempty"`
|
||||
RefundedBy *uuid.UUID `gorm:"type:uuid" json:"refunded_by,omitempty"`
|
||||
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
|
||||
Outlet Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`
|
||||
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||
OrderItems []OrderItem `gorm:"foreignKey:OrderID" json:"order_items,omitempty"`
|
||||
Payments []Payment `gorm:"foreignKey:OrderID" json:"payments,omitempty"`
|
||||
}
|
||||
|
||||
func (o *Order) BeforeCreate(tx *gorm.DB) error {
|
||||
if o.ID == uuid.Nil {
|
||||
o.ID = uuid.New()
|
||||
}
|
||||
|
||||
if o.OrderNumber == "" {
|
||||
timestamp := time.Now().Unix()
|
||||
o.OrderNumber = fmt.Sprintf("ORD/%d", timestamp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Order) TableName() string {
|
||||
return "orders"
|
||||
}
|
||||
|
||||
func (o *Order) CanBeModified() bool {
|
||||
return o.Status == OrderStatusPending
|
||||
}
|
||||
|
||||
func (o *Order) CanBeCancelled() bool {
|
||||
return o.Status != OrderStatusCompleted && o.Status != OrderStatusCancelled
|
||||
}
|
||||
|
||||
func (o *Order) GetTotalPaid() float64 {
|
||||
var total float64
|
||||
for _, payment := range o.Payments {
|
||||
if payment.Status == PaymentTransactionStatusCompleted {
|
||||
total += payment.Amount
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func (o *Order) IsFullyPaid() bool {
|
||||
return o.GetTotalPaid() >= o.TotalAmount
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Modifiers []map[string]interface{}
|
||||
|
||||
func (m Modifiers) Value() (driver.Value, error) {
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
func (m *Modifiers) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*m = make(Modifiers, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
return errors.New("type assertion to []byte failed")
|
||||
}
|
||||
|
||||
return json.Unmarshal(bytes, m)
|
||||
}
|
||||
|
||||
type OrderItemStatus string
|
||||
|
||||
const (
|
||||
OrderItemStatusPending OrderItemStatus = "pending"
|
||||
OrderItemStatusPreparing OrderItemStatus = "preparing"
|
||||
OrderItemStatusReady OrderItemStatus = "ready"
|
||||
OrderItemStatusServed OrderItemStatus = "served"
|
||||
OrderItemStatusCancelled OrderItemStatus = "cancelled"
|
||||
)
|
||||
|
||||
type OrderItem struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
OrderID uuid.UUID `gorm:"type:uuid;not null;index" json:"order_id" validate:"required"`
|
||||
ProductID uuid.UUID `gorm:"type:uuid;not null;index" json:"product_id" validate:"required"`
|
||||
ProductVariantID *uuid.UUID `gorm:"type:uuid;index" json:"product_variant_id"`
|
||||
Quantity int `gorm:"not null" json:"quantity" validate:"required,min=1"`
|
||||
UnitPrice float64 `gorm:"type:decimal(10,2);not null" json:"unit_price" validate:"required,min=0"`
|
||||
TotalPrice float64 `gorm:"type:decimal(10,2);not null" json:"total_price" validate:"required,min=0"`
|
||||
UnitCost float64 `gorm:"type:decimal(10,2);default:0.00" json:"unit_cost"`
|
||||
TotalCost float64 `gorm:"type:decimal(10,2);default:0.00" json:"total_cost"`
|
||||
RefundAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"refund_amount"`
|
||||
RefundQuantity int `gorm:"default:0" json:"refund_quantity"`
|
||||
IsPartiallyRefunded bool `gorm:"default:false" json:"is_partially_refunded"`
|
||||
IsFullyRefunded bool `gorm:"default:false" json:"is_fully_refunded"`
|
||||
RefundReason *string `gorm:"size:255" json:"refund_reason,omitempty"`
|
||||
RefundedAt *time.Time `gorm:"" json:"refunded_at,omitempty"`
|
||||
RefundedBy *uuid.UUID `gorm:"type:uuid" json:"refunded_by,omitempty"`
|
||||
Modifiers Modifiers `gorm:"type:jsonb;default:'[]'" json:"modifiers"`
|
||||
Notes *string `gorm:"size:500" json:"notes,omitempty"`
|
||||
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||
Status OrderItemStatus `gorm:"default:'pending';size:50" json:"status"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Order Order `gorm:"foreignKey:OrderID" json:"order,omitempty"`
|
||||
Product Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
|
||||
ProductVariant *ProductVariant `gorm:"foreignKey:ProductVariantID" json:"product_variant,omitempty"`
|
||||
}
|
||||
|
||||
func (oi *OrderItem) BeforeCreate(tx *gorm.DB) error {
|
||||
if oi.ID == uuid.Nil {
|
||||
oi.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (OrderItem) TableName() string {
|
||||
return "order_items"
|
||||
}
|
||||
|
||||
func (oi *OrderItem) CalculateTotalPrice() {
|
||||
oi.TotalPrice = float64(oi.Quantity) * oi.UnitPrice
|
||||
}
|
||||
|
||||
func (oi *OrderItem) CanBeModified() bool {
|
||||
return oi.Status == OrderItemStatusPending
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type OrderSequence 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"`
|
||||
OutletID uuid.UUID `gorm:"type:uuid;not null;index" json:"outlet_id"`
|
||||
Year int `gorm:"not null" json:"year"`
|
||||
Month int `gorm:"not null" json:"month"`
|
||||
SequenceNumber int `gorm:"not null;default:0" json:"sequence_number"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
|
||||
Outlet Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`
|
||||
}
|
||||
|
||||
func (os *OrderSequence) BeforeCreate(tx *gorm.DB) error {
|
||||
if os.ID == uuid.Nil {
|
||||
os.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (OrderSequence) TableName() string {
|
||||
return "order_sequences"
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Organization struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
|
||||
Email *string `gorm:"size:255" json:"email" validate:"omitempty,email"`
|
||||
PhoneNumber *string `gorm:"size:20" json:"phone_number" validate:"omitempty"`
|
||||
PlanType string `gorm:"not null;size:50" json:"plan_type" validate:"required,oneof=basic premium enterprise"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Outlets []Outlet `gorm:"foreignKey:OrganizationID" json:"outlets,omitempty"`
|
||||
Users []User `gorm:"foreignKey:OrganizationID" json:"users,omitempty"`
|
||||
}
|
||||
|
||||
func (o *Organization) BeforeCreate(tx *gorm.DB) error {
|
||||
if o.ID == uuid.Nil {
|
||||
id := uuid.New()
|
||||
o.ID = id
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Organization) TableName() string {
|
||||
return "organizations"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Outlet 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" validate:"required"`
|
||||
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
|
||||
Address *string `gorm:"type:text" json:"address"`
|
||||
Timezone *string `gorm:"size:50" json:"timezone"`
|
||||
Currency string `gorm:"size:3;default:'USD'" json:"currency" validate:"len=3"`
|
||||
TaxRate float64 `gorm:"type:decimal(5,4);default:0.0000" json:"tax_rate" validate:"min=0,max=1"`
|
||||
IsActive bool `gorm:"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"`
|
||||
Users []User `gorm:"foreignKey:OutletID" json:"users,omitempty"`
|
||||
Orders []Order `gorm:"foreignKey:OutletID" json:"orders,omitempty"`
|
||||
Inventory []Inventory `gorm:"foreignKey:OutletID" json:"inventory,omitempty"`
|
||||
Settings []OutletSetting `gorm:"foreignKey:OutletID" json:"settings,omitempty"`
|
||||
}
|
||||
|
||||
func (o *Outlet) BeforeCreate(tx *gorm.DB) error {
|
||||
if o.ID == uuid.Nil {
|
||||
o.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Outlet) TableName() string {
|
||||
return "outlets"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type OutletSetting struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
OutletID uuid.UUID `gorm:"type:uuid;not null;index" json:"outlet_id" validate:"required"`
|
||||
Key string `gorm:"not null;size:255;index" json:"key" validate:"required,min=1,max=255"`
|
||||
Value string `gorm:"type:text" json:"value"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Outlet Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`
|
||||
}
|
||||
|
||||
func (os *OutletSetting) BeforeCreate(tx *gorm.DB) error {
|
||||
if os.ID == uuid.Nil {
|
||||
os.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (OutletSetting) TableName() string {
|
||||
return "outlet_settings"
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PaymentMethodType string
|
||||
|
||||
const (
|
||||
PaymentMethodTypeCash PaymentMethodType = "cash"
|
||||
PaymentMethodTypeCard PaymentMethodType = "card"
|
||||
PaymentMethodTypeDigitalWallet PaymentMethodType = "digital_wallet"
|
||||
)
|
||||
|
||||
type PaymentMethod 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" validate:"required"`
|
||||
Name string `gorm:"not null;size:100" json:"name" validate:"required,min=1,max=100"`
|
||||
Type PaymentMethodType `gorm:"not null;size:50" json:"type" validate:"required,oneof=cash card digital_wallet"`
|
||||
Processor *string `gorm:"size:100" json:"processor"`
|
||||
Configuration Metadata `gorm:"type:jsonb;default:'{}'" json:"configuration"`
|
||||
IsActive bool `gorm:"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"`
|
||||
Payments []Payment `gorm:"foreignKey:PaymentMethodID" json:"payments,omitempty"`
|
||||
}
|
||||
|
||||
func (pm *PaymentMethod) BeforeCreate(tx *gorm.DB) error {
|
||||
if pm.ID == uuid.Nil {
|
||||
pm.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (PaymentMethod) TableName() string {
|
||||
return "payment_methods"
|
||||
}
|
||||
|
||||
type PaymentTransactionStatus string
|
||||
|
||||
const (
|
||||
PaymentTransactionStatusPending PaymentTransactionStatus = "pending"
|
||||
PaymentTransactionStatusCompleted PaymentTransactionStatus = "completed"
|
||||
PaymentTransactionStatusFailed PaymentTransactionStatus = "failed"
|
||||
PaymentTransactionStatusRefunded PaymentTransactionStatus = "refunded"
|
||||
)
|
||||
|
||||
type Payment struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
OrderID uuid.UUID `gorm:"type:uuid;not null;index" json:"order_id" validate:"required"`
|
||||
PaymentMethodID uuid.UUID `gorm:"type:uuid;not null;index" json:"payment_method_id" validate:"required"`
|
||||
Amount float64 `gorm:"type:decimal(10,2);not null" json:"amount" validate:"required,min=0"`
|
||||
Status PaymentTransactionStatus `gorm:"default:'pending';size:50" json:"status"`
|
||||
TransactionID *string `gorm:"size:255" json:"transaction_id"`
|
||||
SplitNumber int `gorm:"default:1" json:"split_number"`
|
||||
SplitTotal int `gorm:"default:1" json:"split_total"`
|
||||
SplitDescription *string `gorm:"size:255" json:"split_description,omitempty"`
|
||||
RefundAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"refund_amount"`
|
||||
RefundReason *string `gorm:"size:255" json:"refund_reason,omitempty"`
|
||||
RefundedAt *time.Time `gorm:"" json:"refunded_at,omitempty"`
|
||||
RefundedBy *uuid.UUID `gorm:"type:uuid" json:"refunded_by,omitempty"`
|
||||
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Order Order `gorm:"foreignKey:OrderID" json:"order,omitempty"`
|
||||
PaymentMethod PaymentMethod `gorm:"foreignKey:PaymentMethodID" json:"payment_method,omitempty"`
|
||||
PaymentOrderItems []PaymentOrderItem `gorm:"foreignKey:PaymentID" json:"payment_order_items,omitempty"`
|
||||
}
|
||||
|
||||
func (p *Payment) BeforeCreate(tx *gorm.DB) error {
|
||||
if p.ID == uuid.Nil {
|
||||
p.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Payment) TableName() string {
|
||||
return "payments"
|
||||
}
|
||||
|
||||
func (p *Payment) CanBeRefunded() bool {
|
||||
return p.Status == PaymentTransactionStatusCompleted
|
||||
}
|
||||
|
||||
func (p *Payment) IsSuccessful() bool {
|
||||
return p.Status == PaymentTransactionStatusCompleted
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PaymentOrderItem struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
PaymentID uuid.UUID `gorm:"type:uuid;not null;index" json:"payment_id"`
|
||||
OrderItemID uuid.UUID `gorm:"type:uuid;not null;index" json:"order_item_id"`
|
||||
Amount float64 `gorm:"type:decimal(10,2);not null" json:"amount"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Payment Payment `gorm:"foreignKey:PaymentID" json:"payment,omitempty"`
|
||||
OrderItem OrderItem `gorm:"foreignKey:OrderItemID" json:"order_item,omitempty"`
|
||||
}
|
||||
|
||||
func (poi *PaymentOrderItem) BeforeCreate(tx *gorm.DB) error {
|
||||
if poi.ID == uuid.Nil {
|
||||
poi.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (PaymentOrderItem) TableName() string {
|
||||
return "payment_order_items"
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Product 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" validate:"required"`
|
||||
CategoryID uuid.UUID `gorm:"type:uuid;not null;index" json:"category_id" validate:"required"`
|
||||
SKU *string `gorm:"size:100;index" json:"sku"`
|
||||
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
|
||||
Description *string `gorm:"type:text" json:"description"`
|
||||
Price float64 `gorm:"type:decimal(10,2);not null" json:"price" validate:"required,min=0"`
|
||||
Cost float64 `gorm:"type:decimal(10,2);default:0.00" json:"cost" validate:"min=0"`
|
||||
BusinessType string `gorm:"size:50;default:'restaurant'" json:"business_type"`
|
||||
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||
IsActive bool `gorm:"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"`
|
||||
Category Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
|
||||
ProductVariants []ProductVariant `gorm:"foreignKey:ProductID" json:"variants,omitempty"`
|
||||
Inventory []Inventory `gorm:"foreignKey:ProductID" json:"inventory,omitempty"`
|
||||
OrderItems []OrderItem `gorm:"foreignKey:ProductID" json:"order_items,omitempty"`
|
||||
}
|
||||
|
||||
func (p *Product) BeforeCreate(tx *gorm.DB) error {
|
||||
if p.ID == uuid.Nil {
|
||||
p.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Product) TableName() string {
|
||||
return "products"
|
||||
}
|
||||
|
||||
type ProductVariant struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
ProductID uuid.UUID `gorm:"type:uuid;not null;index" json:"product_id" validate:"required"`
|
||||
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
|
||||
PriceModifier float64 `gorm:"type:decimal(10,2);default:0.00" json:"price_modifier"`
|
||||
Cost float64 `gorm:"type:decimal(10,2);default:0.00" json:"cost" validate:"min=0"`
|
||||
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Product Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
|
||||
OrderItems []OrderItem `gorm:"foreignKey:ProductVariantID" json:"order_items,omitempty"`
|
||||
}
|
||||
|
||||
func (pv *ProductVariant) BeforeCreate(tx *gorm.DB) error {
|
||||
if pv.ID == uuid.Nil {
|
||||
pv.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ProductVariant) TableName() string {
|
||||
return "product_variants"
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserRole string
|
||||
|
||||
const (
|
||||
RoleAdmin UserRole = "admin"
|
||||
RoleManager UserRole = "manager"
|
||||
RoleCashier UserRole = "cashier"
|
||||
RoleWaiter UserRole = "waiter"
|
||||
)
|
||||
|
||||
type Permissions map[string]interface{}
|
||||
|
||||
func (p Permissions) Value() (driver.Value, error) {
|
||||
return json.Marshal(p)
|
||||
}
|
||||
|
||||
func (p *Permissions) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*p = make(Permissions)
|
||||
return nil
|
||||
}
|
||||
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
return errors.New("type assertion to []byte failed")
|
||||
}
|
||||
|
||||
return json.Unmarshal(bytes, p)
|
||||
}
|
||||
|
||||
type User 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" validate:"required"`
|
||||
OutletID *uuid.UUID `gorm:"type:uuid;index" json:"outlet_id"`
|
||||
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
|
||||
Email string `gorm:"uniqueIndex;not null;size:255" json:"email" validate:"required,email"`
|
||||
PasswordHash string `gorm:"not null;size:255" json:"-"`
|
||||
Role UserRole `gorm:"not null;size:50" json:"role" validate:"required,oneof=admin manager cashier waiter"`
|
||||
Permissions Permissions `gorm:"type:jsonb;default:'{}'" json:"permissions"`
|
||||
IsActive bool `gorm:"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"`
|
||||
Outlet *Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`
|
||||
Orders []Order `gorm:"foreignKey:UserID" json:"orders,omitempty"`
|
||||
}
|
||||
|
||||
func (u *User) BeforeCreate(tx *gorm.DB) error {
|
||||
if u.ID == uuid.Nil {
|
||||
u.ID = uuid.New()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
func (u *User) HasPermission(permission string) bool {
|
||||
if u.Role == RoleAdmin {
|
||||
return true
|
||||
}
|
||||
|
||||
if value, exists := u.Permissions[permission]; exists {
|
||||
if hasPermission, ok := value.(bool); ok {
|
||||
return hasPermission
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *User) CanAccessOutlet(outletID uuid.UUID) bool {
|
||||
if u.Role == RoleAdmin {
|
||||
return true
|
||||
}
|
||||
|
||||
if u.OutletID != nil && *u.OutletID == outletID {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/constants/role"
|
||||
"enaklo-pos-be/internal/constants/userstatus"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AuthData struct {
|
||||
Token string `json:"token"`
|
||||
UserID int64 `gorm:"column:user_id"`
|
||||
RoleID int `gorm:"column:role_id"`
|
||||
OrganizationID int64 `gorm:"column:organization_id"`
|
||||
}
|
||||
|
||||
type UserDB struct {
|
||||
ID int64 `gorm:"primary_key;column:id" json:"id"`
|
||||
Name string `gorm:"column:name" json:"name"`
|
||||
Email string `gorm:"column:email" json:"email"`
|
||||
Password string `gorm:"column:password" json:"-"`
|
||||
Status userstatus.UserStatus `gorm:"column:status" json:"status"`
|
||||
UserType string `gorm:"column:user_type" json:"user_type"`
|
||||
PhoneNumber string `gorm:"column:phone_number" json:"phone_number"`
|
||||
NIK string `gorm:"column:nik" json:"nik"`
|
||||
RoleID int64 `gorm:"column:role_id" json:"role_id"`
|
||||
RoleName string `gorm:"column:role_name" json:"role_name"`
|
||||
PartnerID *int64 `gorm:"column:partner_id" json:"partner_id"`
|
||||
SiteID *int64 `gorm:"column:site_id" json:"site_id"`
|
||||
SiteName string `gorm:"column:name" json:"site_name"`
|
||||
PartnerName string `gorm:"column:partner_name" json:"partner_name"`
|
||||
PartnerStatus string `gorm:"column:partner_status" json:"partner_status"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
DeletedAt *time.Time `gorm:"column:deleted_at" json:"deleted_at"`
|
||||
CreatedBy int64 `gorm:"column:created_by" json:"created_by"`
|
||||
UpdatedBy int64 `gorm:"column:updated_by" json:"updated_by"`
|
||||
ResetPassword bool `gorm:"column:reset_password" json:"reset_password"`
|
||||
}
|
||||
|
||||
func (u *UserDB) ToCustomer() *Customer {
|
||||
if u == nil {
|
||||
return &Customer{}
|
||||
}
|
||||
|
||||
userEntity := &Customer{
|
||||
ID: u.ID,
|
||||
Name: u.Name,
|
||||
Email: u.Email,
|
||||
PhoneNumber: u.PhoneNumber,
|
||||
Status: u.Status,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
RoleID: role.Role(u.RoleID),
|
||||
RoleName: u.RoleName,
|
||||
PartnerID: u.PartnerID,
|
||||
PartnerName: u.PartnerName,
|
||||
SiteID: u.SiteID,
|
||||
SiteName: u.SiteName,
|
||||
ResetPassword: u.ResetPassword,
|
||||
}
|
||||
|
||||
return userEntity
|
||||
}
|
||||
|
||||
func (u *UserDB) ToUser() *User {
|
||||
if u == nil {
|
||||
return &User{}
|
||||
}
|
||||
|
||||
userEntity := &User{
|
||||
ID: u.ID,
|
||||
Name: u.Name,
|
||||
Email: u.Email,
|
||||
NIK: u.NIK,
|
||||
PhoneNumber: u.PhoneNumber,
|
||||
Status: u.Status,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
RoleID: role.Role(u.RoleID),
|
||||
RoleName: u.RoleName,
|
||||
PartnerID: u.PartnerID,
|
||||
PartnerName: u.PartnerName,
|
||||
SiteID: u.SiteID,
|
||||
SiteName: u.SiteName,
|
||||
ResetPassword: u.ResetPassword,
|
||||
}
|
||||
|
||||
return userEntity
|
||||
}
|
||||
|
||||
func (u *UserDB) ToUserRoleDB() *UserRoleDB {
|
||||
if u == nil {
|
||||
return &UserRoleDB{}
|
||||
}
|
||||
|
||||
userRole := &UserRoleDB{
|
||||
ID: 0,
|
||||
UserID: u.ID,
|
||||
RoleID: u.RoleID,
|
||||
PartnerID: u.PartnerID,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
SiteID: u.SiteID,
|
||||
}
|
||||
|
||||
return userRole
|
||||
}
|
||||
|
||||
func (UserDB) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
func (u *UserDB) ToUserAuthenticate(signedToken string, license PartnerLicense) *AuthenticateUser {
|
||||
return &AuthenticateUser{
|
||||
ID: u.ID,
|
||||
Token: signedToken,
|
||||
Name: u.Name,
|
||||
RoleID: role.Role(u.RoleID),
|
||||
RoleName: u.RoleName,
|
||||
PartnerID: u.PartnerID,
|
||||
PartnerName: u.PartnerName,
|
||||
PartnerStatus: u.PartnerStatus,
|
||||
SiteID: u.SiteID,
|
||||
SiteName: u.SiteName,
|
||||
ResetPassword: u.ResetPassword,
|
||||
PartnerLicense: license,
|
||||
UserType: u.UserType,
|
||||
}
|
||||
}
|
||||
|
||||
type UserSearch struct {
|
||||
Search string
|
||||
Name string
|
||||
RoleID int64
|
||||
PartnerID int64
|
||||
SiteID int64
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
type UserList []*UserDB
|
||||
|
||||
func (b *UserList) ToUserList() []*User {
|
||||
var users []*User
|
||||
for _, user := range *b {
|
||||
users = append(users, user.ToUser())
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
func (u *UserDB) ToUpdatedUser(req User) error {
|
||||
if req.Name != "" {
|
||||
u.Name = req.Name
|
||||
}
|
||||
|
||||
if req.Email != "" {
|
||||
u.Email = req.Email
|
||||
}
|
||||
|
||||
if req.PhoneNumber != "" {
|
||||
u.PhoneNumber = req.PhoneNumber
|
||||
}
|
||||
|
||||
if req.NIK != "" {
|
||||
u.NIK = req.NIK
|
||||
}
|
||||
|
||||
u.RoleID = int64(req.RoleID)
|
||||
|
||||
if req.Password != "" {
|
||||
hashedPassword, err := req.HashedPassword(req.Password)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Password = hashedPassword
|
||||
}
|
||||
|
||||
u.SiteID = req.SiteID
|
||||
u.PartnerID = req.PartnerID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *UserDB) SetDeleted(updatedby int64) {
|
||||
currentTime := time.Now()
|
||||
o.DeletedAt = ¤tTime
|
||||
o.UpdatedBy = updatedby
|
||||
o.Status = userstatus.Inactive
|
||||
}
|
||||
|
||||
type MemberList []*Customer
|
||||
type CustomerList []*UserDB
|
||||
|
||||
type CustomerSearch struct {
|
||||
Search string
|
||||
Name string
|
||||
RoleID int64
|
||||
PartnerID int64
|
||||
SiteID int64
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
func (b *CustomerList) ToCustomerList() []*Customer {
|
||||
var users []*Customer
|
||||
for _, user := range *b {
|
||||
if len(user.Name) > 0 {
|
||||
users = append(users, user.ToCustomer())
|
||||
}
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
type MemberSearch struct {
|
||||
Search string
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package entity
|
||||
|
||||
type Balance struct {
|
||||
PartnerID int64
|
||||
Balance float64
|
||||
AuthBalance float64
|
||||
}
|
||||
|
||||
type BalanceWithdrawInquiry struct {
|
||||
PartnerID int64
|
||||
Amount int64
|
||||
}
|
||||
|
||||
type BalanceWithdrawInquiryResponse struct {
|
||||
PartnerID int64
|
||||
Amount int64
|
||||
Total int64
|
||||
Fee int64
|
||||
Token string
|
||||
}
|
||||
|
||||
type WalletWithdrawResponse struct {
|
||||
TransactionID string
|
||||
Status string
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
type CashierSession struct {
|
||||
ID int64
|
||||
PartnerID int64
|
||||
CashierID int64
|
||||
OpenedAt time.Time
|
||||
ClosedAt *time.Time
|
||||
OpeningAmount float64
|
||||
ClosingAmount *float64
|
||||
ExpectedAmount *float64
|
||||
Status string
|
||||
}
|
||||
|
||||
type PaymentSummary struct {
|
||||
PaymentType string
|
||||
PaymentProvider string
|
||||
TotalAmount float64
|
||||
}
|
||||
|
||||
type CashierSessionReport struct {
|
||||
SessionID int64
|
||||
ExpectedAmount float64
|
||||
ClosingAmount float64
|
||||
Payments []PaymentSummary
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package entity
|
||||
|
||||
type Category struct {
|
||||
ID int64
|
||||
PartnerID int64
|
||||
Name string
|
||||
CreatedAt int64
|
||||
UpdatedAt int64
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
type CustomerResolutionRequest struct {
|
||||
ID *int64
|
||||
Name string
|
||||
Email string
|
||||
PhoneNumber string
|
||||
BirthDate time.Time
|
||||
Password string
|
||||
}
|
||||
|
||||
type CustomerCheckResponse struct {
|
||||
Exists bool
|
||||
Customer *Customer
|
||||
Message string
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
package entity
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package entity
|
||||
|
||||
type DiscoverySearch struct {
|
||||
Lat float64
|
||||
Long float64
|
||||
Name string
|
||||
Region string
|
||||
Status string
|
||||
Discover string
|
||||
Offset int
|
||||
Limit int
|
||||
Radius int
|
||||
}
|
||||
|
||||
type DiscoverySearchResp struct {
|
||||
ExploreRegions []ExploreRegion `json:"exploreRegions"`
|
||||
ExploreDestinations []ExploreDestination `json:"exploreDestinations"`
|
||||
MustVisit []MustVisit `json:"mustVisit"`
|
||||
}
|
||||
|
||||
type ExploreRegion struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ExploreDestination struct {
|
||||
Name string `json:"name"`
|
||||
ImageURL string `json:"image_url"`
|
||||
}
|
||||
|
||||
type MustVisit struct {
|
||||
SiteID int64 `json:"site_id"`
|
||||
Name string `json:"name"`
|
||||
Location string `json:"location"`
|
||||
Rating float64 `json:"rating"`
|
||||
ReviewCount int `json:"reviewCount"`
|
||||
Price float64 `json:"price"`
|
||||
ImageURL string `json:"imageUrl"`
|
||||
Region string `json:"region"`
|
||||
Regency string `json:"regency"`
|
||||
}
|
||||
|
||||
type DiscoveryGetByIDResp struct {
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package entity
|
||||
|
||||
type (
|
||||
SendEmailNotificationParam struct {
|
||||
Sender string
|
||||
Recipient string
|
||||
CcEmails []string
|
||||
Subject string
|
||||
TemplateName string
|
||||
TemplatePath string
|
||||
Data interface{}
|
||||
}
|
||||
)
|
||||
@@ -1,158 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Status string
|
||||
|
||||
type Event struct {
|
||||
ID int64
|
||||
Name string
|
||||
Description string
|
||||
StartDate time.Time
|
||||
EndDate time.Time
|
||||
Location string
|
||||
Level string
|
||||
Included StringArray `gorm:"type:text[]"`
|
||||
Price float64
|
||||
Paid bool
|
||||
LocationID *int64
|
||||
Status Status
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
}
|
||||
|
||||
type StringArray []string
|
||||
|
||||
func (a StringArray) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
joined := "{" + strings.Join(a, ",") + "}"
|
||||
|
||||
return []byte(joined), nil
|
||||
}
|
||||
|
||||
func (a *StringArray) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*a = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
srcStr, ok := src.(string)
|
||||
if !ok {
|
||||
return errors.New("failed to scan StringArray")
|
||||
}
|
||||
|
||||
// Remove the curly braces and split the string into elements
|
||||
if len(srcStr) < 2 || srcStr[0] != '{' || srcStr[len(srcStr)-1] != '}' {
|
||||
return errors.New("invalid format for StringArray")
|
||||
}
|
||||
srcStr = srcStr[1 : len(srcStr)-1]
|
||||
|
||||
*a = strings.Split(srcStr, ",")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type EventSearch struct {
|
||||
Name string
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
type EventList []*EventDB
|
||||
|
||||
type EventDB struct {
|
||||
Event
|
||||
}
|
||||
|
||||
func (e *Event) ToEventDB() *EventDB {
|
||||
return &EventDB{
|
||||
Event: *e,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EventDB) ToEvent() *Event {
|
||||
return &Event{
|
||||
ID: e.ID,
|
||||
Name: e.Name,
|
||||
Description: e.Description,
|
||||
StartDate: e.StartDate,
|
||||
EndDate: e.EndDate,
|
||||
Location: e.Location,
|
||||
Level: e.Level,
|
||||
Included: e.Included,
|
||||
Price: e.Price,
|
||||
Paid: e.Paid,
|
||||
LocationID: e.LocationID,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
Status: e.Status,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EventList) ToEventList() []*Event {
|
||||
var events []*Event
|
||||
for _, event := range *e {
|
||||
events = append(events, event.ToEvent())
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
func (EventDB) TableName() string {
|
||||
return "events"
|
||||
}
|
||||
|
||||
func (o *EventDB) ToUpdatedEvent(req Event) {
|
||||
if req.Name != "" {
|
||||
o.Name = req.Name
|
||||
}
|
||||
|
||||
if req.Description != "" {
|
||||
o.Description = req.Description
|
||||
}
|
||||
|
||||
if !req.StartDate.IsZero() {
|
||||
o.StartDate = req.StartDate
|
||||
}
|
||||
|
||||
if !req.EndDate.IsZero() {
|
||||
o.EndDate = req.EndDate
|
||||
}
|
||||
|
||||
if req.Location != "" {
|
||||
o.Location = req.Location
|
||||
}
|
||||
|
||||
if req.Level != "" {
|
||||
o.Level = req.Level
|
||||
}
|
||||
|
||||
if req.Included != nil && len(req.Included) > 0 {
|
||||
o.Included = req.Included
|
||||
}
|
||||
|
||||
if req.Price != 0 {
|
||||
o.Price = req.Price
|
||||
}
|
||||
|
||||
if req.LocationID != nil {
|
||||
o.LocationID = req.LocationID
|
||||
}
|
||||
|
||||
if req.Status != "" {
|
||||
o.Status = req.Status
|
||||
}
|
||||
}
|
||||
|
||||
func (o *EventDB) SetDeleted() {
|
||||
currentTime := time.Now()
|
||||
o.DeletedAt = ¤tTime
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
type InProgressOrder struct {
|
||||
ID string
|
||||
PartnerID int64
|
||||
CustomerID *int64
|
||||
CustomerName string
|
||||
CreatedBy int64
|
||||
PaymentType string
|
||||
PaymentProvider string
|
||||
OrderItems []InProgressOrderItem
|
||||
Payment Payment
|
||||
User User
|
||||
Source string
|
||||
OrderType string
|
||||
TableNumber string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type InProgressOrderItem struct {
|
||||
ID int64
|
||||
InProgressOrderID int64
|
||||
ItemID int64
|
||||
Quantity int
|
||||
Product *Product
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "github.com/golang-jwt/jwt"
|
||||
|
||||
type JWTAuthClaims struct {
|
||||
UserID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Role int `json:"role"`
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
SiteID int64 `json:"site_id"`
|
||||
SiteName string `json:"site_name"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
type JWTAuthClaimsCustomer struct {
|
||||
UserID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
type JWTOrderClaims struct {
|
||||
PartnerID int64 `json:"id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
InquiryID string `json:"inquiry_id"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
type JWTWithdrawClaims struct {
|
||||
ID int64 `json:"id"`
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
OrderID int64 `json:"order_id"`
|
||||
Amount int64 `json:"amount"`
|
||||
Fee int64 `json:"fee"`
|
||||
Total int64 `json:"total"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
type License struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"`
|
||||
PartnerID int64 `gorm:"type:bigint;not null"`
|
||||
Name string `gorm:"type:varchar(255);not null"`
|
||||
StartDate time.Time `gorm:"type:date;not null"`
|
||||
EndDate time.Time `gorm:"type:date;not null"`
|
||||
RenewalDate *time.Time `gorm:"type:date"`
|
||||
SerialNumber string `gorm:"type:varchar(255);unique;not null"`
|
||||
CreatedBy int64 `gorm:"type:bigint;not null"`
|
||||
UpdatedBy int64 `gorm:"type:bigint;not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
type LicenseGetAll struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"`
|
||||
PartnerID int64 `gorm:"type:bigint;not null"`
|
||||
Name string `gorm:"type:varchar(255);not null"`
|
||||
StartDate time.Time `gorm:"type:date;not null"`
|
||||
EndDate time.Time `gorm:"type:date;not null"`
|
||||
RenewalDate *time.Time `gorm:"type:date"`
|
||||
SerialNumber string `gorm:"type:varchar(255);unique;not null"`
|
||||
CreatedBy int64 `gorm:"type:bigint;not null"`
|
||||
UpdatedBy int64 `gorm:"type:bigint;not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
PartnerName string `gorm:"type:varchar(255);not null"`
|
||||
LicenseStatus string `gorm:"type:string(255);not null"`
|
||||
CreatedByName string `gorm:"type:string(255);not null"`
|
||||
DaysToExpire int64 `gorm:"type:bigint"`
|
||||
}
|
||||
|
||||
func (License) TableName() string {
|
||||
return "licenses"
|
||||
}
|
||||
|
||||
type LicenseDB struct {
|
||||
License
|
||||
}
|
||||
|
||||
func (l *License) ToLicenseDB() *LicenseDB {
|
||||
return &LicenseDB{
|
||||
License: *l,
|
||||
}
|
||||
}
|
||||
|
||||
func (LicenseDB) TableName() string {
|
||||
return "licenses"
|
||||
}
|
||||
|
||||
func (e *LicenseDB) ToLicense() *License {
|
||||
return &License{
|
||||
ID: e.ID,
|
||||
PartnerID: e.PartnerID,
|
||||
Name: e.Name,
|
||||
StartDate: e.StartDate,
|
||||
EndDate: e.EndDate,
|
||||
RenewalDate: e.RenewalDate,
|
||||
SerialNumber: e.SerialNumber,
|
||||
CreatedBy: e.CreatedBy,
|
||||
UpdatedBy: e.UpdatedBy,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LicenseDB) ToUpdatedLicense(updatedBy int64, req License) {
|
||||
o.UpdatedBy = updatedBy
|
||||
|
||||
if req.Name != "" {
|
||||
o.Name = req.Name
|
||||
}
|
||||
|
||||
if !req.StartDate.IsZero() {
|
||||
o.StartDate = req.StartDate
|
||||
}
|
||||
|
||||
if !req.EndDate.IsZero() {
|
||||
o.EndDate = req.EndDate
|
||||
}
|
||||
|
||||
if req.RenewalDate != nil {
|
||||
o.RenewalDate = req.RenewalDate
|
||||
}
|
||||
|
||||
if req.SerialNumber != "" {
|
||||
o.SerialNumber = req.SerialNumber
|
||||
}
|
||||
}
|
||||
|
||||
type PartnerLicense struct {
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
LicenseStatus string `json:"license_status"`
|
||||
DaysToExpire int64 `json:"days_to_expire"`
|
||||
}
|
||||
|
||||
func (l *LicenseDB) ToPartnerLicense() PartnerLicense {
|
||||
location, err := time.LoadLocation("Asia/Jakarta")
|
||||
if err != nil {
|
||||
location = time.FixedZone("GMT+7", 7*60*60)
|
||||
}
|
||||
|
||||
// Reinterpret StartDate as GMT+7 without changing the actual time values
|
||||
startDateInGMT7 := time.Date(
|
||||
l.StartDate.Year(),
|
||||
l.StartDate.Month(),
|
||||
l.StartDate.Day(),
|
||||
l.StartDate.Hour(),
|
||||
l.StartDate.Minute(),
|
||||
l.StartDate.Second(),
|
||||
l.StartDate.Nanosecond(),
|
||||
location,
|
||||
)
|
||||
|
||||
// Convert EndDate similarly, if needed
|
||||
endDateInGMT7 := time.Date(
|
||||
l.EndDate.Year(),
|
||||
l.EndDate.Month(),
|
||||
l.EndDate.Day(),
|
||||
l.EndDate.Hour(),
|
||||
l.EndDate.Minute(),
|
||||
l.EndDate.Second(),
|
||||
l.EndDate.Nanosecond(),
|
||||
location,
|
||||
)
|
||||
|
||||
now := time.Now().In(location)
|
||||
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
|
||||
daysToExpire := int64(endDateInGMT7.Sub(startOfDay).Hours() / 24)
|
||||
var licenseStatus string
|
||||
|
||||
if startDateInGMT7.After(startOfDay) {
|
||||
licenseStatus = "INACTIVE"
|
||||
} else if startDateInGMT7.Equal(startOfDay) || (startDateInGMT7.Before(startOfDay) && endDateInGMT7.After(startOfDay) ||
|
||||
endDateInGMT7.Equal(startOfDay)) {
|
||||
if daysToExpire < 0 {
|
||||
licenseStatus = "EXPIRED"
|
||||
} else if daysToExpire <= 30 {
|
||||
licenseStatus = "EXPIRING_SOON"
|
||||
} else {
|
||||
licenseStatus = "ACTIVE"
|
||||
}
|
||||
} else if endDateInGMT7.Before(startOfDay) {
|
||||
licenseStatus = "EXPIRED"
|
||||
} else {
|
||||
licenseStatus = "ACTIVE"
|
||||
}
|
||||
|
||||
return PartnerLicense{
|
||||
PartnerID: l.PartnerID,
|
||||
DaysToExpire: daysToExpire,
|
||||
LicenseStatus: licenseStatus,
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package entity
|
||||
|
||||
type LinkQuRequest struct {
|
||||
CustomerID string
|
||||
CustomerName string
|
||||
CustomerPhone string
|
||||
CustomerEmail string
|
||||
PaymentReferenceID string
|
||||
PaymentMethod string
|
||||
TotalAmount int64
|
||||
BankCode string
|
||||
OrderItems []OrderItem
|
||||
}
|
||||
|
||||
type LinkQuCallback struct {
|
||||
PartnerReff string
|
||||
PaymentReff string
|
||||
Status string
|
||||
Signature string
|
||||
}
|
||||
|
||||
type LinkQuQRISResponse struct {
|
||||
Time int `json:"time"`
|
||||
Amount int64 `json:"amount"`
|
||||
Expired string `json:"expired"`
|
||||
CustomerPhone string `json:"customer_phone"`
|
||||
CustomerID string `json:"customer_id"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
CustomerEmail string `json:"customer_email"`
|
||||
PartnerReff string `json:"partner_reff"`
|
||||
Username string `json:"username"`
|
||||
Pin string `json:"pin"`
|
||||
Status string `json:"status"`
|
||||
ResponseCode string `json:"response_code"`
|
||||
ResponseDesc string `json:"response_desc"`
|
||||
ImageQRIS string `json:"imageqris"`
|
||||
PartnerReff2 string `json:"partner_reff2"`
|
||||
FeeAdmin int `json:"feeadmin"`
|
||||
QRISText string `json:"qris_text"`
|
||||
Signature string `json:"signature"`
|
||||
URLCallback string `json:"url_callback"`
|
||||
}
|
||||
|
||||
type LinkQuPaymentVAResponse struct {
|
||||
Time int `json:"time"`
|
||||
Amount int `json:"amount"`
|
||||
Expired string `json:"expired"`
|
||||
BankCode string `json:"bank_code"`
|
||||
BankName string `json:"bank_name"`
|
||||
CustomerPhone string `json:"customer_phone"`
|
||||
CustomerID string `json:"customer_id"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
CustomerEmail string `json:"customer_email"`
|
||||
PartnerReff string `json:"partner_reff"`
|
||||
Username string `json:"username"`
|
||||
Pin string `json:"pin"`
|
||||
Status string `json:"status"`
|
||||
ResponseCode string `json:"response_code"`
|
||||
ResponseDesc string `json:"response_desc"`
|
||||
VirtualAccount string `json:"virtual_account"`
|
||||
PartnerReff2 string `json:"partner_reff2"`
|
||||
Remark string `json:"remark"`
|
||||
Signature string `json:"signature"`
|
||||
UrlCallback string `json:"url_callback"`
|
||||
}
|
||||
|
||||
type LinkQuCheckStatusResponse struct {
|
||||
ResponseCode string `json:"rc"`
|
||||
ResponseDesc string `json:"rd"`
|
||||
Total int64 `json:"total"`
|
||||
Balance int64 `json:"balance"`
|
||||
Data LinkQuCheckStatusData `json:"data"`
|
||||
Request map[string]interface{} `json:"request"`
|
||||
LastUpdate string `json:"lastUpdate"`
|
||||
DataAdditional map[string]interface{} `json:"dataadditional"`
|
||||
}
|
||||
|
||||
type LinkQuCheckStatusData struct {
|
||||
InquiryReff int64 `json:"inquiry_reff"`
|
||||
PaymentReff int64 `json:"payment_reff"`
|
||||
PartnerReff string `json:"partner_reff"`
|
||||
Reference string `json:"reference"`
|
||||
ProductID string `json:"id_produk"`
|
||||
ProductName string `json:"nama_produk"`
|
||||
ProductGroup string `json:"grup_produk"`
|
||||
Debitted int64 `json:"debitted"`
|
||||
Amount int64 `json:"amount"`
|
||||
AmountFee int64 `json:"amountfee"`
|
||||
Info1 string `json:"info1"`
|
||||
Info2 string `json:"info2"`
|
||||
Info3 string `json:"info3"`
|
||||
Info4 string `json:"info4"`
|
||||
StatusTrx string `json:"status_trx"`
|
||||
StatusDesc string `json:"status_desc"`
|
||||
StatusPaid string `json:"status_paid"`
|
||||
Balance int64 `json:"balance"`
|
||||
TipsQRIS int64 `json:"tips_qris"`
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/constants"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MemberRegistrationRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Phone string `json:"phone" validate:"required"`
|
||||
BirthDate time.Time `json:"birth_date"`
|
||||
BranchID int64 `json:"branch_id" validate:"required"`
|
||||
CashierID int64 `json:"cashier_id" validate:"required"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func (m *MemberRegistrationRequest) GetHashPassword() string {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(m.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(hashedPassword)
|
||||
}
|
||||
|
||||
type MemberRegistrationResponse struct {
|
||||
Token string `json:"token"`
|
||||
Status string `json:"status"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type MemberRegistration struct {
|
||||
ID string `json:"id"`
|
||||
Token string `json:"token"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
BirthDate time.Time `json:"birth_date"`
|
||||
OTP string `json:"-"` // Not exposed in JSON responses
|
||||
Status constants.RegistrationStatus `json:"status"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
BranchID int64 `json:"branch_id"`
|
||||
CashierID int64 `json:"cashier_id"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type MemberVerificationRequest struct {
|
||||
Token string `json:"token" validate:"required"`
|
||||
OTP string `json:"otp" validate:"required"`
|
||||
}
|
||||
|
||||
type MemberVerificationResponse struct {
|
||||
Auth *AuthenticateUser
|
||||
CustomerID int64 `json:"customer_id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Points int `json:"points"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type MemberRegistrationStatus struct {
|
||||
Token string `json:"token"`
|
||||
Status string `json:"status"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
IsExpired bool `json:"is_expired"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type ResendOTPRequest struct {
|
||||
Token string `json:"token" validate:"required"`
|
||||
}
|
||||
|
||||
type ResendOTPResponse struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package entity
|
||||
|
||||
type MidtransResponse struct {
|
||||
Token string
|
||||
RedirectURL string
|
||||
}
|
||||
|
||||
type MidtransRequest struct {
|
||||
PaymentReferenceID string
|
||||
PaymentMethod string
|
||||
TotalAmount int64
|
||||
OrderItems []OrderItem
|
||||
}
|
||||
|
||||
type MidtransQrisResponse struct {
|
||||
QrCodeUrl string
|
||||
OrderID string
|
||||
Amount int64
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Order struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
PartnerID int64 `gorm:"type:int;column:partner_id"`
|
||||
Status string `gorm:"type:varchar;column:status"`
|
||||
Amount float64 `gorm:"type:numeric;not null;column:amount"`
|
||||
Total float64 `gorm:"type:numeric;not null;column:total"`
|
||||
Tax float64 `gorm:"type:numeric;not null;column:tax"`
|
||||
CustomerID *int64
|
||||
CustomerName string
|
||||
InquiryID *string
|
||||
Site *Site `gorm:"foreignKey:SiteID;constraint:OnDelete:CASCADE;"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
|
||||
CreatedBy int64 `gorm:"type:int;column:created_by"`
|
||||
PaymentType string `gorm:"type:varchar;column:payment_type"`
|
||||
PaymentProvider string `gorm:"type:varchar;column:payment_provider"`
|
||||
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
|
||||
OrderItems []OrderItem `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"`
|
||||
Payment Payment `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"`
|
||||
User User `gorm:"foreignKey:CreatedBy;constraint:OnDelete:CASCADE;"`
|
||||
Source string `gorm:"type:varchar;column:source"`
|
||||
OrderType string `gorm:"type:varchar;column:order_type"`
|
||||
CashierSessionID int64 `gorm:"type:varchar;column:cashier_session_id"`
|
||||
TableNumber string
|
||||
InProgressOrderID int64
|
||||
}
|
||||
|
||||
func (o *Order) IsMemberOrder() bool {
|
||||
return o.CustomerID != nil && *o.CustomerID > 0
|
||||
}
|
||||
|
||||
type OrderDB struct {
|
||||
Order
|
||||
}
|
||||
|
||||
func (b *Order) ToOrderDB() *OrderDB {
|
||||
return &OrderDB{
|
||||
Order: *b,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *OrderDB) ToSumAmount() *Order {
|
||||
return &Order{
|
||||
Amount: e.Amount,
|
||||
}
|
||||
}
|
||||
|
||||
type OrderResponse struct {
|
||||
Order *Order
|
||||
}
|
||||
|
||||
type CheckinResponse struct {
|
||||
Order *Order
|
||||
Token string
|
||||
}
|
||||
|
||||
type CheckinExecute struct {
|
||||
Order *Order
|
||||
Token string
|
||||
}
|
||||
|
||||
type ExecuteOrderResponse struct {
|
||||
Order *Order
|
||||
QRCode string
|
||||
VirtualAccount string
|
||||
BankName string
|
||||
BankCode string
|
||||
PaymentToken string
|
||||
RedirectURL string
|
||||
}
|
||||
|
||||
func (Order) TableName() string {
|
||||
return "orders"
|
||||
}
|
||||
|
||||
type OrderItem struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:order_item_id"`
|
||||
OrderID int64 `gorm:"type:int;column:order_id"`
|
||||
ItemID int64 `gorm:"type:int;column:item_id"`
|
||||
ItemType string `gorm:"type:varchar;column:item_type"`
|
||||
Price float64 `gorm:"type:numeric;not null;column:price"`
|
||||
Quantity int `gorm:"type:int;column:quantity"`
|
||||
Status string `gorm:"type:varchar;column:status;default:ACTIVE"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
|
||||
CreatedBy int64 `gorm:"type:int;column:created_by"`
|
||||
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
|
||||
Product *Product `gorm:"foreignKey:ItemID;references:ID"`
|
||||
ItemName string `gorm:"type:varchar;column:item_name"`
|
||||
Notes string `gorm:"type:varchar;column:notes"`
|
||||
}
|
||||
|
||||
func (OrderItem) TableName() string {
|
||||
return "order_items"
|
||||
}
|
||||
|
||||
type OrderRequest struct {
|
||||
Source string
|
||||
CreatedBy int64
|
||||
PartnerID int64
|
||||
PaymentMethod string
|
||||
OrderItems []OrderItemRequest
|
||||
CustomerID *int64
|
||||
CustomerName string
|
||||
CustomerEmail string
|
||||
CustomerPhoneNumber string
|
||||
TableNumber string
|
||||
PaymentProvider string
|
||||
OrderType string
|
||||
ID int64
|
||||
CashierSessionID int64
|
||||
}
|
||||
|
||||
type OrderItemRequest struct {
|
||||
ProductID int64 `json:"product_id" validate:"required"`
|
||||
Quantity int `json:"quantity" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
type PartialRefundItem struct {
|
||||
OrderItemID int64 `json:"order_item_id" validate:"required"`
|
||||
Quantity int `json:"quantity" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
type VoidItem struct {
|
||||
OrderItemID int64 `json:"order_item_id" validate:"required"`
|
||||
Quantity int `json:"quantity" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
type SplitBillSplit struct {
|
||||
CustomerName string `json:"customer_name" validate:"required"`
|
||||
CustomerID *int64 `json:"customer_id"`
|
||||
Items []SplitBillItem `json:"items,omitempty" validate:"required_if=Type ITEM,dive"`
|
||||
Amount float64 `json:"amount,omitempty" validate:"required_if=Type AMOUNT,min=0"`
|
||||
}
|
||||
|
||||
type SplitBillItem struct {
|
||||
OrderItemID int64 `json:"order_item_id" validate:"required"`
|
||||
Quantity int `json:"quantity" validate:"required,min=1"`
|
||||
CustomerName string `json:"customer_name" validate:"required"`
|
||||
CustomerID *int64 `json:"customer_id"`
|
||||
}
|
||||
|
||||
type OrderExecuteRequest struct {
|
||||
CreatedBy int64
|
||||
PartnerID int64
|
||||
Token string
|
||||
}
|
||||
|
||||
func (o *Order) SetExecutePaymentStatus() {
|
||||
o.Status = "PAID"
|
||||
}
|
||||
|
||||
type CallbackRequest struct {
|
||||
TransactionStatus string `json:"transaction_status"`
|
||||
TransactionID string `json:"transaction_id"`
|
||||
}
|
||||
|
||||
type HistoryOrder struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
Employee string `gorm:"type:varchar;column:employee"`
|
||||
Site string `gorm:"type:varchar;column:site"`
|
||||
Timestamp time.Time `gorm:"autoCreateTime;column:timestamp"`
|
||||
BookingTime time.Time `gorm:"autoCreateTime;column:booking_time"`
|
||||
Tickets []string `gorm:"-"`
|
||||
RawTickets string `gorm:"type:text;column:tickets"`
|
||||
PaymentType string `gorm:"type:varchar;column:payment_type"`
|
||||
Status string `gorm:"type:varchar;column:status"`
|
||||
Amount float64 `gorm:"type:numeric;column:amount"`
|
||||
VisitDate time.Time `gorm:"type:date;column:visit_date"`
|
||||
TicketStatus string `gorm:"type:varchar;column:ticket_status"`
|
||||
Source string `gorm:"type:numeric;column:source"`
|
||||
}
|
||||
|
||||
func (h *HistoryOrder) GetPaymentStatus() string {
|
||||
if h.Status == "PAID" {
|
||||
return "E-TICKET TELAH TERBIT"
|
||||
}
|
||||
|
||||
if h.Status == "PENDING" {
|
||||
return "MENUNGGU PEMBAYARAN"
|
||||
}
|
||||
|
||||
if h.Status == "EXPIRED" {
|
||||
return "KADALUWARSA"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
type HistoryOrderDB struct {
|
||||
HistoryOrder
|
||||
}
|
||||
|
||||
type OrderSearch struct {
|
||||
PartnerID *int64
|
||||
SiteID *int64
|
||||
IsAdmin bool
|
||||
CreatedBy int64
|
||||
PaymentType string
|
||||
Status string
|
||||
Limit int
|
||||
Offset int
|
||||
StartDate string
|
||||
EndDate string
|
||||
Period string
|
||||
IsCustomer bool
|
||||
Source string
|
||||
}
|
||||
|
||||
type HistoryOrderList []*HistoryOrderDB
|
||||
|
||||
func (b *HistoryOrder) ToHistoryOrderDB() *HistoryOrderDB {
|
||||
return &HistoryOrderDB{
|
||||
HistoryOrder: *b,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *HistoryOrderDB) ToHistoryOrder() *HistoryOrder {
|
||||
return &HistoryOrder{
|
||||
ID: e.ID,
|
||||
Employee: e.Employee,
|
||||
Site: e.Site,
|
||||
Timestamp: e.Timestamp,
|
||||
BookingTime: e.BookingTime,
|
||||
Tickets: e.Tickets,
|
||||
RawTickets: e.RawTickets,
|
||||
PaymentType: e.PaymentType,
|
||||
Status: e.Status,
|
||||
Amount: e.Amount,
|
||||
VisitDate: e.VisitDate,
|
||||
TicketStatus: e.TicketStatus,
|
||||
Source: e.Source,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *HistoryOrderList) ToHistoryOrderList() []*HistoryOrder {
|
||||
var HistoryOrders []*HistoryOrder
|
||||
for _, historyOrder := range *b {
|
||||
if historyOrder.Status != "NEW" && historyOrder.Tickets != nil {
|
||||
HistoryOrders = append(HistoryOrders, historyOrder.ToHistoryOrder())
|
||||
}
|
||||
}
|
||||
return HistoryOrders
|
||||
}
|
||||
|
||||
type TicketSold struct {
|
||||
Count int64 `gorm:"type:int;column:count"`
|
||||
}
|
||||
|
||||
type TicketSoldDB struct {
|
||||
TicketSold
|
||||
}
|
||||
|
||||
func (b *TicketSold) ToTicketSoldDB() *TicketSoldDB {
|
||||
return &TicketSoldDB{
|
||||
TicketSold: *b,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *TicketSoldDB) ToTicketSold() *TicketSold {
|
||||
return &TicketSold{
|
||||
Count: e.Count,
|
||||
}
|
||||
}
|
||||
|
||||
type ProductDailySales struct {
|
||||
Day time.Time
|
||||
SiteID int64
|
||||
SiteName string
|
||||
PaymentType string
|
||||
Total float64
|
||||
}
|
||||
|
||||
type PaymentTypeDistribution struct {
|
||||
PaymentType string
|
||||
Count int
|
||||
}
|
||||
|
||||
type OrderPrintDetail struct {
|
||||
ID int64 `gorm:"column:id"`
|
||||
Logo string `gorm:"logo"`
|
||||
PartnerName string `gorm:"column:partner_name"`
|
||||
SiteName string `gorm:"column:site_name"`
|
||||
OrderID string `gorm:"column:order_id"`
|
||||
VisitDate time.Time `gorm:"column:visit_date"`
|
||||
PaymentType string `gorm:"column:payment_type"`
|
||||
OrderItems []OrderItem `gorm:"foreignKey:OrderID;constraint:OnDelete:CASCADE;"`
|
||||
Source string `gorm:"column:source"`
|
||||
TicketStatus string `gorm:"column:ticket_status"`
|
||||
Total float64 `gorm:"column:total"`
|
||||
Fee float64 `gorm:"column:fee"`
|
||||
}
|
||||
|
||||
func (o *OrderPrintDetail) GetPaymanetType() string {
|
||||
if o.PaymentType == "CASH" {
|
||||
return "TUNAI"
|
||||
}
|
||||
|
||||
return o.PaymentType
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/constants"
|
||||
"time"
|
||||
)
|
||||
|
||||
type OrderInquiry struct {
|
||||
ID string `json:"id"`
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
CustomerID int64 `json:"customer_id,omitempty"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
CustomerPhoneNumber string `json:"customer_phone_number"`
|
||||
CustomerEmail string `json:"customer_email"`
|
||||
Status string `json:"status"`
|
||||
Amount float64 `json:"amount"`
|
||||
Tax float64 `json:"tax"`
|
||||
Total float64 `json:"total"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
Source string `json:"source"`
|
||||
CreatedBy int64 `json:"created_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
OrderItems []OrderItem `json:"order_items"`
|
||||
PaymentProvider string `json:"payment_provider"`
|
||||
TableNumber string `json:"table_number"`
|
||||
OrderType string `json:"order_type"`
|
||||
CashierSessionID int64 `json:"cashier_session_id"`
|
||||
}
|
||||
|
||||
type OrderCalculation struct {
|
||||
Subtotal float64 `json:"subtotal"`
|
||||
Tax float64 `json:"tax"`
|
||||
Total float64 `json:"total"`
|
||||
}
|
||||
|
||||
type OrderInquiryResponse struct {
|
||||
OrderInquiry *OrderInquiry `json:"order_inquiry"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func NewOrderInquiry(
|
||||
partnerID int64,
|
||||
customerID int64,
|
||||
amount float64,
|
||||
tax float64,
|
||||
total float64,
|
||||
paymentType string,
|
||||
source string,
|
||||
createdBy int64,
|
||||
customerName string,
|
||||
customerPhoneNumber string,
|
||||
customerEmail string,
|
||||
paymentProvider string,
|
||||
tableNumber string,
|
||||
orderType string,
|
||||
cashierSessionID int64,
|
||||
) *OrderInquiry {
|
||||
return &OrderInquiry{
|
||||
ID: constants.GenerateUUID(),
|
||||
PartnerID: partnerID,
|
||||
Status: "PENDING",
|
||||
Amount: amount,
|
||||
Tax: tax,
|
||||
Total: total,
|
||||
PaymentType: paymentType,
|
||||
CustomerID: customerID,
|
||||
Source: source,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: time.Now(),
|
||||
ExpiresAt: time.Now().Add(2 * time.Minute),
|
||||
OrderItems: []OrderItem{},
|
||||
CustomerName: customerName,
|
||||
CustomerEmail: customerEmail,
|
||||
CustomerPhoneNumber: customerPhoneNumber,
|
||||
PaymentProvider: paymentProvider,
|
||||
TableNumber: tableNumber,
|
||||
OrderType: orderType,
|
||||
CashierSessionID: cashierSessionID,
|
||||
}
|
||||
}
|
||||
|
||||
func (oi *OrderInquiry) AddOrderItem(item OrderItemRequest, product *Product) {
|
||||
oi.OrderItems = append(oi.OrderItems, OrderItem{
|
||||
ItemID: item.ProductID,
|
||||
ItemType: product.Type,
|
||||
Price: product.Price,
|
||||
ItemName: product.Name,
|
||||
Quantity: item.Quantity,
|
||||
CreatedBy: oi.CreatedBy,
|
||||
Product: product,
|
||||
Notes: item.Notes,
|
||||
})
|
||||
}
|
||||
|
||||
func (i *OrderInquiry) ToOrder(paymentMethod, paymentProvider string) *Order {
|
||||
now := time.Now()
|
||||
|
||||
order := &Order{
|
||||
PartnerID: i.PartnerID,
|
||||
CustomerID: &i.CustomerID,
|
||||
InquiryID: &i.ID,
|
||||
Status: constants.StatusPaid,
|
||||
Amount: i.Amount,
|
||||
Tax: i.Tax,
|
||||
Total: i.Total,
|
||||
PaymentType: paymentMethod,
|
||||
PaymentProvider: paymentProvider,
|
||||
Source: i.Source,
|
||||
CreatedBy: i.CreatedBy,
|
||||
CreatedAt: now,
|
||||
OrderItems: make([]OrderItem, len(i.OrderItems)),
|
||||
OrderType: i.OrderType,
|
||||
CustomerName: i.CustomerName,
|
||||
TableNumber: i.TableNumber,
|
||||
CashierSessionID: i.CashierSessionID,
|
||||
}
|
||||
|
||||
for idx, item := range i.OrderItems {
|
||||
order.OrderItems[idx] = OrderItem{
|
||||
ItemID: item.ItemID,
|
||||
ItemType: item.ItemType,
|
||||
Price: item.Price,
|
||||
ItemName: item.ItemName,
|
||||
Quantity: item.Quantity,
|
||||
CreatedBy: i.CreatedBy,
|
||||
CreatedAt: now,
|
||||
Product: item.Product,
|
||||
Notes: item.Notes,
|
||||
}
|
||||
}
|
||||
|
||||
return order
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "mime/multipart"
|
||||
|
||||
type UploadFileRequest struct {
|
||||
FileHeader *multipart.FileHeader
|
||||
FolderName string
|
||||
FileSize int64 `validate:"max=10000000"` // 10Mb = 10000000 byte
|
||||
Ext string `validate:"oneof=.png .jpeg .jpg .pdf .xlsx .csv"`
|
||||
}
|
||||
|
||||
type DownloadFileRequest struct {
|
||||
FileName string `query:"file_name" validate:"required"`
|
||||
FolderName string `query:"folder_name" validate:"required"`
|
||||
}
|
||||
|
||||
type UploadFileResponse struct {
|
||||
FilePath string `json:"file_path"`
|
||||
FileUrl string `json:"file_url"`
|
||||
}
|
||||
|
||||
type DownloadFileResponse struct {
|
||||
FileUrl string `json:"file_url"`
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/constants/role"
|
||||
"enaklo-pos-be/internal/constants/userstatus"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CreatePartnerRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Address string `json:"address"`
|
||||
FullName string `json:"full_name"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
NIK string `json:"nik"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
BankName string `json:"bank_name"`
|
||||
BankAccountNumber string `json:"bank_account_number"`
|
||||
Status string `json:"status"`
|
||||
BankAccountHolderName string `json:"bank_account_holder_name"`
|
||||
Logo string `json:"logo"`
|
||||
}
|
||||
|
||||
type Partner struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
Name string `gorm:"type:varchar(255);not null;column:name"`
|
||||
Status string `gorm:"type:varchar(50);column:status"`
|
||||
LicenseExpiredDate *time.Time `gorm:"type:date;column:license_expired_date"`
|
||||
Address string `gorm:"type:varchar(255);column:address"`
|
||||
BankName string `gorm:"type:varchar(255);column:bank_name"`
|
||||
BankAccountNumber string `gorm:"type:varchar(50);column:bank_account_number"`
|
||||
BankAccountHolderName string `gorm:"type:varchar(255);column:bank_account_holder_name"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
|
||||
DeletedAt *time.Time `gorm:"column:deleted_at"`
|
||||
CreatedBy int64 `gorm:"type:int;column:created_by"`
|
||||
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
|
||||
AdminUserID int64 `gorm:"type:int;column:admin_user_id"`
|
||||
Balance float64 `gorm:"-"`
|
||||
AdminName string `gorm:"-"`
|
||||
AdminPhoneNumber string `gorm:"-"`
|
||||
AdminEmail string `gorm:"-"`
|
||||
Logo string `gorm:"type:varchar;column:logo"`
|
||||
}
|
||||
|
||||
type PartnerUpdate struct {
|
||||
ID int64
|
||||
Email string
|
||||
Name string
|
||||
Status string
|
||||
Address string
|
||||
PhoneNumber string
|
||||
BankName string
|
||||
BankAccountNumber string
|
||||
BankAccountHolderName string
|
||||
NIK string
|
||||
AdminUserID int64
|
||||
AdminName string
|
||||
Password string
|
||||
Logo string
|
||||
}
|
||||
|
||||
func (c *PartnerUpdate) ToUserAdmin(partnerID *int64) *User {
|
||||
return &User{
|
||||
ID: c.AdminUserID,
|
||||
Name: c.Name,
|
||||
Password: c.Password,
|
||||
Email: c.Email,
|
||||
NIK: c.NIK,
|
||||
PhoneNumber: c.PhoneNumber,
|
||||
Status: userstatus.UserStatus(c.Status),
|
||||
PartnerID: partnerID,
|
||||
}
|
||||
}
|
||||
|
||||
func (Partner) TableName() string {
|
||||
return "partners"
|
||||
}
|
||||
|
||||
type PartnerSearch struct {
|
||||
Search string
|
||||
PartnerID *int64
|
||||
Name string
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
type PartnerList []*PartnerDB
|
||||
|
||||
type PartnerDB struct {
|
||||
Partner
|
||||
}
|
||||
|
||||
type PartnerDBSearch struct {
|
||||
Partner
|
||||
WalletBalance float64 `gorm:"type:number;column:wallet_balance"`
|
||||
AdminName string `gorm:"type:varchar;column:admin_name"`
|
||||
AdminEmail string `gorm:"type:varchar;column:admin_email"`
|
||||
AdminPhoneNumber string `gorm:"type:varchar;column:admin_phone_number"`
|
||||
}
|
||||
|
||||
func (p *Partner) ToPartnerDB() *PartnerDB {
|
||||
return &PartnerDB{
|
||||
Partner: *p,
|
||||
}
|
||||
}
|
||||
|
||||
func (PartnerDB) TableName() string {
|
||||
return "partners"
|
||||
}
|
||||
|
||||
func (e *PartnerDB) ToPartner() *Partner {
|
||||
return &Partner{
|
||||
ID: e.ID,
|
||||
Name: e.Name,
|
||||
Status: e.Status,
|
||||
Address: e.Address,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
CreatedBy: e.CreatedBy,
|
||||
Balance: e.Balance,
|
||||
AdminEmail: e.AdminEmail,
|
||||
AdminPhoneNumber: e.AdminPhoneNumber,
|
||||
AdminName: e.AdminName,
|
||||
BankAccountHolderName: e.BankAccountHolderName,
|
||||
BankName: e.BankName,
|
||||
BankAccountNumber: e.BankAccountNumber,
|
||||
Logo: e.Logo,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PartnerList) ToPartnerList() []*Partner {
|
||||
var partners []*Partner
|
||||
for _, partner := range *p {
|
||||
partners = append(partners, partner.ToPartner())
|
||||
}
|
||||
return partners
|
||||
}
|
||||
|
||||
func (o *PartnerDB) ToUpdatedPartner(updatedBy int64, req Partner) {
|
||||
o.UpdatedBy = updatedBy
|
||||
|
||||
if req.Name != "" {
|
||||
o.Name = req.Name
|
||||
}
|
||||
|
||||
if req.Status != "" {
|
||||
o.Status = req.Status
|
||||
}
|
||||
|
||||
if req.Address != "" {
|
||||
o.Address = req.Address
|
||||
}
|
||||
|
||||
if req.BankAccountNumber != "" {
|
||||
o.BankAccountNumber = req.BankAccountNumber
|
||||
}
|
||||
|
||||
if req.BankAccountHolderName != "" {
|
||||
o.BankAccountHolderName = req.BankAccountHolderName
|
||||
}
|
||||
|
||||
if req.Status != "" {
|
||||
o.Status = req.Status
|
||||
}
|
||||
}
|
||||
|
||||
func (o *PartnerDB) ToUpdatedPartnerData(updatedBy int64, req PartnerUpdate) {
|
||||
o.UpdatedBy = updatedBy
|
||||
|
||||
if req.Name != "" {
|
||||
o.Name = req.Name
|
||||
}
|
||||
|
||||
if req.Status != "" {
|
||||
o.Status = req.Status
|
||||
}
|
||||
|
||||
if req.Address != "" {
|
||||
o.Address = req.Address
|
||||
}
|
||||
|
||||
if req.BankName != "" {
|
||||
o.BankName = req.BankName
|
||||
}
|
||||
|
||||
if req.BankAccountNumber != "" {
|
||||
o.BankAccountNumber = req.BankAccountNumber
|
||||
}
|
||||
|
||||
if req.BankAccountHolderName != "" {
|
||||
o.BankAccountHolderName = req.BankAccountHolderName
|
||||
}
|
||||
|
||||
if req.Status != "" {
|
||||
o.Status = req.Status
|
||||
}
|
||||
|
||||
if req.Logo != "" {
|
||||
o.Logo = req.Logo
|
||||
}
|
||||
}
|
||||
|
||||
func (o *PartnerDB) SetDeleted(updatedBy int64) {
|
||||
currentTime := time.Now()
|
||||
o.DeletedAt = ¤tTime
|
||||
o.UpdatedBy = updatedBy
|
||||
}
|
||||
|
||||
func (c *CreatePartnerRequest) ToUserAdmin(partnerID int64) *User {
|
||||
return &User{
|
||||
Name: c.FullName,
|
||||
Password: c.Password,
|
||||
Email: c.Email,
|
||||
NIK: c.NIK,
|
||||
PhoneNumber: c.PhoneNumber,
|
||||
Status: "Active",
|
||||
RoleID: role.PartnerAdmin,
|
||||
PartnerID: &partnerID,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *CreatePartnerRequest) ToPartnerDB(createdBy int64) *PartnerDB {
|
||||
twoDays := 48 * time.Hour
|
||||
licenseExpiredDate := time.Now().Add(twoDays)
|
||||
|
||||
return &PartnerDB{
|
||||
Partner: Partner{
|
||||
Name: e.Name,
|
||||
Status: e.Status,
|
||||
Address: e.Address,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
BankAccountHolderName: e.BankAccountHolderName,
|
||||
BankAccountNumber: e.BankAccountNumber,
|
||||
BankName: e.BankName,
|
||||
LicenseExpiredDate: &licenseExpiredDate,
|
||||
Logo: e.Logo,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (e *CreatePartnerRequest) ToWallet(partnerID int64) *Wallet {
|
||||
return &Wallet{
|
||||
PartnerID: partnerID,
|
||||
Balance: 0,
|
||||
Currency: "IDR",
|
||||
Status: "active",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type PartnerSettings struct {
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
TaxEnabled bool `json:"tax_enabled"`
|
||||
TaxPercentage float64 `json:"tax_percentage"`
|
||||
InvoicePrefix string `json:"invoice_prefix"`
|
||||
BusinessHours string `json:"business_hours"`
|
||||
LogoURL string `json:"logo_url"`
|
||||
ThemeColor string `json:"theme_color"`
|
||||
ReceiptFooterText string `json:"receipt_footer_text"`
|
||||
ReceiptHeaderText string `json:"receipt_header_text"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type PartnerPaymentMethod struct {
|
||||
ID int64 `json:"id"`
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
IsEnabled bool `json:"is_enabled"`
|
||||
DisplayName string `json:"display_name"`
|
||||
DisplayOrder int `json:"display_order"`
|
||||
AdditionalInfo string `json:"additional_info"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type PartnerFeatureFlag struct {
|
||||
ID int64 `json:"id"`
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
FeatureKey string `json:"feature_key"`
|
||||
IsEnabled bool `json:"is_enabled"`
|
||||
Config string `json:"config"` // JSON string
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type BusinessHoursSetting struct {
|
||||
Monday DayHours `json:"monday"`
|
||||
Tuesday DayHours `json:"tuesday"`
|
||||
Wednesday DayHours `json:"wednesday"`
|
||||
Thursday DayHours `json:"thursday"`
|
||||
Friday DayHours `json:"friday"`
|
||||
Saturday DayHours `json:"saturday"`
|
||||
Sunday DayHours `json:"sunday"`
|
||||
}
|
||||
|
||||
type DayHours struct {
|
||||
Open string `json:"open"` // Format: "HH:MM"
|
||||
Close string `json:"close"` // Format: "HH:MM"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/datatypes"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Payment struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey;column:id"`
|
||||
PartnerID int64 `gorm:"type:numeric;not null;column:partner_id"`
|
||||
OrderID int64 `gorm:"type:numeric;not null;column:order_id"`
|
||||
ReferenceID string `gorm:"type:varchar;not null;column:reference_id"`
|
||||
Channel string `gorm:"type:varchar;not null;column:channel"`
|
||||
PaymentType string `gorm:"type:varchar;not null;column:payment_type"`
|
||||
Amount float64 `gorm:"type:numeric;not null;column:amount"`
|
||||
State string `gorm:"type:varchar;not null;column:state"`
|
||||
RequestMetadata datatypes.JSON `gorm:"type:json;not null;column:request_metadata"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
|
||||
FinishedAt time.Time `gorm:"column:finished_at"`
|
||||
}
|
||||
|
||||
func (Payment) TableName() string {
|
||||
return "payments"
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package entity
|
||||
|
||||
type PaymentRequest struct {
|
||||
PaymentReferenceID string
|
||||
Provider string
|
||||
TotalAmount int64
|
||||
CustomerID string
|
||||
CustomerName string
|
||||
CustomerPhone string
|
||||
CustomerEmail string
|
||||
BankCode string
|
||||
}
|
||||
|
||||
type PaymentResponse struct {
|
||||
Token string
|
||||
RedirectURL string
|
||||
QRCodeURL string
|
||||
OrderID string
|
||||
Amount int64
|
||||
VirtualAccountNumber string
|
||||
BankName string
|
||||
BankCode string
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/constants/product"
|
||||
"enaklo-pos-be/internal/repository/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Product struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
PartnerID int64 `gorm:"type:int;column:partner_id"`
|
||||
Name string `gorm:"type:varchar(255);not null;column:name"`
|
||||
Type string `gorm:"type:varchar;column:type"`
|
||||
Price float64 `gorm:"type:decimal;column:price"`
|
||||
Status string `gorm:"type:varchar;column:status"`
|
||||
Description string `gorm:"type:varchar(255);not null;column:description"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
|
||||
DeletedAt *time.Time `gorm:"column:deleted_at"`
|
||||
CreatedBy int64 `gorm:"type:int;column:created_by"`
|
||||
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
|
||||
Image string `gorm:"type:varchar;column:image"`
|
||||
CategoryID *int64 `gorm:"column:category_id"`
|
||||
Category *models.CategoryDB `gorm:"foreignKey:CategoryID;references:ID"`
|
||||
}
|
||||
|
||||
func (Product) TableName() string {
|
||||
return "products"
|
||||
}
|
||||
|
||||
type ProductSearch struct {
|
||||
Search string
|
||||
Name string
|
||||
Type product.ProductType
|
||||
BranchID int64
|
||||
PartnerID int64
|
||||
Available product.ProductStock
|
||||
Limit int
|
||||
Offset int
|
||||
CategoryID int64
|
||||
}
|
||||
|
||||
type ProductPOS struct {
|
||||
PartnerID int64
|
||||
SiteID int64
|
||||
}
|
||||
|
||||
type ProductList []*ProductDB
|
||||
|
||||
type ProductDB struct {
|
||||
Product
|
||||
}
|
||||
|
||||
func (b *Product) ToProductDB() *ProductDB {
|
||||
return &ProductDB{
|
||||
Product: *b,
|
||||
}
|
||||
}
|
||||
|
||||
func (ProductDB) TableName() string {
|
||||
return "products"
|
||||
}
|
||||
|
||||
func (e *ProductDB) ToProduct() *Product {
|
||||
return &Product{
|
||||
ID: e.ID,
|
||||
Name: e.Name,
|
||||
Type: e.Type,
|
||||
Price: e.Price,
|
||||
Status: e.Status,
|
||||
Description: e.Description,
|
||||
PartnerID: e.PartnerID,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
DeletedAt: e.DeletedAt,
|
||||
CreatedBy: e.CreatedBy,
|
||||
UpdatedBy: e.UpdatedBy,
|
||||
Image: e.Image,
|
||||
Category: e.Category,
|
||||
CategoryID: e.CategoryID,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *ProductList) ToProductList() []*Product {
|
||||
var Products []*Product
|
||||
|
||||
for _, p := range *b {
|
||||
Products = append(Products, p.ToProduct())
|
||||
}
|
||||
|
||||
return Products
|
||||
}
|
||||
|
||||
func (b *ProductList) ToProductListPOS() []*Product {
|
||||
var Products []*Product
|
||||
|
||||
for _, p := range *b {
|
||||
Products = append(Products, p.ToProduct())
|
||||
}
|
||||
|
||||
return Products
|
||||
}
|
||||
|
||||
func (o *ProductDB) ToUpdatedProduct(updatedby int64, req Product) {
|
||||
o.UpdatedBy = updatedby
|
||||
|
||||
if req.Name != "" {
|
||||
o.Name = req.Name
|
||||
}
|
||||
|
||||
if req.Image != "" {
|
||||
o.Image = req.Image
|
||||
}
|
||||
|
||||
if req.Type != "" {
|
||||
o.Type = req.Type
|
||||
}
|
||||
|
||||
if req.Price > 0 {
|
||||
o.Price = req.Price
|
||||
}
|
||||
|
||||
if req.Status != "" {
|
||||
o.Status = req.Status
|
||||
}
|
||||
|
||||
if req.Description != "" {
|
||||
o.Description = req.Description
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ProductDB) SetDeleted(updatedby int64) {
|
||||
currentTime := time.Now()
|
||||
o.DeletedAt = ¤tTime
|
||||
o.UpdatedBy = updatedby
|
||||
}
|
||||
|
||||
type ProductDetails struct {
|
||||
Products map[int64]*Product // Map for quick lookups by ID
|
||||
PartnerID int64 // Common site ID for all products
|
||||
}
|
||||
|
||||
type PaymentMethodBreakdown struct {
|
||||
PaymentType string `json:"payment_type"`
|
||||
PaymentProvider string `json:"payment_provider"`
|
||||
TotalTransactions int64 `json:"total_transactions"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
}
|
||||
|
||||
type OrderPaymentAnalysis struct {
|
||||
TotalTransactions int64 `json:"total"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
PaymentMethodBreakdown []PaymentMethodBreakdown `json:"payment_method_breakdown"`
|
||||
}
|
||||
|
||||
type RevenueOverviewItem struct {
|
||||
Period string `json:"period"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
}
|
||||
|
||||
type SalesByCategoryItem struct {
|
||||
Category string `json:"category"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
TotalQuantity int64 `json:"total_quantity"`
|
||||
Percentage float64 `json:"percentage"`
|
||||
}
|
||||
|
||||
type PopularProductItem struct {
|
||||
ProductID int64 `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
Category string `json:"category"`
|
||||
TotalSales int64 `json:"total_sales"`
|
||||
TotalRevenue float64 `json:"total_revenue"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
Percentage float64 `json:"percentage"`
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
type SearchRequest struct {
|
||||
Status string // Filter by order status (e.g., "COMPLETED", "PENDING", etc.)
|
||||
Start time.Time // Start date for filtering orders
|
||||
End time.Time // End date for filtering orders
|
||||
Limit int // Maximum number of records to return
|
||||
Offset int // Number of records to skip for pagination
|
||||
}
|
||||
|
||||
type RevenueOverviewRequest struct {
|
||||
PartnerID int64
|
||||
Year int
|
||||
Granularity string // "monthly" or "weekly"
|
||||
Status string
|
||||
}
|
||||
|
||||
type SalesByCategoryRequest struct {
|
||||
PartnerID int64
|
||||
Period string // "d" (daily), "w" (weekly), "m" (monthly)
|
||||
Status string
|
||||
}
|
||||
|
||||
type PopularProductsRequest struct {
|
||||
PartnerID int64
|
||||
Period string // "d" (daily), "w" (weekly), "m" (monthly)
|
||||
Status string
|
||||
Limit int
|
||||
SortBy string // "sales" or "revenue"
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Site struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
Name string `gorm:"type:varchar(255);not null;column:name"`
|
||||
PartnerID int64 `gorm:"type:int;column:partner_id"`
|
||||
Image string `gorm:"type:varchar;column:image"`
|
||||
Address string `gorm:"type:varchar;column:address"`
|
||||
LocationLink string `gorm:"type:varchar;column:location_link"`
|
||||
Description string `gorm:"type:varchar;column:description"`
|
||||
Highlight string `gorm:"type:varchar;column:highlight"`
|
||||
ContactPerson string `gorm:"type:varchar;column:contact_person"`
|
||||
TnC string `gorm:"type:varchar;column:tnc"`
|
||||
AdditionalInfo string `gorm:"type:varchar;column:additional_info"`
|
||||
Status string `gorm:"type:varchar;column:status"`
|
||||
IsSeasonTicket bool `gorm:"type:bool;column:is_season_ticket"`
|
||||
IsDiscountActive bool `gorm:"type:bool;column:is_discount_active"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
|
||||
DeletedAt *time.Time `gorm:"column:deleted_at"`
|
||||
CreatedBy int64 `gorm:"type:int;column:created_by"`
|
||||
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
|
||||
Products []Product `gorm:"foreignKey:SiteID;constraint:OnDelete:CASCADE;"`
|
||||
Latitude *float64 `json:"latitude"`
|
||||
Longitude *float64 `json:"longitude"`
|
||||
Region string `json:"region"`
|
||||
Regency string `json:"regency"`
|
||||
Distance float64 `gorm:"-"`
|
||||
}
|
||||
|
||||
type SiteSearch struct {
|
||||
PartnerID *int64
|
||||
SiteID *int64
|
||||
IsAdmin bool
|
||||
Search string
|
||||
Name string
|
||||
Limit int
|
||||
Offset int
|
||||
Status string
|
||||
}
|
||||
|
||||
type SiteList []*SiteDB
|
||||
|
||||
type SiteDB struct {
|
||||
Site
|
||||
}
|
||||
|
||||
func (s *Site) ToSiteDB() *SiteDB {
|
||||
return &SiteDB{
|
||||
Site: *s,
|
||||
}
|
||||
}
|
||||
|
||||
func (SiteDB) TableName() string {
|
||||
return "sites"
|
||||
}
|
||||
|
||||
func (e *SiteDB) ToSite() *Site {
|
||||
return &Site{
|
||||
ID: e.ID,
|
||||
Name: e.Name,
|
||||
PartnerID: e.PartnerID,
|
||||
Image: e.Image,
|
||||
Address: e.Address,
|
||||
LocationLink: e.LocationLink,
|
||||
Description: e.Description,
|
||||
Highlight: e.Highlight,
|
||||
ContactPerson: e.ContactPerson,
|
||||
TnC: e.TnC,
|
||||
AdditionalInfo: e.AdditionalInfo,
|
||||
Status: e.Status,
|
||||
IsSeasonTicket: e.IsSeasonTicket,
|
||||
IsDiscountActive: e.IsDiscountActive,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
DeletedAt: e.DeletedAt,
|
||||
CreatedBy: e.CreatedBy,
|
||||
UpdatedBy: e.UpdatedBy,
|
||||
Regency: e.Regency,
|
||||
Region: e.Region,
|
||||
Latitude: e.Latitude,
|
||||
Longitude: e.Longitude,
|
||||
Products: e.Products,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SiteList) ToSiteList() []*Site {
|
||||
var sites []*Site
|
||||
for _, site := range *s {
|
||||
sites = append(sites, site.ToSite())
|
||||
}
|
||||
return sites
|
||||
}
|
||||
|
||||
func (o *SiteDB) ToUpdatedSite(updatedBy int64, req Site) {
|
||||
o.UpdatedBy = updatedBy
|
||||
|
||||
if req.Name != "" {
|
||||
o.Name = req.Name
|
||||
}
|
||||
|
||||
if req.PartnerID != 0 {
|
||||
o.PartnerID = req.PartnerID
|
||||
}
|
||||
|
||||
if req.Image != "" {
|
||||
o.Image = req.Image
|
||||
}
|
||||
|
||||
if req.Address != "" {
|
||||
o.Address = req.Address
|
||||
}
|
||||
|
||||
if req.LocationLink != "" {
|
||||
o.LocationLink = req.LocationLink
|
||||
}
|
||||
|
||||
if req.Description != "" {
|
||||
o.Description = req.Description
|
||||
}
|
||||
|
||||
if req.Highlight != "" {
|
||||
o.Highlight = req.Highlight
|
||||
}
|
||||
|
||||
if req.ContactPerson != "" {
|
||||
o.ContactPerson = req.ContactPerson
|
||||
}
|
||||
|
||||
if req.TnC != "" {
|
||||
o.TnC = req.TnC
|
||||
}
|
||||
|
||||
if req.AdditionalInfo != "" {
|
||||
o.AdditionalInfo = req.AdditionalInfo
|
||||
}
|
||||
|
||||
if req.Status != "" {
|
||||
o.Status = req.Status
|
||||
}
|
||||
|
||||
if req.IsSeasonTicket {
|
||||
o.IsSeasonTicket = req.IsSeasonTicket
|
||||
}
|
||||
|
||||
if req.IsDiscountActive {
|
||||
o.IsDiscountActive = req.IsDiscountActive
|
||||
}
|
||||
}
|
||||
|
||||
func (o *SiteDB) SetDeleted(updatedBy int64) {
|
||||
currentTime := time.Now()
|
||||
o.DeletedAt = ¤tTime
|
||||
o.UpdatedBy = updatedBy
|
||||
}
|
||||
|
||||
type SiteCount struct {
|
||||
Count int `gorm:"type:int;column:count"`
|
||||
}
|
||||
|
||||
type SiteCountDB struct {
|
||||
SiteCount
|
||||
}
|
||||
|
||||
func (b *SiteCount) ToSiteCountDB() *SiteCountDB {
|
||||
return &SiteCountDB{
|
||||
SiteCount: *b,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *SiteCountDB) ToSiteCount() *SiteCount {
|
||||
return &SiteCount{
|
||||
Count: e.Count,
|
||||
}
|
||||
}
|
||||
|
||||
type SiteProductInfo struct {
|
||||
SiteID int64 `json:"site_id"`
|
||||
SiteName string `json:"site_name"`
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
Image string `json:"image"`
|
||||
Address string `json:"address"`
|
||||
LocationLink string `json:"location_link"`
|
||||
Description string `json:"description"`
|
||||
Highlight string `json:"highlight"`
|
||||
ContactPerson string `json:"contact_person"`
|
||||
TnC string `json:"tnc"`
|
||||
AdditionalInfo string `json:"additional_info"`
|
||||
Status string `json:"status"`
|
||||
IsSeasonTicket bool `json:"is_season_ticket"`
|
||||
IsDiscountActive bool `json:"is_discount_active"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Distance float64 `json:"distance"` // Calculated field
|
||||
ProductID int64 `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
ProductType string `json:"product_type"`
|
||||
ProductPrice float64 `json:"product_price"`
|
||||
IsWeekendTicket bool `json:"is_weekend_ticket"`
|
||||
ProductStatus string `json:"product_status"`
|
||||
ProductDescription string `json:"product_description"`
|
||||
Region string `json:"region"`
|
||||
Regency string `json:"regency"`
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/constants/studio"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Studio struct {
|
||||
ID int64
|
||||
BranchId int64
|
||||
Name string
|
||||
Status studio.StudioStatus
|
||||
Price float64
|
||||
Metadata []byte `gorm:"type:jsonb"` // Use jsonb data type for JSON data
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
CreatedBy int64
|
||||
UpdatedBy int64
|
||||
}
|
||||
|
||||
func (s *Studio) TableName() string {
|
||||
return "studios"
|
||||
}
|
||||
|
||||
func (s *Studio) NewStudiosDB() *StudioDB {
|
||||
return &StudioDB{
|
||||
Studio: *s,
|
||||
}
|
||||
}
|
||||
|
||||
type StudioList []*StudioDB
|
||||
|
||||
type StudioDB struct {
|
||||
Studio
|
||||
}
|
||||
|
||||
func (s *StudioDB) ToStudio() *Studio {
|
||||
return &Studio{
|
||||
ID: s.ID,
|
||||
BranchId: s.BranchId,
|
||||
Name: s.Name,
|
||||
Status: s.Status,
|
||||
Price: s.Price,
|
||||
Metadata: s.Metadata,
|
||||
CreatedAt: s.CreatedAt,
|
||||
UpdatedAt: s.UpdatedAt,
|
||||
CreatedBy: s.CreatedBy,
|
||||
UpdatedBy: s.UpdatedBy,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StudioList) ToStudioList() []*Studio {
|
||||
var studios []*Studio
|
||||
for _, studio := range *s {
|
||||
studios = append(studios, studio.ToStudio())
|
||||
}
|
||||
return studios
|
||||
}
|
||||
|
||||
func (s *StudioDB) ToUpdatedStudio(updatedBy int64, req Studio) {
|
||||
s.UpdatedBy = updatedBy
|
||||
|
||||
if req.BranchId != 0 {
|
||||
s.BranchId = req.BranchId
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
s.Name = req.Name
|
||||
}
|
||||
|
||||
if req.Status != "" {
|
||||
s.Status = req.Status
|
||||
}
|
||||
|
||||
if req.Price != 0 {
|
||||
s.Price = req.Price
|
||||
}
|
||||
|
||||
if req.Metadata != nil {
|
||||
s.Metadata = req.Metadata
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StudioDB) ToStudioDB() *StudioDB {
|
||||
return s
|
||||
}
|
||||
|
||||
type StudioSearch struct {
|
||||
Id int64
|
||||
Name string
|
||||
Status studio.StudioStatus
|
||||
BranchId int64
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Transaction struct {
|
||||
ID string `gorm:"type:uuid;primaryKey;default:uuid_generate_v4()"`
|
||||
OrderID int64
|
||||
PartnerID int64 `gorm:"not null"`
|
||||
TransactionType string `gorm:"not null"`
|
||||
Status string `gorm:"size:255"`
|
||||
CreatedBy int64 `gorm:"not null"`
|
||||
UpdatedBy int64 `gorm:"not null"`
|
||||
Amount float64 `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
Fee float64
|
||||
Total float64
|
||||
}
|
||||
|
||||
type TransactionDB struct {
|
||||
Transaction
|
||||
}
|
||||
|
||||
func (b *Transaction) ToTransactionDB() *TransactionDB {
|
||||
return &TransactionDB{
|
||||
Transaction: *b,
|
||||
}
|
||||
}
|
||||
|
||||
func (TransactionDB) TableName() string {
|
||||
return "transactions"
|
||||
}
|
||||
|
||||
type TransactionSearch struct {
|
||||
PartnerID *int64
|
||||
SiteID *int64
|
||||
Type string
|
||||
Status string
|
||||
Limit int
|
||||
Offset int
|
||||
Date string
|
||||
}
|
||||
|
||||
type TransactionList struct {
|
||||
ID string
|
||||
TransactionType string
|
||||
Status string
|
||||
CreatedAt time.Time
|
||||
SiteName string
|
||||
PartnerName string
|
||||
Amount int64
|
||||
Total int64
|
||||
Fee int64
|
||||
}
|
||||
|
||||
type TransactionApproval struct {
|
||||
TransactionID string
|
||||
Status string
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
type UndianEventDB struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
Title string `gorm:"size:255;not null" json:"title"`
|
||||
Description *string `gorm:"type:text" json:"description"`
|
||||
ImageURL *string `gorm:"size:500" json:"image_url"`
|
||||
Status string `gorm:"size:20;not null;default:upcoming" json:"status"`
|
||||
StartDate time.Time `gorm:"not null" json:"start_date"`
|
||||
EndDate time.Time `gorm:"not null" json:"end_date"`
|
||||
DrawDate time.Time `gorm:"not null" json:"draw_date"`
|
||||
MinimumPurchase float64 `gorm:"type:numeric(10,2);default:50000" json:"minimum_purchase"`
|
||||
DrawCompleted bool `gorm:"default:false" json:"draw_completed"`
|
||||
DrawCompletedAt *time.Time `json:"draw_completed_at"`
|
||||
TermsAndConditions *string `gorm:"column:terms_and_conditions;type:text" json:"terms_and_conditions"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
Prefix *string `json:"prefix"`
|
||||
Prizes []UndianPrizeDB `gorm:"foreignKey:UndianEventID" json:"prizes,omitempty"`
|
||||
Vouchers []UndianVoucherDB `gorm:"foreignKey:UndianEventID" json:"vouchers,omitempty"`
|
||||
}
|
||||
|
||||
func (UndianEventDB) TableName() string {
|
||||
return "undian_events"
|
||||
}
|
||||
|
||||
// UndianPrizeDB represents the undian_prizes table
|
||||
type UndianPrizeDB struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
UndianEventID int64 `gorm:"not null" json:"undian_event_id"`
|
||||
Rank int `gorm:"not null" json:"rank"`
|
||||
PrizeName string `gorm:"size:255;not null" json:"prize_name"`
|
||||
PrizeValue *float64 `gorm:"type:numeric(15,2)" json:"prize_value"`
|
||||
PrizeDescription *string `gorm:"type:text" json:"prize_description"`
|
||||
PrizeType string `gorm:"size:50;default:voucher" json:"prize_type"`
|
||||
PrizeImageURL *string `gorm:"size:500" json:"prize_image_url"`
|
||||
WinningVoucherID *int64 `json:"winning_voucher_id"`
|
||||
WinnerUserID *int64 `json:"winner_user_id"`
|
||||
Amount *int64 `json:"amount"`
|
||||
// Relations
|
||||
UndianEvent UndianEventDB `gorm:"foreignKey:UndianEventID" json:"undian_event,omitempty"`
|
||||
}
|
||||
|
||||
func (UndianPrizeDB) TableName() string {
|
||||
return "undian_prizes"
|
||||
}
|
||||
|
||||
// UndianVoucherDB represents the undian_vouchers table
|
||||
type UndianVoucherDB struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
UndianEventID int64 `gorm:"not null" json:"undian_event_id"`
|
||||
CustomerID int64 `gorm:"not null" json:"customer_id"`
|
||||
OrderID *int64 `json:"order_id"`
|
||||
VoucherCode string `gorm:"size:50;not null;uniqueIndex" json:"voucher_code"`
|
||||
VoucherNumber *int `json:"voucher_number"`
|
||||
IsWinner bool `gorm:"default:false" json:"is_winner"`
|
||||
PrizeRank *int `json:"prize_rank"`
|
||||
WonAt *time.Time `json:"won_at"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
|
||||
// Relations
|
||||
UndianEvent UndianEventDB `gorm:"foreignKey:UndianEventID" json:"undian_event,omitempty"`
|
||||
}
|
||||
|
||||
func (UndianVoucherDB) TableName() string {
|
||||
return "undian_vouchers"
|
||||
}
|
||||
|
||||
// Response Models
|
||||
type UndianListResponse struct {
|
||||
Events []*UndianEventResponse `json:"events"`
|
||||
}
|
||||
|
||||
type UndianEventResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
ImageURL *string `json:"image_url"`
|
||||
Status string `json:"status"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
DrawDate time.Time `json:"draw_date"`
|
||||
MinimumPurchase float64 `json:"minimum_purchase"`
|
||||
DrawCompleted bool `json:"draw_completed"`
|
||||
DrawCompletedAt *time.Time `json:"draw_completed_at"`
|
||||
TermsConditions *string `json:"terms_and_conditions"`
|
||||
Prefix *string `json:"prefix"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
VoucherCount int `json:"voucher_count"`
|
||||
Vouchers []*UndianVoucherResponse `json:"vouchers"`
|
||||
Prizes []*UndianPrizeResponse `json:"prizes"`
|
||||
}
|
||||
|
||||
type UndianVoucherResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
VoucherCode string `json:"voucher_code"`
|
||||
VoucherNumber *int `json:"voucher_number"`
|
||||
IsWinner bool `json:"is_winner"`
|
||||
PrizeRank *int `json:"prize_rank"`
|
||||
WonAt *time.Time `json:"won_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type UndianPrizeResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Rank int `json:"rank"`
|
||||
PrizeName string `json:"prize_name"`
|
||||
PrizeValue *float64 `json:"prize_value"`
|
||||
PrizeDescription *string `json:"prize_description"`
|
||||
PrizeType string `json:"prize_type"`
|
||||
PrizeImageURL *string `json:"prize_image_url"`
|
||||
WinningVoucherID *int64 `json:"winning_voucher_id"`
|
||||
WinnerUserID *int64 `json:"winner_user_id"`
|
||||
Amount *int64 `json:"amount"`
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/constants/role"
|
||||
"enaklo-pos-be/internal/constants/userstatus"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
Name string
|
||||
Email string
|
||||
Password string
|
||||
Status userstatus.UserStatus
|
||||
NIK string
|
||||
UserType string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
RoleID role.Role
|
||||
PhoneNumber string
|
||||
RoleName string
|
||||
PartnerID *int64
|
||||
SiteID *int64
|
||||
SiteName string
|
||||
PartnerName string
|
||||
ResetPassword bool
|
||||
}
|
||||
|
||||
type Customer struct {
|
||||
ID int64
|
||||
Name string
|
||||
Email string
|
||||
Password string
|
||||
Phone string
|
||||
Points int
|
||||
Status userstatus.UserStatus
|
||||
NIK string
|
||||
UserType string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
RoleID role.Role
|
||||
PhoneNumber string
|
||||
RoleName string
|
||||
PartnerID *int64
|
||||
SiteID *int64
|
||||
SiteName string
|
||||
PartnerName string
|
||||
ResetPassword bool
|
||||
CustomerID string
|
||||
BirthDate time.Time
|
||||
VerificationID string
|
||||
OTP string
|
||||
}
|
||||
|
||||
type CustomerPoints struct {
|
||||
ID uint64
|
||||
CustomerID uint64
|
||||
TotalPoints int
|
||||
AvailablePoints int
|
||||
}
|
||||
|
||||
type AuthenticateUser struct {
|
||||
ID int64
|
||||
Token string
|
||||
Name string
|
||||
RoleID role.Role
|
||||
RoleName string
|
||||
PartnerID *int64
|
||||
PartnerName string
|
||||
PartnerStatus string
|
||||
SiteID *int64
|
||||
SiteName string
|
||||
ResetPassword bool
|
||||
PartnerLicense PartnerLicense
|
||||
UserType string
|
||||
}
|
||||
|
||||
type UserRoleDB struct {
|
||||
ID int64 `gorm:"primary_key;column:user_role_id" `
|
||||
UserID int64 `gorm:"column:user_id"`
|
||||
RoleID int64 `gorm:"column:role_id"`
|
||||
PartnerID *int64 `gorm:"column:partner_id"`
|
||||
SiteID *int64 `gorm:"column:site_id"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||
}
|
||||
|
||||
func (UserRoleDB) TableName() string {
|
||||
return "user_roles"
|
||||
}
|
||||
|
||||
func (u *User) ToUserDB(createdBy int64) (*UserDB, error) {
|
||||
hashedPassword, err := u.HashedPassword(u.Password)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.RoleID == role.Admin && u.PartnerID == nil {
|
||||
return nil, errors.New("invalid request")
|
||||
}
|
||||
|
||||
return &UserDB{
|
||||
Name: u.Name,
|
||||
Email: u.Email,
|
||||
Password: hashedPassword,
|
||||
RoleID: int64(u.RoleID),
|
||||
PartnerID: u.PartnerID,
|
||||
Status: userstatus.Active,
|
||||
CreatedBy: createdBy,
|
||||
SiteID: u.SiteID,
|
||||
PhoneNumber: u.PhoneNumber,
|
||||
NIK: u.NIK,
|
||||
UserType: u.UserType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u User) HashedPassword(password string) (string, error) {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(hashedPassword), nil
|
||||
}
|
||||
|
||||
func (c Customer) HashedPassword() string {
|
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(c.Password), bcrypt.DefaultCost)
|
||||
|
||||
return string(hashedPassword)
|
||||
}
|
||||
|
||||
func (u *Customer) ToUserAuthenticate(signedToken string) *AuthenticateUser {
|
||||
return &AuthenticateUser{
|
||||
ID: u.ID,
|
||||
Token: signedToken,
|
||||
Name: u.Name,
|
||||
UserType: u.UserType,
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
type Wallet struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement;column:id"`
|
||||
PartnerID int64 `gorm:"type:int;not null;column:partner_id"`
|
||||
Balance float64 `gorm:"type:decimal(18,2);not null;default:0.00;column:balance"`
|
||||
AuthBalance float64 `gorm:"type:decimal(18,2);not null;default:0.00;column:auth_balance"`
|
||||
Currency string `gorm:"type:varchar(3);not null;column:currency"`
|
||||
Status string `gorm:"type:varchar(50);column:status"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"`
|
||||
}
|
||||
|
||||
func (Wallet) TableName() string {
|
||||
return "wallets"
|
||||
}
|
||||
|
||||
type WalletWithdrawRequest struct {
|
||||
ID int64
|
||||
Token string
|
||||
PartnerID int64
|
||||
Amount int64
|
||||
Fee int64
|
||||
Total int64
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"apskel-pos-be/internal/appcontext"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/service"
|
||||
"apskel-pos-be/internal/transformer"
|
||||
"apskel-pos-be/internal/util"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AnalyticsHandler struct {
|
||||
analyticsService service.AnalyticsService
|
||||
transformer transformer.Transformer
|
||||
}
|
||||
|
||||
func NewAnalyticsHandler(
|
||||
analyticsService service.AnalyticsService,
|
||||
transformer transformer.Transformer,
|
||||
) *AnalyticsHandler {
|
||||
return &AnalyticsHandler{
|
||||
analyticsService: analyticsService,
|
||||
transformer: transformer,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AnalyticsHandler) GetPaymentMethodAnalytics(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
var req contract.PaymentMethodAnalyticsRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "AnalyticsHandler::GetPaymentMethodAnalytics", err.Error())}), "AnalyticsHandler::GetPaymentMethodAnalytics")
|
||||
return
|
||||
}
|
||||
|
||||
req.OrganizationID = contextInfo.OrganizationID
|
||||
modelReq := transformer.PaymentMethodAnalyticsContractToModel(&req)
|
||||
|
||||
response, err := h.analyticsService.GetPaymentMethodAnalytics(ctx, modelReq)
|
||||
if err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "AnalyticsHandler::GetPaymentMethodAnalytics", err.Error())}), "AnalyticsHandler::GetPaymentMethodAnalytics")
|
||||
return
|
||||
}
|
||||
|
||||
// Transform model to contract
|
||||
contractResp := transformer.PaymentMethodAnalyticsModelToContract(response)
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "AnalyticsHandler::GetPaymentMethodAnalytics")
|
||||
}
|
||||
|
||||
// GetSalesAnalytics handles the request to get sales analytics
|
||||
func (h *AnalyticsHandler) GetSalesAnalytics(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
var req contract.SalesAnalyticsRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "AnalyticsHandler::GetSalesAnalytics", err.Error())}), "AnalyticsHandler::GetSalesAnalytics")
|
||||
return
|
||||
}
|
||||
|
||||
req.OrganizationID = contextInfo.OrganizationID
|
||||
modelReq := transformer.SalesAnalyticsContractToModel(&req)
|
||||
|
||||
// Call service
|
||||
response, err := h.analyticsService.GetSalesAnalytics(ctx, modelReq)
|
||||
if err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "AnalyticsHandler::GetSalesAnalytics", err.Error())}), "AnalyticsHandler::GetSalesAnalytics")
|
||||
return
|
||||
}
|
||||
|
||||
// Transform model to contract
|
||||
contractResp := transformer.SalesAnalyticsModelToContract(response)
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "AnalyticsHandler::GetSalesAnalytics")
|
||||
}
|
||||
|
||||
// GetProductAnalytics handles the request to get product analytics
|
||||
func (h *AnalyticsHandler) GetProductAnalytics(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
var req contract.ProductAnalyticsRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "AnalyticsHandler::GetProductAnalytics", err.Error())}), "AnalyticsHandler::GetProductAnalytics")
|
||||
return
|
||||
}
|
||||
|
||||
req.OrganizationID = contextInfo.OrganizationID
|
||||
// Transform contract to model
|
||||
modelReq := transformer.ProductAnalyticsContractToModel(&req)
|
||||
|
||||
// Call service
|
||||
response, err := h.analyticsService.GetProductAnalytics(ctx, modelReq)
|
||||
if err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "AnalyticsHandler::GetProductAnalytics", err.Error())}), "AnalyticsHandler::GetProductAnalytics")
|
||||
return
|
||||
}
|
||||
|
||||
// Transform model to contract
|
||||
contractResp := transformer.ProductAnalyticsModelToContract(response)
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "AnalyticsHandler::GetProductAnalytics")
|
||||
}
|
||||
|
||||
// GetDashboardAnalytics handles the request to get dashboard analytics
|
||||
func (h *AnalyticsHandler) GetDashboardAnalytics(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
var req contract.DashboardAnalyticsRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "AnalyticsHandler::GetDashboardAnalytics", err.Error())}), "AnalyticsHandler::GetDashboardAnalytics")
|
||||
return
|
||||
}
|
||||
|
||||
req.OrganizationID = contextInfo.OrganizationID
|
||||
modelReq := transformer.DashboardAnalyticsContractToModel(&req)
|
||||
|
||||
response, err := h.analyticsService.GetDashboardAnalytics(ctx, modelReq)
|
||||
if err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "AnalyticsHandler::GetDashboardAnalytics", err.Error())}), "AnalyticsHandler::GetDashboardAnalytics")
|
||||
return
|
||||
}
|
||||
|
||||
// Transform model to contract
|
||||
contractResp := transformer.DashboardAnalyticsModelToContract(response)
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "AnalyticsHandler::GetDashboardAnalytics")
|
||||
}
|
||||
|
||||
func (h *AnalyticsHandler) GetProfitLossAnalytics(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
var req contract.ProfitLossAnalyticsRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "AnalyticsHandler::GetProfitLossAnalytics", err.Error())}), "AnalyticsHandler::GetProfitLossAnalytics")
|
||||
return
|
||||
}
|
||||
|
||||
req.OrganizationID = contextInfo.OrganizationID
|
||||
modelReq, err := transformer.ProfitLossAnalyticsContractToModel(&req)
|
||||
if err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "AnalyticsHandler::GetProfitLossAnalytics", err.Error())}), "AnalyticsHandler::GetProfitLossAnalytics")
|
||||
return
|
||||
}
|
||||
|
||||
// Call service
|
||||
response, err := h.analyticsService.GetProfitLossAnalytics(ctx, modelReq)
|
||||
if err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "AnalyticsHandler::GetProfitLossAnalytics", err.Error())}), "AnalyticsHandler::GetProfitLossAnalytics")
|
||||
return
|
||||
}
|
||||
|
||||
// Transform model to contract
|
||||
contractResp := transformer.ProfitLossAnalyticsModelToContract(response)
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "AnalyticsHandler::GetProfitLossAnalytics")
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/logger"
|
||||
"apskel-pos-be/internal/service"
|
||||
"apskel-pos-be/internal/transformer"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
authService service.AuthService
|
||||
}
|
||||
|
||||
func NewAuthHandler(authService service.AuthService) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
authService: authService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var req contract.LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("AuthHandler::Login -> request binding failed")
|
||||
h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.TrimSpace(req.Email) == "" {
|
||||
logger.FromContext(c.Request.Context()).Error("AuthHandler::Login -> email is required")
|
||||
h.sendValidationErrorResponse(c, "Email is required", constants.MissingFieldErrorCode)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.TrimSpace(req.Password) == "" {
|
||||
logger.FromContext(c.Request.Context()).Error("AuthHandler::Login -> password is required")
|
||||
h.sendValidationErrorResponse(c, "Password is required", constants.MissingFieldErrorCode)
|
||||
return
|
||||
}
|
||||
|
||||
loginResponse, err := h.authService.Login(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("AuthHandler::Login -> Failed to login")
|
||||
h.sendErrorResponse(c, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
logger.FromContext(c.Request.Context()).Infof("AuthHandler::Login -> Successfully logged in user = %s", loginResponse.User.Email)
|
||||
c.JSON(http.StatusOK, loginResponse)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Logout(c *gin.Context) {
|
||||
token := h.extractTokenFromHeader(c)
|
||||
if token == "" {
|
||||
logger.FromContext(c.Request.Context()).Error("AuthHandler::Logout -> token is required")
|
||||
h.sendErrorResponse(c, "Token is required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.authService.Logout(c.Request.Context(), token)
|
||||
if err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("AuthHandler::Logout -> Failed to logout")
|
||||
h.sendErrorResponse(c, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
logger.FromContext(c.Request.Context()).Info("AuthHandler::Logout -> Successfully logged out")
|
||||
c.JSON(http.StatusOK, transformer.CreateSuccessResponse("Successfully logged out", nil))
|
||||
}
|
||||
|
||||
func (h *AuthHandler) RefreshToken(c *gin.Context) {
|
||||
token := h.extractTokenFromHeader(c)
|
||||
if token == "" {
|
||||
logger.FromContext(c.Request.Context()).Error("AuthHandler::RefreshToken -> token is required")
|
||||
h.sendErrorResponse(c, "Token is required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
loginResponse, err := h.authService.RefreshToken(c.Request.Context(), token)
|
||||
if err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("AuthHandler::RefreshToken -> Failed to refresh token")
|
||||
h.sendErrorResponse(c, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
logger.FromContext(c.Request.Context()).Infof("AuthHandler::RefreshToken -> Successfully refreshed token for user = %s", loginResponse.User.Email)
|
||||
c.JSON(http.StatusOK, loginResponse)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ValidateToken(c *gin.Context) {
|
||||
token := h.extractTokenFromHeader(c)
|
||||
if token == "" {
|
||||
logger.FromContext(c.Request.Context()).Error("AuthHandler::ValidateToken -> token is required")
|
||||
h.sendErrorResponse(c, "Token is required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
userResponse, err := h.authService.ValidateToken(token)
|
||||
if err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("AuthHandler::ValidateToken -> Failed to validate token")
|
||||
h.sendErrorResponse(c, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
logger.FromContext(c.Request.Context()).Infof("AuthHandler::ValidateToken -> Successfully validated token for user = %s", userResponse.Email)
|
||||
c.JSON(http.StatusOK, userResponse)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) GetProfile(c *gin.Context) {
|
||||
token := h.extractTokenFromHeader(c)
|
||||
if token == "" {
|
||||
logger.FromContext(c.Request.Context()).Error("AuthHandler::GetProfile -> token is required")
|
||||
h.sendErrorResponse(c, "Token is required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
userResponse, err := h.authService.ValidateToken(token)
|
||||
if err != nil {
|
||||
logger.FromContext(c.Request.Context()).WithError(err).Error("AuthHandler::GetProfile -> Failed to get profile")
|
||||
h.sendErrorResponse(c, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
logger.FromContext(c.Request.Context()).Infof("AuthHandler::GetProfile -> Successfully retrieved profile for user = %s", userResponse.Email)
|
||||
c.JSON(http.StatusOK, userResponse)
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
func (h *AuthHandler) extractTokenFromHeader(c *gin.Context) string {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Expected format: "Bearer <token>"
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return parts[1]
|
||||
}
|
||||
|
||||
func (h *AuthHandler) sendErrorResponse(c *gin.Context, message string, statusCode int) {
|
||||
errorResponse := &contract.ErrorResponse{
|
||||
Error: "error",
|
||||
Message: message,
|
||||
Code: statusCode,
|
||||
}
|
||||
c.JSON(statusCode, errorResponse)
|
||||
}
|
||||
|
||||
func (h *AuthHandler) sendValidationErrorResponse(c *gin.Context, message string, errorCode string) {
|
||||
errorResponse := &contract.ErrorResponse{
|
||||
Error: "validation_error",
|
||||
Message: message,
|
||||
Code: http.StatusBadRequest,
|
||||
Details: map[string]interface{}{
|
||||
"error_code": errorCode,
|
||||
"entity": constants.AuthHandlerEntity,
|
||||
},
|
||||
}
|
||||
c.JSON(http.StatusBadRequest, errorResponse)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user