This commit is contained in:
aditya.siregar
2025-07-18 20:10:29 +07:00
parent 1bceae010b
commit 4f5950543e
511 changed files with 24132 additions and 34137 deletions
+242
View File
@@ -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!** 🎉
+284
View File
@@ -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
View File
@@ -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 {
+80
View File
@@ -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 ""
}
+81
View File
@@ -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 ""
}
BIN
View File
Binary file not shown.
-5
View File
@@ -1,5 +0,0 @@
package database
type Config interface {
ConnString() string
}
-52
View File
@@ -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)
}
-142
View File
@@ -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
}
}
-46
View File
@@ -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
}
-56
View File
@@ -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
}
-96
View File
@@ -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,
}
}
-53
View File
@@ -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{})
}
-40
View File
@@ -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{}
}
BIN
View File
Binary file not shown.
-12
View File
@@ -1,12 +0,0 @@
package branch
type BranchStatus string
const (
Active BranchStatus = "Active"
Inactive BranchStatus = "Inactive"
)
func (b BranchStatus) toString() string {
return string(b)
}
+63
View File
@@ -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
}
+17
View File
@@ -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,
}
-66
View File
@@ -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)
}
-12
View File
@@ -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)
}
+57
View File
@@ -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")
)
+80
View File
@@ -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
}
+27
View File
@@ -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"
)
+87
View File
@@ -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
}
-87
View File
@@ -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
}
+26
View File
@@ -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
}
-9
View File
@@ -1,9 +0,0 @@
package constants
const (
OssLogLevelLogOff = "LogOff"
OssLogLevelDebug = "Debug"
OssLogLevelError = "Error"
OssLogLevelWarn = "Warn"
OssLogLevelInfo = "Info"
)
+27
View File
@@ -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",
}
+82
View File
@@ -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
}
-58
View File
@@ -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
}
-12
View File
@@ -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
)
-12
View File
@@ -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)
}
+28
View File
@@ -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)
}
+210
View File
@@ -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"`
}
+49
View File
@@ -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"`
}
+49
View File
@@ -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"`
}
+58
View File
@@ -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"`
}
+73
View File
@@ -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"`
}
+72
View 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"`
}
+215
View File
@@ -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"`
}
+57
View File
@@ -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"`
}
+103
View File
@@ -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"`
}
+39
View File
@@ -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
}
+33
View File
@@ -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())
}
+69
View File
@@ -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
+102
View File
@@ -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"`
}
+56
View File
@@ -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"
}
+36
View File
@@ -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"
}
+26
View File
@@ -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()...)
}
+28
View File
@@ -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"
}
+42
View File
@@ -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
}
}
+110
View File
@@ -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
}
+89
View File
@@ -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
}
+33
View File
@@ -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"
}
+34
View File
@@ -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"
}
+38
View File
@@ -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"
}
+30
View File
@@ -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"
}
+93
View File
@@ -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
}
+31
View File
@@ -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"
}
+66
View File
@@ -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"
}
+94
View File
@@ -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
}
-219
View File
@@ -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 = &currentTime
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
}
-25
View File
@@ -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
}
-28
View File
@@ -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
}
-9
View File
@@ -1,9 +0,0 @@
package entity
type Category struct {
ID int64
PartnerID int64
Name string
CreatedAt int64
UpdatedAt int64
}
-18
View File
@@ -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
}
-2
View File
@@ -1,2 +0,0 @@
package entity
-43
View File
@@ -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 {
}
-13
View File
@@ -1,13 +0,0 @@
package entity
type (
SendEmailNotificationParam struct {
Sender string
Recipient string
CcEmails []string
Subject string
TemplateName string
TemplatePath string
Data interface{}
}
)
-158
View File
@@ -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 = &currentTime
}
-29
View File
@@ -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
}
-38
View File
@@ -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
}
-162
View File
@@ -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,
}
}
-98
View File
@@ -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"`
}
-83
View File
@@ -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"`
}
-19
View File
@@ -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
}
-308
View File
@@ -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
}
-135
View File
@@ -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
}
-24
View File
@@ -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"`
}
-253
View File
@@ -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 = &currentTime
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(),
}
}
-56
View File
@@ -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"
}
-26
View File
@@ -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"
}
-23
View File
@@ -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
}
-177
View File
@@ -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 = &currentTime
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"`
}
-32
View File
@@ -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"
}
-210
View File
@@ -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 = &currentTime
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"`
}
-95
View File
@@ -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
}
-62
View File
@@ -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
}
-118
View File
@@ -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"`
}
-143
View File
@@ -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,
}
}
-27
View File
@@ -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
}
+157
View File
@@ -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")
}
+169
View File
@@ -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