Compare commits

...
Author SHA1 Message Date
Efril d9b51a7616 update ordedr list 2026-05-14 16:17:28 +07:00
ryan b27e40b531 fix filter order by outlet id 2026-05-14 15:57:46 +07:00
ryan 44aca7641f Revert "add filter order by outlet id"
This reverts commit a89ff00d94.
2026-05-14 15:28:45 +07:00
ryan a89ff00d94 add filter order by outlet id 2026-05-14 15:15:32 +07:00
ryan 227f11359c Revert "add list order by outlet id"
This reverts commit 7a737d7f83.
2026-05-14 14:46:32 +07:00
ryan 7a737d7f83 add list order by outlet id 2026-05-14 14:41:50 +07:00
ryan 312ea94e62 fix scheduler counting void and refund 2026-05-14 14:22:07 +07:00
Efril cb8a830345 fix pointer 2026-05-14 13:54:15 +07:00
Efril 222cadd8df table and order grouping by outlet 2026-05-14 01:40:17 +07:00
Efril 50d633ee3a fix products 2026-05-14 01:19:45 +07:00
Efril 21fa21d089 get products all 2026-05-14 00:15:28 +07:00
ryan 5f379faf17 change product list to retrieve its data from product outlets 2026-05-13 23:15:09 +07:00
ryan 3b62504798 fix 2026-05-13 22:30:55 +07:00
ryan 4130cb66df refactor and add outlet product table 2026-05-13 21:58:54 +07:00
efrilm fa037b4d2a fix request outlet id at analytic 2026-05-13 14:23:27 +07:00
ryan d38a770ec5 Add omset milestone scheduler with owner role and revenue tracking 2026-05-13 09:48:17 +07:00
ryan 015292e830 Refactor: extract outlet ID filtering to helper method 2026-05-12 21:50:53 +07:00
ryan c573b23d76 Add outlet_id to use context or request 2026-05-12 18:32:36 +07:00
Efril f73a5d533c add notif at create order 2026-05-10 23:36:22 +07:00
Efril 4ea8e32a8e Merge branch 'self-order+notification' of https://gits.altru.id/apksel-dev/apskel-pos-backend into self-order+notification 2026-05-10 23:10:03 +07:00
Efril 06d79046d0 fix order and self order response 2026-05-10 23:07:57 +07:00
ryan 8eb19c57ba Change self-order response 2026-05-10 21:34:45 +07:00
Efril f123de7233 Revert "change order response at self order"
This reverts commit 7ba776555e.
2026-05-10 21:31:17 +07:00
Efril 7ba776555e change order response at self order 2026-05-10 21:19:37 +07:00
Efril bccf02b5f7 rename session_id 2026-05-10 19:56:34 +07:00
Efril c24a8a8c13 rename organization_id, add customer_name and order_type at order self order 2026-05-10 19:15:50 +07:00
ryan 6064ef8fde Update QR token generation 2026-05-10 14:52:02 +07:00
Efril 1834dd0b19 update url qrcode table 2026-05-10 14:13:20 +07:00
Efril 9f653eef37 fix migration number 2026-05-10 13:30:40 +07:00
Efril ddaf6df436 migration notification 2026-05-10 12:35:44 +07:00
ryan 0708ce816e Update Redis host 2026-05-10 12:34:36 +07:00
ryan 2c34578a98 Merge remote-tracking branch 'origin/feature/notification' into self-order+notification
# Conflicts:
#	go.mod
#	go.sum
#	internal/app/app.go
#	internal/router/router.go
2026-05-10 12:23:16 +07:00
Efril 9d71b339b5 notification 2026-05-10 10:57:38 +07:00
Efril bbd6666299 user devices 2026-05-10 10:42:09 +07:00
Efril 23b6293502 fcm setup 2026-05-09 12:24:44 +07:00
ryan 07b186c986 Barcode generation with Boombuler 2026-05-09 01:01:25 +07:00
ryan 4cc563f6f1 Add self-order/orders/:sessionId 2026-05-09 00:12:00 +07:00
ryan e7c4681102 Add outlet_id to self-order/categories 2026-05-08 23:56:06 +07:00
ryan f957b07d23 Change self-order/menu from POST to GET 2026-05-08 23:18:52 +07:00
ryan 3c103b7692 Token and session implementation with Redis 2026-05-08 18:41:14 +07:00
ryan fe57aab3b4 Fix categories with table 2026-05-08 14:19:40 +07:00
ryan 3721fb3cd7 List categories for self-order 2026-05-06 11:56:11 +07:00
ryan 2d6df8e4c6 Self Order - BE 2026-05-05 21:07:03 +07:00
aefril 2c76962959 Merge pull request 'ingredient composition' (#2) from feature/ingredient-composition into main
Reviewed-on: #2
2026-04-27 14:22:22 +00:00
Efril a7022dd4c1 ingredient composition 2026-04-27 21:17:12 +07:00
Efril 3542104050 fix sales 2026-04-27 13:24:16 +07:00
Efril eb95459578 fix if order amount 0 2026-04-26 23:40:01 +07:00
Efril 1a5ddd2b34 update 2026-04-26 23:25:54 +07:00
Efril 0aa280462c fix cors 2026-04-26 23:14:11 +07:00
aefril 421475006b Merge pull request 'hpp' (#1) from hpp into main
Reviewed-on: #1
2026-04-26 16:08:02 +00:00
Efril 75fdb8e847 Merge branch 'hpp' of https://gits.altru.id/apksel-dev/apskel-pos-backend into hpp 2026-04-26 23:04:47 +07:00
Efril 8efa644680 payment amount fix 0 2026-04-26 23:04:31 +07:00
ryan ce99aef289 delete leftover 2026-04-24 18:48:40 +07:00
ryan ba970229a9 Fix HPP 2026-04-24 17:56:38 +07:00
ryan 9b606b4c8b Implementasi HPP std dan real 2026-04-23 11:28:51 +07:00
Efril d3dddea1c7 add category name at product response 2026-04-20 14:21:27 +07:00
Efril 80a78137a0 fix create outlet 2026-04-20 12:31:33 +07:00
efrilm 3826a6b7a9 create outlet route 2026-04-16 19:23:36 +07:00
efrilm d695bedc97 update product response 2026-04-16 17:14:18 +07:00
efrilm 3db4afbce6 fix update ingredients 2026-04-16 15:44:13 +07:00
efrilm 535e4c84f6 product variant route 2026-04-16 14:30:48 +07:00
Efril f25ec1c06f create outlet 2026-04-14 19:09:09 +07:00
efrilm b3359fa6ff update product analytic 2025-11-25 21:44:20 +07:00
efrilm 0c331dce6a update product analytic if exiest order 2025-11-25 13:23:59 +07:00
efrilm cd784624c9 update total sales in dashboard 2025-10-15 18:55:35 +07:00
efrilm 3e0d75a4d0 update products 2025-10-09 20:03:20 +07:00
efrilm 54dc8662d6 merge 2025-10-07 00:07:38 +07:00
efrilm 27a2535dde update product analytic 2025-10-07 00:02:15 +07:00
efrilm f55ea1ceb0 add order at categories 2025-10-06 22:46:01 +07:00
Aditya Siregar 670a283c7b update backend 2025-09-25 20:31:57 +07:00
Aditya Siregar b29677a192 Fix Product Analysitcs 2025-09-22 22:12:54 +07:00
Aditya Siregar 26ac7a2752 update refresh token 2025-09-20 17:24:39 +07:00
Aditya Siregar f85929c575 Add Refresh token 2025-09-20 17:17:00 +07:00
Aditya Siregar 9db3dcb472 Fix Commit 2025-09-18 17:21:58 +07:00
Aditya Siregar a520d0ed11 fix campaign rules 2025-09-18 13:39:37 +07:00
Aditya Siregar c7828a5cad push points 2025-09-18 12:42:07 +07:00
Aditya Siregar d3db08fd15 wheel 2025-09-18 12:29:27 +07:00
Aditya Siregar 67812a1d75 fix login issue 2025-09-18 12:27:03 +07:00
Aditya Siregar be92ec8b23 test wheels 2025-09-18 12:01:20 +07:00
Aditya Siregar f64fec1fe2 campaign 2025-09-18 02:01:50 +07:00
Aditya Siregar 259b8a11b5 Push 2025-09-18 01:38:03 +07:00
Aditya Siregar 65f61b65cf user auth register 2025-09-18 01:32:01 +07:00
Aditya Siregar c68b536480 Fixed tabled 2025-09-18 00:37:46 +07:00
Aditya Siregar 155016dec8 Add Campaign 2025-09-17 23:55:11 +07:00
Aditya Siregar 201e24041b Add Tiers and Game Prize 2025-09-17 19:30:17 +07:00
Aditya Siregar 12ee54390f Revert "voucher"
This reverts commit cfe690a40f.
2025-09-16 19:31:39 +07:00
Aditya Siregar 36c2352cb2 Revert "fix"
This reverts commit 691456af87.
2025-09-16 19:31:28 +07:00
Aditya Siregar b37d21d366 Revert "fix backend"
This reverts commit afa8782eb2.
2025-09-16 19:31:14 +07:00
Aditya Siregar afa8782eb2 fix backend 2025-09-13 17:34:34 +07:00
Aditya Siregar 691456af87 fix 2025-09-13 16:53:12 +07:00
Aditya Siregar cfe690a40f voucher 2025-09-13 15:37:26 +07:00
Aditya Siregar 4f6208e479 fix 2025-09-13 02:17:51 +07:00
Aditya Siregar 75ec5274d2 coa fix 2025-09-12 16:37:16 +07:00
Aditya Siregar 91f51d129e Fix 2025-09-12 15:41:08 +07:00
Aditya Siregar 3a04990ec8 add product ingredients 2025-09-12 15:37:19 +07:00
Aditya Siregar efe09c21e4 Add coa purchase and vendors 2025-09-12 01:12:11 +07:00
Aditya Siregar c107733add Update 2025-09-01 16:20:27 +07:00
Aditya Siregar 4c6dc5c8b4 deployment 2025-09-01 16:18:34 +07:00
Aditya Siregar f91f85202e add cors check and update docker file 2025-09-01 16:14:15 +07:00
Aditya Siregar 13d8c75be7 add total out 2025-08-14 01:35:19 +07:00
Aditya Siregar 07b3eda263 addd inventory movement 2025-08-14 01:00:36 +07:00
Aditya Siregar 3a0c262c77 Update 2025-08-14 00:45:14 +07:00
Aditya Siregar 7adba2c8f5 Add Inventory Report 2025-08-14 00:38:26 +07:00
Aditya Siregar bb9a81c7c1 Updarte 2025-08-13 23:51:58 +07:00
Aditya Siregar 4a720f439b Update 2025-08-13 23:49:35 +07:00
Aditya Siregar ee7d0e529b update order status 2025-08-13 23:36:31 +07:00
Aditya Siregar ccb0458189 Update payment 2025-08-13 23:14:26 +07:00
Aditya Siregar 451697b783 Add organizatin ID 2025-08-13 21:38:09 +07:00
Aditya Siregar 1c7f7feb2e Add list Paymenty method 2025-08-13 21:21:32 +07:00
Aditya Siregar 9e74d415b3 add product inventory 2025-08-13 21:02:26 +07:00
Aditya Siregar 58dc92c722 Add Outlet ID 2025-08-13 20:57:57 +07:00
Aditya Siregar 4cade376b9 Add Category 2025-08-13 20:37:55 +07:00
Aditya Siregar 82f72fc3eb Push products 2025-08-13 20:30:39 +07:00
Aditya Siregar b72ab4ef3d Update product receipe 2025-08-10 21:46:44 +07:00
Aditya Siregar 265248ba49 update middlewared 2025-08-10 21:09:51 +07:00
Aditya Siregar db7c862fa8 fix unit 2025-08-10 20:58:32 +07:00
Aditya Siregar 835097d381 Update Ingredients 2025-08-10 20:57:26 +07:00
Aditya Siregar 5a42523f0f Update 2025-08-10 20:41:34 +07:00
Aditya Siregar dc23a318cd add deployment 2025-08-08 22:48:57 +07:00
Aditya Siregar 721235e6fe Update Order 2025-08-08 22:46:55 +07:00
Aditya Siregar 8e9c14b860 Add order items 2025-08-08 22:33:08 +07:00
Aditya Siregar c18e915d1e add users 2025-08-08 00:32:05 +07:00
Aditya Siregar fe4f17b34d Add ingredieints 2025-08-08 00:22:28 +07:00
Aditya Siregar 93a3b29ae9 Fix Split Bill 2025-08-07 22:45:02 +07:00
417 changed files with 37822 additions and 2230 deletions
+3
View File
@@ -6,3 +6,6 @@ config/env/*
!.env
vendor
# Firebase service account credentials
infra/firebase-service-account.json
+7 -86
View File
@@ -1,99 +1,20 @@
# Build Stage
# 1) Build stage
FROM golang:1.21-alpine AS build
# Install necessary packages including CA certificates
RUN apk --no-cache add ca-certificates tzdata git curl
WORKDIR /src
# Copy go mod files first for better caching
COPY go.mod go.sum ./
# Download dependencies
RUN go mod download
# Copy source code
COPY . .
# Build the application
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /app cmd/server/main.go
# Development Stage
FROM golang:1.21-alpine AS development
# Install air for live reload and other dev tools
RUN go install github.com/cosmtrek/air@latest
# Install necessary packages
RUN apk --no-cache add ca-certificates tzdata git curl
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /out/app ./cmd/server
# Set timezone
ENV TZ=Asia/Jakarta
# Expose port
EXPOSE 3300
# Use air for live reload in development
CMD ["air", "-c", ".air.toml"]
# Migration Stage
FROM build AS migration
# Install migration tool
RUN go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
WORKDIR /app
# Copy migration files
COPY migrations ./migrations
COPY infra ./infra
# Set the entrypoint for migrations
ENTRYPOINT ["migrate"]
# Production Stage
# 2) Production stage
FROM debian:bullseye-slim AS production
# Install minimal runtime dependencies
RUN apt-get update && apt-get install -y \
ca-certificates \
tzdata \
curl \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user for security
RUN apt-get update && apt-get install -y ca-certificates tzdata curl && rm -rf /var/lib/apt/lists/*
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Copy the binary
COPY --from=build /app /app
# Copy configuration files
COPY --from=build /src/infra /infra
# Change ownership to non-root user
RUN chown -R appuser:appuser /app /infra
# Set timezone
COPY --from=build /out/app /app
ENV TZ=Asia/Jakarta
# Expose port
EXPOSE 3300
# Health check
EXPOSE 4000
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:3300/health || exit 1
# Switch to non-root user
CMD curl -fsS http://localhost:3300/health || exit 1
USER appuser
# Set the entrypoint
ENTRYPOINT ["/app"]
+7 -2
View File
@@ -12,13 +12,18 @@ func main() {
cfg := config.LoadConfig()
logger.Setup(cfg.LogLevel(), cfg.LogFormat())
db, err := db.NewPostgres(cfg.Database)
pg, err := db.NewPostgres(cfg.Database)
if err != nil {
log.Fatal(err)
}
redisClient, err := db.NewRedisClient(cfg.Redis)
if err != nil {
log.Fatal(err)
}
logger.NonContext.Info("helloworld")
application := app.NewApp(db)
application := app.NewApp(pg, redisClient)
if err := application.Initialize(cfg); err != nil {
log.Fatalf("Failed to initialize application: %v", err)
+21
View File
@@ -26,9 +26,12 @@ var (
type Config struct {
Server Server `mapstructure:"server"`
Database Database `mapstructure:"postgresql"`
Redis Redis `mapstructure:"redis"`
Jwt Jwt `mapstructure:"jwt"`
Log Log `mapstructure:"log"`
S3Config S3Config `mapstructure:"s3"`
Fonnte Fonnte `mapstructure:"fonnte"`
FCM FCM `mapstructure:"fcm"`
}
var (
@@ -65,9 +68,19 @@ func (c *Config) Auth() *AuthConfig {
return &AuthConfig{
jwtTokenSecret: c.Jwt.Token.Secret,
jwtTokenExpiresTTL: c.Jwt.Token.ExpiresTTL,
refreshTokenSecret: c.Jwt.RefreshToken.Secret,
refreshTokenExpiresTTL: c.Jwt.RefreshToken.ExpiresTTL,
}
}
func (c *Config) GetCustomerJWTSecret() string {
return c.Jwt.Customer.Secret
}
func (c *Config) GetCustomerJWTExpiresTTL() int {
return c.Jwt.Customer.ExpiresTTL
}
func (c *Config) LogLevel() string {
return c.Log.LogLevel
}
@@ -79,3 +92,11 @@ func (c *Config) Port() string {
func (c *Config) LogFormat() string {
return c.Log.LogFormat
}
func (c *Config) GetFonnte() *Fonnte {
return &c.Fonnte
}
func (c *Config) GetFCM() *FCM {
return &c.FCM
}
+19
View File
@@ -5,6 +5,8 @@ import "time"
type AuthConfig struct {
jwtTokenExpiresTTL int
jwtTokenSecret string
refreshTokenExpiresTTL int
refreshTokenSecret string
}
type JWT struct {
@@ -20,3 +22,20 @@ func (c *AuthConfig) AccessTokenExpiresDate() time.Time {
duration := time.Duration(c.jwtTokenExpiresTTL)
return time.Now().UTC().Add(time.Minute * duration)
}
func (c *AuthConfig) RefreshTokenSecret() string {
return c.refreshTokenSecret
}
func (c *AuthConfig) RefreshTokenExpiresDate() time.Time {
duration := time.Duration(c.refreshTokenExpiresTTL)
return time.Now().UTC().Add(time.Minute * duration)
}
func (c *AuthConfig) AccessTokenTTL() time.Duration {
return time.Duration(c.jwtTokenExpiresTTL) * time.Minute
}
func (c *AuthConfig) RefreshTokenTTL() time.Duration {
return time.Duration(c.refreshTokenExpiresTTL) * time.Minute
}
+14
View File
@@ -0,0 +1,14 @@
package config
type FCM struct {
CredentialsFile string `mapstructure:"credentials_file"`
ProjectID string `mapstructure:"project_id"`
}
func (f *FCM) GetCredentialsFile() string {
return f.CredentialsFile
}
func (f *FCM) GetProjectID() string {
return f.ProjectID
}
+19
View File
@@ -0,0 +1,19 @@
package config
type Fonnte struct {
ApiUrl string `mapstructure:"api_url"`
Token string `mapstructure:"token"`
Timeout int `mapstructure:"timeout"`
}
func (f *Fonnte) GetApiUrl() string {
return f.ApiUrl
}
func (f *Fonnte) GetToken() string {
return f.Token
}
func (f *Fonnte) GetTimeout() int {
return f.Timeout
}
+12
View File
@@ -2,9 +2,21 @@ package config
type Jwt struct {
Token Token `mapstructure:"token"`
RefreshToken RefreshToken `mapstructure:"refresh_token"`
Customer Customer `mapstructure:"customer"`
}
type Token struct {
ExpiresTTL int `mapstructure:"expires-ttl"`
Secret string `mapstructure:"secret"`
}
type RefreshToken struct {
ExpiresTTL int `mapstructure:"expires-ttl"`
Secret string `mapstructure:"secret"`
}
type Customer struct {
ExpiresTTL int `mapstructure:"expires-ttl"`
Secret string `mapstructure:"secret"`
}
+55
View File
@@ -0,0 +1,55 @@
package config
import (
"fmt"
"time"
)
type Redis struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Password string `mapstructure:"password"`
DB int `mapstructure:"db"`
DialTimeout string `mapstructure:"dial_timeout"`
ReadTimeout string `mapstructure:"read_timeout"`
WriteTimeout string `mapstructure:"write_timeout"`
PoolSize int `mapstructure:"pool_size"`
MinIdleConnections int `mapstructure:"min_idle_connections"`
}
func (r Redis) Addr() string {
return fmt.Sprintf("%s:%d", r.Host, r.Port)
}
func (r Redis) ParseDialTimeout() time.Duration {
if r.DialTimeout == "" {
return 5 * time.Second
}
d, err := time.ParseDuration(r.DialTimeout)
if err != nil {
return 5 * time.Second
}
return d
}
func (r Redis) ParseReadTimeout() time.Duration {
if r.ReadTimeout == "" {
return 3 * time.Second
}
d, err := time.ParseDuration(r.ReadTimeout)
if err != nil {
return 3 * time.Second
}
return d
}
func (r Redis) ParseWriteTimeout() time.Duration {
if r.WriteTimeout == "" {
return 3 * time.Second
}
d, err := time.ParseDuration(r.WriteTimeout)
if err != nil {
return 3 * time.Second
}
return d
}
+1
View File
@@ -4,4 +4,5 @@ type Server struct {
Port string `mapstructure:"port"`
BaseUrl string `mapstructure:"common-url"`
LocalUrl string `mapstructure:"local-url"`
SelfOrderUrl string `mapstructure:"self-order-url"`
}
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
set -euo pipefail
APP_NAME="apskel-pos"
PORT="4000"
echo "🔄 Pulling latest code..."
git pull
echo "🐳 Building Docker image (production target)..."
docker build --target production -t $APP_NAME:latest .
echo "🛑 Stopping and removing old container..."
docker rm -f $APP_NAME 2>/dev/null || true
echo "🚀 Running new container..."
docker run -d --name $APP_NAME \
-p $PORT:$PORT \
-e TZ=Asia/Jakarta \
-v "$(pwd)/infra":/infra:ro \
-v "$(pwd)/templates":/templates:ro \
$APP_NAME:latest
echo "✅ Deployment complete."
+63 -14
View File
@@ -1,28 +1,54 @@
module apskel-pos-be
go 1.21
go 1.24
require (
github.com/gin-gonic/gin v1.9.1
github.com/go-playground/validator/v10 v10.17.0
github.com/google/uuid v1.1.2
github.com/google/uuid v1.6.0
github.com/lib/pq v1.2.0
github.com/spf13/viper v1.16.0
gopkg.in/yaml.v3 v3.0.1
)
require (
cel.dev/expr v0.23.1 // indirect
cloud.google.com/go v0.121.0 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/firestore v1.18.0 // indirect
cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/longrunning v0.6.7 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/storage v1.53.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/MicahParks/keyfunc v1.9.0 // indirect
github.com/bytedance/sonic v1.10.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
@@ -31,7 +57,7 @@ require (
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -39,34 +65,57 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/afero v1.10.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/atomic v1.10.0 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/arch v0.7.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.20.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.11.0 // indirect
google.golang.org/appengine/v2 v2.0.6 // indirect
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/grpc v1.72.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
require (
firebase.google.com/go/v4 v4.19.0
github.com/aws/aws-sdk-go v1.55.7
github.com/boombuler/barcode v1.1.0
github.com/golang-jwt/jwt/v5 v5.2.3
github.com/redis/go-redis/v9 v9.19.0
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.10.0
go.uber.org/zap v1.21.0
golang.org/x/crypto v0.28.0
golang.org/x/crypto v0.40.0
google.golang.org/api v0.231.0
gorm.io/driver/postgres v1.5.0
gorm.io/gorm v1.30.0
)
+156 -23
View File
@@ -1,3 +1,5 @@
cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg=
cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -17,14 +19,32 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go v0.121.0 h1:pgfwva8nGw7vivjZiRfrmglGWiCJBP+0OmDpenG/Fwg=
cloud.google.com/go v0.121.0/go.mod h1:rS7Kytwheu/y9buoDmu5EIpMMCI4Mb8ND4aeN4Vwj7Q=
cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.18.0 h1:cuydCaLS7Vl2SatAeivXyhbhDEIR8BDmtn4egDhIn2s=
cloud.google.com/go/firestore v1.18.0/go.mod h1:5ye0v48PhseZBdcl0qbl3uttu7FIEwEYVaWm0UIEOEU=
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@@ -35,18 +55,42 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
cloud.google.com/go/storage v1.53.0 h1:gg0ERZwL17pJ+Cz3cD2qS60w1WMDnwcm5YPAIQBHUAw=
cloud.google.com/go/storage v1.53.0/go.mod h1:7/eO2a/srr9ImZW9k5uufcNahT2+fPb8w5it1i5boaA=
cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
firebase.google.com/go/v4 v4.19.0 h1:f5NMlC2YHFsncz00c2+ecBr+ZYlRMhKIhj1z8Iz0lD8=
firebase.google.com/go/v4 v4.19.0/go.mod h1:P7UfBpzc8+Z3MckX79+zsWzKVfpGryr6HLbAe7gCWfs=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=
github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
@@ -61,6 +105,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -70,7 +116,17 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
@@ -84,6 +140,13 @@ github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SU
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -94,6 +157,9 @@ github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -121,6 +187,9 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -132,12 +201,16 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -149,10 +222,17 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -181,8 +261,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -215,17 +295,21 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
@@ -234,10 +318,13 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -247,8 +334,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -261,17 +349,41 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 h1:PB3Zrjs1sG1GBX51SXyTSoOTqcDglmsk7nT6tkKPb/k=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
@@ -289,8 +401,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -361,8 +473,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -372,6 +484,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -385,6 +499,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -428,8 +544,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -441,12 +557,15 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -519,6 +638,8 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.231.0 h1:LbUD5FUl0C4qwia2bjXhCMH65yz1MLPzA/0OYEsYY7Q=
google.golang.org/api v0.231.0/go.mod h1:H52180fPI/QQlUc0F4xWfGZILdv09GCWKt2bcsn164A=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -526,6 +647,8 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -562,6 +685,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 h1:IqsN8hx+lWLqlN+Sc3DoMy/watjofWiU8sRFgQ8fhKM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -578,6 +707,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -588,8 +719,10 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+28 -1
View File
@@ -1,12 +1,19 @@
server:
base-url:
local-url:
self-order-url: http://localhost:5173
port: 4000
jwt:
token:
expires-ttl: 1440
expires-ttl: 144000
secret: "5Lm25V3Qd7aut8dr4QUxm5PZUrSFs"
refresh_token:
expires-ttl: 7776000 # 3 months in minutes (90 days * 24 hours * 60 minutes)
secret: "R3fr3sh_T0k3n_S3cr3t_K3y_2024_P0S"
customer:
expires-ttl: 7776000
secret: "z8d5TlFCT58Q$i0%S^2M&3WtE$PMgd"
postgresql:
host: 62.72.45.250
@@ -21,6 +28,17 @@ postgresql:
connection-max-life-time-in-second: 600
debug: false
redis:
host: 194.233.78.1
port: 6379
password: "CmICdmnX1EZPhVBYzQPEGw==U"
db: 0
dial_timeout: 5s
read_timeout: 3s
write_timeout: 3s
pool_size: 10
min_idle_connections: 5
s3:
access_key_id: cf9a475e18bc7626cbdbf09709d82a64
access_key_secret: 91f3321294d3e23035427a0ecb893ada
@@ -32,3 +50,12 @@ s3:
log:
log_format: 'json'
log_level: 'debug'
fonnte:
api_url: "https://api.fonnte.com/send"
token: "bADQrf9NTXfLZQCK2wGg"
timeout: 30
fcm:
credentials_file: "infra/firebase-service-account.json"
project_id: "apskel-pos-v2"
+296 -12
View File
@@ -20,19 +20,23 @@ import (
"apskel-pos-be/internal/service"
"apskel-pos-be/internal/validator"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
type App struct {
server *http.Server
db *gorm.DB
redisClient *redis.Client
router *router.Router
shutdown chan os.Signal
omsetScheduler *service.OmsetMilestoneScheduler
}
func NewApp(db *gorm.DB) *App {
func NewApp(db *gorm.DB, redisClient *redis.Client) *App {
return &App{
db: db,
redisClient: redisClient,
shutdown: make(chan os.Signal, 1),
}
}
@@ -40,10 +44,29 @@ func NewApp(db *gorm.DB) *App {
func (a *App) Initialize(cfg *config.Config) error {
repos := a.initRepositories()
processors := a.initProcessors(cfg, repos)
services := a.initServices(processors, cfg)
// Initialize omset milestone scheduler
a.omsetScheduler = service.NewOmsetMilestoneScheduler(
repos.organizationRepo,
repos.userRepo,
processors.notificationProcessor,
)
services := a.initServices(processors, repos, cfg)
validators := a.initValidators()
middleware := a.initMiddleware(services)
middleware := a.initMiddleware(services, cfg)
healthHandler := handler.NewHealthHandler()
selfOrderHandler := handler.NewSelfOrderHandler(
services.orderService,
services.categoryService,
services.productService,
repos.tableRepo,
repos.outletRepo,
repos.userRepo,
repos.sessionRepo,
repos.orderRepo,
services.productOutletPriceService,
)
a.router = router.NewRouter(
cfg,
@@ -74,16 +97,55 @@ func (a *App) Initialize(cfg *config.Config) error {
services.paymentMethodService,
validators.paymentMethodValidator,
services.analyticsService,
services.reportService,
services.tableService,
validators.tableValidator,
services.unitService,
services.ingredientService,
services.productRecipeService,
services.vendorService,
validators.vendorValidator,
services.purchaseOrderService,
validators.purchaseOrderValidator,
services.unitConverterService,
validators.unitConverterValidator,
services.chartOfAccountTypeService,
validators.chartOfAccountTypeValidator,
services.chartOfAccountService,
validators.chartOfAccountValidator,
services.accountService,
validators.accountValidator,
*services.orderIngredientTransactionService,
validators.orderIngredientTransactionValidator,
services.gamificationService,
validators.gamificationValidator,
services.rewardService,
validators.rewardValidator,
services.campaignService,
validators.campaignValidator,
services.customerAuthService,
validators.customerAuthValidator,
services.customerPointsService,
services.spinGameService,
middleware.customerAuthMiddleware,
services.userDeviceService,
validators.userDeviceValidator,
services.notificationService,
validators.notificationValidator,
services.productOutletPriceService,
validators.productOutletPriceValidator,
selfOrderHandler,
)
return nil
}
func (a *App) Start(port string) error {
// Start the omset milestone scheduler (checks every hour)
if a.omsetScheduler != nil {
a.omsetScheduler.Start(1 * time.Hour)
}
engine := a.router.Init()
a.server = &http.Server{
@@ -119,6 +181,9 @@ func (a *App) Start(port string) error {
}
func (a *App) Shutdown() {
if a.omsetScheduler != nil {
a.omsetScheduler.Stop()
}
close(a.shutdown)
}
@@ -135,6 +200,7 @@ type repositories struct {
orderRepo *repository.OrderRepositoryImpl
orderItemRepo *repository.OrderItemRepositoryImpl
paymentRepo *repository.PaymentRepositoryImpl
paymentOrderItemRepo *repository.PaymentOrderItemRepositoryImpl
paymentMethodRepo *repository.PaymentMethodRepositoryImpl
fileRepo *repository.FileRepositoryImpl
customerRepo *repository.CustomerRepository
@@ -142,6 +208,34 @@ type repositories struct {
tableRepo *repository.TableRepository
unitRepo *repository.UnitRepository
ingredientRepo *repository.IngredientRepository
ingredientCompositionRepo *repository.IngredientCompositionRepository
productRecipeRepo *repository.ProductRecipeRepository
vendorRepo *repository.VendorRepositoryImpl
purchaseOrderRepo *repository.PurchaseOrderRepositoryImpl
unitConverterRepo *repository.IngredientUnitConverterRepositoryImpl
chartOfAccountTypeRepo *repository.ChartOfAccountTypeRepositoryImpl
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
accountRepo *repository.AccountRepositoryImpl
orderIngredientTransactionRepo *repository.OrderIngredientTransactionRepositoryImpl
customerTokensRepo *repository.CustomerTokensRepository
tierRepo *repository.TierRepository
gameRepo *repository.GameRepository
gamePrizeRepo *repository.GamePrizeRepository
gamePlayRepo repository.GamePlayRepository
omsetTrackerRepo *repository.OmsetTrackerRepository
rewardRepo repository.RewardRepository
campaignRepo repository.CampaignRepository
campaignRuleRepo repository.CampaignRuleRepository
customerAuthRepo repository.CustomerAuthRepository
customerPointsRepo repository.CustomerPointsRepository
otpRepo repository.OtpRepository
sessionRepo repository.SessionRepository
txManager *repository.TxManager
userDeviceRepo *repository.UserDeviceRepositoryImpl
notificationRepo *repository.NotificationRepositoryImpl
notificationReceiverRepo *repository.NotificationReceiverRepositoryImpl
notificationDeliveryRepo *repository.NotificationDeliveryRepositoryImpl
productOutletPriceRepo *repository.ProductOutletPriceRepositoryImpl
}
func (a *App) initRepositories() *repositories {
@@ -158,6 +252,7 @@ func (a *App) initRepositories() *repositories {
orderRepo: repository.NewOrderRepositoryImpl(a.db),
orderItemRepo: repository.NewOrderItemRepositoryImpl(a.db),
paymentRepo: repository.NewPaymentRepositoryImpl(a.db),
paymentOrderItemRepo: repository.NewPaymentOrderItemRepositoryImpl(a.db),
paymentMethodRepo: repository.NewPaymentMethodRepositoryImpl(a.db),
fileRepo: repository.NewFileRepositoryImpl(a.db),
customerRepo: repository.NewCustomerRepository(a.db),
@@ -165,6 +260,34 @@ func (a *App) initRepositories() *repositories {
tableRepo: repository.NewTableRepository(a.db),
unitRepo: repository.NewUnitRepository(a.db),
ingredientRepo: repository.NewIngredientRepository(a.db),
ingredientCompositionRepo: repository.NewIngredientCompositionRepository(a.db),
productRecipeRepo: repository.NewProductRecipeRepository(a.db),
vendorRepo: repository.NewVendorRepositoryImpl(a.db),
purchaseOrderRepo: repository.NewPurchaseOrderRepositoryImpl(a.db),
unitConverterRepo: repository.NewIngredientUnitConverterRepositoryImpl(a.db).(*repository.IngredientUnitConverterRepositoryImpl),
chartOfAccountTypeRepo: repository.NewChartOfAccountTypeRepositoryImpl(a.db),
chartOfAccountRepo: repository.NewChartOfAccountRepositoryImpl(a.db),
accountRepo: repository.NewAccountRepositoryImpl(a.db),
orderIngredientTransactionRepo: repository.NewOrderIngredientTransactionRepositoryImpl(a.db).(*repository.OrderIngredientTransactionRepositoryImpl),
customerTokensRepo: repository.NewCustomerTokensRepository(a.db),
tierRepo: repository.NewTierRepository(a.db),
gameRepo: repository.NewGameRepository(a.db),
gamePrizeRepo: repository.NewGamePrizeRepository(a.db),
gamePlayRepo: repository.NewGamePlayRepository(a.db),
omsetTrackerRepo: repository.NewOmsetTrackerRepository(a.db),
rewardRepo: repository.NewRewardRepository(a.db),
campaignRepo: repository.NewCampaignRepository(a.db),
campaignRuleRepo: repository.NewCampaignRuleRepository(a.db),
customerAuthRepo: repository.NewCustomerAuthRepository(a.db),
customerPointsRepo: repository.NewCustomerPointsRepository(a.db),
otpRepo: repository.NewOtpRepository(a.db),
sessionRepo: repository.NewSessionRepository(a.redisClient),
txManager: repository.NewTxManager(a.db),
userDeviceRepo: repository.NewUserDeviceRepositoryImpl(a.db),
notificationRepo: repository.NewNotificationRepository(a.db),
notificationReceiverRepo: repository.NewNotificationReceiverRepository(a.db),
notificationDeliveryRepo: repository.NewNotificationDeliveryRepository(a.db),
productOutletPriceRepo: repository.NewProductOutletPriceRepositoryImpl(a.db),
}
}
@@ -185,10 +308,38 @@ type processors struct {
tableProcessor *processor.TableProcessor
unitProcessor *processor.UnitProcessorImpl
ingredientProcessor *processor.IngredientProcessorImpl
productRecipeProcessor *processor.ProductRecipeProcessorImpl
vendorProcessor *processor.VendorProcessorImpl
purchaseOrderProcessor *processor.PurchaseOrderProcessorImpl
unitConverterProcessor *processor.IngredientUnitConverterProcessorImpl
chartOfAccountTypeProcessor *processor.ChartOfAccountTypeProcessorImpl
chartOfAccountProcessor *processor.ChartOfAccountProcessorImpl
accountProcessor *processor.AccountProcessorImpl
orderIngredientTransactionProcessor *processor.OrderIngredientTransactionProcessorImpl
customerTokensProcessor *processor.CustomerTokensProcessor
tierProcessor *processor.TierProcessor
gameProcessor *processor.GameProcessor
gamePrizeProcessor *processor.GamePrizeProcessor
gamePlayProcessor *processor.GamePlayProcessor
omsetTrackerProcessor *processor.OmsetTrackerProcessor
rewardProcessor processor.RewardProcessor
campaignProcessor processor.CampaignProcessor
campaignRuleProcessor processor.CampaignRuleProcessor
customerAuthProcessor processor.CustomerAuthProcessor
customerPointsProcessor *processor.CustomerPointsProcessor
otpProcessor processor.OtpProcessor
fileClient processor.FileClient
inventoryMovementService service.InventoryMovementService
userDeviceProcessor *processor.UserDeviceProcessorImpl
notificationProcessor *processor.NotificationProcessorImpl
productOutletPriceProcessor processor.ProductOutletPriceProcessor
}
func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors {
fileClient := client.NewFileClient(cfg.S3Config)
fonnteClient := client.NewFonnteClient(cfg.GetFonnte())
otpProcessor := processor.NewOtpProcessor(fonnteClient, repos.otpRepo)
inventoryMovementService := service.NewInventoryMovementService(repos.inventoryMovementRepo, repos.ingredientRepo)
return &processors{
userProcessor: processor.NewUserProcessor(repos.userRepo, repos.organizationRepo, repos.outletRepo),
@@ -196,17 +347,42 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
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),
productProcessor: processor.NewProductProcessorImpl(repos.productRepo, repos.categoryRepo, repos.productVariantRepo, repos.inventoryRepo, repos.outletRepo, repos.productOutletPriceRepo),
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.inventoryMovementRepo, repos.productVariantRepo, repos.outletRepo, repos.customerRepo),
inventoryProcessor: processor.NewInventoryProcessorImpl(repos.inventoryRepo, repos.productRepo, repos.outletRepo, repos.ingredientRepo, repos.inventoryMovementRepo),
orderProcessor: processor.NewOrderProcessorImpl(repos.orderRepo, repos.orderItemRepo, repos.paymentRepo, repos.paymentOrderItemRepo, repos.productRepo, repos.paymentMethodRepo, repos.inventoryRepo, repos.inventoryMovementRepo, repos.productVariantRepo, repos.outletRepo, repos.customerRepo, repos.txManager, repos.productRecipeRepo, repos.ingredientRepo, inventoryMovementService, repos.productOutletPriceRepo),
paymentMethodProcessor: processor.NewPaymentMethodProcessorImpl(repos.paymentMethodRepo),
fileProcessor: processor.NewFileProcessorImpl(repos.fileRepo, fileClient),
customerProcessor: processor.NewCustomerProcessor(repos.customerRepo),
analyticsProcessor: processor.NewAnalyticsProcessorImpl(repos.analyticsRepo),
tableProcessor: processor.NewTableProcessor(repos.tableRepo, repos.orderRepo),
unitProcessor: processor.NewUnitProcessor(repos.unitRepo),
ingredientProcessor: processor.NewIngredientProcessor(repos.ingredientRepo, repos.unitRepo),
ingredientProcessor: processor.NewIngredientProcessor(repos.ingredientRepo, repos.unitRepo, repos.ingredientCompositionRepo),
productRecipeProcessor: processor.NewProductRecipeProcessor(repos.productRecipeRepo, repos.productRepo, repos.ingredientRepo),
vendorProcessor: processor.NewVendorProcessorImpl(repos.vendorRepo),
purchaseOrderProcessor: processor.NewPurchaseOrderProcessorImpl(repos.purchaseOrderRepo, repos.vendorRepo, repos.ingredientRepo, repos.unitRepo, repos.fileRepo, inventoryMovementService, repos.unitConverterRepo),
unitConverterProcessor: processor.NewIngredientUnitConverterProcessorImpl(repos.unitConverterRepo, repos.ingredientRepo, repos.unitRepo),
chartOfAccountTypeProcessor: processor.NewChartOfAccountTypeProcessorImpl(repos.chartOfAccountTypeRepo),
chartOfAccountProcessor: processor.NewChartOfAccountProcessorImpl(repos.chartOfAccountRepo, repos.chartOfAccountTypeRepo),
accountProcessor: processor.NewAccountProcessorImpl(repos.accountRepo, repos.chartOfAccountRepo),
orderIngredientTransactionProcessor: processor.NewOrderIngredientTransactionProcessorImpl(repos.orderIngredientTransactionRepo, repos.productRecipeRepo, repos.ingredientRepo, repos.unitRepo).(*processor.OrderIngredientTransactionProcessorImpl),
customerTokensProcessor: processor.NewCustomerTokensProcessor(repos.customerTokensRepo),
tierProcessor: processor.NewTierProcessor(repos.tierRepo),
gameProcessor: processor.NewGameProcessor(repos.gameRepo),
gamePrizeProcessor: processor.NewGamePrizeProcessor(repos.gamePrizeRepo),
gamePlayProcessor: processor.NewGamePlayProcessor(repos.gamePlayRepo, repos.gameRepo, repos.gamePrizeRepo, repos.customerTokensRepo, repos.customerPointsRepo),
omsetTrackerProcessor: processor.NewOmsetTrackerProcessor(repos.omsetTrackerRepo),
rewardProcessor: processor.NewRewardProcessor(repos.rewardRepo),
campaignProcessor: processor.NewCampaignProcessor(repos.campaignRepo),
campaignRuleProcessor: processor.NewCampaignRuleProcessor(repos.campaignRuleRepo),
customerAuthProcessor: processor.NewCustomerAuthProcessor(repos.customerAuthRepo, otpProcessor, repos.otpRepo, cfg.GetCustomerJWTSecret(), cfg.GetCustomerJWTExpiresTTL()),
customerPointsProcessor: processor.NewCustomerPointsProcessor(repos.customerPointsRepo, repos.gameRepo),
otpProcessor: otpProcessor,
fileClient: fileClient,
inventoryMovementService: inventoryMovementService,
userDeviceProcessor: processor.NewUserDeviceProcessorImpl(repos.userDeviceRepo),
notificationProcessor: buildNotificationProcessor(cfg, repos),
productOutletPriceProcessor: processor.NewProductOutletPriceProcessorImpl(repos.productOutletPriceRepo, repos.productRepo, repos.outletRepo),
}
}
@@ -225,15 +401,32 @@ type services struct {
fileService service.FileService
customerService service.CustomerService
analyticsService *service.AnalyticsServiceImpl
reportService service.ReportService
tableService *service.TableServiceImpl
unitService *service.UnitServiceImpl
ingredientService *service.IngredientServiceImpl
productRecipeService *service.ProductRecipeServiceImpl
vendorService *service.VendorServiceImpl
purchaseOrderService *service.PurchaseOrderServiceImpl
unitConverterService *service.IngredientUnitConverterServiceImpl
chartOfAccountTypeService service.ChartOfAccountTypeService
chartOfAccountService service.ChartOfAccountService
accountService service.AccountService
orderIngredientTransactionService *service.OrderIngredientTransactionService
gamificationService service.GamificationService
rewardService service.RewardService
campaignService service.CampaignService
customerAuthService service.CustomerAuthService
customerPointsService service.CustomerPointsService
spinGameService service.SpinGameService
userDeviceService service.UserDeviceService
notificationService service.NotificationService
productOutletPriceService service.ProductOutletPriceService
}
func (a *App) initServices(processors *processors, cfg *config.Config) *services {
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
authConfig := cfg.Auth()
jwtSecret := authConfig.AccessTokenSecret()
authService := service.NewAuthService(processors.userProcessor, jwtSecret)
authService := service.NewAuthService(processors.userProcessor, processors.userDeviceProcessor, authConfig)
organizationService := service.NewOrganizationService(processors.organizationProcessor)
outletService := service.NewOutletService(processors.outletProcessor)
outletSettingService := service.NewOutletSettingService(processors.outletSettingProcessor)
@@ -241,14 +434,34 @@ func (a *App) initServices(processors *processors, cfg *config.Config) *services
productService := service.NewProductService(processors.productProcessor)
productVariantService := service.NewProductVariantService(processors.productVariantProcessor)
inventoryService := service.NewInventoryService(processors.inventoryProcessor)
orderService := service.NewOrderServiceImpl(processors.orderProcessor)
orderService := service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, nil, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager, repos.sessionRepo, processors.notificationProcessor, repos.userRepo) // Will be updated after orderIngredientTransactionService is created
paymentMethodService := service.NewPaymentMethodService(processors.paymentMethodProcessor)
fileService := service.NewFileServiceImpl(processors.fileProcessor)
var customerService service.CustomerService = service.NewCustomerService(processors.customerProcessor)
analyticsService := service.NewAnalyticsServiceImpl(processors.analyticsProcessor)
reportService := service.NewReportService(analyticsService, repos.organizationRepo, repos.outletRepo, processors.fileClient)
tableService := service.NewTableService(processors.tableProcessor, transformer.NewTableTransformer())
unitService := service.NewUnitService(processors.unitProcessor)
ingredientService := service.NewIngredientService(processors.ingredientProcessor)
productRecipeService := service.NewProductRecipeService(processors.productRecipeProcessor)
vendorService := service.NewVendorService(processors.vendorProcessor)
purchaseOrderService := service.NewPurchaseOrderService(processors.purchaseOrderProcessor)
unitConverterService := service.NewIngredientUnitConverterService(processors.unitConverterProcessor)
chartOfAccountTypeService := service.NewChartOfAccountTypeService(processors.chartOfAccountTypeProcessor)
chartOfAccountService := service.NewChartOfAccountService(processors.chartOfAccountProcessor)
accountService := service.NewAccountService(processors.accountProcessor)
orderIngredientTransactionService := service.NewOrderIngredientTransactionService(processors.orderIngredientTransactionProcessor, repos.txManager)
gamificationService := service.NewGamificationService(processors.customerPointsProcessor, processors.customerTokensProcessor, processors.tierProcessor, processors.gameProcessor, processors.gamePrizeProcessor, processors.gamePlayProcessor, processors.omsetTrackerProcessor)
rewardService := service.NewRewardService(processors.rewardProcessor)
campaignService := service.NewCampaignService(processors.campaignProcessor, processors.campaignRuleProcessor)
customerAuthService := service.NewCustomerAuthService(processors.customerAuthProcessor)
customerPointsService := service.NewCustomerPointsService(processors.customerPointsProcessor)
spinGameService := service.NewSpinGameService(processors.gamePlayProcessor, repos.txManager)
userDeviceService := service.NewUserDeviceService(processors.userDeviceProcessor)
notificationService := service.NewNotificationService(processors.notificationProcessor)
// Update order service with order ingredient transaction service
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager, repos.sessionRepo, processors.notificationProcessor, repos.userRepo)
return &services{
userService: service.NewUserService(processors.userProcessor),
@@ -265,19 +478,39 @@ func (a *App) initServices(processors *processors, cfg *config.Config) *services
fileService: fileService,
customerService: customerService,
analyticsService: analyticsService,
reportService: reportService,
tableService: tableService,
unitService: unitService,
ingredientService: ingredientService,
productRecipeService: productRecipeService,
vendorService: vendorService,
purchaseOrderService: purchaseOrderService,
unitConverterService: unitConverterService,
chartOfAccountTypeService: chartOfAccountTypeService,
chartOfAccountService: chartOfAccountService,
accountService: accountService,
orderIngredientTransactionService: orderIngredientTransactionService,
gamificationService: gamificationService,
rewardService: rewardService,
campaignService: campaignService,
customerAuthService: customerAuthService,
customerPointsService: customerPointsService,
spinGameService: spinGameService,
userDeviceService: userDeviceService,
notificationService: notificationService,
productOutletPriceService: service.NewProductOutletPriceService(processors.productOutletPriceProcessor),
}
}
type middlewares struct {
authMiddleware *middleware.AuthMiddleware
customerAuthMiddleware *middleware.CustomerAuthMiddleware
}
func (a *App) initMiddleware(services *services) *middlewares {
func (a *App) initMiddleware(services *services, cfg *config.Config) *middlewares {
return &middlewares{
authMiddleware: middleware.NewAuthMiddleware(services.authService),
customerAuthMiddleware: middleware.NewCustomerAuthMiddleware(cfg.GetCustomerJWTSecret()),
}
}
@@ -294,6 +527,20 @@ type validators struct {
fileValidator validator.FileValidator
customerValidator validator.CustomerValidator
tableValidator *validator.TableValidator
vendorValidator *validator.VendorValidatorImpl
purchaseOrderValidator *validator.PurchaseOrderValidatorImpl
unitConverterValidator *validator.IngredientUnitConverterValidatorImpl
chartOfAccountTypeValidator *validator.ChartOfAccountTypeValidatorImpl
chartOfAccountValidator *validator.ChartOfAccountValidatorImpl
accountValidator *validator.AccountValidatorImpl
orderIngredientTransactionValidator *validator.OrderIngredientTransactionValidatorImpl
gamificationValidator *validator.GamificationValidatorImpl
rewardValidator validator.RewardValidator
campaignValidator validator.CampaignValidator
customerAuthValidator validator.CustomerAuthValidator
userDeviceValidator *validator.UserDeviceValidatorImpl
notificationValidator *validator.NotificationValidatorImpl
productOutletPriceValidator *validator.ProductOutletPriceValidatorImpl
}
func (a *App) initValidators() *validators {
@@ -310,5 +557,42 @@ func (a *App) initValidators() *validators {
fileValidator: validator.NewFileValidatorImpl(),
customerValidator: validator.NewCustomerValidator(),
tableValidator: validator.NewTableValidator(),
vendorValidator: validator.NewVendorValidator(),
purchaseOrderValidator: validator.NewPurchaseOrderValidator(),
unitConverterValidator: validator.NewIngredientUnitConverterValidator().(*validator.IngredientUnitConverterValidatorImpl),
chartOfAccountTypeValidator: validator.NewChartOfAccountTypeValidator().(*validator.ChartOfAccountTypeValidatorImpl),
chartOfAccountValidator: validator.NewChartOfAccountValidator().(*validator.ChartOfAccountValidatorImpl),
accountValidator: validator.NewAccountValidator().(*validator.AccountValidatorImpl),
orderIngredientTransactionValidator: validator.NewOrderIngredientTransactionValidator().(*validator.OrderIngredientTransactionValidatorImpl),
gamificationValidator: validator.NewGamificationValidator(),
rewardValidator: validator.NewRewardValidator(),
campaignValidator: validator.NewCampaignValidator(),
customerAuthValidator: validator.NewCustomerAuthValidator(),
userDeviceValidator: validator.NewUserDeviceValidator(),
notificationValidator: validator.NewNotificationValidator(),
productOutletPriceValidator: validator.NewProductOutletPriceValidator(),
}
}
// buildNotificationProcessor creates the notification processor with FCM integration.
// If FCM is not configured, it returns a processor with a nil FCM client (FCM dispatch will be skipped).
func buildNotificationProcessor(cfg *config.Config, repos *repositories) *processor.NotificationProcessorImpl {
var fcmClient client.FCMClient
if cfg.FCM.CredentialsFile != "" {
var err error
fcmClient, err = client.NewFCMClient(&cfg.FCM)
if err != nil {
// FCM init failure is non-fatal; notifications will still be persisted.
fcmClient = nil
}
}
return processor.NewNotificationProcessor(
repos.notificationRepo,
repos.notificationReceiverRepo,
repos.notificationDeliveryRepo,
repos.userDeviceRepo,
repos.userRepo,
fcmClient,
)
}
+15 -1
View File
@@ -2,6 +2,7 @@ package appcontext
import (
"context"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
)
@@ -77,5 +78,18 @@ func FromContext(ctx context.Context) *ContextInfo {
if info, ok := ctx.Value(ctxKey).(*ContextInfo); ok {
return info
}
return nil
// Fallback: construct ContextInfo from individual context values
return &ContextInfo{
CorrelationID: value(ctx, CorrelationIDKey),
UserID: uuidValue(ctx, UserIDKey),
OutletID: uuidValue(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),
}
}
+142
View File
@@ -0,0 +1,142 @@
package client
import (
"context"
"fmt"
firebase "firebase.google.com/go/v4"
"firebase.google.com/go/v4/messaging"
"google.golang.org/api/option"
)
type FCMConfig interface {
GetCredentialsFile() string
GetProjectID() string
}
type FCMClient interface {
SendNotification(ctx context.Context, token string, title string, body string, data map[string]string) error
SendMulticastNotification(ctx context.Context, tokens []string, title string, body string, data map[string]string) error
SendToTopic(ctx context.Context, topic string, title string, body string, data map[string]string) error
}
type fcmClient struct {
messaging *messaging.Client
}
func NewFCMClient(cfg FCMConfig) (FCMClient, error) {
ctx := context.Background()
opt := option.WithCredentialsFile(cfg.GetCredentialsFile())
app, err := firebase.NewApp(ctx, &firebase.Config{
ProjectID: cfg.GetProjectID(),
}, opt)
if err != nil {
return nil, fmt.Errorf("failed to initialize firebase app: %w", err)
}
msgClient, err := app.Messaging(ctx)
if err != nil {
return nil, fmt.Errorf("failed to initialize firebase messaging client: %w", err)
}
return &fcmClient{
messaging: msgClient,
}, nil
}
// SendNotification sends a push notification to a single device token.
func (f *fcmClient) SendNotification(ctx context.Context, token string, title string, body string, data map[string]string) error {
message := &messaging.Message{
Token: token,
Notification: &messaging.Notification{
Title: title,
Body: body,
},
Data: data,
Android: &messaging.AndroidConfig{
Priority: "high",
},
APNS: &messaging.APNSConfig{
Payload: &messaging.APNSPayload{
Aps: &messaging.Aps{
Sound: "default",
},
},
},
}
_, err := f.messaging.Send(ctx, message)
if err != nil {
return fmt.Errorf("failed to send FCM notification: %w", err)
}
return nil
}
// SendMulticastNotification sends a push notification to multiple device tokens.
func (f *fcmClient) SendMulticastNotification(ctx context.Context, tokens []string, title string, body string, data map[string]string) error {
if len(tokens) == 0 {
return nil
}
message := &messaging.MulticastMessage{
Tokens: tokens,
Notification: &messaging.Notification{
Title: title,
Body: body,
},
Data: data,
Android: &messaging.AndroidConfig{
Priority: "high",
},
APNS: &messaging.APNSConfig{
Payload: &messaging.APNSPayload{
Aps: &messaging.Aps{
Sound: "default",
},
},
},
}
batchResp, err := f.messaging.SendEachForMulticast(ctx, message)
if err != nil {
return fmt.Errorf("failed to send FCM multicast notification: %w", err)
}
if batchResp.FailureCount > 0 {
return fmt.Errorf("FCM multicast: %d/%d messages failed to send", batchResp.FailureCount, len(tokens))
}
return nil
}
// SendToTopic sends a push notification to all devices subscribed to a topic.
func (f *fcmClient) SendToTopic(ctx context.Context, topic string, title string, body string, data map[string]string) error {
message := &messaging.Message{
Topic: topic,
Notification: &messaging.Notification{
Title: title,
Body: body,
},
Data: data,
Android: &messaging.AndroidConfig{
Priority: "high",
},
APNS: &messaging.APNSConfig{
Payload: &messaging.APNSPayload{
Aps: &messaging.Aps{
Sound: "default",
},
},
},
}
_, err := f.messaging.Send(ctx, message)
if err != nil {
return fmt.Errorf("failed to send FCM topic notification: %w", err)
}
return nil
}
+77
View File
@@ -0,0 +1,77 @@
package client
import (
"bytes"
"fmt"
"io"
"net/http"
"net/url"
"time"
"apskel-pos-be/config"
)
type FonnteClient interface {
SendWhatsAppMessage(target string, message string) error
}
type fonnteClient struct {
httpClient *http.Client
apiUrl string
token string
}
type FonnteResponse struct {
Status bool `json:"status"`
Message string `json:"message"`
}
func NewFonnteClient(cfg *config.Fonnte) FonnteClient {
return &fonnteClient{
httpClient: &http.Client{
Timeout: time.Duration(cfg.GetTimeout()) * time.Second,
},
apiUrl: cfg.GetApiUrl(),
token: cfg.GetToken(),
}
}
func (c *fonnteClient) SendWhatsAppMessage(target string, message string) error {
// Prepare form data
data := url.Values{}
data.Set("target", target)
data.Set("message", message)
// Create request
req, err := http.NewRequest("POST", c.apiUrl, bytes.NewBufferString(data.Encode()))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
// Set headers
req.Header.Set("Authorization", c.token)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// Send request
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
// Read response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}
// Check HTTP status
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("fonnte API returned status %d: %s", resp.StatusCode, string(body))
}
// Log the response for debugging
fmt.Printf("Fonnte API response: %s\n", string(body))
return nil
}
@@ -0,0 +1,272 @@
{
"chart_of_accounts": [
{
"name": "Current Assets",
"code": "1000",
"chart_of_account_type": "ASSET",
"parent_code": null,
"is_system": true,
"accounts": [
{
"name": "Cash on Hand",
"number": "1001",
"account_type": "cash",
"opening_balance": 0.00,
"description": "Physical cash available at the outlet"
},
{
"name": "Petty Cash",
"number": "1002",
"account_type": "cash",
"opening_balance": 0.00,
"description": "Small amount of cash for minor expenses"
},
{
"name": "Bank Account - Main",
"number": "1003",
"account_type": "bank",
"opening_balance": 0.00,
"description": "Primary business bank account"
},
{
"name": "Digital Wallet",
"number": "1004",
"account_type": "wallet",
"opening_balance": 0.00,
"description": "Digital payment wallet"
}
]
},
{
"name": "Inventory",
"code": "1100",
"chart_of_account_type": "ASSET",
"parent_code": "1000",
"is_system": true,
"accounts": [
{
"name": "Raw Materials",
"number": "1101",
"account_type": "asset",
"opening_balance": 0.00,
"description": "Raw materials and ingredients inventory"
},
{
"name": "Finished Goods",
"number": "1102",
"account_type": "asset",
"opening_balance": 0.00,
"description": "Finished products ready for sale"
},
{
"name": "Work in Progress",
"number": "1103",
"account_type": "asset",
"opening_balance": 0.00,
"description": "Products in production process"
}
]
},
{
"name": "Fixed Assets",
"code": "1500",
"chart_of_account_type": "ASSET",
"parent_code": null,
"is_system": true,
"accounts": [
{
"name": "Equipment",
"number": "1501",
"account_type": "asset",
"opening_balance": 0.00,
"description": "Business equipment and machinery"
},
{
"name": "Furniture & Fixtures",
"number": "1502",
"account_type": "asset",
"opening_balance": 0.00,
"description": "Furniture and fixtures"
}
]
},
{
"name": "Current Liabilities",
"code": "2000",
"chart_of_account_type": "LIABILITY",
"parent_code": null,
"is_system": true,
"accounts": [
{
"name": "Accounts Payable",
"number": "2001",
"account_type": "liability",
"opening_balance": 0.00,
"description": "Amounts owed to suppliers and vendors"
},
{
"name": "Accrued Expenses",
"number": "2002",
"account_type": "liability",
"opening_balance": 0.00,
"description": "Expenses incurred but not yet paid"
},
{
"name": "Sales Tax Payable",
"number": "2003",
"account_type": "liability",
"opening_balance": 0.00,
"description": "Sales tax collected but not yet remitted"
}
]
},
{
"name": "Owner's Equity",
"code": "3000",
"chart_of_account_type": "EQUITY",
"parent_code": null,
"is_system": true,
"accounts": [
{
"name": "Owner's Capital",
"number": "3001",
"account_type": "equity",
"opening_balance": 0.00,
"description": "Owner's initial investment in the business"
},
{
"name": "Retained Earnings",
"number": "3002",
"account_type": "equity",
"opening_balance": 0.00,
"description": "Accumulated profits retained in the business"
}
]
},
{
"name": "Revenue",
"code": "4000",
"chart_of_account_type": "REVENUE",
"parent_code": null,
"is_system": true,
"accounts": [
{
"name": "Sales Revenue",
"number": "4001",
"account_type": "revenue",
"opening_balance": 0.00,
"description": "Revenue from product sales"
},
{
"name": "Service Revenue",
"number": "4002",
"account_type": "revenue",
"opening_balance": 0.00,
"description": "Revenue from services provided"
},
{
"name": "Other Income",
"number": "4003",
"account_type": "revenue",
"opening_balance": 0.00,
"description": "Other sources of income"
}
]
},
{
"name": "Cost of Goods Sold",
"code": "5000",
"chart_of_account_type": "EXPENSE",
"parent_code": null,
"is_system": true,
"accounts": [
{
"name": "Raw Materials Cost",
"number": "5001",
"account_type": "expense",
"opening_balance": 0.00,
"description": "Cost of raw materials used in production"
},
{
"name": "Direct Labor Cost",
"number": "5002",
"account_type": "expense",
"opening_balance": 0.00,
"description": "Direct labor costs for production"
},
{
"name": "Manufacturing Overhead",
"number": "5003",
"account_type": "expense",
"opening_balance": 0.00,
"description": "Manufacturing overhead costs"
}
]
},
{
"name": "Operating Expenses",
"code": "6000",
"chart_of_account_type": "EXPENSE",
"parent_code": null,
"is_system": true,
"accounts": [
{
"name": "Rent Expense",
"number": "6001",
"account_type": "expense",
"opening_balance": 0.00,
"description": "Rent for business premises"
},
{
"name": "Utilities Expense",
"number": "6002",
"account_type": "expense",
"opening_balance": 0.00,
"description": "Electricity, water, and other utilities"
},
{
"name": "Salaries & Wages",
"number": "6003",
"account_type": "expense",
"opening_balance": 0.00,
"description": "Employee salaries and wages"
},
{
"name": "Marketing Expense",
"number": "6004",
"account_type": "expense",
"opening_balance": 0.00,
"description": "Marketing and advertising expenses"
},
{
"name": "Office Supplies",
"number": "6005",
"account_type": "expense",
"opening_balance": 0.00,
"description": "Office supplies and stationery"
},
{
"name": "Professional Services",
"number": "6006",
"account_type": "expense",
"opening_balance": 0.00,
"description": "Legal, accounting, and consulting fees"
},
{
"name": "Insurance Expense",
"number": "6007",
"account_type": "expense",
"opening_balance": 0.00,
"description": "Business insurance premiums"
},
{
"name": "Depreciation Expense",
"number": "6008",
"account_type": "expense",
"opening_balance": 0.00,
"description": "Depreciation of fixed assets"
}
]
}
]
}
+21
View File
@@ -38,7 +38,28 @@ const (
PaymentMethodValidatorEntity = "payment_method_validator"
PaymentMethodHandlerEntity = "payment_method_handler"
OutletServiceEntity = "outlet_service"
VendorServiceEntity = "vendor_service"
PurchaseOrderServiceEntity = "purchase_order_service"
IngredientUnitConverterServiceEntity = "ingredient_unit_converter_service"
IngredientCompositionServiceEntity = "ingredient_composition_service"
TableEntity = "table"
// Gamification entities
CustomerPointsEntity = "customer_points"
CustomerTokensEntity = "customer_tokens"
TierEntity = "tier"
GameEntity = "game"
GamePrizeEntity = "game_prize"
GamePlayEntity = "game_play"
OmsetTrackerEntity = "omset_tracker"
RewardEntity = "reward"
CampaignEntity = "campaign"
CampaignRuleEntity = "campaign_rule"
CustomerEntity = "customer"
SpinGameHandlerEntity = "spin_game_handler"
UserDeviceServiceEntity = "user_device_service"
NotificationServiceEntity = "notification_service"
NotificationHandlerEntity = "notification_handler"
ProductOutletPriceServiceEntity = "product_outlet_price_service"
)
var HttpErrorMap = map[string]int{
+1
View File
@@ -28,6 +28,7 @@ const (
OrderItemStatusServed OrderItemStatus = "served"
OrderItemStatusCancelled OrderItemStatus = "cancelled"
OrderItemStatusCompleted OrderItemStatus = "completed"
OrderItemStatusPaid OrderItemStatus = "paid"
)
func GetAllOrderTypes() []OrderType {
+2
View File
@@ -7,6 +7,7 @@ const (
RoleManager UserRole = "manager"
RoleCashier UserRole = "cashier"
RoleWaiter UserRole = "waiter"
RoleOwner UserRole = "owner"
)
func GetAllUserRoles() []UserRole {
@@ -15,6 +16,7 @@ func GetAllUserRoles() []UserRole {
RoleManager,
RoleCashier,
RoleWaiter,
RoleOwner,
}
}
+19
View File
@@ -0,0 +1,19 @@
package contract
import (
"context"
"github.com/google/uuid"
)
type AccountContract interface {
CreateAccount(ctx context.Context, req *CreateAccountRequest) (*AccountResponse, error)
GetAccountByID(ctx context.Context, id uuid.UUID) (*AccountResponse, error)
UpdateAccount(ctx context.Context, id uuid.UUID, req *UpdateAccountRequest) (*AccountResponse, error)
DeleteAccount(ctx context.Context, id uuid.UUID) error
ListAccounts(ctx context.Context, req *ListAccountsRequest) ([]AccountResponse, int, error)
GetAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]AccountResponse, error)
GetAccountsByChartOfAccount(ctx context.Context, chartOfAccountID uuid.UUID) ([]AccountResponse, error)
UpdateAccountBalance(ctx context.Context, id uuid.UUID, req *UpdateAccountBalanceRequest) error
GetAccountBalance(ctx context.Context, id uuid.UUID) (float64, error)
}
+57
View File
@@ -0,0 +1,57 @@
package contract
import (
"github.com/google/uuid"
)
type CreateAccountRequest struct {
ChartOfAccountID uuid.UUID `json:"chart_of_account_id" validate:"required"`
Name string `json:"name" validate:"required,min=1,max=255"`
Number string `json:"number" validate:"required,min=1,max=50"`
AccountType string `json:"account_type" validate:"required,oneof=cash wallet bank credit debit asset liability equity revenue expense"`
OpeningBalance float64 `json:"opening_balance"`
Description *string `json:"description"`
}
type UpdateAccountRequest struct {
ChartOfAccountID *uuid.UUID `json:"chart_of_account_id"`
Name *string `json:"name" validate:"omitempty,min=1,max=255"`
Number *string `json:"number" validate:"omitempty,min=1,max=50"`
AccountType *string `json:"account_type" validate:"omitempty,oneof=cash wallet bank credit debit asset liability equity revenue expense"`
OpeningBalance *float64 `json:"opening_balance"`
Description *string `json:"description"`
IsActive *bool `json:"is_active"`
}
type AccountResponse struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"organization_id"`
OutletID *uuid.UUID `json:"outlet_id"`
ChartOfAccountID uuid.UUID `json:"chart_of_account_id"`
Name string `json:"name"`
Number string `json:"number"`
AccountType string `json:"account_type"`
OpeningBalance float64 `json:"opening_balance"`
CurrentBalance float64 `json:"current_balance"`
Description *string `json:"description"`
IsActive bool `json:"is_active"`
IsSystem bool `json:"is_system"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
ChartOfAccount *ChartOfAccountResponse `json:"chart_of_account,omitempty"`
}
type ListAccountsRequest struct {
OrganizationID *uuid.UUID `form:"organization_id"`
OutletID *uuid.UUID `form:"outlet_id"`
ChartOfAccountID *uuid.UUID `form:"chart_of_account_id"`
AccountType *string `form:"account_type"`
IsActive *bool `form:"is_active"`
IsSystem *bool `form:"is_system"`
Page int `form:"page,default=1"`
Limit int `form:"limit,default=10"`
}
type UpdateAccountBalanceRequest struct {
Amount float64 `json:"amount" binding:"required"`
}
+43 -7
View File
@@ -8,7 +8,7 @@ import (
type PaymentMethodAnalyticsRequest struct {
OrganizationID uuid.UUID `form:"organization_id"`
OutletID *uuid.UUID `form:"outlet_id,omitempty"`
OutletID *string `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"`
@@ -45,7 +45,7 @@ type PaymentMethodAnalyticsData struct {
type SalesAnalyticsRequest struct {
OrganizationID uuid.UUID
OutletID *uuid.UUID `form:"outlet_id,omitempty"`
OutletID *string `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"`
@@ -86,10 +86,10 @@ type SalesAnalyticsData struct {
// ProductAnalyticsRequest represents the request for product analytics
type ProductAnalyticsRequest struct {
OrganizationID uuid.UUID
OutletID *uuid.UUID `form:"outlet_id,omitempty"`
OutletID *string `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"`
Limit int `form:"limit,default=1000" validate:"min=1,max=1000"`
}
// ProductAnalyticsResponse represents the response for product analytics
@@ -101,22 +101,58 @@ type ProductAnalyticsResponse struct {
Data []ProductAnalyticsData `json:"data"`
}
// ProductAnalyticsData represents individual product analytics data
type ProductAnalyticsData struct {
ProductID uuid.UUID `json:"product_id"`
ProductName string `json:"product_name"`
ProductSku string `json:"product_sku"`
CategoryID uuid.UUID `json:"category_id"`
CategoryName string `json:"category_name"`
CategoryOrder int `json:"category_order"`
QuantitySold int64 `json:"quantity_sold"`
Revenue float64 `json:"revenue"`
AveragePrice float64 `json:"average_price"`
OrderCount int64 `json:"order_count"`
StandardHppPerUnit float64 `json:"standard_hpp_per_unit"`
StandardHppTotal float64 `json:"standard_hpp_total"`
FifoHppPerUnit float64 `json:"fifo_hpp_per_unit"`
FifoHppTotal float64 `json:"fifo_hpp_total"`
MovingAverageHppPerUnit float64 `json:"moving_average_hpp_per_unit"`
MovingAverageHppTotal float64 `json:"moving_average_hpp_total"`
}
// ProductAnalyticsPerCategoryRequest represents the request for product analytics per category
type ProductAnalyticsPerCategoryRequest struct {
OrganizationID uuid.UUID
OutletID *string `form:"outlet_id,omitempty"`
DateFrom string `form:"date_from" validate:"required"`
DateTo string `form:"date_to" validate:"required"`
}
// ProductAnalyticsPerCategoryResponse represents the response for product analytics per category
type ProductAnalyticsPerCategoryResponse 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 []ProductAnalyticsPerCategoryData `json:"data"`
}
type ProductAnalyticsPerCategoryData struct {
CategoryID uuid.UUID `json:"category_id"`
CategoryName string `json:"category_name"`
TotalRevenue float64 `json:"total_revenue"`
TotalQuantity int64 `json:"total_quantity"`
ProductCount int64 `json:"product_count"`
OrderCount int64 `json:"order_count"`
TotalStandardHpp float64 `json:"total_standard_hpp"`
TotalFifoHpp float64 `json:"total_fifo_hpp"`
TotalMovingAverageHpp float64 `json:"total_moving_average_hpp"`
}
// DashboardAnalyticsRequest represents the request for dashboard analytics
type DashboardAnalyticsRequest struct {
OrganizationID uuid.UUID
OutletID *uuid.UUID `form:"outlet_id,omitempty"`
OutletID *string `form:"outlet_id,omitempty"`
DateFrom string `form:"date_from" validate:"required"`
DateTo string `form:"date_to" validate:"required"`
}
@@ -146,7 +182,7 @@ type DashboardOverview struct {
// ProfitLossAnalyticsRequest represents the request for profit and loss analytics
type ProfitLossAnalyticsRequest struct {
OrganizationID uuid.UUID
OutletID *uuid.UUID `form:"outlet_id,omitempty"`
OutletID *string `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"`
+150
View File
@@ -0,0 +1,150 @@
package contract
import (
"time"
"apskel-pos-be/internal/entities"
"github.com/google/uuid"
)
// Request Contracts
type CreateCampaignRequest struct {
Name string `json:"name" binding:"required,min=1,max=150"`
Description *string `json:"description,omitempty"`
Type string `json:"type" binding:"required,oneof=REWARD POINTS TOKENS MIXED"`
StartDate time.Time `json:"start_date" binding:"required"`
EndDate time.Time `json:"end_date" binding:"required"`
IsActive bool `json:"is_active"`
ShowOnApp bool `json:"show_on_app"`
Position int `json:"position" binding:"min=0"`
Metadata *entities.Metadata `json:"metadata,omitempty"`
}
type UpdateCampaignRequest struct {
ID uuid.UUID `json:"id" binding:"required"`
Name string `json:"name" binding:"required,min=1,max=150"`
Description *string `json:"description,omitempty"`
Type string `json:"type" binding:"required,oneof=REWARD POINTS TOKENS MIXED"`
StartDate time.Time `json:"start_date" binding:"required"`
EndDate time.Time `json:"end_date" binding:"required"`
IsActive bool `json:"is_active"`
ShowOnApp bool `json:"show_on_app"`
Position int `json:"position" binding:"min=0"`
Metadata *entities.Metadata `json:"metadata,omitempty"`
}
type ListCampaignsRequest struct {
Page int `form:"page" binding:"min=1"`
Limit int `form:"limit" binding:"min=1,max=100"`
Search string `form:"search"`
Type string `form:"type"`
IsActive *bool `form:"is_active"`
ShowOnApp *bool `form:"show_on_app"`
StartDate *time.Time `form:"start_date"`
EndDate *time.Time `form:"end_date"`
}
type GetCampaignRequest struct {
ID uuid.UUID `uri:"id" binding:"required"`
}
type DeleteCampaignRequest struct {
ID uuid.UUID `uri:"id" binding:"required"`
}
// Campaign Rule Request Contracts
type CreateCampaignRuleRequest struct {
CampaignID uuid.UUID `json:"campaign_id" binding:"required"`
RuleType string `json:"rule_type" binding:"required,oneof=TIER SPEND PRODUCT CATEGORY DAY LOCATION"`
ConditionValue *string `json:"condition_value,omitempty"`
RewardType string `json:"reward_type" binding:"required,oneof=POINTS TOKENS REWARD"`
RewardValue *int64 `json:"reward_value,omitempty"`
RewardSubtype *string `json:"reward_subtype,omitempty"`
RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"`
Metadata *entities.Metadata `json:"metadata,omitempty"`
}
type UpdateCampaignRuleRequest struct {
ID uuid.UUID `json:"id" binding:"required"`
CampaignID uuid.UUID `json:"campaign_id" binding:"required"`
RuleType string `json:"rule_type" binding:"required,oneof=TIER SPEND PRODUCT CATEGORY DAY LOCATION"`
ConditionValue *string `json:"condition_value,omitempty"`
RewardType string `json:"reward_type" binding:"required,oneof=POINTS TOKENS REWARD"`
RewardValue *int64 `json:"reward_value,omitempty"`
RewardSubtype *string `json:"reward_subtype,omitempty"`
RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"`
Metadata *entities.Metadata `json:"metadata,omitempty"`
}
type ListCampaignRulesRequest struct {
Page int `form:"page" binding:"min=1"`
Limit int `form:"limit" binding:"min=1,max=100"`
CampaignID string `form:"campaign_id"`
RuleType string `form:"rule_type"`
RewardType string `form:"reward_type"`
}
type GetCampaignRuleRequest struct {
ID uuid.UUID `uri:"id" binding:"required"`
}
type DeleteCampaignRuleRequest struct {
ID uuid.UUID `uri:"id" binding:"required"`
}
// Response Contracts
type CampaignResponse struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
Type string `json:"type"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
IsActive bool `json:"is_active"`
ShowOnApp bool `json:"show_on_app"`
Position int `json:"position"`
Metadata *entities.Metadata `json:"metadata,omitempty"`
Rules []CampaignRuleResponse `json:"rules,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type CampaignRuleResponse struct {
ID uuid.UUID `json:"id"`
CampaignID uuid.UUID `json:"campaign_id"`
RuleType string `json:"rule_type"`
ConditionValue *string `json:"condition_value,omitempty"`
RewardType string `json:"reward_type"`
RewardValue *int64 `json:"reward_value,omitempty"`
RewardSubtype *string `json:"reward_subtype,omitempty"`
RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"`
Metadata *entities.Metadata `json:"metadata,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListCampaignsResponse struct {
Campaigns []CampaignResponse `json:"campaigns"`
Total int64 `json:"total"`
Page int `json:"page"`
Limit int `json:"limit"`
}
type ListCampaignRulesResponse struct {
Rules []CampaignRuleResponse `json:"rules"`
Total int64 `json:"total"`
Page int `json:"page"`
Limit int `json:"limit"`
}
// Helper structs
type CampaignRuleStruct struct {
RuleType string `json:"rule_type" binding:"required,oneof=TIER SPEND PRODUCT CATEGORY DAY LOCATION"`
ConditionValue *string `json:"condition_value,omitempty"`
RewardType string `json:"reward_type" binding:"required,oneof=POINTS TOKENS REWARD"`
RewardValue *int64 `json:"reward_value,omitempty"`
RewardSubtype *string `json:"reward_subtype,omitempty"`
RewardRefID *uuid.UUID `json:"reward_ref_id,omitempty"`
Metadata *entities.Metadata `json:"metadata,omitempty"`
}
+3
View File
@@ -10,6 +10,7 @@ type CreateCategoryRequest struct {
Name string `json:"name" validate:"required,min=1,max=255"`
Description *string `json:"description,omitempty"`
BusinessType *string `json:"business_type,omitempty"`
Order *int `json:"order,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
@@ -17,6 +18,7 @@ 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"`
Order *int `json:"order,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
@@ -35,6 +37,7 @@ type CategoryResponse struct {
Name string `json:"name"`
Description *string `json:"description"`
BusinessType string `json:"business_type"`
Order int `json:"order"`
Metadata map[string]interface{} `json:"metadata"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
@@ -0,0 +1,17 @@
package contract
import (
"context"
"github.com/google/uuid"
)
type ChartOfAccountContract interface {
CreateChartOfAccount(ctx context.Context, req *CreateChartOfAccountRequest) (*ChartOfAccountResponse, error)
GetChartOfAccountByID(ctx context.Context, id uuid.UUID) (*ChartOfAccountResponse, error)
UpdateChartOfAccount(ctx context.Context, id uuid.UUID, req *UpdateChartOfAccountRequest) (*ChartOfAccountResponse, error)
DeleteChartOfAccount(ctx context.Context, id uuid.UUID) error
ListChartOfAccounts(ctx context.Context, req *ListChartOfAccountsRequest) ([]ChartOfAccountResponse, int, error)
GetChartOfAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]ChartOfAccountResponse, error)
GetChartOfAccountsByType(ctx context.Context, organizationID uuid.UUID, chartOfAccountTypeID uuid.UUID, outletID *uuid.UUID) ([]ChartOfAccountResponse, error)
}
@@ -0,0 +1,49 @@
package contract
import (
"github.com/google/uuid"
)
type CreateChartOfAccountRequest struct {
ChartOfAccountTypeID uuid.UUID `json:"chart_of_account_type_id" validate:"required"`
ParentID *uuid.UUID `json:"parent_id"`
Name string `json:"name" validate:"required,min=1,max=255"`
Code string `json:"code" validate:"required,min=1,max=20"`
Description *string `json:"description"`
}
type UpdateChartOfAccountRequest struct {
ChartOfAccountTypeID *uuid.UUID `json:"chart_of_account_type_id"`
ParentID *uuid.UUID `json:"parent_id"`
Name *string `json:"name" validate:"omitempty,min=1,max=255"`
Code *string `json:"code" validate:"omitempty,min=1,max=20"`
Description *string `json:"description"`
IsActive *bool `json:"is_active"`
}
type ChartOfAccountResponse struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"organization_id"`
OutletID *uuid.UUID `json:"outlet_id"`
ChartOfAccountTypeID uuid.UUID `json:"chart_of_account_type_id"`
ParentID *uuid.UUID `json:"parent_id"`
Name string `json:"name"`
Code string `json:"code"`
Description *string `json:"description"`
IsActive bool `json:"is_active"`
IsSystem bool `json:"is_system"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
ChartOfAccountType *ChartOfAccountTypeResponse `json:"chart_of_account_type,omitempty"`
Parent *ChartOfAccountResponse `json:"parent,omitempty"`
Children []ChartOfAccountResponse `json:"children,omitempty"`
}
type ListChartOfAccountsRequest struct {
ChartOfAccountTypeID *uuid.UUID `form:"chart_of_account_type_id"`
ParentID *uuid.UUID `form:"parent_id"`
IsActive *bool `form:"is_active"`
IsSystem *bool `form:"is_system"`
Page int `form:"page,default=1"`
Limit int `form:"limit,default=10"`
}
@@ -0,0 +1,15 @@
package contract
import (
"context"
"github.com/google/uuid"
)
type ChartOfAccountTypeContract interface {
CreateChartOfAccountType(ctx context.Context, req *CreateChartOfAccountTypeRequest) (*ChartOfAccountTypeResponse, error)
GetChartOfAccountTypeByID(ctx context.Context, id uuid.UUID) (*ChartOfAccountTypeResponse, error)
UpdateChartOfAccountType(ctx context.Context, id uuid.UUID, req *UpdateChartOfAccountTypeRequest) (*ChartOfAccountTypeResponse, error)
DeleteChartOfAccountType(ctx context.Context, id uuid.UUID) error
ListChartOfAccountTypes(ctx context.Context, filters map[string]interface{}, page, limit int) ([]ChartOfAccountTypeResponse, int, error)
}
@@ -0,0 +1,28 @@
package contract
import (
"github.com/google/uuid"
)
type CreateChartOfAccountTypeRequest struct {
Name string `json:"name" validate:"required,min=1,max=100"`
Code string `json:"code" validate:"required,min=1,max=10"`
Description *string `json:"description"`
}
type UpdateChartOfAccountTypeRequest struct {
Name *string `json:"name" validate:"omitempty,min=1,max=100"`
Code *string `json:"code" validate:"omitempty,min=1,max=10"`
Description *string `json:"description"`
IsActive *bool `json:"is_active"`
}
type ChartOfAccountTypeResponse struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
Description *string `json:"description"`
IsActive bool `json:"is_active"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
+144
View File
@@ -0,0 +1,144 @@
package contract
import (
"time"
"github.com/google/uuid"
)
// Request Contracts
type CheckPhoneRequest struct {
PhoneNumber string `json:"phone_number" binding:"required"`
Password string `json:"password,omitempty"` // Optional - only required if user exists
}
type RegisterStartRequest struct {
PhoneNumber string `json:"phone_number" binding:"required"`
Name string `json:"name" binding:"required"`
BirthDate string `json:"birth_date" binding:"required"`
}
type RegisterVerifyOtpRequest struct {
RegistrationToken string `json:"registration_token" binding:"required"`
OtpCode string `json:"otp_code" binding:"required"`
}
type RegisterSetPasswordRequest struct {
RegistrationToken string `json:"registration_token" binding:"required"`
Password string `json:"password" binding:"required,min=8"`
ConfirmPassword string `json:"confirm_password" binding:"required"`
}
type CustomerLoginRequest struct {
PhoneNumber string `json:"phone_number" binding:"required"`
Password string `json:"password" binding:"required"`
}
type ResendOtpRequest struct {
PhoneNumber string `json:"phone_number" binding:"required"`
Purpose string `json:"purpose" binding:"required,oneof=login registration"`
}
// Response Contracts
type CheckPhoneResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *CheckPhoneResponseData `json:"data,omitempty"`
}
type CheckPhoneResponseData struct {
// For NOT_REGISTERED status
PhoneNumber string `json:"phone_number,omitempty"`
// For PASSWORD_REQUIRED status
AccessToken string `json:"access_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
User *CustomerUserData `json:"user,omitempty"`
// For OTP_REQUIRED status (if password doesn't exist)
OtpToken string `json:"otp_token,omitempty"`
ExpiresIn int `json:"expires_in,omitempty"`
}
type RegisterStartResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *RegisterStartResponseData `json:"data,omitempty"`
}
type RegisterStartResponseData struct {
RegistrationToken string `json:"registration_token"`
OtpToken string `json:"otp_token"`
ExpiresIn int `json:"expires_in"`
}
type RegisterVerifyOtpResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *RegisterVerifyOtpResponseData `json:"data,omitempty"`
}
type RegisterVerifyOtpResponseData struct {
RegistrationToken string `json:"registration_token"`
}
type RegisterSetPasswordResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *RegisterSetPasswordResponseData `json:"data,omitempty"`
}
type RegisterSetPasswordResponseData struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
User *CustomerUserData `json:"user"`
}
type CustomerUserData struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
PhoneNumber string `json:"phone_number"`
BirthDate string `json:"birth_date"`
}
type CustomerLoginResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *CustomerLoginResponseData `json:"data,omitempty"`
}
type CustomerLoginResponseData struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
User *CustomerUserData `json:"user"`
}
type ResendOtpResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *ResendOtpResponseData `json:"data,omitempty"`
}
type ResendOtpResponseData struct {
OtpToken string `json:"otp_token"`
ExpiresIn int `json:"expires_in"`
NextResendIn int `json:"next_resend_in"` // Seconds until next resend is allowed
}
// Internal structures for OTP and registration tokens
type OtpSession struct {
Token string `json:"token"`
Code string `json:"code"`
PhoneNumber string `json:"phone_number"`
ExpiresAt time.Time `json:"expires_at"`
Purpose string `json:"purpose"` // "login" or "registration"
}
type RegistrationSession struct {
Token string `json:"token"`
PhoneNumber string `json:"phone_number"`
Name string `json:"name"`
BirthDate string `json:"birth_date"`
ExpiresAt time.Time `json:"expires_at"`
Step string `json:"step"` // "otp_sent", "otp_verified", "password_set"
}
@@ -0,0 +1,60 @@
package contract
import (
"time"
"github.com/google/uuid"
)
// Request Contracts
type GetCustomerGamesRequest struct {
// No additional fields needed - customer ID comes from JWT token
}
// Response Contracts
type GetCustomerGamesResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *GetCustomerGamesResponseData `json:"data,omitempty"`
}
type GetCustomerGamesResponseData struct {
Games []CustomerGameResponse `json:"games"`
}
type CustomerGameResponse struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
IsActive bool `json:"is_active"`
Metadata *map[string]interface{} `json:"metadata,omitempty"`
Prizes []CustomerGamePrizeResponse `json:"prizes,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type CustomerGamePrizeResponse struct {
ID uuid.UUID `json:"id"`
GameID uuid.UUID `json:"game_id"`
Name string `json:"name"`
Image *string `json:"image,omitempty"`
Metadata *map[string]interface{} `json:"metadata,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Ferris Wheel Game Contracts
type GetFerrisWheelGameRequest struct {
// No additional fields needed - customer ID comes from JWT token
}
type GetFerrisWheelGameResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *GetFerrisWheelGameResponseData `json:"data,omitempty"`
}
type GetFerrisWheelGameResponseData struct {
Game CustomerGameResponse `json:"game"`
Prizes []CustomerGamePrizeResponse `json:"prizes"`
}
@@ -0,0 +1,118 @@
package contract
import (
"time"
"github.com/google/uuid"
)
// Existing gamification contracts
type CreateCustomerPointsRequest struct {
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
Balance int64 `json:"balance" validate:"min=0"`
}
type UpdateCustomerPointsRequest struct {
Balance int64 `json:"balance" validate:"min=0"`
}
type AddCustomerPointsRequest struct {
Points int64 `json:"points" validate:"required,min=1"`
}
type DeductCustomerPointsRequest struct {
Points int64 `json:"points" validate:"required,min=1"`
}
type CustomerPointsResponse struct {
ID uuid.UUID `json:"id"`
CustomerID uuid.UUID `json:"customer_id"`
Balance int64 `json:"balance"`
Customer *CustomerResponse `json:"customer,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListCustomerPointsRequest struct {
Page int `json:"page" validate:"min=1"`
Limit int `json:"limit" validate:"min=1,max=100"`
Search string `json:"search"`
SortBy string `json:"sort_by" validate:"omitempty,oneof=balance created_at updated_at"`
SortOrder string `json:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedCustomerPointsResponse struct {
Data []CustomerPointsResponse `json:"data"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}
// New customer API contracts
type GetCustomerPointsRequest struct {
// No additional fields needed - customer ID comes from JWT token
}
type GetCustomerTokensRequest struct {
// No additional fields needed - customer ID comes from JWT token
}
type GetCustomerWalletRequest struct {
// No additional fields needed - customer ID comes from JWT token
}
// Response Contracts
type GetCustomerPointsResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *GetCustomerPointsResponseData `json:"data,omitempty"`
}
type GetCustomerPointsResponseData struct {
TotalPoints int64 `json:"total_points"`
PointsHistory []PointsHistoryItem `json:"points_history,omitempty"`
LastUpdated time.Time `json:"last_updated"`
}
type PointsHistoryItem struct {
ID string `json:"id"`
Points int64 `json:"points"`
Type string `json:"type"` // EARNED, REDEEMED, EXPIRED
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
}
type GetCustomerTokensResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *GetCustomerTokensResponseData `json:"data,omitempty"`
}
type GetCustomerTokensResponseData struct {
TotalTokens int64 `json:"total_tokens"`
TokensHistory []TokensHistoryItem `json:"tokens_history,omitempty"`
LastUpdated time.Time `json:"last_updated"`
}
type TokensHistoryItem struct {
ID string `json:"id"`
Tokens int64 `json:"tokens"`
Type string `json:"type"` // EARNED, REDEEMED, EXPIRED
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
}
type GetCustomerWalletResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *GetCustomerWalletResponseData `json:"data,omitempty"`
}
type GetCustomerWalletResponseData struct {
TotalPoints int64 `json:"total_points"`
TotalTokens int64 `json:"total_tokens"`
PointsHistory []PointsHistoryItem `json:"points_history,omitempty"`
TokensHistory []TokensHistoryItem `json:"tokens_history,omitempty"`
LastUpdated time.Time `json:"last_updated"`
}
@@ -0,0 +1,52 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateCustomerTokensRequest struct {
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
TokenType string `json:"token_type" validate:"required,oneof=SPIN RAFFLE MINIGAME"`
Balance int64 `json:"balance" validate:"min=0"`
}
type UpdateCustomerTokensRequest struct {
Balance int64 `json:"balance" validate:"min=0"`
}
type AddCustomerTokensRequest struct {
Tokens int64 `json:"tokens" validate:"required,min=1"`
}
type DeductCustomerTokensRequest struct {
Tokens int64 `json:"tokens" validate:"required,min=1"`
}
type CustomerTokensResponse struct {
ID uuid.UUID `json:"id"`
CustomerID uuid.UUID `json:"customer_id"`
TokenType string `json:"token_type"`
Balance int64 `json:"balance"`
Customer *CustomerResponse `json:"customer,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListCustomerTokensRequest struct {
Page int `json:"page" validate:"min=1"`
Limit int `json:"limit" validate:"min=1,max=100"`
Search string `json:"search"`
TokenType string `json:"token_type" validate:"omitempty,oneof=SPIN RAFFLE MINIGAME"`
SortBy string `json:"sort_by" validate:"omitempty,oneof=balance token_type created_at updated_at"`
SortOrder string `json:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedCustomerTokensResponse struct {
Data []CustomerTokensResponse `json:"data"`
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"
"github.com/google/uuid"
)
type CreateGameRequest struct {
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required,oneof=SPIN RAFFLE MINIGAME"`
IsActive bool `json:"is_active"`
Metadata map[string]interface{} `json:"metadata"`
}
type UpdateGameRequest struct {
Name *string `json:"name,omitempty" validate:"omitempty,required"`
Type *string `json:"type,omitempty" validate:"omitempty,oneof=SPIN RAFFLE MINIGAME"`
IsActive *bool `json:"is_active,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type GameResponse struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
IsActive bool `json:"is_active"`
Metadata map[string]interface{} `json:"metadata"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListGamesRequest struct {
Page int `json:"page" form:"page" validate:"min=1"`
Limit int `json:"limit" form:"limit" validate:"min=1,max=100"`
Search string `json:"search" form:"search"`
Type string `json:"type" form:"type" validate:"omitempty,oneof=SPIN RAFFLE MINIGAME"`
IsActive *bool `json:"is_active" form:"is_active"`
SortBy string `json:"sort_by" form:"sort_by" validate:"omitempty,oneof=name type created_at updated_at"`
SortOrder string `json:"sort_order" form:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedGamesResponse struct {
Data []GameResponse `json:"data"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}
+58
View File
@@ -0,0 +1,58 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateGamePlayRequest struct {
GameID uuid.UUID `json:"game_id" validate:"required"`
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
TokenUsed int `json:"token_used" validate:"min=0"`
RandomSeed *string `json:"random_seed,omitempty"`
}
type GamePlayResponse struct {
ID uuid.UUID `json:"id"`
GameID uuid.UUID `json:"game_id"`
CustomerID uuid.UUID `json:"customer_id"`
PrizeID *uuid.UUID `json:"prize_id,omitempty"`
TokenUsed int `json:"token_used"`
RandomSeed *string `json:"random_seed,omitempty"`
CreatedAt time.Time `json:"created_at"`
Game *GameResponse `json:"game,omitempty"`
Customer *CustomerResponse `json:"customer,omitempty"`
Prize *GamePrizeResponse `json:"prize,omitempty"`
}
type ListGamePlaysRequest struct {
Page int `json:"page" validate:"min=1"`
Limit int `json:"limit" validate:"min=1,max=100"`
Search string `json:"search"`
GameID *uuid.UUID `json:"game_id"`
CustomerID *uuid.UUID `json:"customer_id"`
PrizeID *uuid.UUID `json:"prize_id"`
SortBy string `json:"sort_by" validate:"omitempty,oneof=created_at token_used"`
SortOrder string `json:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedGamePlaysResponse struct {
Data []GamePlayResponse `json:"data"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}
type PlayGameRequest struct {
GameID uuid.UUID `json:"game_id" validate:"required"`
CustomerID uuid.UUID `json:"customer_id" validate:"required"`
TokenUsed int `json:"token_used" validate:"min=0"`
}
type PlayGameResponse struct {
GamePlay GamePlayResponse `json:"game_play"`
PrizeWon *GamePrizeResponse `json:"prize_won,omitempty"`
TokensRemaining int64 `json:"tokens_remaining"`
}
+64
View File
@@ -0,0 +1,64 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateGamePrizeRequest struct {
GameID uuid.UUID `json:"game_id" validate:"required"`
Name string `json:"name" validate:"required"`
Weight int `json:"weight" validate:"min=1"`
Stock int `json:"stock" validate:"min=0"`
MaxStock *int `json:"max_stock,omitempty"`
Threshold *int64 `json:"threshold,omitempty"`
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
Image *string `json:"image,omitempty" validate:"omitempty,max=500"`
Metadata map[string]interface{} `json:"metadata"`
}
type UpdateGamePrizeRequest struct {
Name *string `json:"name,omitempty" validate:"omitempty,required"`
Weight *int `json:"weight,omitempty" validate:"omitempty,min=1"`
Stock *int `json:"stock,omitempty" validate:"omitempty,min=0"`
MaxStock *int `json:"max_stock,omitempty"`
Threshold *int64 `json:"threshold,omitempty"`
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
Image *string `json:"image,omitempty" validate:"omitempty,max=500"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type GamePrizeResponse struct {
ID uuid.UUID `json:"id"`
GameID uuid.UUID `json:"game_id"`
Name string `json:"name"`
Weight int `json:"weight"`
Stock int `json:"stock"`
MaxStock *int `json:"max_stock,omitempty"`
Threshold *int64 `json:"threshold,omitempty"`
FallbackPrizeID *uuid.UUID `json:"fallback_prize_id,omitempty"`
Image *string `json:"image,omitempty"`
Metadata map[string]interface{} `json:"metadata"`
Game *GameResponse `json:"game,omitempty"`
FallbackPrize *GamePrizeResponse `json:"fallback_prize,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListGamePrizesRequest struct {
Page int `json:"page" form:"page" validate:"min=1"`
Limit int `json:"limit" form:"limit" validate:"min=1,max=100"`
Search string `json:"search" form:"search"`
GameID *uuid.UUID `json:"game_id" form:"game_id"`
SortBy string `json:"sort_by" form:"sort_by" validate:"omitempty,oneof=name weight stock created_at updated_at"`
SortOrder string `json:"sort_order" form:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedGamePrizesResponse struct {
Data []GamePrizeResponse `json:"data"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}
@@ -1,16 +0,0 @@
package contract
import (
"apskel-pos-be/internal/models"
"github.com/google/uuid"
)
type IngredientCompositionContract interface {
Create(request *models.CreateIngredientCompositionRequest, organizationID uuid.UUID) (*models.IngredientCompositionResponse, error)
GetByID(id uuid.UUID, organizationID uuid.UUID) (*models.IngredientCompositionResponse, error)
GetByParentIngredientID(parentIngredientID uuid.UUID, organizationID uuid.UUID) ([]*models.IngredientCompositionResponse, error)
GetByChildIngredientID(childIngredientID uuid.UUID, organizationID uuid.UUID) ([]*models.IngredientCompositionResponse, error)
Update(id uuid.UUID, request *models.UpdateIngredientCompositionRequest, organizationID uuid.UUID) (*models.IngredientCompositionResponse, error)
Delete(id uuid.UUID, organizationID uuid.UUID) error
}
@@ -0,0 +1,84 @@
package contract
import (
"time"
"github.com/google/uuid"
)
// Request DTOs
type CreateIngredientUnitConverterRequest struct {
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
FromUnitID uuid.UUID `json:"from_unit_id" validate:"required"`
ToUnitID uuid.UUID `json:"to_unit_id" validate:"required"`
ConversionFactor float64 `json:"conversion_factor" validate:"required,gt=0"`
IsActive *bool `json:"is_active,omitempty" validate:"omitempty"`
}
type UpdateIngredientUnitConverterRequest struct {
FromUnitID *uuid.UUID `json:"from_unit_id,omitempty" validate:"omitempty"`
ToUnitID *uuid.UUID `json:"to_unit_id,omitempty" validate:"omitempty"`
ConversionFactor *float64 `json:"conversion_factor,omitempty" validate:"omitempty,gt=0"`
IsActive *bool `json:"is_active,omitempty" validate:"omitempty"`
}
type ListIngredientUnitConvertersRequest struct {
IngredientID *uuid.UUID `json:"ingredient_id,omitempty"`
FromUnitID *uuid.UUID `json:"from_unit_id,omitempty"`
ToUnitID *uuid.UUID `json:"to_unit_id,omitempty"`
IsActive *bool `json:"is_active,omitempty"`
Search string `json:"search,omitempty"`
Page int `json:"page" validate:"required,min=1"`
Limit int `json:"limit" validate:"required,min=1,max=100"`
}
type ConvertUnitRequest struct {
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
FromUnitID uuid.UUID `json:"from_unit_id" validate:"required"`
ToUnitID uuid.UUID `json:"to_unit_id" validate:"required"`
Quantity float64 `json:"quantity" validate:"required,gt=0"`
}
// Response DTOs
type IngredientUnitConverterResponse struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"organization_id"`
IngredientID uuid.UUID `json:"ingredient_id"`
FromUnitID uuid.UUID `json:"from_unit_id"`
ToUnitID uuid.UUID `json:"to_unit_id"`
ConversionFactor float64 `json:"conversion_factor"`
IsActive bool `json:"is_active"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CreatedBy uuid.UUID `json:"created_by"`
UpdatedBy uuid.UUID `json:"updated_by"`
Ingredient *IngredientResponse `json:"ingredient,omitempty"`
FromUnit *UnitResponse `json:"from_unit,omitempty"`
ToUnit *UnitResponse `json:"to_unit,omitempty"`
}
type ConvertUnitResponse struct {
FromQuantity float64 `json:"from_quantity"`
FromUnit *UnitResponse `json:"from_unit"`
ToQuantity float64 `json:"to_quantity"`
ToUnit *UnitResponse `json:"to_unit"`
ConversionFactor float64 `json:"conversion_factor"`
Ingredient *IngredientResponse `json:"ingredient,omitempty"`
}
type ListIngredientUnitConvertersResponse struct {
Converters []IngredientUnitConverterResponse `json:"converters"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}
type IngredientUnitsResponse struct {
IngredientID uuid.UUID `json:"ingredient_id"`
IngredientName string `json:"ingredient_name"`
BaseUnitID uuid.UUID `json:"base_unit_id"`
BaseUnitName string `json:"base_unit_name"`
Units []*UnitResponse `json:"units"`
}
+86 -1
View File
@@ -1,8 +1,9 @@
package contract
import (
"github.com/google/uuid"
"time"
"github.com/google/uuid"
)
type CreateInventoryRequest struct {
@@ -24,6 +25,18 @@ type AdjustInventoryRequest struct {
Reason string `json:"reason" validate:"required,min=1,max=255"`
}
type RestockInventoryRequest struct {
OutletID uuid.UUID `json:"outlet_id" validate:"required"`
Items []RestockItem `json:"items" validate:"required,min=1,dive"`
Reason string `json:"reason" validate:"required,min=1,max=255"`
}
type RestockItem struct {
ItemID uuid.UUID `json:"item_id" validate:"required"`
ItemType string `json:"item_type" validate:"required,oneof=PRODUCT INGREDIENT"`
Quantity int `json:"quantity" validate:"required,min=1"`
}
type ListInventoryRequest struct {
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
ProductID *uuid.UUID `json:"product_id,omitempty"`
@@ -67,3 +80,75 @@ type InventoryAdjustmentResponse struct {
Reason string `json:"reason"`
AdjustedAt time.Time `json:"adjusted_at"`
}
type RestockInventoryResponse struct {
OutletID uuid.UUID `json:"outlet_id"`
Items []RestockItemResult `json:"items"`
Reason string `json:"reason"`
RestockedAt time.Time `json:"restocked_at"`
}
type RestockItemResult struct {
ItemID uuid.UUID `json:"item_id"`
ItemType string `json:"item_type"`
ItemName string `json:"item_name"`
PreviousQty int `json:"previous_quantity"`
NewQty int `json:"new_quantity"`
AddedQty int `json:"added_quantity"`
Success bool `json:"success"`
Error string `json:"error,omitempty"`
}
// Inventory Report Contracts
type InventoryReportSummaryResponse struct {
TotalProducts int `json:"total_products"`
TotalIngredients int `json:"total_ingredients"`
TotalValue float64 `json:"total_value"`
LowStockProducts int `json:"low_stock_products"`
LowStockIngredients int `json:"low_stock_ingredients"`
ZeroStockProducts int `json:"zero_stock_products"`
ZeroStockIngredients int `json:"zero_stock_ingredients"`
TotalSoldProducts float64 `json:"total_sold_products"`
TotalSoldIngredients float64 `json:"total_sold_ingredients"`
OutletID string `json:"outlet_id"`
OutletName string `json:"outlet_name"`
GeneratedAt string `json:"generated_at"`
}
type InventoryReportDetailResponse struct {
Summary *InventoryReportSummaryResponse `json:"summary"`
Products []*InventoryProductDetailResponse `json:"products"`
Ingredients []*InventoryIngredientDetailResponse `json:"ingredients"`
}
type InventoryProductDetailResponse struct {
ID string `json:"id"`
ProductID string `json:"product_id"`
ProductName string `json:"product_name"`
CategoryName string `json:"category_name"`
Quantity int `json:"quantity"`
ReorderLevel int `json:"reorder_level"`
UnitCost float64 `json:"unit_cost"`
TotalValue float64 `json:"total_value"`
TotalIn float64 `json:"total_in"`
TotalOut float64 `json:"total_out"`
IsLowStock bool `json:"is_low_stock"`
IsZeroStock bool `json:"is_zero_stock"`
UpdatedAt string `json:"updated_at"`
}
type InventoryIngredientDetailResponse struct {
ID string `json:"id"`
IngredientID string `json:"ingredient_id"`
IngredientName string `json:"ingredient_name"`
UnitName string `json:"unit_name"`
Quantity int `json:"quantity"`
ReorderLevel int `json:"reorder_level"`
UnitCost float64 `json:"unit_cost"`
TotalValue float64 `json:"total_value"`
TotalIn float64 `json:"total_in"`
TotalOut float64 `json:"total_out"`
IsLowStock bool `json:"is_low_stock"`
IsZeroStock bool `json:"is_zero_stock"`
UpdatedAt string `json:"updated_at"`
}
@@ -0,0 +1,92 @@
package contract
import (
"time"
"apskel-pos-be/internal/entities"
"github.com/google/uuid"
)
// ---- Request contracts ----
type SendNotificationRequest struct {
Title string `json:"title" validate:"required,min=1,max=255"`
Body string `json:"body" validate:"required"`
Type string `json:"type,omitempty" validate:"omitempty,max=100"`
Category string `json:"category,omitempty" validate:"omitempty,max=100"`
Priority entities.NotificationPriority `json:"priority,omitempty" validate:"omitempty,oneof=low normal high"`
ImageURL string `json:"image_url,omitempty" validate:"omitempty,max=512"`
ActionURL string `json:"action_url,omitempty" validate:"omitempty,max=512"`
NotifiableType string `json:"notifiable_type,omitempty" validate:"omitempty,max=100"`
NotifiableID *uuid.UUID `json:"notifiable_id,omitempty"`
Data map[string]interface{} `json:"data,omitempty"`
ReceiverIDs []uuid.UUID `json:"receiver_ids" validate:"required,min=1"`
ScheduledAt *time.Time `json:"scheduled_at,omitempty"`
ExpiredAt *time.Time `json:"expired_at,omitempty"`
}
type BroadcastNotificationRequest struct {
Title string `json:"title" validate:"required,min=1,max=255"`
Body string `json:"body" validate:"required"`
Type string `json:"type,omitempty" validate:"omitempty,max=100"`
Category string `json:"category,omitempty" validate:"omitempty,max=100"`
Priority entities.NotificationPriority `json:"priority,omitempty" validate:"omitempty,oneof=low normal high"`
ImageURL string `json:"image_url,omitempty" validate:"omitempty,max=512"`
ActionURL string `json:"action_url,omitempty" validate:"omitempty,max=512"`
NotifiableType string `json:"notifiable_type,omitempty" validate:"omitempty,max=100"`
NotifiableID *uuid.UUID `json:"notifiable_id,omitempty"`
Data map[string]interface{} `json:"data,omitempty"`
ScheduledAt *time.Time `json:"scheduled_at,omitempty"`
ExpiredAt *time.Time `json:"expired_at,omitempty"`
}
type ListNotificationsRequest struct {
Page int `form:"page" validate:"min=1"`
Limit int `form:"limit" validate:"min=1,max=100"`
IsRead *bool `form:"is_read"`
}
// ---- Response contracts ----
type NotificationResponse struct {
ID uuid.UUID `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
Type string `json:"type"`
Category string `json:"category"`
Priority entities.NotificationPriority `json:"priority"`
ImageURL string `json:"image_url"`
ActionURL string `json:"action_url"`
NotifiableType string `json:"notifiable_type"`
NotifiableID *uuid.UUID `json:"notifiable_id"`
Data map[string]interface{} `json:"data"`
ScheduledAt *time.Time `json:"scheduled_at"`
SentAt *time.Time `json:"sent_at"`
ExpiredAt *time.Time `json:"expired_at"`
CreatedBy *uuid.UUID `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type NotificationReceiverResponse struct {
ID uuid.UUID `json:"id"`
NotificationID uuid.UUID `json:"notification_id"`
UserID uuid.UUID `json:"user_id"`
IsRead bool `json:"is_read"`
ReadAt *time.Time `json:"read_at"`
IsDeleted bool `json:"is_deleted"`
DeletedAt *time.Time `json:"deleted_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Notification *NotificationResponse `json:"notification,omitempty"`
}
type ListNotificationsResponse struct {
Notifications []*NotificationReceiverResponse `json:"notifications"`
TotalCount int64 `json:"total_count"`
UnreadCount int64 `json:"unread_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}
@@ -0,0 +1,59 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateOmsetTrackerRequest struct {
PeriodType string `json:"period_type" validate:"required,oneof=DAILY WEEKLY MONTHLY TOTAL"`
PeriodStart time.Time `json:"period_start" validate:"required"`
PeriodEnd time.Time `json:"period_end" validate:"required"`
Total int64 `json:"total" validate:"min=0"`
GameID *uuid.UUID `json:"game_id,omitempty"`
}
type UpdateOmsetTrackerRequest struct {
PeriodType *string `json:"period_type,omitempty" validate:"omitempty,oneof=DAILY WEEKLY MONTHLY TOTAL"`
PeriodStart *time.Time `json:"period_start,omitempty"`
PeriodEnd *time.Time `json:"period_end,omitempty"`
Total *int64 `json:"total,omitempty" validate:"omitempty,min=0"`
GameID *uuid.UUID `json:"game_id,omitempty"`
}
type AddOmsetRequest struct {
Amount int64 `json:"amount" validate:"required,min=1"`
}
type OmsetTrackerResponse struct {
ID uuid.UUID `json:"id"`
PeriodType string `json:"period_type"`
PeriodStart time.Time `json:"period_start"`
PeriodEnd time.Time `json:"period_end"`
Total int64 `json:"total"`
GameID *uuid.UUID `json:"game_id,omitempty"`
Game *GameResponse `json:"game,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListOmsetTrackerRequest struct {
Page int `json:"page" validate:"min=1"`
Limit int `json:"limit" validate:"min=1,max=100"`
Search string `json:"search"`
PeriodType string `json:"period_type" validate:"omitempty,oneof=DAILY WEEKLY MONTHLY TOTAL"`
GameID *uuid.UUID `json:"game_id"`
From *time.Time `json:"from"`
To *time.Time `json:"to"`
SortBy string `json:"sort_by" validate:"omitempty,oneof=period_type period_start total created_at updated_at"`
SortOrder string `json:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedOmsetTrackerResponse struct {
Data []OmsetTrackerResponse `json:"data"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}
+54 -3
View File
@@ -10,6 +10,7 @@ type CreateOrderRequest struct {
OutletID uuid.UUID `json:"outlet_id" validate:"required"`
UserID uuid.UUID `json:"user_id" validate:"required"`
CustomerID *uuid.UUID `json:"customer_id"`
TableID *uuid.UUID `json:"table_id,omitempty" validate:"omitempty"`
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"`
@@ -67,12 +68,27 @@ type OrderResponse struct {
TaxAmount float64 `json:"tax_amount"`
DiscountAmount float64 `json:"discount_amount"`
TotalAmount float64 `json:"total_amount"`
TotalCost float64 `json:"total_cost"`
RemainingAmount float64 `json:"remaining_amount"`
PaymentStatus string `json:"payment_status"`
RefundAmount float64 `json:"refund_amount"`
IsVoid bool `json:"is_void"`
IsRefund bool `json:"is_refund"`
VoidReason *string `json:"void_reason,omitempty"`
VoidedAt *time.Time `json:"voided_at,omitempty"`
VoidedBy *uuid.UUID `json:"voided_by,omitempty"`
RefundReason *string `json:"refund_reason,omitempty"`
RefundedAt *time.Time `json:"refunded_at,omitempty"`
RefundedBy *uuid.UUID `json:"refunded_by,omitempty"`
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"`
IsRefund bool `json:"is_refund"`
Payments []PaymentResponse `json:"payments,omitempty"`
TotalPaid float64 `json:"total_paid"`
PaymentCount int `json:"payment_count"`
SplitType *string `json:"split_type,omitempty"`
}
type OrderItemResponse struct {
@@ -92,6 +108,7 @@ type OrderItemResponse struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
PrinterType string `json:"printer_type"`
PaidQuantity int `json:"paid_quantity"`
}
type ListOrdersQuery struct {
@@ -124,6 +141,7 @@ type ListOrdersRequest struct {
type ListOrdersResponse struct {
Orders []OrderResponse `json:"orders"`
Payments []PaymentResponse `json:"payments"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
@@ -152,7 +170,6 @@ type SetOrderCustomerResponse struct {
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"`
@@ -160,6 +177,7 @@ type CreatePaymentRequest struct {
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"`
SplitType *string `json:"split_type,omitempty" validate:"omitempty,oneof=AMOUNT ITEM"`
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"`
@@ -167,18 +185,21 @@ type CreatePaymentRequest struct {
type CreatePaymentOrderItemRequest struct {
OrderItemID uuid.UUID `json:"order_item_id" validate:"required"`
Amount float64 `json:"amount" validate:"required,min=0"`
Amount float64 `json:"amount" validate:"min=0"`
}
type PaymentResponse struct {
ID uuid.UUID `json:"id"`
OrderID uuid.UUID `json:"order_id"`
PaymentMethodID uuid.UUID `json:"payment_method_id"`
PaymentMethodName string `json:"payment_method_name"`
PaymentMethodType string `json:"payment_method_type"`
Amount float64 `json:"amount"`
Status string `json:"status"`
TransactionID *string `json:"transaction_id,omitempty"`
SplitNumber int `json:"split_number"`
SplitTotal int `json:"split_total"`
SplitType *string `json:"split_type,omitempty"`
SplitDescription *string `json:"split_description,omitempty"`
RefundAmount float64 `json:"refund_amount"`
RefundReason *string `json:"refund_reason,omitempty"`
@@ -216,3 +237,33 @@ type RefundPaymentRequest struct {
RefundAmount float64 `json:"refund_amount" validate:"required,min=0"`
Reason string `json:"reason" validate:"omitempty,max=255"`
}
type SplitBillRequest struct {
OrderID uuid.UUID `json:"order_id" validate:"required"`
OrganizationID uuid.UUID `json:"organization_id"`
PaymentMethodID uuid.UUID `json:"payment_method_id" validate:"required"`
CustomerID uuid.UUID `json:"customer_id"`
Type string `json:"type" validate:"required,oneof=ITEM AMOUNT"`
Items []SplitBillItemRequest `json:"items,omitempty" validate:"required_if=Type ITEM,dive"`
Amount float64 `json:"amount,omitempty" validate:"required_if=Type AMOUNT,min=0"`
}
type SplitBillItemRequest struct {
OrderItemID uuid.UUID `json:"order_item_id" validate:"required"`
Quantity int `json:"quantity" validate:"required,min=0"`
}
type SplitBillResponse struct {
PaymentID uuid.UUID `json:"payment_id"`
OrderID uuid.UUID `json:"order_id"`
CustomerID uuid.UUID `json:"customer_id"`
Type string `json:"type"`
Amount float64 `json:"amount"`
Items []SplitBillItemResponse `json:"items,omitempty"`
Message string `json:"message"`
}
type SplitBillItemResponse struct {
OrderItemID uuid.UUID `json:"order_item_id"`
Amount float64 `json:"amount"`
}
@@ -0,0 +1,20 @@
package contract
import (
"context"
"github.com/google/uuid"
)
type OrderIngredientTransactionContract interface {
CreateOrderIngredientTransaction(ctx context.Context, req *CreateOrderIngredientTransactionRequest) (*OrderIngredientTransactionResponse, error)
GetOrderIngredientTransactionByID(ctx context.Context, id uuid.UUID) (*OrderIngredientTransactionResponse, error)
UpdateOrderIngredientTransaction(ctx context.Context, id uuid.UUID, req *UpdateOrderIngredientTransactionRequest) (*OrderIngredientTransactionResponse, error)
DeleteOrderIngredientTransaction(ctx context.Context, id uuid.UUID) error
ListOrderIngredientTransactions(ctx context.Context, req *ListOrderIngredientTransactionsRequest) ([]*OrderIngredientTransactionResponse, int64, error)
GetOrderIngredientTransactionsByOrder(ctx context.Context, orderID uuid.UUID) ([]*OrderIngredientTransactionResponse, error)
GetOrderIngredientTransactionsByOrderItem(ctx context.Context, orderItemID uuid.UUID) ([]*OrderIngredientTransactionResponse, error)
GetOrderIngredientTransactionsByIngredient(ctx context.Context, ingredientID uuid.UUID) ([]*OrderIngredientTransactionResponse, error)
GetOrderIngredientTransactionSummary(ctx context.Context, req *ListOrderIngredientTransactionsRequest) ([]*OrderIngredientTransactionSummary, error)
BulkCreateOrderIngredientTransactions(ctx context.Context, transactions []*CreateOrderIngredientTransactionRequest) ([]*OrderIngredientTransactionResponse, error)
}
@@ -0,0 +1,79 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateOrderIngredientTransactionRequest struct {
OrderID uuid.UUID `json:"order_id" validate:"required"`
OrderItemID *uuid.UUID `json:"order_item_id,omitempty"`
ProductID uuid.UUID `json:"product_id" validate:"required"`
ProductVariantID *uuid.UUID `json:"product_variant_id,omitempty"`
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
GrossQty float64 `json:"gross_qty" validate:"required,gt=0"`
NetQty float64 `json:"net_qty" validate:"required,gt=0"`
WasteQty float64 `json:"waste_qty" validate:"min=0"`
Unit string `json:"unit" validate:"required,max=50"`
TransactionDate *time.Time `json:"transaction_date,omitempty"`
}
type UpdateOrderIngredientTransactionRequest struct {
GrossQty *float64 `json:"gross_qty,omitempty" validate:"omitempty,gt=0"`
NetQty *float64 `json:"net_qty,omitempty" validate:"omitempty,gt=0"`
WasteQty *float64 `json:"waste_qty,omitempty" validate:"min=0"`
Unit *string `json:"unit,omitempty" validate:"omitempty,max=50"`
TransactionDate *time.Time `json:"transaction_date,omitempty"`
}
type OrderIngredientTransactionResponse struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"organization_id"`
OutletID *uuid.UUID `json:"outlet_id"`
OrderID uuid.UUID `json:"order_id"`
OrderItemID *uuid.UUID `json:"order_item_id"`
ProductID uuid.UUID `json:"product_id"`
ProductVariantID *uuid.UUID `json:"product_variant_id"`
IngredientID uuid.UUID `json:"ingredient_id"`
GrossQty float64 `json:"gross_qty"`
NetQty float64 `json:"net_qty"`
WasteQty float64 `json:"waste_qty"`
Unit string `json:"unit"`
TransactionDate time.Time `json:"transaction_date"`
CreatedBy uuid.UUID `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Relations - these would be populated by the service layer
Organization interface{} `json:"organization,omitempty"`
Outlet interface{} `json:"outlet,omitempty"`
Order interface{} `json:"order,omitempty"`
OrderItem interface{} `json:"order_item,omitempty"`
Product interface{} `json:"product,omitempty"`
ProductVariant interface{} `json:"product_variant,omitempty"`
Ingredient interface{} `json:"ingredient,omitempty"`
CreatedByUser interface{} `json:"created_by_user,omitempty"`
}
type ListOrderIngredientTransactionsRequest struct {
OrderID *uuid.UUID `json:"order_id,omitempty"`
OrderItemID *uuid.UUID `json:"order_item_id,omitempty"`
ProductID *uuid.UUID `json:"product_id,omitempty"`
ProductVariantID *uuid.UUID `json:"product_variant_id,omitempty"`
IngredientID *uuid.UUID `json:"ingredient_id,omitempty"`
StartDate *time.Time `json:"start_date,omitempty"`
EndDate *time.Time `json:"end_date,omitempty"`
Page int `json:"page" validate:"min=1"`
Limit int `json:"limit" validate:"min=1,max=100"`
}
type OrderIngredientTransactionSummary struct {
IngredientID uuid.UUID `json:"ingredient_id"`
IngredientName string `json:"ingredient_name"`
TotalGrossQty float64 `json:"total_gross_qty"`
TotalNetQty float64 `json:"total_net_qty"`
TotalWasteQty float64 `json:"total_waste_qty"`
WastePercentage float64 `json:"waste_percentage"`
Unit string `json:"unit"`
}
+4
View File
@@ -59,10 +59,13 @@ type ProductResponse struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"organization_id"`
CategoryID uuid.UUID `json:"category_id"`
CategoryName string `json:"category_name"`
SKU *string `json:"sku"`
Name string `json:"name"`
Description *string `json:"description"`
Price float64 `json:"price"`
OutletPrice *float64 `json:"outlet_price,omitempty"`
OutletPrices []ProductOutletPriceResponse `json:"outlet_prices,omitempty"`
Cost float64 `json:"cost"`
BusinessType string `json:"business_type"`
ImageURL *string `json:"image_url"`
@@ -88,6 +91,7 @@ type ProductVariantResponse struct {
type ListProductsRequest struct {
OrganizationID *uuid.UUID `json:"organization_id,omitempty"`
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
CategoryID *uuid.UUID `json:"category_id,omitempty"`
BusinessType string `json:"business_type,omitempty"`
IsActive *bool `json:"is_active,omitempty"`
@@ -0,0 +1,42 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateProductOutletPriceRequest struct {
ProductID uuid.UUID `json:"product_id" validate:"required"`
OutletID uuid.UUID `json:"outlet_id" validate:"required"`
Price float64 `json:"price" validate:"required,min=0"`
}
type UpdateProductOutletPriceRequest struct {
Price float64 `json:"price" validate:"required,min=0"`
}
type ProductOutletPriceResponse struct {
ID uuid.UUID `json:"id,omitempty"`
ProductID uuid.UUID `json:"product_id,omitempty"`
OutletID uuid.UUID `json:"outlet_id"`
OutletName string `json:"outlet_name,omitempty"`
Price float64 `json:"price"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
type ListProductOutletPricesResponse struct {
Prices []ProductOutletPriceResponse `json:"prices"`
TotalCount int `json:"total_count"`
}
type BulkCreateProductOutletPriceRequest struct {
ProductID uuid.UUID `json:"product_id" validate:"required"`
Prices []CreateProductOutletPricePerOutletRequest `json:"prices" validate:"required,dive"`
}
type CreateProductOutletPricePerOutletRequest struct {
OutletID uuid.UUID `json:"outlet_id" validate:"required"`
Price float64 `json:"price" validate:"required,min=0"`
}
@@ -0,0 +1,74 @@
package contract
import (
"time"
"github.com/google/uuid"
)
// Request structures
type CreateProductRecipeRequest struct {
OutletID *uuid.UUID `json:"outlet_id"`
ProductID uuid.UUID `json:"product_id" validate:"required"`
VariantID *uuid.UUID `json:"variant_id"`
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
Quantity float64 `json:"quantity" validate:"required,gt=0"`
WastePercentage float64 `json:"waste_percentage" validate:"min=0,max=100"`
}
type UpdateProductRecipeRequest struct {
OutletID *uuid.UUID `json:"outlet_id"`
VariantID *uuid.UUID `json:"variant_id"`
Quantity float64 `json:"quantity" validate:"required,gt=0"`
WastePercentage float64 `json:"waste_percentage" validate:"min=0,max=100"`
}
type GetProductRecipeByProductIDRequest struct {
ProductID uuid.UUID `json:"-"`
VariantID *uuid.UUID `json:"-"`
}
type BulkCreateProductRecipeRequest struct {
Recipes []CreateProductRecipeRequest `json:"recipes" validate:"required,min=1"`
}
// Response structures
type ProductRecipeResponse struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"organization_id"`
OutletID *uuid.UUID `json:"outlet_id"`
ProductID uuid.UUID `json:"product_id"`
VariantID *uuid.UUID `json:"variant_id"`
IngredientID uuid.UUID `json:"ingredient_id"`
Quantity float64 `json:"quantity"`
WastePercentage float64 `json:"waste_percentage"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Product *ProductResponse `json:"product,omitempty"`
ProductVariant *ProductVariantResponse `json:"product_variant,omitempty"`
Ingredient *ProductRecipeIngredientResponse `json:"ingredient,omitempty"`
}
type ProductRecipeIngredientResponse struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"organization_id"`
OutletID *uuid.UUID `json:"outlet_id"`
Name string `json:"name"`
UnitID uuid.UUID `json:"unit_id"`
Cost float64 `json:"cost"`
Stock float64 `json:"stock"`
IsSemiFinished bool `json:"is_semi_finished"`
IsActive bool `json:"is_active"`
Metadata map[string]interface{} `json:"metadata"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Unit *ProductRecipeUnitResponse `json:"unit,omitempty"`
}
type ProductRecipeUnitResponse struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Symbol string `json:"symbol"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
@@ -0,0 +1,117 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreatePurchaseOrderRequest struct {
VendorID uuid.UUID `json:"vendor_id" validate:"required"`
PONumber string `json:"po_number" validate:"required,min=1,max=50"`
TransactionDate string `json:"transaction_date" validate:"required"` // Format: YYYY-MM-DD
DueDate string `json:"due_date" validate:"required"` // Format: YYYY-MM-DD
Reference *string `json:"reference,omitempty" validate:"omitempty,max=100"`
Status *string `json:"status,omitempty" validate:"omitempty,oneof=draft sent approved received cancelled"`
Message *string `json:"message,omitempty" validate:"omitempty"`
Items []CreatePurchaseOrderItemRequest `json:"items" validate:"required,min=1,dive"`
AttachmentFileIDs []uuid.UUID `json:"attachment_file_ids,omitempty"`
}
type CreatePurchaseOrderItemRequest struct {
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
Description *string `json:"description,omitempty" validate:"omitempty"`
Quantity float64 `json:"quantity" validate:"required,gt=0"`
UnitID uuid.UUID `json:"unit_id" validate:"required"`
Amount float64 `json:"amount" validate:"required,gte=0"`
}
type UpdatePurchaseOrderRequest struct {
VendorID *uuid.UUID `json:"vendor_id,omitempty" validate:"omitempty"`
PONumber *string `json:"po_number,omitempty" validate:"omitempty,min=1,max=50"`
TransactionDate *string `json:"transaction_date,omitempty" validate:"omitempty"` // Format: YYYY-MM-DD
DueDate *string `json:"due_date,omitempty" validate:"omitempty"` // Format: YYYY-MM-DD
Reference *string `json:"reference,omitempty" validate:"omitempty,max=100"`
Status *string `json:"status,omitempty" validate:"omitempty,oneof=draft sent approved received cancelled"`
Message *string `json:"message,omitempty" validate:"omitempty"`
Items []UpdatePurchaseOrderItemRequest `json:"items,omitempty" validate:"omitempty,dive"`
AttachmentFileIDs []uuid.UUID `json:"attachment_file_ids,omitempty"`
}
type UpdatePurchaseOrderItemRequest struct {
ID *uuid.UUID `json:"id,omitempty"` // For existing items
IngredientID *uuid.UUID `json:"ingredient_id,omitempty" validate:"omitempty"`
Description *string `json:"description,omitempty" validate:"omitempty"`
Quantity *float64 `json:"quantity,omitempty" validate:"omitempty,gt=0"`
UnitID *uuid.UUID `json:"unit_id,omitempty" validate:"omitempty"`
Amount *float64 `json:"amount,omitempty" validate:"omitempty,gte=0"`
}
type PurchaseOrderResponse struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"organization_id"`
VendorID uuid.UUID `json:"vendor_id"`
PONumber string `json:"po_number"`
TransactionDate time.Time `json:"transaction_date"`
DueDate time.Time `json:"due_date"`
Reference *string `json:"reference"`
Status string `json:"status"`
Message *string `json:"message"`
TotalAmount float64 `json:"total_amount"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Vendor *VendorResponse `json:"vendor,omitempty"`
Items []PurchaseOrderItemResponse `json:"items,omitempty"`
Attachments []PurchaseOrderAttachmentResponse `json:"attachments,omitempty"`
}
type PurchaseOrderItemResponse struct {
ID uuid.UUID `json:"id"`
PurchaseOrderID uuid.UUID `json:"purchase_order_id"`
IngredientID uuid.UUID `json:"ingredient_id"`
Description *string `json:"description"`
Quantity float64 `json:"quantity"`
UnitID uuid.UUID `json:"unit_id"`
Amount float64 `json:"amount"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Ingredient *IngredientResponse `json:"ingredient,omitempty"`
Unit *UnitResponse `json:"unit,omitempty"`
}
type PurchaseOrderAttachmentResponse struct {
ID uuid.UUID `json:"id"`
PurchaseOrderID uuid.UUID `json:"purchase_order_id"`
FileID uuid.UUID `json:"file_id"`
CreatedAt time.Time `json:"created_at"`
File *FileResponse `json:"file,omitempty"`
}
type ListPurchaseOrdersRequest struct {
Page int `json:"page" validate:"min=1"`
Limit int `json:"limit" validate:"min=1,max=100"`
Search string `json:"search,omitempty"`
Status string `json:"status,omitempty" validate:"omitempty,oneof=draft sent approved received cancelled"`
VendorID *uuid.UUID `json:"vendor_id,omitempty"`
StartDate *time.Time `json:"start_date,omitempty"`
EndDate *time.Time `json:"end_date,omitempty"`
}
type ListPurchaseOrdersResponse struct {
PurchaseOrders []PurchaseOrderResponse `json:"purchase_orders"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}
// Helper types for ingredient and unit responses
type IngredientResponse struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
}
type UnitResponse struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
}
+82
View File
@@ -0,0 +1,82 @@
package contract
import (
"time"
"github.com/google/uuid"
)
// Request Contracts
type CreateRewardRequest struct {
Name string `json:"name" binding:"required,min=1,max=150"`
RewardType string `json:"reward_type" binding:"required,oneof=VOUCHER PHYSICAL DIGITAL"`
CostPoints int64 `json:"cost_points" binding:"required,min=1"`
Stock *int `json:"stock,omitempty"`
MaxPerCustomer int `json:"max_per_customer" binding:"min=1"`
Tnc *TermsAndConditionsStruct `json:"tnc,omitempty"`
Metadata *map[string]interface{} `json:"metadata,omitempty"`
Images *[]string `json:"images,omitempty"`
}
type UpdateRewardRequest struct {
ID uuid.UUID `json:"id" binding:"required"`
Name string `json:"name" binding:"required,min=1,max=150"`
RewardType string `json:"reward_type" binding:"required,oneof=VOUCHER PHYSICAL DIGITAL BALANCE"`
CostPoints int64 `json:"cost_points" binding:"required,min=1"`
Stock *int `json:"stock,omitempty"`
MaxPerCustomer int `json:"max_per_customer" binding:"min=1"`
Tnc *TermsAndConditionsStruct `json:"tnc,omitempty"`
Metadata *map[string]interface{} `json:"metadata,omitempty"`
Images *[]string `json:"images,omitempty"`
}
type ListRewardsRequest struct {
Page int `form:"page" binding:"min=1"`
Limit int `form:"limit" binding:"min=1,max=100"`
Search string `form:"search"`
RewardType string `form:"reward_type"`
MinPoints *int64 `form:"min_points"`
MaxPoints *int64 `form:"max_points"`
HasStock *bool `form:"has_stock"`
}
type GetRewardRequest struct {
ID uuid.UUID `uri:"id" binding:"required"`
}
type DeleteRewardRequest struct {
ID uuid.UUID `uri:"id" binding:"required"`
}
// Response Contracts
type RewardResponse struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
RewardType string `json:"reward_type"`
CostPoints int64 `json:"cost_points"`
Stock *int `json:"stock,omitempty"`
MaxPerCustomer int `json:"max_per_customer"`
Tnc *TermsAndConditionsStruct `json:"tnc,omitempty"`
Metadata *map[string]interface{} `json:"metadata,omitempty"`
Images *[]string `json:"images,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListRewardsResponse struct {
Rewards []RewardResponse `json:"rewards"`
Total int64 `json:"total"`
Page int `json:"page"`
Limit int `json:"limit"`
}
// Helper structs
type TermsAndConditionsStruct struct {
Sections []TncSectionStruct `json:"sections"`
ExpiryDays int `json:"expiry_days"`
}
type TncSectionStruct struct {
Title string `json:"title"`
Rules []string `json:"rules"`
}
+82
View File
@@ -0,0 +1,82 @@
package contract
import (
"github.com/google/uuid"
)
type SelfOrderTableTokenResponse struct {
SessionID string `json:"session_id"`
TableID string `json:"table_id"`
OrganizationID string `json:"organization_id"`
OutletID string `json:"outlet_id"`
TableName string `json:"table_name"`
OutletName string `json:"outlet_name"`
Status string `json:"status"`
}
type SelfOrderMenuRequest struct {
SessionID string `form:"session_id" validate:"required"`
}
type SelfOrderMenuResponse struct {
OutletName string `json:"outlet_name"`
TableName string `json:"table_name"`
Categories []SelfOrderMenuCategory `json:"categories"`
}
type SelfOrderMenuCategory struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
Order int `json:"order"`
Products []SelfOrderMenuItem `json:"products"`
}
type SelfOrderMenuItem struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
Price float64 `json:"price"`
ImageURL *string `json:"image_url,omitempty"`
Variants []SelfOrderMenuVariant `json:"variants,omitempty"`
}
type SelfOrderMenuVariant struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
PriceModifier float64 `json:"price_modifier"`
}
type SelfOrderCreateOrderRequest struct {
SessionID string `json:"session_id" validate:"required"`
CustomerName string `json:"customer_name" validate:"required"`
OrderType string `json:"order_type" validate:"required,oneof=dine_in takeaway delivery"`
OrderItems []SelfOrderCreateOrderItem `json:"order_items" validate:"required,min=1,dive"`
}
type SelfOrderCreateOrderItem 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"`
Notes *string `json:"notes,omitempty"`
}
type SelfOrderListCategoriesRequest struct {
OrganizationID string `form:"organization_id" validate:"required"`
OutletID string `form:"outlet_id" validate:"required"`
}
type SelfOrderCategoryItem struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
Order int `json:"order"`
}
type SelfOrderListCategoriesResponse struct {
Categories []SelfOrderCategoryItem `json:"categories"`
}
type SelfOrderListOrdersResponse struct {
Orders []OrderResponse `json:"orders"`
}
+20
View File
@@ -0,0 +1,20 @@
package contract
// SpinGameRequest represents the request to play a spin game
type SpinGameRequest struct {
SpinID string `json:"spin_id" validate:"required,uuid"`
}
// SpinGameResponse represents the response from playing a spin game
type SpinGameResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *SpinGameResponseData `json:"data,omitempty"`
}
// SpinGameResponseData contains the game play result
type SpinGameResponseData struct {
GamePlay GamePlayResponse `json:"game_play"`
PrizeWon *CustomerGamePrizeResponse `json:"prize_won,omitempty"`
TokensRemaining int64 `json:"tokens_remaining"`
}
+44
View File
@@ -0,0 +1,44 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateTierRequest struct {
Name string `json:"name" validate:"required"`
MinPoints int64 `json:"min_points" validate:"min=0"`
Benefits map[string]interface{} `json:"benefits"`
}
type UpdateTierRequest struct {
Name *string `json:"name,omitempty" validate:"omitempty,required"`
MinPoints *int64 `json:"min_points,omitempty" validate:"omitempty,min=0"`
Benefits map[string]interface{} `json:"benefits,omitempty"`
}
type TierResponse struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
MinPoints int64 `json:"min_points"`
Benefits map[string]interface{} `json:"benefits"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListTiersRequest struct {
Page int `form:"page" validate:"min=1"`
Limit int `form:"limit" validate:"min=1,max=100"`
Search string `form:"search"`
SortBy string `form:"sort_by" validate:"omitempty,oneof=name min_points created_at updated_at"`
SortOrder string `form:"sort_order" validate:"omitempty,oneof=asc desc"`
}
type PaginatedTiersResponse struct {
Data []TierResponse `json:"data"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}
+10 -1
View File
@@ -7,7 +7,7 @@ import (
)
type CreateUserRequest struct {
OrganizationID uuid.UUID `json:"organization_id" validate:"required"`
OrganizationID uuid.UUID `json:"organization_id"`
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
Name string `json:"name" validate:"required,min=1,max=255"`
Email string `json:"email" validate:"required,email"`
@@ -37,11 +37,20 @@ type UpdateUserOutletRequest struct {
type LoginRequest struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required"`
DeviceID string `json:"device_id,omitempty"`
DeviceName string `json:"device_name,omitempty"`
DeviceType string `json:"device_type,omitempty"`
Platform string `json:"platform,omitempty"`
FCMToken string `json:"fcm_token,omitempty"`
AppVersion string `json:"app_version,omitempty"`
OsVersion string `json:"os_version,omitempty"`
}
type LoginResponse struct {
Token string `json:"token"`
RefreshToken string `json:"refresh_token"`
ExpiresAt time.Time `json:"expires_at"`
RefreshExpiresAt time.Time `json:"refresh_expires_at"`
User UserResponse `json:"user"`
}
+59
View File
@@ -0,0 +1,59 @@
package contract
import (
"time"
"apskel-pos-be/internal/entities"
"github.com/google/uuid"
)
type RegisterUserDeviceRequest struct {
DeviceID string `json:"device_id" validate:"required,min=1,max=255"`
DeviceName string `json:"device_name,omitempty" validate:"omitempty,max=255"`
DeviceType entities.DeviceType `json:"device_type,omitempty" validate:"omitempty,oneof=mobile tablet desktop"`
Platform entities.DevicePlatform `json:"platform,omitempty" validate:"omitempty,oneof=android ios web"`
FCMToken string `json:"fcm_token,omitempty" validate:"omitempty,max=512"`
AppVersion string `json:"app_version,omitempty" validate:"omitempty,max=50"`
OsVersion string `json:"os_version,omitempty" validate:"omitempty,max=50"`
}
type UpdateUserDeviceRequest struct {
DeviceName string `json:"device_name,omitempty" validate:"omitempty,max=255"`
DeviceType entities.DeviceType `json:"device_type,omitempty" validate:"omitempty,oneof=mobile tablet desktop"`
Platform entities.DevicePlatform `json:"platform,omitempty" validate:"omitempty,oneof=android ios web"`
FCMToken string `json:"fcm_token,omitempty" validate:"omitempty,max=512"`
AppVersion string `json:"app_version,omitempty" validate:"omitempty,max=50"`
OsVersion string `json:"os_version,omitempty" validate:"omitempty,max=50"`
}
type UserDeviceResponse struct {
ID uuid.UUID `json:"id"`
UserID uuid.UUID `json:"user_id"`
DeviceID string `json:"device_id"`
DeviceName string `json:"device_name"`
DeviceType entities.DeviceType `json:"device_type"`
Platform entities.DevicePlatform `json:"platform"`
FCMToken string `json:"fcm_token"`
AppVersion string `json:"app_version"`
OsVersion string `json:"os_version"`
IPAddress string `json:"ip_address"`
LastActiveAt *time.Time `json:"last_active_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListUserDevicesRequest struct {
Page int `json:"page" validate:"min=1"`
Limit int `json:"limit" validate:"min=1,max=100"`
UserID string `json:"user_id,omitempty"`
Platform string `json:"platform,omitempty"`
}
type ListUserDevicesResponse struct {
Devices []UserDeviceResponse `json:"devices"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}
+62
View File
@@ -0,0 +1,62 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateVendorRequest struct {
Name string `json:"name" validate:"required,min=1,max=255"`
Email *string `json:"email,omitempty" validate:"omitempty,email"`
PhoneNumber *string `json:"phone_number,omitempty" validate:"omitempty"`
Address *string `json:"address,omitempty" validate:"omitempty"`
ContactPerson *string `json:"contact_person,omitempty" validate:"omitempty,max=255"`
TaxNumber *string `json:"tax_number,omitempty" validate:"omitempty,max=50"`
PaymentTerms *string `json:"payment_terms,omitempty" validate:"omitempty,max=100"`
Notes *string `json:"notes,omitempty" validate:"omitempty"`
IsActive *bool `json:"is_active,omitempty"`
}
type UpdateVendorRequest 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" validate:"omitempty"`
Address *string `json:"address,omitempty" validate:"omitempty"`
ContactPerson *string `json:"contact_person,omitempty" validate:"omitempty,max=255"`
TaxNumber *string `json:"tax_number,omitempty" validate:"omitempty,max=50"`
PaymentTerms *string `json:"payment_terms,omitempty" validate:"omitempty,max=100"`
Notes *string `json:"notes,omitempty" validate:"omitempty"`
IsActive *bool `json:"is_active,omitempty"`
}
type VendorResponse struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"organization_id"`
Name string `json:"name"`
Email *string `json:"email"`
PhoneNumber *string `json:"phone_number"`
Address *string `json:"address"`
ContactPerson *string `json:"contact_person"`
TaxNumber *string `json:"tax_number"`
PaymentTerms *string `json:"payment_terms"`
Notes *string `json:"notes"`
IsActive bool `json:"is_active"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListVendorsRequest struct {
Page int `json:"page" validate:"min=1"`
Limit int `json:"limit" validate:"min=1,max=100"`
Search string `json:"search,omitempty"`
IsActive *bool `json:"is_active,omitempty"`
}
type ListVendorsResponse struct {
Vendors []VendorResponse `json:"vendors"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
}
+30
View File
@@ -0,0 +1,30 @@
package db
import (
"apskel-pos-be/config"
"fmt"
"github.com/redis/go-redis/v9"
)
func NewRedisClient(c config.Redis) (*redis.Client, error) {
opts := &redis.Options{
Addr: c.Addr(),
Password: c.Password,
DB: c.DB,
DialTimeout: c.ParseDialTimeout(),
ReadTimeout: c.ParseReadTimeout(),
WriteTimeout: c.ParseWriteTimeout(),
}
if c.PoolSize > 0 {
opts.PoolSize = c.PoolSize
}
if c.MinIdleConnections > 0 {
opts.MinIdleConns = c.MinIdleConnections
}
client := redis.NewClient(opts)
fmt.Println("Successfully connected to Redis")
return client, nil
}
+55
View File
@@ -0,0 +1,55 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type AccountType string
const (
AccountTypeCash AccountType = "cash"
AccountTypeWallet AccountType = "wallet"
AccountTypeBank AccountType = "bank"
AccountTypeCredit AccountType = "credit"
AccountTypeDebit AccountType = "debit"
AccountTypeAsset AccountType = "asset"
AccountTypeLiability AccountType = "liability"
AccountTypeEquity AccountType = "equity"
AccountTypeRevenue AccountType = "revenue"
AccountTypeExpense AccountType = "expense"
)
type Account 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"`
ChartOfAccountID uuid.UUID `gorm:"type:uuid;not null;index" json:"chart_of_account_id" validate:"required"`
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
Number string `gorm:"not null;size:50" json:"number" validate:"required,min=1,max=50"`
AccountType AccountType `gorm:"not null;size:20" json:"account_type" validate:"required,oneof=cash wallet bank credit debit asset liability equity revenue expense"`
OpeningBalance float64 `gorm:"type:decimal(15,2);default:0.00" json:"opening_balance"`
CurrentBalance float64 `gorm:"type:decimal(15,2);default:0.00" json:"current_balance"`
Description *string `gorm:"type:text" json:"description"`
IsActive bool `gorm:"default:true" json:"is_active"`
IsSystem bool `gorm:"default:false" json:"is_system"`
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"`
ChartOfAccount ChartOfAccount `gorm:"foreignKey:ChartOfAccountID" json:"chart_of_account,omitempty"`
}
func (a *Account) BeforeCreate(tx *gorm.DB) error {
if a.ID == uuid.Nil {
a.ID = uuid.New()
}
return nil
}
func (Account) TableName() string {
return "accounts"
}
+20 -1
View File
@@ -27,16 +27,35 @@ type SalesAnalytics struct {
NetSales float64 `json:"net_sales"`
}
// ProductAnalytics represents product analytics data
type ProductAnalytics struct {
ProductID uuid.UUID `json:"product_id"`
ProductName string `json:"product_name"`
ProductSku string `json:"product_sku"`
CategoryID uuid.UUID `json:"category_id"`
CategoryName string `json:"category_name"`
CategoryOrder int `json:"category_order"`
QuantitySold int64 `json:"quantity_sold"`
Revenue float64 `json:"revenue"`
AveragePrice float64 `json:"average_price"`
OrderCount int64 `json:"order_count"`
StandardHppPerUnit float64 `json:"standard_hpp_per_unit"`
StandardHppTotal float64 `json:"standard_hpp_total"`
FifoHppPerUnit float64 `json:"fifo_hpp_per_unit"`
FifoHppTotal float64 `json:"fifo_hpp_total"`
MovingAverageHppPerUnit float64 `json:"moving_average_hpp_per_unit"`
MovingAverageHppTotal float64 `json:"moving_average_hpp_total"`
}
type ProductAnalyticsPerCategory struct {
CategoryID uuid.UUID `json:"category_id"`
CategoryName string `json:"category_name"`
TotalRevenue float64 `json:"total_revenue"`
TotalQuantity int64 `json:"total_quantity"`
ProductCount int64 `json:"product_count"`
OrderCount int64 `json:"order_count"`
TotalStandardHpp float64 `json:"total_standard_hpp"`
TotalFifoHpp float64 `json:"total_fifo_hpp"`
TotalMovingAverageHpp float64 `json:"total_moving_average_hpp"`
}
// DashboardOverview represents dashboard overview data
+131
View File
@@ -0,0 +1,131 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type CampaignType string
const (
CampaignTypeReward CampaignType = "REWARD"
CampaignTypePoints CampaignType = "POINTS"
CampaignTypeTokens CampaignType = "TOKENS"
CampaignTypeMixed CampaignType = "MIXED"
)
type RuleType string
const (
RuleTypeTier RuleType = "TIER"
RuleTypeSpend RuleType = "SPEND"
RuleTypeProduct RuleType = "PRODUCT"
RuleTypeCategory RuleType = "CATEGORY"
RuleTypeDay RuleType = "DAY"
RuleTypeLocation RuleType = "LOCATION"
)
type CampaignRewardType string
const (
CampaignRewardTypePoints CampaignRewardType = "POINTS"
CampaignRewardTypeTokens CampaignRewardType = "TOKENS"
CampaignRewardTypeReward CampaignRewardType = "REWARD"
)
type RewardSubtype string
const (
RewardSubtypeMultiplier RewardSubtype = "MULTIPLIER"
RewardSubtypeSpin RewardSubtype = "SPIN"
RewardSubtypeBonus RewardSubtype = "BONUS"
RewardSubtypePhysical RewardSubtype = "PHYSICAL"
)
type Campaign struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
Name string `gorm:"type:varchar(150);not null" json:"name"`
Description *string `gorm:"type:text" json:"description,omitempty"`
Type CampaignType `gorm:"type:varchar(50);not null" json:"type"`
StartDate time.Time `gorm:"type:timestamp;not null" json:"start_date"`
EndDate time.Time `gorm:"type:timestamp;not null" json:"end_date"`
IsActive bool `gorm:"type:boolean;default:true" json:"is_active"`
ShowOnApp bool `gorm:"type:boolean;default:true" json:"show_on_app"`
Position int `gorm:"type:int;default:0" json:"position"`
Metadata *Metadata `gorm:"type:jsonb" json:"metadata,omitempty"`
CreatedAt time.Time `gorm:"type:timestamp;default:now()" json:"created_at"`
UpdatedAt time.Time `gorm:"type:timestamp;default:now()" json:"updated_at"`
// Relations
Rules []CampaignRule `gorm:"foreignKey:CampaignID;constraint:OnDelete:CASCADE" json:"rules,omitempty"`
}
func (Campaign) TableName() string {
return "campaigns"
}
func (c *Campaign) BeforeCreate(tx *gorm.DB) error {
if c.ID == uuid.Nil {
c.ID = uuid.New()
}
return nil
}
func (c *Campaign) BeforeUpdate(tx *gorm.DB) error {
c.UpdatedAt = time.Now()
return nil
}
type CampaignRule struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
CampaignID uuid.UUID `gorm:"type:uuid;not null" json:"campaign_id"`
RuleType RuleType `gorm:"type:varchar(50);not null" json:"rule_type"`
ConditionValue *string `gorm:"type:varchar(255)" json:"condition_value,omitempty"`
RewardType CampaignRewardType `gorm:"type:varchar(50);not null" json:"reward_type"`
RewardValue *int64 `gorm:"type:bigint" json:"reward_value,omitempty"`
RewardSubtype *RewardSubtype `gorm:"type:varchar(50)" json:"reward_subtype,omitempty"`
RewardRefID *uuid.UUID `gorm:"type:uuid" json:"reward_ref_id,omitempty"`
Metadata *Metadata `gorm:"type:jsonb" json:"metadata,omitempty"`
CreatedAt time.Time `gorm:"type:timestamp;default:now()" json:"created_at"`
UpdatedAt time.Time `gorm:"type:timestamp;default:now()" json:"updated_at"`
// Relations
Campaign Campaign `gorm:"foreignKey:CampaignID" json:"campaign,omitempty"`
}
func (CampaignRule) TableName() string {
return "campaign_rules"
}
func (cr *CampaignRule) BeforeCreate(tx *gorm.DB) error {
if cr.ID == uuid.Nil {
cr.ID = uuid.New()
}
return nil
}
func (cr *CampaignRule) BeforeUpdate(tx *gorm.DB) error {
cr.UpdatedAt = time.Now()
return nil
}
type ListCampaignsRequest struct {
Page int `form:"page" binding:"min=1"`
Limit int `form:"limit" binding:"min=1,max=100"`
Search string `form:"search"`
Type string `form:"type"`
IsActive *bool `form:"is_active"`
ShowOnApp *bool `form:"show_on_app"`
StartDate *time.Time `form:"start_date"`
EndDate *time.Time `form:"end_date"`
}
type ListCampaignRulesRequest struct {
Page int `form:"page" binding:"min=1"`
Limit int `form:"limit" binding:"min=1,max=100"`
CampaignID string `form:"campaign_id"`
RuleType string `form:"rule_type"`
RewardType string `form:"reward_type"`
}
+1
View File
@@ -35,6 +35,7 @@ type Category struct {
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"`
Order int `gorm:"default:0" json:"order"`
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"`
+41
View File
@@ -0,0 +1,41 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type ChartOfAccount 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"`
ChartOfAccountTypeID uuid.UUID `gorm:"type:uuid;not null;index" json:"chart_of_account_type_id" validate:"required"`
ParentID *uuid.UUID `gorm:"type:uuid;index" json:"parent_id"`
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
Code string `gorm:"not null;size:20" json:"code" validate:"required,min=1,max=20"`
Description *string `gorm:"type:text" json:"description"`
IsActive bool `gorm:"default:true" json:"is_active"`
IsSystem bool `gorm:"default:false" json:"is_system"`
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"`
ChartOfAccountType ChartOfAccountType `gorm:"foreignKey:ChartOfAccountTypeID" json:"chart_of_account_type,omitempty"`
Parent *ChartOfAccount `gorm:"foreignKey:ParentID" json:"parent,omitempty"`
Children []ChartOfAccount `gorm:"foreignKey:ParentID" json:"children,omitempty"`
Accounts []Account `gorm:"foreignKey:ChartOfAccountID" json:"accounts,omitempty"`
}
func (c *ChartOfAccount) BeforeCreate(tx *gorm.DB) error {
if c.ID == uuid.Nil {
c.ID = uuid.New()
}
return nil
}
func (ChartOfAccount) TableName() string {
return "chart_of_accounts"
}
@@ -0,0 +1,31 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type ChartOfAccountType struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
Name string `gorm:"not null;size:100" json:"name" validate:"required,min=1,max=100"`
Code string `gorm:"not null;size:10;unique" json:"code" validate:"required,min=1,max=10"`
Description *string `gorm:"type:text" json:"description"`
IsActive bool `gorm:"default:true" json:"is_active"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
ChartOfAccounts []ChartOfAccount `gorm:"foreignKey:ChartOfAccountTypeID" json:"chart_of_accounts,omitempty"`
}
func (c *ChartOfAccountType) BeforeCreate(tx *gorm.DB) error {
if c.ID == uuid.Nil {
c.ID = uuid.New()
}
return nil
}
func (ChartOfAccountType) TableName() string {
return "chart_of_account_types"
}
+3
View File
@@ -13,7 +13,10 @@ type Customer struct {
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"`
PhoneNumber *string `gorm:"size:20;uniqueIndex" json:"phone_number,omitempty"`
Address *string `gorm:"size:500" json:"address,omitempty"`
BirthDate *time.Time `gorm:"type:date" json:"birth_date,omitempty"`
PasswordHash *string `gorm:"size:255" json:"-"`
IsDefault bool `gorm:"default:false" json:"is_default"`
IsActive bool `gorm:"default:true" json:"is_active"`
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
+29
View File
@@ -0,0 +1,29 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type CustomerPoints struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
CustomerID uuid.UUID `gorm:"type:uuid;not null;index" json:"customer_id" validate:"required"`
Balance int64 `gorm:"not null;default:0" json:"balance" validate:"min=0"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Customer Customer `gorm:"foreignKey:CustomerID" json:"customer,omitempty"`
}
func (cp *CustomerPoints) BeforeCreate(tx *gorm.DB) error {
if cp.ID == uuid.Nil {
cp.ID = uuid.New()
}
return nil
}
func (CustomerPoints) TableName() string {
return "customer_points"
}
+38
View File
@@ -0,0 +1,38 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type TokenType string
const (
TokenTypeSpin TokenType = "SPIN"
TokenTypeRaffle TokenType = "RAFFLE"
TokenTypeMinigame TokenType = "MINIGAME"
)
type CustomerTokens struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
CustomerID uuid.UUID `gorm:"type:uuid;not null;index" json:"customer_id" validate:"required"`
TokenType TokenType `gorm:"type:varchar(50);not null" json:"token_type" validate:"required,oneof=SPIN RAFFLE MINIGAME"`
Balance int64 `gorm:"not null;default:0" json:"balance" validate:"min=0"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Customer Customer `gorm:"foreignKey:CustomerID" json:"customer,omitempty"`
}
func (ct *CustomerTokens) BeforeCreate(tx *gorm.DB) error {
if ct.ID == uuid.Nil {
ct.ID = uuid.New()
}
return nil
}
func (CustomerTokens) TableName() string {
return "customer_tokens"
}
+23
View File
@@ -18,7 +18,30 @@ func GetAllEntities() []interface{} {
&Payment{},
&Customer{},
&Table{},
&Vendor{},
&PurchaseOrder{},
&PurchaseOrderItem{},
&PurchaseOrderAttachment{},
&IngredientUnitConverter{},
// Gamification entities
&CustomerPoints{},
&CustomerTokens{},
&Tier{},
&Game{},
&GamePrize{},
&GamePlay{},
&OmsetTracker{},
&Reward{},
&Campaign{},
&CampaignRule{},
&OtpSession{},
// Analytics entities are not database tables, they are query results
&UserDevice{},
// Notification entities
&Notification{},
&NotificationReceiver{},
&NotificationDelivery{},
&ProductOutletPrice{},
}
}
+40
View File
@@ -0,0 +1,40 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type GameType string
const (
GameTypeSpin GameType = "SPIN"
GameTypeRaffle GameType = "RAFFLE"
GameTypeMinigame GameType = "MINIGAME"
)
type Game struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
Name string `gorm:"type:varchar(255);not null" json:"name" validate:"required"`
Type GameType `gorm:"type:varchar(50);not null" json:"type" validate:"required,oneof=SPIN RAFFLE MINIGAME"`
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"`
Prizes []GamePrize `gorm:"foreignKey:GameID" json:"prizes,omitempty"`
Plays []GamePlay `gorm:"foreignKey:GameID" json:"plays,omitempty"`
}
func (g *Game) BeforeCreate(tx *gorm.DB) error {
if g.ID == uuid.Nil {
g.ID = uuid.New()
}
return nil
}
func (Game) TableName() string {
return "games"
}
+33
View File
@@ -0,0 +1,33 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type GamePlay struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
GameID uuid.UUID `gorm:"type:uuid;not null;index" json:"game_id" validate:"required"`
CustomerID uuid.UUID `gorm:"type:uuid;not null;index" json:"customer_id" validate:"required"`
PrizeID *uuid.UUID `gorm:"type:uuid" json:"prize_id,omitempty"`
TokenUsed int `gorm:"default:0" json:"token_used" validate:"min=0"`
RandomSeed *string `gorm:"type:varchar(255)" json:"random_seed,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
Game Game `gorm:"foreignKey:GameID" json:"game,omitempty"`
Customer Customer `gorm:"foreignKey:CustomerID" json:"customer,omitempty"`
Prize *GamePrize `gorm:"foreignKey:PrizeID" json:"prize,omitempty"`
}
func (gp *GamePlay) BeforeCreate(tx *gorm.DB) error {
if gp.ID == uuid.Nil {
gp.ID = uuid.New()
}
return nil
}
func (GamePlay) TableName() string {
return "game_plays"
}
+38
View File
@@ -0,0 +1,38 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type GamePrize struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
GameID uuid.UUID `gorm:"type:uuid;not null;index" json:"game_id" validate:"required"`
Name string `gorm:"type:varchar(255);not null" json:"name" validate:"required"`
Weight int `gorm:"not null" json:"weight" validate:"min=1"`
Stock int `gorm:"default:0" json:"stock" validate:"min=0"`
MaxStock *int `gorm:"" json:"max_stock,omitempty"`
Threshold *int64 `gorm:"" json:"threshold,omitempty"`
FallbackPrizeID *uuid.UUID `gorm:"type:uuid" json:"fallback_prize_id,omitempty"`
Image *string `gorm:"type:varchar(500)" json:"image,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"`
Game Game `gorm:"foreignKey:GameID" json:"game,omitempty"`
FallbackPrize *GamePrize `gorm:"foreignKey:FallbackPrizeID" json:"fallback_prize,omitempty"`
Plays []GamePlay `gorm:"foreignKey:PrizeID" json:"plays,omitempty"`
}
func (gp *GamePrize) BeforeCreate(tx *gorm.DB) error {
if gp.ID == uuid.Nil {
gp.ID = uuid.New()
}
return nil
}
func (GamePrize) TableName() string {
return "game_prizes"
}
+3 -2
View File
@@ -16,8 +16,9 @@ type Ingredient struct {
Stock float64 `gorm:"type:decimal(10,2);default:0.00" json:"stock"`
IsSemiFinished bool `gorm:"default:false" json:"is_semi_finished"`
IsActive bool `gorm:"default:true" json:"is_active"`
Metadata map[string]any `gorm:"type:jsonb;default:'{}'" json:"metadata"`
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Unit *Unit `gorm:"foreignKey:UnitID" json:"unit,omitempty"`
Unit *Unit `gorm:"foreignKey:UnitID;references:ID" json:"unit,omitempty"`
Compositions []IngredientComposition `gorm:"foreignKey:ParentIngredientID;references:ID" json:"compositions,omitempty"`
}
+10 -10
View File
@@ -7,14 +7,14 @@ import (
)
type IngredientComposition struct {
ID uuid.UUID `json:"id" db:"id"`
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
OutletID *uuid.UUID `json:"outlet_id" db:"outlet_id"`
ParentIngredientID uuid.UUID `json:"parent_ingredient_id" db:"parent_ingredient_id"`
ChildIngredientID uuid.UUID `json:"child_ingredient_id" db:"child_ingredient_id"`
Quantity float64 `json:"quantity" db:"quantity"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
ParentIngredient *Ingredient `json:"parent_ingredient,omitempty"`
ChildIngredient *Ingredient `json:"child_ingredient,omitempty"`
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;index" json:"outlet_id"`
ParentIngredientID uuid.UUID `gorm:"type:uuid;not null;index" json:"parent_ingredient_id"`
ChildIngredientID uuid.UUID `gorm:"type:uuid;not null;index" json:"child_ingredient_id"`
Quantity float64 `gorm:"type:decimal(10,4);not null" json:"quantity"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
ParentIngredient *Ingredient `gorm:"foreignKey:ParentIngredientID;references:ID" json:"parent_ingredient,omitempty"`
ChildIngredient *Ingredient `gorm:"foreignKey:ChildIngredientID;references:ID" json:"child_ingredient,omitempty"`
}
@@ -0,0 +1,42 @@
package entities
import (
"time"
"github.com/google/uuid"
)
type IngredientUnitConverter 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"`
IngredientID uuid.UUID `gorm:"type:uuid;not null" json:"ingredient_id"`
FromUnitID uuid.UUID `gorm:"type:uuid;not null" json:"from_unit_id"`
ToUnitID uuid.UUID `gorm:"type:uuid;not null" json:"to_unit_id"`
ConversionFactor float64 `gorm:"type:decimal(15,6);not null" json:"conversion_factor"`
IsActive bool `gorm:"default:true" json:"is_active"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
CreatedBy uuid.UUID `gorm:"type:uuid;not null" json:"created_by"`
UpdatedBy uuid.UUID `gorm:"type:uuid;not null" json:"updated_by"`
// Relationships
Organization *Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
Ingredient *Ingredient `gorm:"foreignKey:IngredientID" json:"ingredient,omitempty"`
FromUnit *Unit `gorm:"foreignKey:FromUnitID" json:"from_unit,omitempty"`
ToUnit *Unit `gorm:"foreignKey:ToUnitID" json:"to_unit,omitempty"`
CreatedByUser *User `gorm:"foreignKey:CreatedBy" json:"created_by_user,omitempty"`
UpdatedByUser *User `gorm:"foreignKey:UpdatedBy" json:"updated_by_user,omitempty"`
}
func (IngredientUnitConverter) TableName() string {
return "ingredient_unit_converters"
}
// BeforeCreate hook to set default values
func (iuc *IngredientUnitConverter) BeforeCreate() error {
if iuc.ID == uuid.Nil {
iuc.ID = uuid.New()
}
return nil
}
+1
View File
@@ -20,6 +20,7 @@ const (
InventoryMovementTypeTransferOut InventoryMovementType = "transfer_out"
InventoryMovementTypeDamage InventoryMovementType = "damage"
InventoryMovementTypeExpiry InventoryMovementType = "expiry"
InventoryMovementTypeIngredient InventoryMovementType = "ingredient"
)
type InventoryMovementReferenceType string
+150
View File
@@ -0,0 +1,150 @@
package entities
import (
"database/sql/driver"
"encoding/json"
"errors"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type NotificationPriority string
type NotificationDeliveryStatus string
type NotificationChannel string
type NotificationProvider string
const (
NotificationPriorityLow NotificationPriority = "low"
NotificationPriorityNormal NotificationPriority = "normal"
NotificationPriorityHigh NotificationPriority = "high"
NotificationDeliveryStatusPending NotificationDeliveryStatus = "pending"
NotificationDeliveryStatusSent NotificationDeliveryStatus = "sent"
NotificationDeliveryStatusDelivered NotificationDeliveryStatus = "delivered"
NotificationDeliveryStatusFailed NotificationDeliveryStatus = "failed"
NotificationChannelPush NotificationChannel = "push"
NotificationChannelWebsocket NotificationChannel = "websocket"
NotificationChannelEmail NotificationChannel = "email"
NotificationProviderFirebase NotificationProvider = "firebase"
)
// NotificationData is a JSON-serializable map for extra notification payload.
type NotificationData map[string]interface{}
func (d NotificationData) Value() (driver.Value, error) {
if d == nil {
return nil, nil
}
return json.Marshal(d)
}
func (d *NotificationData) Scan(value interface{}) error {
if value == nil {
*d = nil
return nil
}
bytes, ok := value.([]byte)
if !ok {
return errors.New("type assertion to []byte failed")
}
return json.Unmarshal(bytes, d)
}
// Notification is the master notification record.
type Notification struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
Title string `gorm:"not null;size:255" json:"title"`
Body string `gorm:"type:text" json:"body"`
Type string `gorm:"size:100" json:"type"`
Category string `gorm:"size:100" json:"category"`
Priority NotificationPriority `gorm:"size:50;default:'normal'" json:"priority"`
ImageURL string `gorm:"size:512" json:"image_url"`
ActionURL string `gorm:"size:512" json:"action_url"`
NotifiableType string `gorm:"size:100" json:"notifiable_type"`
NotifiableID *uuid.UUID `gorm:"type:uuid" json:"notifiable_id"`
Data NotificationData `gorm:"type:jsonb" json:"data"`
ScheduledAt *time.Time `gorm:"type:timestamptz" json:"scheduled_at"`
SentAt *time.Time `gorm:"type:timestamptz" json:"sent_at"`
ExpiredAt *time.Time `gorm:"type:timestamptz" json:"expired_at"`
CreatedBy *uuid.UUID `gorm:"type:uuid" json:"created_by"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Creator *User `gorm:"foreignKey:CreatedBy" json:"creator,omitempty"`
Receivers []*NotificationReceiver `gorm:"foreignKey:NotificationID" json:"receivers,omitempty"`
}
func (n *Notification) BeforeCreate(tx *gorm.DB) error {
if n.ID == uuid.Nil {
n.ID = uuid.New()
}
return nil
}
func (Notification) TableName() string {
return "notifications"
}
// NotificationReceiver links a notification to a specific user.
type NotificationReceiver struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
NotificationID uuid.UUID `gorm:"type:uuid;not null;index" json:"notification_id"`
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"`
IsRead bool `gorm:"default:false" json:"is_read"`
ReadAt *time.Time `gorm:"type:timestamptz" json:"read_at"`
IsDeleted bool `gorm:"default:false" json:"is_deleted"`
DeletedAt *time.Time `gorm:"type:timestamptz" json:"deleted_at"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Notification *Notification `gorm:"foreignKey:NotificationID" json:"notification,omitempty"`
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
Deliveries []*NotificationDelivery `gorm:"foreignKey:NotificationReceiverID" json:"deliveries,omitempty"`
}
func (n *NotificationReceiver) BeforeCreate(tx *gorm.DB) error {
if n.ID == uuid.Nil {
n.ID = uuid.New()
}
return nil
}
func (NotificationReceiver) TableName() string {
return "notification_receivers"
}
// NotificationDelivery tracks per-device delivery attempts.
type NotificationDelivery struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
NotificationReceiverID uuid.UUID `gorm:"type:uuid;not null;index" json:"notification_receiver_id"`
UserDeviceID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_device_id"`
Channel NotificationChannel `gorm:"size:50;default:'push'" json:"channel"`
DeliveryStatus NotificationDeliveryStatus `gorm:"size:50;default:'pending'" json:"delivery_status"`
Provider NotificationProvider `gorm:"size:50" json:"provider"`
ProviderMessageID string `gorm:"size:255" json:"provider_message_id"`
SentAt *time.Time `gorm:"type:timestamptz" json:"sent_at"`
DeliveredAt *time.Time `gorm:"type:timestamptz" json:"delivered_at"`
FailedAt *time.Time `gorm:"type:timestamptz" json:"failed_at"`
FailureReason string `gorm:"type:text" json:"failure_reason"`
RetryCount int `gorm:"default:0" json:"retry_count"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
NotificationReceiver *NotificationReceiver `gorm:"foreignKey:NotificationReceiverID" json:"notification_receiver,omitempty"`
UserDevice *UserDevice `gorm:"foreignKey:UserDeviceID" json:"user_device,omitempty"`
}
func (n *NotificationDelivery) BeforeCreate(tx *gorm.DB) error {
if n.ID == uuid.Nil {
n.ID = uuid.New()
}
return nil
}
func (NotificationDelivery) TableName() string {
return "notification_deliveries"
}
+41
View File
@@ -0,0 +1,41 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type PeriodType string
const (
PeriodTypeDaily PeriodType = "DAILY"
PeriodTypeWeekly PeriodType = "WEEKLY"
PeriodTypeMonthly PeriodType = "MONTHLY"
PeriodTypeTotal PeriodType = "TOTAL"
)
type OmsetTracker struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
PeriodType PeriodType `gorm:"type:varchar(20);not null" json:"period_type" validate:"required,oneof=DAILY WEEKLY MONTHLY TOTAL"`
PeriodStart time.Time `gorm:"type:date;not null" json:"period_start" validate:"required"`
PeriodEnd time.Time `gorm:"type:date;not null" json:"period_end" validate:"required"`
Total int64 `gorm:"not null;default:0" json:"total" validate:"min=0"`
GameID *uuid.UUID `gorm:"type:uuid" json:"game_id,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Game *Game `gorm:"foreignKey:GameID" json:"game,omitempty"`
}
func (ot *OmsetTracker) BeforeCreate(tx *gorm.DB) error {
if ot.ID == uuid.Nil {
ot.ID = uuid.New()
}
return nil
}
func (OmsetTracker) TableName() string {
return "omset_tracker"
}
+2
View File
@@ -28,6 +28,7 @@ const (
const (
PaymentStatusPending PaymentStatus = "pending"
PaymentStatusPartial PaymentStatus = "partial"
PaymentStatusCompleted PaymentStatus = "completed"
PaymentStatusFailed PaymentStatus = "failed"
PaymentStatusRefunded PaymentStatus = "refunded"
@@ -49,6 +50,7 @@ type Order struct {
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"`
RemainingAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"remaining_amount"`
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"`
@@ -0,0 +1,48 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type OrderIngredientTransaction 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"`
OrderID uuid.UUID `gorm:"type:uuid;not null;index" json:"order_id" validate:"required"`
OrderItemID *uuid.UUID `gorm:"type:uuid;index" json:"order_item_id"`
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"`
IngredientID uuid.UUID `gorm:"type:uuid;not null;index" json:"ingredient_id" validate:"required"`
GrossQty float64 `gorm:"type:decimal(12,3);not null" json:"gross_qty" validate:"required,gt=0"`
NetQty float64 `gorm:"type:decimal(12,3);not null" json:"net_qty" validate:"required,gt=0"`
WasteQty float64 `gorm:"type:decimal(12,3);not null" json:"waste_qty" validate:"min=0"`
Unit string `gorm:"size:50;not null" json:"unit" validate:"required,max=50"`
TransactionDate time.Time `gorm:"not null;index" json:"transaction_date"`
CreatedBy uuid.UUID `gorm:"type:uuid;not null;index" json:"created_by" validate:"required"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
// Relations
Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
Outlet *Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`
Order Order `gorm:"foreignKey:OrderID" json:"order,omitempty"`
OrderItem *OrderItem `gorm:"foreignKey:OrderItemID" json:"order_item,omitempty"`
Product Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
ProductVariant *ProductVariant `gorm:"foreignKey:ProductVariantID" json:"product_variant,omitempty"`
Ingredient Ingredient `gorm:"foreignKey:IngredientID" json:"ingredient,omitempty"`
CreatedByUser User `gorm:"foreignKey:CreatedBy" json:"created_by_user,omitempty"`
}
func (oit *OrderIngredientTransaction) BeforeCreate(tx *gorm.DB) error {
if oit.ID == uuid.Nil {
oit.ID = uuid.New()
}
return nil
}
func (OrderIngredientTransaction) TableName() string {
return "order_ingredients_transactions"
}
+1
View File
@@ -38,6 +38,7 @@ const (
OrderItemStatusReady OrderItemStatus = "ready"
OrderItemStatusServed OrderItemStatus = "served"
OrderItemStatusCancelled OrderItemStatus = "cancelled"
OrderItemStatusPaid OrderItemStatus = "paid"
)
type OrderItem struct {
+54
View File
@@ -0,0 +1,54 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type OtpSession struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
Token string `gorm:"type:varchar(255);uniqueIndex;not null" json:"token"`
Code string `gorm:"type:varchar(10);not null" json:"code"`
PhoneNumber string `gorm:"type:varchar(20);not null;index" json:"phone_number"`
Purpose string `gorm:"type:varchar(50);not null;index" json:"purpose"`
ExpiresAt time.Time `gorm:"not null;index" json:"expires_at"`
IsUsed bool `gorm:"default:false;index" json:"is_used"`
AttemptsCount int `gorm:"default:0" json:"attempts_count"`
MaxAttempts int `gorm:"default:3" json:"max_attempts"`
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
func (o *OtpSession) BeforeCreate(tx *gorm.DB) error {
if o.ID == uuid.Nil {
o.ID = uuid.New()
}
return nil
}
func (OtpSession) TableName() string {
return "otp_sessions"
}
func (o *OtpSession) IsExpired() bool {
return time.Now().After(o.ExpiresAt)
}
func (o *OtpSession) IsMaxAttemptsReached() bool {
return o.AttemptsCount >= o.MaxAttempts
}
func (o *OtpSession) CanBeUsed() bool {
return !o.IsUsed && !o.IsExpired() && !o.IsMaxAttemptsReached()
}
func (o *OtpSession) IncrementAttempts() {
o.AttemptsCount++
}
func (o *OtpSession) MarkAsUsed() {
o.IsUsed = true
}
+8
View File
@@ -50,6 +50,13 @@ const (
PaymentTransactionStatusRefunded PaymentTransactionStatus = "refunded"
)
type SplitType string
const (
SplitTypeAmount SplitType = "AMOUNT"
SplitTypeItem SplitType = "ITEM"
)
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"`
@@ -59,6 +66,7 @@ type Payment struct {
TransactionID *string `gorm:"size:255" json:"transaction_id"`
SplitNumber int `gorm:"default:1" json:"split_number"`
SplitTotal int `gorm:"default:1" json:"split_total"`
SplitType *SplitType `gorm:"size:20" json:"split_type,omitempty"`
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"`
+1
View File
@@ -11,6 +11,7 @@ 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"`
Quantity int `gorm:"not null;default:0" json:"quantity"` // Quantity paid for this specific payment
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"`
+1 -1
View File
@@ -30,7 +30,7 @@ type Product struct {
Category Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
Unit *Unit `gorm:"foreignKey:UnitID" json:"unit,omitempty"`
ProductVariants []ProductVariant `gorm:"foreignKey:ProductID" json:"variants,omitempty"`
ProductIngredients []ProductIngredient `gorm:"foreignKey:ProductID" json:"product_ingredients,omitempty"`
ProductRecipes []ProductRecipe `gorm:"foreignKey:ProductID" json:"product_recipes,omitempty"`
Inventory []Inventory `gorm:"foreignKey:ProductID" json:"inventory,omitempty"`
OrderItems []OrderItem `gorm:"foreignKey:ProductID" json:"order_items,omitempty"`
}
+1
View File
@@ -13,6 +13,7 @@ type ProductIngredient struct {
ProductID uuid.UUID `json:"product_id" db:"product_id"`
IngredientID uuid.UUID `json:"ingredient_id" db:"ingredient_id"`
Quantity float64 `json:"quantity" db:"quantity"`
WastePercentage float64 `json:"waste_percentage" db:"waste_percentage"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
+31
View File
@@ -0,0 +1,31 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type ProductOutletPrice 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"`
OutletID uuid.UUID `gorm:"type:uuid;not null;index" json:"outlet_id"`
Price float64 `gorm:"type:decimal(10,2);not null" json:"price"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Product Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
Outlet Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`
}
func (p *ProductOutletPrice) BeforeCreate(tx *gorm.DB) error {
if p.ID == uuid.Nil {
p.ID = uuid.New()
}
return nil
}
func (ProductOutletPrice) TableName() string {
return "product_outlet_prices"
}
+37
View File
@@ -0,0 +1,37 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type ProductRecipe 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;index" json:"outlet_id"`
ProductID uuid.UUID `gorm:"type:uuid;not null;index" json:"product_id"`
VariantID *uuid.UUID `gorm:"type:uuid;index" json:"variant_id"`
IngredientID uuid.UUID `gorm:"type:uuid;not null;index" json:"ingredient_id"`
Quantity float64 `gorm:"type:decimal(12,3);not null" json:"quantity"`
WastePercentage float64 `gorm:"type:decimal(5,2);default:0" json:"waste_percentage"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
// Relations
Product *Product `gorm:"foreignKey:ProductID" json:"product,omitempty"`
ProductVariant *ProductVariant `gorm:"foreignKey:VariantID" json:"product_variant,omitempty"`
Ingredient *Ingredient `gorm:"foreignKey:IngredientID" json:"ingredient,omitempty"`
}
func (pr *ProductRecipe) BeforeCreate(tx *gorm.DB) error {
if pr.ID == uuid.Nil {
pr.ID = uuid.New()
}
return nil
}
func (ProductRecipe) TableName() string {
return "product_recipes"
}
+91
View File
@@ -0,0 +1,91 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type PurchaseOrder 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" validate:"required"`
VendorID uuid.UUID `gorm:"type:uuid;not null" json:"vendor_id" validate:"required"`
PONumber string `gorm:"not null;size:50" json:"po_number" validate:"required,min=1,max=50"`
TransactionDate time.Time `gorm:"type:date;not null" json:"transaction_date" validate:"required"`
DueDate time.Time `gorm:"type:date;not null" json:"due_date" validate:"required"`
Reference *string `gorm:"size:100" json:"reference" validate:"omitempty,max=100"`
Status string `gorm:"not null;size:20;default:'draft'" json:"status" validate:"required,oneof=draft sent approved received cancelled"`
Message *string `gorm:"type:text" json:"message" validate:"omitempty"`
TotalAmount float64 `gorm:"type:decimal(15,2);not null;default:0" json:"total_amount"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Organization *Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
Vendor *Vendor `gorm:"foreignKey:VendorID" json:"vendor,omitempty"`
Items []PurchaseOrderItem `gorm:"foreignKey:PurchaseOrderID" json:"items,omitempty"`
Attachments []PurchaseOrderAttachment `gorm:"foreignKey:PurchaseOrderID" json:"attachments,omitempty"`
}
func (po *PurchaseOrder) BeforeCreate(tx *gorm.DB) error {
if po.ID == uuid.Nil {
id := uuid.New()
po.ID = id
}
return nil
}
func (PurchaseOrder) TableName() string {
return "purchase_orders"
}
type PurchaseOrderItem struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
PurchaseOrderID uuid.UUID `gorm:"type:uuid;not null" json:"purchase_order_id" validate:"required"`
IngredientID uuid.UUID `gorm:"type:uuid;not null" json:"ingredient_id" validate:"required"`
Description *string `gorm:"type:text" json:"description" validate:"omitempty"`
Quantity float64 `gorm:"type:decimal(10,3);not null" json:"quantity" validate:"required,gt=0"`
UnitID uuid.UUID `gorm:"type:uuid;not null" json:"unit_id" validate:"required"`
Amount float64 `gorm:"type:decimal(15,2);not null" json:"amount" validate:"required,gte=0"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
PurchaseOrder *PurchaseOrder `gorm:"foreignKey:PurchaseOrderID" json:"purchase_order,omitempty"`
Ingredient *Ingredient `gorm:"foreignKey:IngredientID" json:"ingredient,omitempty"`
Unit *Unit `gorm:"foreignKey:UnitID" json:"unit,omitempty"`
}
func (poi *PurchaseOrderItem) BeforeCreate(tx *gorm.DB) error {
if poi.ID == uuid.Nil {
id := uuid.New()
poi.ID = id
}
return nil
}
func (PurchaseOrderItem) TableName() string {
return "purchase_order_items"
}
type PurchaseOrderAttachment struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
PurchaseOrderID uuid.UUID `gorm:"type:uuid;not null" json:"purchase_order_id" validate:"required"`
FileID uuid.UUID `gorm:"type:uuid;not null" json:"file_id" validate:"required"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
PurchaseOrder *PurchaseOrder `gorm:"foreignKey:PurchaseOrderID" json:"purchase_order,omitempty"`
File *File `gorm:"foreignKey:FileID" json:"file,omitempty"`
}
func (poa *PurchaseOrderAttachment) BeforeCreate(tx *gorm.DB) error {
if poa.ID == uuid.Nil {
id := uuid.New()
poa.ID = id
}
return nil
}
func (PurchaseOrderAttachment) TableName() string {
return "purchase_order_attachments"
}
+126
View File
@@ -0,0 +1,126 @@
package entities
import (
"database/sql/driver"
"encoding/json"
"fmt"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type RewardType string
const (
RewardTypeVoucher RewardType = "VOUCHER"
RewardTypePhysical RewardType = "PHYSICAL"
RewardTypeDigital RewardType = "DIGITAL"
RewardTypeBalance RewardType = "BALANCE"
)
// StringSlice is a custom type for []string that implements sql.Scanner and driver.Valuer
type StringSlice []string
// Scan implements the sql.Scanner interface for StringSlice
func (s *StringSlice) Scan(value interface{}) error {
if value == nil {
*s = StringSlice{}
return nil
}
var bytes []byte
switch v := value.(type) {
case []byte:
bytes = v
case string:
bytes = []byte(v)
default:
return fmt.Errorf("cannot scan %T into StringSlice", value)
}
return json.Unmarshal(bytes, s)
}
// Value implements the driver.Valuer interface for StringSlice
func (s StringSlice) Value() (driver.Value, error) {
if s == nil {
return nil, nil
}
return json.Marshal(s)
}
type TermsAndConditions struct {
Sections []TncSection `json:"sections"`
ExpiryDays int `json:"expiry_days"`
}
// Scan implements the sql.Scanner interface for TermsAndConditions
func (t *TermsAndConditions) Scan(value interface{}) error {
if value == nil {
*t = TermsAndConditions{}
return nil
}
var bytes []byte
switch v := value.(type) {
case []byte:
bytes = v
case string:
bytes = []byte(v)
default:
return fmt.Errorf("cannot scan %T into TermsAndConditions", value)
}
return json.Unmarshal(bytes, t)
}
// Value implements the driver.Valuer interface for TermsAndConditions
func (t TermsAndConditions) Value() (driver.Value, error) {
return json.Marshal(t)
}
type TncSection struct {
Title string `json:"title"`
Rules []string `json:"rules"`
}
type Reward struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
Name string `gorm:"type:varchar(150);not null" json:"name"`
RewardType RewardType `gorm:"type:varchar(50);not null" json:"reward_type"`
CostPoints int64 `gorm:"type:bigint;not null" json:"cost_points"`
Stock *int `gorm:"type:int" json:"stock,omitempty"`
MaxPerCustomer int `gorm:"type:int;default:1" json:"max_per_customer"`
Tnc *TermsAndConditions `gorm:"type:jsonb" json:"tnc,omitempty"`
Metadata *map[string]interface{} `gorm:"type:jsonb" json:"metadata,omitempty"`
Images *StringSlice `gorm:"type:jsonb" json:"images,omitempty"`
CreatedAt time.Time `gorm:"type:timestamp;default:now()" json:"created_at"`
UpdatedAt time.Time `gorm:"type:timestamp;default:now()" json:"updated_at"`
}
func (Reward) TableName() string {
return "rewards"
}
func (r *Reward) BeforeCreate(tx *gorm.DB) error {
if r.ID == uuid.Nil {
r.ID = uuid.New()
}
return nil
}
func (r *Reward) BeforeUpdate(tx *gorm.DB) error {
r.UpdatedAt = time.Now()
return nil
}
type ListRewardsRequest struct {
Page int `form:"page" binding:"min=1"`
Limit int `form:"limit" binding:"min=1,max=100"`
Search string `form:"search"`
RewardType string `form:"reward_type"`
MinPoints *int64 `form:"min_points"`
MaxPoints *int64 `form:"max_points"`
HasStock *bool `form:"has_stock"`
}
+5
View File
@@ -1,6 +1,7 @@
package entities
import (
"apskel-pos-be/internal/pkg/tabletoken"
"time"
"github.com/google/uuid"
@@ -12,6 +13,7 @@ type Table struct {
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"`
TableName string `gorm:"not null;size:100" json:"table_name" validate:"required"`
Token string `gorm:"uniqueIndex;not null;size:255" json:"token"`
StartTime *time.Time `gorm:"" json:"start_time"`
Status string `gorm:"default:'available';size:50" json:"status"`
OrderID *uuid.UUID `gorm:"type:uuid;index" json:"order_id"`
@@ -33,6 +35,9 @@ func (t *Table) BeforeCreate(tx *gorm.DB) error {
if t.ID == uuid.Nil {
t.ID = uuid.New()
}
if t.Token == "" {
t.Token = tabletoken.Encode(t.ID, t.OrganizationID, t.OutletID)
}
return nil
}
+28
View File
@@ -0,0 +1,28 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type Tier struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
Name string `gorm:"type:varchar(100);not null;unique" json:"name" validate:"required"`
MinPoints int64 `gorm:"not null" json:"min_points" validate:"min=0"`
Benefits Metadata `gorm:"type:jsonb;default:'{}'" json:"benefits"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
func (t *Tier) BeforeCreate(tx *gorm.DB) error {
if t.ID == uuid.Nil {
t.ID = uuid.New()
}
return nil
}
func (Tier) TableName() string {
return "tiers"
}
+5
View File
@@ -13,6 +13,11 @@ type Unit struct {
Name string `gorm:"not null;size:255" json:"name"`
Abbreviation *string `gorm:"size:50" json:"abbreviation"`
IsActive bool `gorm:"default:true" json:"is_active"`
DeletedAt *time.Time `gorm:"index" json:"deleted_at,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
}
func (Unit) TableName() string {
return "units"
}
+50
View File
@@ -0,0 +1,50 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type DeviceType string
type DevicePlatform string
const (
DeviceTypeMobile DeviceType = "mobile"
DeviceTypeTablet DeviceType = "tablet"
DeviceTypeDesktop DeviceType = "desktop"
DevicePlatformAndroid DevicePlatform = "android"
DevicePlatformIOS DevicePlatform = "ios"
DevicePlatformWeb DevicePlatform = "web"
)
type UserDevice struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id"`
DeviceID string `gorm:"not null;size:255;index" json:"device_id"`
DeviceName string `gorm:"size:255" json:"device_name"`
DeviceType DeviceType `gorm:"size:50" json:"device_type"`
Platform DevicePlatform `gorm:"size:50" json:"platform"`
FCMToken string `gorm:"size:512" json:"fcm_token"`
AppVersion string `gorm:"size:50" json:"app_version"`
OsVersion string `gorm:"size:50" json:"os_version"`
IPAddress string `gorm:"size:45" json:"ip_address"`
LastActiveAt *time.Time `gorm:"type:timestamptz" json:"last_active_at"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
}
func (u *UserDevice) BeforeCreate(tx *gorm.DB) error {
if u.ID == uuid.Nil {
u.ID = uuid.New()
}
return nil
}
func (UserDevice) TableName() string {
return "user_devices"
}
+39
View File
@@ -0,0 +1,39 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type Vendor 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" validate:"required"`
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"`
Address *string `gorm:"type:text" json:"address" validate:"omitempty"`
ContactPerson *string `gorm:"size:255" json:"contact_person" validate:"omitempty,max=255"`
TaxNumber *string `gorm:"size:50" json:"tax_number" validate:"omitempty,max=50"`
PaymentTerms *string `gorm:"size:100" json:"payment_terms" validate:"omitempty,max=100"`
Notes *string `gorm:"type:text" json:"notes" validate:"omitempty"`
IsActive bool `gorm:"not null;default:true" json:"is_active"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Organization *Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
}
func (v *Vendor) BeforeCreate(tx *gorm.DB) error {
if v.ID == uuid.Nil {
id := uuid.New()
v.ID = id
}
return nil
}
func (Vendor) TableName() string {
return "vendors"
}
+197
View File
@@ -0,0 +1,197 @@
package handler
import (
"apskel-pos-be/internal/contract"
"apskel-pos-be/internal/util"
"apskel-pos-be/internal/validator"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type AccountHandler struct {
service contract.AccountContract
validator validator.AccountValidator
}
func NewAccountHandler(service contract.AccountContract, validator validator.AccountValidator) *AccountHandler {
return &AccountHandler{
service: service,
validator: validator,
}
}
func (h *AccountHandler) CreateAccount(c *gin.Context) {
var req contract.CreateAccountRequest
if err := c.ShouldBindJSON(&req); err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
response, err := h.service.CreateAccount(c, &req)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "AccountHandler")
}
func (h *AccountHandler) GetAccountByID(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "AccountHandler")
return
}
response, err := h.service.GetAccountByID(c, id)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "AccountHandler")
}
func (h *AccountHandler) UpdateAccount(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "AccountHandler")
return
}
var req contract.UpdateAccountRequest
if err := c.ShouldBindJSON(&req); err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
response, err := h.service.UpdateAccount(c, id, &req)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "AccountHandler")
}
func (h *AccountHandler) DeleteAccount(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "AccountHandler")
return
}
err = h.service.DeleteAccount(c, id)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{"message": "Account deleted successfully"}), "AccountHandler")
}
func (h *AccountHandler) ListAccounts(c *gin.Context) {
var req contract.ListAccountsRequest
if err := c.ShouldBindQuery(&req); err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
response, total, err := h.service.ListAccounts(c, &req)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{
"data": response,
"total": total,
"page": req.Page,
"limit": req.Limit,
}), "AccountHandler")
}
func (h *AccountHandler) GetAccountsByOrganization(c *gin.Context) {
organizationIDStr := c.Param("organization_id")
organizationID, err := uuid.Parse(organizationIDStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid organization ID format"}}), "AccountHandler")
return
}
var outletID *uuid.UUID
if outletIDStr := c.Query("outlet_id"); outletIDStr != "" {
if parsedOutletID, err := uuid.Parse(outletIDStr); err == nil {
outletID = &parsedOutletID
}
}
response, err := h.service.GetAccountsByOrganization(c, organizationID, outletID)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "AccountHandler")
}
func (h *AccountHandler) GetAccountsByChartOfAccount(c *gin.Context) {
chartOfAccountIDStr := c.Param("chart_of_account_id")
chartOfAccountID, err := uuid.Parse(chartOfAccountIDStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid chart of account ID format"}}), "AccountHandler")
return
}
response, err := h.service.GetAccountsByChartOfAccount(c, chartOfAccountID)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "AccountHandler")
}
func (h *AccountHandler) UpdateAccountBalance(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "AccountHandler")
return
}
var req contract.UpdateAccountBalanceRequest
if err := c.ShouldBindJSON(&req); err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
err = h.service.UpdateAccountBalance(c, id, &req)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{"message": "Account balance updated successfully"}), "AccountHandler")
}
func (h *AccountHandler) GetAccountBalance(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "AccountHandler")
return
}
balance, err := h.service.GetAccountBalance(c, id)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{"balance": balance}), "AccountHandler")
}
+41 -4
View File
@@ -8,6 +8,7 @@ import (
"apskel-pos-be/internal/util"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type AnalyticsHandler struct {
@@ -25,6 +26,17 @@ func NewAnalyticsHandler(
}
}
func (h *AnalyticsHandler) resolveOutletID(c *gin.Context, contextOutletID uuid.UUID) *string {
if outletIDStr := c.Query("outlet_id"); outletIDStr != "" {
return &outletIDStr
}
if contextOutletID != uuid.Nil {
s := contextOutletID.String()
return &s
}
return nil
}
func (h *AnalyticsHandler) GetPaymentMethodAnalytics(c *gin.Context) {
ctx := c.Request.Context()
contextInfo := appcontext.FromGinContext(ctx)
@@ -36,7 +48,7 @@ func (h *AnalyticsHandler) GetPaymentMethodAnalytics(c *gin.Context) {
}
req.OrganizationID = contextInfo.OrganizationID
req.OutletID = &contextInfo.OutletID
req.OutletID = h.resolveOutletID(c, contextInfo.OutletID)
modelReq := transformer.PaymentMethodAnalyticsContractToModel(&req)
response, err := h.analyticsService.GetPaymentMethodAnalytics(ctx, modelReq)
@@ -60,7 +72,7 @@ func (h *AnalyticsHandler) GetSalesAnalytics(c *gin.Context) {
}
req.OrganizationID = contextInfo.OrganizationID
req.OutletID = &contextInfo.OutletID
req.OutletID = h.resolveOutletID(c, contextInfo.OutletID)
modelReq := transformer.SalesAnalyticsContractToModel(&req)
response, err := h.analyticsService.GetSalesAnalytics(ctx, modelReq)
@@ -84,7 +96,7 @@ func (h *AnalyticsHandler) GetProductAnalytics(c *gin.Context) {
}
req.OrganizationID = contextInfo.OrganizationID
req.OutletID = &contextInfo.OutletID
req.OutletID = h.resolveOutletID(c, contextInfo.OutletID)
modelReq := transformer.ProductAnalyticsContractToModel(&req)
response, err := h.analyticsService.GetProductAnalytics(ctx, modelReq)
@@ -97,6 +109,30 @@ func (h *AnalyticsHandler) GetProductAnalytics(c *gin.Context) {
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "AnalyticsHandler::GetProductAnalytics")
}
func (h *AnalyticsHandler) GetProductAnalyticsPerCategory(c *gin.Context) {
ctx := c.Request.Context()
contextInfo := appcontext.FromGinContext(ctx)
var req contract.ProductAnalyticsPerCategoryRequest
if err := c.ShouldBindQuery(&req); err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "AnalyticsHandler::GetProductAnalyticsPerCategory", err.Error())}), "AnalyticsHandler::GetProductAnalyticsPerCategory")
return
}
req.OrganizationID = contextInfo.OrganizationID
req.OutletID = h.resolveOutletID(c, contextInfo.OutletID)
modelReq := transformer.ProductAnalyticsPerCategoryContractToModel(&req)
response, err := h.analyticsService.GetProductAnalyticsPerCategory(ctx, modelReq)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "AnalyticsHandler::GetProductAnalyticsPerCategory", err.Error())}), "AnalyticsHandler::GetProductAnalyticsPerCategory")
return
}
contractResp := transformer.ProductAnalyticsPerCategoryModelToContract(response)
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "AnalyticsHandler::GetProductAnalyticsPerCategory")
}
func (h *AnalyticsHandler) GetDashboardAnalytics(c *gin.Context) {
ctx := c.Request.Context()
contextInfo := appcontext.FromGinContext(ctx)
@@ -108,7 +144,7 @@ func (h *AnalyticsHandler) GetDashboardAnalytics(c *gin.Context) {
}
req.OrganizationID = contextInfo.OrganizationID
req.OutletID = &contextInfo.OutletID
req.OutletID = h.resolveOutletID(c, contextInfo.OutletID)
modelReq := transformer.DashboardAnalyticsContractToModel(&req)
response, err := h.analyticsService.GetDashboardAnalytics(ctx, modelReq)
@@ -132,6 +168,7 @@ func (h *AnalyticsHandler) GetProfitLossAnalytics(c *gin.Context) {
}
req.OrganizationID = contextInfo.OrganizationID
req.OutletID = h.resolveOutletID(c, contextInfo.OutletID)
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")
+307
View File
@@ -0,0 +1,307 @@
package handler
import (
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"apskel-pos-be/internal/logger"
"apskel-pos-be/internal/service"
"apskel-pos-be/internal/util"
"apskel-pos-be/internal/validator"
"github.com/gin-gonic/gin"
)
type CampaignHandler struct {
campaignService service.CampaignService
campaignValidator validator.CampaignValidator
}
func NewCampaignHandler(campaignService service.CampaignService, campaignValidator validator.CampaignValidator) *CampaignHandler {
return &CampaignHandler{
campaignService: campaignService,
campaignValidator: campaignValidator,
}
}
func (h *CampaignHandler) CreateCampaign(c *gin.Context) {
ctx := c.Request.Context()
var req contract.CreateCampaignRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::CreateCampaign -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::CreateCampaign")
return
}
validationError, validationErrorCode := h.campaignValidator.ValidateCreateCampaignRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("CampaignHandler::CreateCampaign -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::CreateCampaign")
return
}
response, err := h.campaignService.CreateCampaign(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::CreateCampaign -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignEntity, err.Error())}), "CampaignHandler::CreateCampaign")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::CreateCampaign")
}
func (h *CampaignHandler) GetCampaign(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
if idStr == "" {
logger.FromContext(c.Request.Context()).Error("CampaignHandler::GetCampaign -> missing ID parameter")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.CampaignEntity, "ID parameter is required")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::GetCampaign")
return
}
response, err := h.campaignService.GetCampaign(ctx, idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::GetCampaign -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignEntity, err.Error())}), "CampaignHandler::GetCampaign")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::GetCampaign")
}
func (h *CampaignHandler) ListCampaigns(c *gin.Context) {
ctx := c.Request.Context()
var req contract.ListCampaignsRequest
if err := c.ShouldBindQuery(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::ListCampaigns -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::ListCampaigns")
return
}
validationError, validationErrorCode := h.campaignValidator.ValidateListCampaignsRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("CampaignHandler::ListCampaigns -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::ListCampaigns")
return
}
response, err := h.campaignService.ListCampaigns(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::ListCampaigns -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignEntity, err.Error())}), "CampaignHandler::ListCampaigns")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::ListCampaigns")
}
func (h *CampaignHandler) UpdateCampaign(c *gin.Context) {
ctx := c.Request.Context()
var req contract.UpdateCampaignRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::UpdateCampaign -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::UpdateCampaign")
return
}
validationError, validationErrorCode := h.campaignValidator.ValidateUpdateCampaignRequest(&req)
if validationError != nil {
logger.FromContext(c.Request.Context()).WithError(validationError).Error("CampaignHandler::UpdateCampaign -> request validation failed")
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::UpdateCampaign")
return
}
response, err := h.campaignService.UpdateCampaign(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::UpdateCampaign -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignEntity, err.Error())}), "CampaignHandler::UpdateCampaign")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::UpdateCampaign")
}
func (h *CampaignHandler) DeleteCampaign(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
if idStr == "" {
logger.FromContext(c.Request.Context()).Error("CampaignHandler::DeleteCampaign -> missing ID parameter")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.CampaignEntity, "ID parameter is required")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::DeleteCampaign")
return
}
err := h.campaignService.DeleteCampaign(ctx, idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::DeleteCampaign -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignEntity, err.Error())}), "CampaignHandler::DeleteCampaign")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse("Campaign deleted successfully"), "CampaignHandler::DeleteCampaign")
}
func (h *CampaignHandler) GetActiveCampaigns(c *gin.Context) {
ctx := c.Request.Context()
response, err := h.campaignService.GetActiveCampaigns(ctx)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::GetActiveCampaigns -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignEntity, err.Error())}), "CampaignHandler::GetActiveCampaigns")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::GetActiveCampaigns")
}
func (h *CampaignHandler) GetCampaignsForApp(c *gin.Context) {
ctx := c.Request.Context()
response, err := h.campaignService.GetCampaignsForApp(ctx)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::GetCampaignsForApp -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignEntity, err.Error())}), "CampaignHandler::GetCampaignsForApp")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::GetCampaignsForApp")
}
// Campaign Rules Handlers
func (h *CampaignHandler) CreateCampaignRule(c *gin.Context) {
ctx := c.Request.Context()
var req contract.CreateCampaignRuleRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::CreateCampaignRule -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::CreateCampaignRule")
return
}
response, err := h.campaignService.CreateCampaignRule(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::CreateCampaignRule -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignRuleEntity, err.Error())}), "CampaignHandler::CreateCampaignRule")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::CreateCampaignRule")
}
func (h *CampaignHandler) GetCampaignRule(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
if idStr == "" {
logger.FromContext(c.Request.Context()).Error("CampaignHandler::GetCampaignRule -> missing ID parameter")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.CampaignRuleEntity, "ID parameter is required")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::GetCampaignRule")
return
}
response, err := h.campaignService.GetCampaignRule(ctx, idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::GetCampaignRule -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignRuleEntity, err.Error())}), "CampaignHandler::GetCampaignRule")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::GetCampaignRule")
}
func (h *CampaignHandler) ListCampaignRules(c *gin.Context) {
ctx := c.Request.Context()
var req contract.ListCampaignRulesRequest
if err := c.ShouldBindQuery(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::ListCampaignRules -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::ListCampaignRules")
return
}
response, err := h.campaignService.ListCampaignRules(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::ListCampaignRules -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignRuleEntity, err.Error())}), "CampaignHandler::ListCampaignRules")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::ListCampaignRules")
}
func (h *CampaignHandler) UpdateCampaignRule(c *gin.Context) {
ctx := c.Request.Context()
var req contract.UpdateCampaignRuleRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::UpdateCampaignRule -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::UpdateCampaignRule")
return
}
response, err := h.campaignService.UpdateCampaignRule(ctx, &req)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::UpdateCampaignRule -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignRuleEntity, err.Error())}), "CampaignHandler::UpdateCampaignRule")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::UpdateCampaignRule")
}
func (h *CampaignHandler) DeleteCampaignRule(c *gin.Context) {
ctx := c.Request.Context()
idStr := c.Param("id")
if idStr == "" {
logger.FromContext(c.Request.Context()).Error("CampaignHandler::DeleteCampaignRule -> missing ID parameter")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.CampaignRuleEntity, "ID parameter is required")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::DeleteCampaignRule")
return
}
err := h.campaignService.DeleteCampaignRule(ctx, idStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::DeleteCampaignRule -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignRuleEntity, err.Error())}), "CampaignHandler::DeleteCampaignRule")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse("Campaign rule deleted successfully"), "CampaignHandler::DeleteCampaignRule")
}
func (h *CampaignHandler) GetCampaignRulesByCampaignID(c *gin.Context) {
ctx := c.Request.Context()
campaignIDStr := c.Param("campaign_id")
if campaignIDStr == "" {
logger.FromContext(c.Request.Context()).Error("CampaignHandler::GetCampaignRulesByCampaignID -> missing campaign_id parameter")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.CampaignRuleEntity, "campaign_id parameter is required")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "CampaignHandler::GetCampaignRulesByCampaignID")
return
}
response, err := h.campaignService.GetCampaignRulesByCampaignID(ctx, campaignIDStr)
if err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CampaignHandler::GetCampaignRulesByCampaignID -> service call failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError(constants.InternalServerErrorCode, constants.CampaignRuleEntity, err.Error())}), "CampaignHandler::GetCampaignRulesByCampaignID")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "CampaignHandler::GetCampaignRulesByCampaignID")
}
+5
View File
@@ -1,6 +1,7 @@
package handler
import (
"fmt"
"strconv"
"apskel-pos-be/internal/appcontext"
@@ -35,6 +36,7 @@ func (h *CategoryHandler) CreateCategory(c *gin.Context) {
contextInfo := appcontext.FromGinContext(ctx)
var req contract.CreateCategoryRequest
fmt.Printf("CategoryHandler::CreateCategory -> Request: %+v\n", req)
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c.Request.Context()).WithError(err).Error("CategoryHandler::CreateCategory -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
@@ -71,6 +73,7 @@ func (h *CategoryHandler) UpdateCategory(c *gin.Context) {
}
var req contract.UpdateCategoryRequest
fmt.Printf("CategoryHandler::UpdateCategory -> Request: %+v\n", req)
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(ctx).WithError(err).Error("CategoryHandler::UpdateCategory -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "Invalid request body")
@@ -138,10 +141,12 @@ func (h *CategoryHandler) GetCategory(c *gin.Context) {
func (h *CategoryHandler) ListCategories(c *gin.Context) {
ctx := c.Request.Context()
contextInfo := appcontext.FromGinContext(ctx)
req := &contract.ListCategoriesRequest{
Page: 1,
Limit: 10,
OrganizationID: &contextInfo.OrganizationID,
}
// Parse query parameters
@@ -0,0 +1,171 @@
package handler
import (
"apskel-pos-be/internal/contract"
"apskel-pos-be/internal/util"
"apskel-pos-be/internal/validator"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type ChartOfAccountHandler struct {
service contract.ChartOfAccountContract
validator validator.ChartOfAccountValidator
}
func NewChartOfAccountHandler(service contract.ChartOfAccountContract, validator validator.ChartOfAccountValidator) *ChartOfAccountHandler {
return &ChartOfAccountHandler{
service: service,
validator: validator,
}
}
func (h *ChartOfAccountHandler) CreateChartOfAccount(c *gin.Context) {
var req contract.CreateChartOfAccountRequest
if err := c.ShouldBindJSON(&req); err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
return
}
response, err := h.service.CreateChartOfAccount(c, &req)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountHandler")
}
func (h *ChartOfAccountHandler) GetChartOfAccountByID(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "ChartOfAccountHandler")
return
}
response, err := h.service.GetChartOfAccountByID(c, id)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountHandler")
}
func (h *ChartOfAccountHandler) UpdateChartOfAccount(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "ChartOfAccountHandler")
return
}
var req contract.UpdateChartOfAccountRequest
if err := c.ShouldBindJSON(&req); err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
return
}
response, err := h.service.UpdateChartOfAccount(c, id, &req)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountHandler")
}
func (h *ChartOfAccountHandler) DeleteChartOfAccount(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "ChartOfAccountHandler")
return
}
err = h.service.DeleteChartOfAccount(c, id)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{"message": "Chart of account deleted successfully"}), "ChartOfAccountHandler")
}
func (h *ChartOfAccountHandler) ListChartOfAccounts(c *gin.Context) {
var req contract.ListChartOfAccountsRequest
if err := c.ShouldBindQuery(&req); err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
return
}
response, total, err := h.service.ListChartOfAccounts(c, &req)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{
"data": response,
"total": total,
"page": req.Page,
"limit": req.Limit,
}), "ChartOfAccountHandler")
}
func (h *ChartOfAccountHandler) GetChartOfAccountsByOrganization(c *gin.Context) {
organizationIDStr := c.Param("organization_id")
organizationID, err := uuid.Parse(organizationIDStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid organization ID format"}}), "ChartOfAccountHandler")
return
}
var outletID *uuid.UUID
if outletIDStr := c.Query("outlet_id"); outletIDStr != "" {
if parsedOutletID, err := uuid.Parse(outletIDStr); err == nil {
outletID = &parsedOutletID
}
}
response, err := h.service.GetChartOfAccountsByOrganization(c, organizationID, outletID)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountHandler")
}
func (h *ChartOfAccountHandler) GetChartOfAccountsByType(c *gin.Context) {
organizationIDStr := c.Param("organization_id")
organizationID, err := uuid.Parse(organizationIDStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid organization ID format"}}), "ChartOfAccountHandler")
return
}
typeIDStr := c.Param("type_id")
typeID, err := uuid.Parse(typeIDStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid type ID format"}}), "ChartOfAccountHandler")
return
}
var outletID *uuid.UUID
if outletIDStr := c.Query("outlet_id"); outletIDStr != "" {
if parsedOutletID, err := uuid.Parse(outletIDStr); err == nil {
outletID = &parsedOutletID
}
}
response, err := h.service.GetChartOfAccountsByType(c, organizationID, typeID, outletID)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountHandler")
}
@@ -0,0 +1,124 @@
package handler
import (
"strconv"
"apskel-pos-be/internal/contract"
"apskel-pos-be/internal/util"
"apskel-pos-be/internal/validator"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type ChartOfAccountTypeHandler struct {
service contract.ChartOfAccountTypeContract
validator validator.ChartOfAccountTypeValidator
}
func NewChartOfAccountTypeHandler(service contract.ChartOfAccountTypeContract, validator validator.ChartOfAccountTypeValidator) *ChartOfAccountTypeHandler {
return &ChartOfAccountTypeHandler{
service: service,
validator: validator,
}
}
func (h *ChartOfAccountTypeHandler) CreateChartOfAccountType(c *gin.Context) {
var req contract.CreateChartOfAccountTypeRequest
if err := c.ShouldBindJSON(&req); err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
return
}
response, err := h.service.CreateChartOfAccountType(c, &req)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountTypeHandler")
}
func (h *ChartOfAccountTypeHandler) GetChartOfAccountTypeByID(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "ChartOfAccountTypeHandler")
return
}
response, err := h.service.GetChartOfAccountTypeByID(c, id)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountTypeHandler")
}
func (h *ChartOfAccountTypeHandler) UpdateChartOfAccountType(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "ChartOfAccountTypeHandler")
return
}
var req contract.UpdateChartOfAccountTypeRequest
if err := c.ShouldBindJSON(&req); err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
return
}
response, err := h.service.UpdateChartOfAccountType(c, id, &req)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountTypeHandler")
}
func (h *ChartOfAccountTypeHandler) DeleteChartOfAccountType(c *gin.Context) {
idStr := c.Param("id")
id, err := uuid.Parse(idStr)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "ChartOfAccountTypeHandler")
return
}
err = h.service.DeleteChartOfAccountType(c, id)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{"message": "Chart of account type deleted successfully"}), "ChartOfAccountTypeHandler")
}
func (h *ChartOfAccountTypeHandler) ListChartOfAccountTypes(c *gin.Context) {
// Parse query parameters
filters := make(map[string]interface{})
if isActive := c.Query("is_active"); isActive != "" {
if isActiveBool, err := strconv.ParseBool(isActive); err == nil {
filters["is_active"] = isActiveBool
}
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
response, total, err := h.service.ListChartOfAccountTypes(c, filters, page, limit)
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
return
}
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{
"data": response,
"total": total,
"page": page,
"limit": limit,
}), "ChartOfAccountTypeHandler")
}

Some files were not shown because too many files have changed in this diff Show More