commit 9c39c8cbd7d4668971bd52c4cdf9985d12b6ff9e Author: Chever John Date: Sun Jun 14 11:33:31 2026 +0800 init: iloom WMS monorepo with K8s deployment manifests Go workspace (go.work) with 5 microservices + shared library: - gateway (8080), auth-service (8081), purchaser-service (8082) - textile-service (8083), washing-service (8084) - shared: proto definitions, common packages Infrastructure: docker-compose for local dev, K8s manifests for K3s cluster deployment (mysql/redis/etcd + traefik ingress). Frontend (iloom-flatten) added as git submodule. Co-Authored-By: Claude Opus 4.6 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f77b5b4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.git +.github +.claude +*.md +*.swp +*~ +.env +.env.* +!.env.example +iloom-flatten/node_modules +iloom-flatten/dist +iloom-flatten/.cache +**/*_test.go +**/*.test.ts +**/*.test.tsx diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4dc9e31 --- /dev/null +++ b/.env.example @@ -0,0 +1,28 @@ +# iloom 微服务环境变量配置 + +# MySQL (共享 muyu_wms 数据库) +DB_DSN=root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4 + +# JWT (复用 muyu 认证) +JWT_SECRET=muyu-wms-jwt-secret-key-2026 +JWT_REFRESH_SECRET=muyu-wms-jwt-refresh-2026 + +# muyu gRPC 地址 +MUYU_SYSTEM_RPC_ADDR=system-rpc:9001 +MUYU_INVENTORY_RPC_ADDR=inventory-rpc:9002 + +# 服务间调用密钥 +INTERNAL_SERVICE_KEY=internal-service-shared-secret + +# 服务端口 +GATEWAY_PORT=8080 +AUTH_SERVICE_PORT=8081 +PURCHASER_SERVICE_PORT=8082 +TEXTILE_SERVICE_PORT=8083 +WASHING_SERVICE_PORT=8084 + +# 前端 +FRONTEND_URL=http://localhost:3015 + +# CORS +ALLOW_ORIGINS=http://localhost:3015,http://localhost:3000 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d30ca7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +# ======================== +# Go +# ======================== +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +*.prof +coverage.out +coverage.html +bin/ +vendor/ + +# ======================== +# Node / Frontend +# ======================== +node_modules/ +dist/ +build/ +.cache/ +coverage/ +*.tsbuildinfo +.eslintcache + +# ======================== +# Frontend build artifacts +# ======================== +.microcompact/ +.meoo/ +outputs/ +test-results/ +playwright-report/ + +# ======================== +# Environment & Secrets +# ======================== +.env +.env.local +.env.*.local +!.env.example +!.env.production + +# K8s secrets (real values) +k8s/01-secrets.yaml + +# ======================== +# IDE & Editor +# ======================== +.vscode/ +.idea/ +*.swp +*.swo +*~ +.project +.classpath + +# ======================== +# OS +# ======================== +.DS_Store +Thumbs.db +*.log +*.tmp +*.bak + +# ======================== +# Docker +# ======================== +docker-compose.override.yml + +# ======================== +# Planning / task tracking +# ======================== +.todo/ +.plan/ +.claude/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a688985 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "iloom-flatten"] + path = iloom-flatten + url = https://github.com/kihaneseifu/iloom-flatten.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2a8177f --- /dev/null +++ b/Makefile @@ -0,0 +1,74 @@ +.PHONY: all build run stop test lint migrate-up clean help + +SERVICES = gateway auth-service purchaser-service textile-service washing-service + +help: ## 显示帮助信息 + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +all: build ## 构建所有服务 + +build: ## 构建所有服务二进制 + @for svc in $(SERVICES); do \ + echo "==> Building $$svc..."; \ + cd $$svc && go build -o bin/$$svc ./cmd/ && cd ..; \ + done + @echo "==> All services built." + +run: ## 启动所有服务 (Docker Compose) + docker-compose up --build -d + +run-dev: ## 启动开发环境 (前台,含日志) + docker-compose up --build + +stop: ## 停止所有服务 + docker-compose down + +restart: stop run ## 重启所有服务 + +logs: ## 查看所有服务日志 + docker-compose logs -f + +logs-%: ## 查看指定服务日志 (如: make logs-gateway) + docker-compose logs -f $* + +test: ## 运行所有服务的测试 + @for svc in $(SERVICES); do \ + echo "==> Testing $$svc..."; \ + cd $$svc && go test ./... -v && cd ..; \ + done + +lint: ## 代码检查 + @for svc in $(SERVICES); do \ + echo "==> Linting $$svc..."; \ + cd $$svc && go vet ./... && cd ..; \ + done + +tidy: ## 整理所有模块依赖 + @for svc in shared $(SERVICES); do \ + echo "==> Tidying $$svc..."; \ + cd $$svc && go mod tidy && cd ..; \ + done + +migrate-up: ## 执行数据库迁移 + @echo "==> Running database migrations..." + @PGPASSWORD=$${DB_PASSWORD:-iloom_secret_2024} psql -h localhost -U $${POSTGRES_USER:-iloom} -d $${POSTGRES_DB:-iloom} -f scripts/init-schemas.sql + @echo "==> Migrations complete." + +clean: ## 清理构建产物 + @for svc in $(SERVICES); do \ + rm -rf $$svc/bin; \ + done + @echo "==> Cleaned." + +dev-%: ## 单独运行指定服务 (如: make dev-gateway) + cd $* && go run ./cmd/ + +docker-build-%: ## 单独构建指定服务镜像 (如: make docker-build-gateway) + docker-compose build $* + +health: ## 检查所有服务健康状态 + @echo "Gateway: $$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/health)" + @echo "Auth: $$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8081/health)" + @echo "Purchaser: $$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8082/health)" + @echo "Textile: $$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8083/health)" + @echo "Washing: $$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8084/health)" diff --git a/auth-service/Dockerfile b/auth-service/Dockerfile new file mode 100644 index 0000000..2a30e08 --- /dev/null +++ b/auth-service/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.24-alpine AS builder +WORKDIR /app +COPY shared/ ./shared/ +COPY auth-service/ ./auth-service/ +WORKDIR /app/auth-service +RUN go build -o /auth-service ./cmd/ + +FROM alpine:3.20 +RUN apk add --no-cache wget +COPY --from=builder /auth-service /auth-service +EXPOSE 8081 +CMD ["/auth-service"] diff --git a/auth-service/cmd/main.go b/auth-service/cmd/main.go new file mode 100644 index 0000000..3cf9a8c --- /dev/null +++ b/auth-service/cmd/main.go @@ -0,0 +1,93 @@ +package main + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/auth-service/internal/config" + "github.com/muyuqingfeng/iloom/auth-service/internal/handler" + "github.com/muyuqingfeng/iloom/auth-service/internal/repository" + "github.com/muyuqingfeng/iloom/auth-service/internal/service" + "github.com/muyuqingfeng/iloom/shared/pkg/auth" + "github.com/muyuqingfeng/iloom/shared/pkg/database" + sgrpc "github.com/muyuqingfeng/iloom/shared/pkg/grpc" + sharedMW "github.com/muyuqingfeng/iloom/shared/pkg/middleware" +) + +func main() { + cfg := config.Load() + + db, err := database.NewDBFromDSN(cfg.DBDSN) + if err != nil { + log.Fatalf("connect database: %v", err) + } + defer db.Close() + + sysClient, err := sgrpc.NewSystemClient(cfg.SystemRPCAddr) + if err != nil { + log.Fatalf("connect system-rpc: %v", err) + } + defer sysClient.Close() + + jwtMgr := auth.NewJWTManager(cfg.JWTSecret, cfg.JWTRefreshSecret) + + profileRepo := repository.NewProfileRepo(db) + companyRepo := repository.NewCompanyRepo(db) + memberRepo := repository.NewMemberRepo(db) + notifRepo := repository.NewNotificationRepo(db) + shareLinkRepo := repository.NewShareLinkRepo(db) + auditLogRepo := repository.NewAuditLogRepo(db) + + authSvc := service.NewAuthService(profileRepo, companyRepo, memberRepo, sysClient, jwtMgr) + companySvc := service.NewCompanyService(companyRepo) + memberSvc := service.NewMemberService(memberRepo, sysClient) + notifSvc := service.NewNotificationService(notifRepo) + + authH := handler.NewAuthHandler(authSvc) + companyH := handler.NewCompanyHandler(companySvc) + memberH := handler.NewMemberHandler(memberSvc) + notifH := handler.NewNotificationHandler(notifSvc) + commonH := handler.NewCommonHandler(shareLinkRepo, auditLogRepo) + + r := gin.New() + r.Use( + sharedMW.RequestID(), + sharedMW.Logger(), + sharedMW.Recovery(), + ) + + r.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "ok"}) + }) + + authGroup := r.Group("/api/v1/auth") + { + authGroup.POST("/register", authH.Register) + authGroup.POST("/login", authH.Login) + authGroup.POST("/logout", authH.Logout) + authGroup.POST("/refresh", authH.Refresh) + authGroup.GET("/me", authH.Me) + } + + commonGroup := r.Group("/api/v1/common") + { + commonGroup.GET("/companies", companyH.ListRelationships) + commonGroup.GET("/companies/:id", companyH.GetCompany) + commonGroup.GET("/members", memberH.List) + commonGroup.POST("/members", memberH.Create) + commonGroup.DELETE("/members/:id", memberH.Delete) + commonGroup.GET("/notifications", notifH.List) + commonGroup.PATCH("/notifications/:id", notifH.MarkRead) + commonGroup.POST("/share-links", commonH.CreateShareLink) + commonGroup.GET("/share-links/:code", commonH.GetShareLink) + commonGroup.POST("/feedback", commonH.Feedback) + commonGroup.POST("/crash-reports", commonH.CrashReport) + } + + addr := ":" + cfg.Port + log.Printf("auth-service listening on %s", addr) + if err := r.Run(addr); err != nil { + log.Fatalf("server error: %v", err) + } +} diff --git a/auth-service/go.mod b/auth-service/go.mod new file mode 100644 index 0000000..5f84f04 --- /dev/null +++ b/auth-service/go.mod @@ -0,0 +1,48 @@ +module github.com/muyuqingfeng/iloom/auth-service + +go 1.23 + +toolchain go1.24.4 + +require github.com/muyuqingfeng/iloom/shared v0.0.0 + +replace github.com/muyuqingfeng/iloom/shared => ../shared + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/google/uuid v1.6.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + 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.2.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect + google.golang.org/grpc v1.72.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/auth-service/go.sum b/auth-service/go.sum new file mode 100644 index 0000000..bc9a3ab --- /dev/null +++ b/auth-service/go.sum @@ -0,0 +1,117 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +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= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +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= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +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/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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/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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +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/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= +google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/auth-service/internal/config/config.go b/auth-service/internal/config/config.go new file mode 100644 index 0000000..2c4ec80 --- /dev/null +++ b/auth-service/internal/config/config.go @@ -0,0 +1,28 @@ +package config + +import "os" + +type Config struct { + Port string + DBDSN string + JWTSecret string + JWTRefreshSecret string + SystemRPCAddr string +} + +func Load() *Config { + return &Config{ + Port: getEnv("PORT", "8081"), + DBDSN: getEnv("DB_DSN", "root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?parseTime=true&charset=utf8mb4"), + JWTSecret: getEnv("JWT_SECRET", "muyu-wms-jwt-secret-key-2026"), + JWTRefreshSecret: getEnv("JWT_REFRESH_SECRET", "muyu-wms-jwt-refresh-2026"), + SystemRPCAddr: getEnv("MUYU_SYSTEM_RPC_ADDR", "127.0.0.1:9001"), + } +} + +func getEnv(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} diff --git a/auth-service/internal/handler/auth.go b/auth-service/internal/handler/auth.go new file mode 100644 index 0000000..21fc6e4 --- /dev/null +++ b/auth-service/internal/handler/auth.go @@ -0,0 +1,116 @@ +package handler + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/auth-service/internal/model" + "github.com/muyuqingfeng/iloom/auth-service/internal/service" + "github.com/muyuqingfeng/iloom/shared/pkg/response" +) + +type AuthHandler struct { + svc *service.AuthService +} + +func NewAuthHandler(svc *service.AuthService) *AuthHandler { + return &AuthHandler{svc: svc} +} + +type RegisterRequest struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required,min=6"` + CompanyName string `json:"company_name" binding:"required"` + Role string `json:"role" binding:"required,oneof=purchaser textile washing"` +} + +type LoginRequest struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` +} + +type LoginResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + User *model.Profile `json:"user"` + Company *model.Company `json:"company"` +} + +type RefreshRequest struct { + RefreshToken string `json:"refresh_token" binding:"required"` +} + +func (h *AuthHandler) Register(c *gin.Context) { + var req RegisterRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + svcReq := service.RegisterRequest{ + Username: req.Username, + Password: req.Password, + CompanyName: req.CompanyName, + Role: req.Role, + } + + result, err := h.svc.Register(c.Request.Context(), svcReq) + if err != nil { + response.Error(c, http.StatusConflict, response.ErrCodeBadRequest, err.Error()) + return + } + + response.OK(c, result) +} + +func (h *AuthHandler) Login(c *gin.Context) { + var req LoginRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + result, err := h.svc.Login(c.Request.Context(), req.Username, req.Password) + if err != nil { + response.Unauthorized(c, err.Error()) + return + } + + response.OK(c, result) +} + +func (h *AuthHandler) Logout(c *gin.Context) { + response.OK(c, gin.H{"message": "logged out"}) +} + +func (h *AuthHandler) Refresh(c *gin.Context) { + var req RefreshRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + accessToken, err := h.svc.RefreshToken(c.Request.Context(), req.RefreshToken) + if err != nil { + response.Unauthorized(c, err.Error()) + return + } + + response.OK(c, gin.H{"access_token": accessToken}) +} + +func (h *AuthHandler) Me(c *gin.Context) { + userID := c.GetHeader("X-User-ID") + if userID == "" { + response.Unauthorized(c, "user id not found") + return + } + + result, err := h.svc.GetMe(c.Request.Context(), userID) + if err != nil { + response.NotFound(c, err.Error()) + return + } + + response.OK(c, result) +} diff --git a/auth-service/internal/handler/common.go b/auth-service/internal/handler/common.go new file mode 100644 index 0000000..343c4f4 --- /dev/null +++ b/auth-service/internal/handler/common.go @@ -0,0 +1,134 @@ +package handler + +import ( + "time" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/auth-service/internal/repository" + "github.com/muyuqingfeng/iloom/shared/pkg/response" +) + +type CommonHandler struct { + shareLinkRepo *repository.ShareLinkRepo + auditLogRepo *repository.AuditLogRepo +} + +func NewCommonHandler(slRepo *repository.ShareLinkRepo, alRepo *repository.AuditLogRepo) *CommonHandler { + return &CommonHandler{shareLinkRepo: slRepo, auditLogRepo: alRepo} +} + +type createShareLinkReq struct { + ResourceType string `json:"resource_type" binding:"required"` + ResourceID string `json:"resource_id" binding:"required"` + ExpiresIn int `json:"expires_in"` // seconds, 0 = no expiry +} + +func (h *CommonHandler) CreateShareLink(c *gin.Context) { + var req createShareLinkReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "invalid request: "+err.Error()) + return + } + + userID := c.GetHeader("X-User-ID") + companyID := c.GetHeader("X-Company-ID") + + var expiresAt *time.Time + if req.ExpiresIn > 0 { + t := time.Now().Add(time.Duration(req.ExpiresIn) * time.Second) + expiresAt = &t + } + + link, err := h.shareLinkRepo.Create(c.Request.Context(), companyID, userID, req.ResourceType, req.ResourceID, expiresAt) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, link) +} + +func (h *CommonHandler) GetShareLink(c *gin.Context) { + code := c.Param("code") + + link, err := h.shareLinkRepo.GetByCode(c.Request.Context(), code) + if err != nil { + response.InternalError(c, err.Error()) + return + } + if link == nil { + response.NotFound(c, "share link not found") + return + } + + // check expiry + if link.ExpiresAt != nil && link.ExpiresAt.Before(time.Now()) { + response.BadRequest(c, "share link expired") + return + } + + response.OK(c, link) +} + +type feedbackReq struct { + Content string `json:"content" binding:"required"` + Type string `json:"type"` +} + +func (h *CommonHandler) Feedback(c *gin.Context) { + var req feedbackReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "invalid request: "+err.Error()) + return + } + + userID := c.GetHeader("X-User-ID") + companyID := c.GetHeader("X-Company-ID") + + feedbackType := req.Type + if feedbackType == "" { + feedbackType = "general" + } + + err := h.auditLogRepo.Create( + c.Request.Context(), userID, companyID, + "feedback", feedbackType, "", + c.ClientIP(), map[string]string{"content": req.Content}, + ) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, gin.H{"message": "feedback received"}) +} + +type crashReportReq struct { + Error string `json:"error" binding:"required"` + StackTrace string `json:"stack_trace"` + UserAgent string `json:"user_agent"` + URL string `json:"url"` +} + +func (h *CommonHandler) CrashReport(c *gin.Context) { + var req crashReportReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "invalid request: "+err.Error()) + return + } + + userID := c.GetHeader("X-User-ID") + companyID := c.GetHeader("X-Company-ID") + + err := h.auditLogRepo.Create( + c.Request.Context(), userID, companyID, + "crash_report", "frontend", "", + c.ClientIP(), req, + ) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, gin.H{"message": "crash report received"}) +} diff --git a/auth-service/internal/handler/company.go b/auth-service/internal/handler/company.go new file mode 100644 index 0000000..d58c0aa --- /dev/null +++ b/auth-service/internal/handler/company.go @@ -0,0 +1,41 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/auth-service/internal/service" + "github.com/muyuqingfeng/iloom/shared/pkg/response" +) + +type CompanyHandler struct { + svc *service.CompanyService +} + +func NewCompanyHandler(svc *service.CompanyService) *CompanyHandler { + return &CompanyHandler{svc: svc} +} + +func (h *CompanyHandler) GetCompany(c *gin.Context) { + id := c.Param("id") + company, err := h.svc.GetByID(c.Request.Context(), id) + if err != nil { + response.NotFound(c, err.Error()) + return + } + response.OK(c, company) +} + +func (h *CompanyHandler) ListRelationships(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "company id not found") + return + } + + rels, err := h.svc.ListRelationships(c.Request.Context(), companyID) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, rels) +} diff --git a/auth-service/internal/handler/member.go b/auth-service/internal/handler/member.go new file mode 100644 index 0000000..4c7f82b --- /dev/null +++ b/auth-service/internal/handler/member.go @@ -0,0 +1,84 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/auth-service/internal/service" + "github.com/muyuqingfeng/iloom/shared/pkg/response" +) + +type MemberHandler struct { + svc *service.MemberService +} + +func NewMemberHandler(svc *service.MemberService) *MemberHandler { + return &MemberHandler{svc: svc} +} + +type CreateMemberRequest struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required,min=6"` + RealName string `json:"real_name"` + Phone string `json:"phone"` +} + +func (h *MemberHandler) List(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "company id not found") + return + } + + members, err := h.svc.List(c.Request.Context(), companyID) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, members) +} + +func (h *MemberHandler) Create(c *gin.Context) { + var req CreateMemberRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + companyID := c.GetHeader("X-Company-ID") + roleKey := c.GetHeader("X-User-Role") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + svcReq := service.CreateMemberRequest{ + Username: req.Username, + Password: req.Password, + RealName: req.RealName, + Phone: req.Phone, + } + + member, err := h.svc.Create(c.Request.Context(), companyID, roleKey, svcReq) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, member) +} + +func (h *MemberHandler) Delete(c *gin.Context) { + memberID := c.Param("id") + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "company id not found") + return + } + + if err := h.svc.Delete(c.Request.Context(), memberID, companyID); err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, gin.H{"message": "member deleted"}) +} diff --git a/auth-service/internal/handler/notification.go b/auth-service/internal/handler/notification.go new file mode 100644 index 0000000..ad515f6 --- /dev/null +++ b/auth-service/internal/handler/notification.go @@ -0,0 +1,47 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/auth-service/internal/service" + "github.com/muyuqingfeng/iloom/shared/pkg/response" +) + +type NotificationHandler struct { + svc *service.NotificationService +} + +func NewNotificationHandler(svc *service.NotificationService) *NotificationHandler { + return &NotificationHandler{svc: svc} +} + +func (h *NotificationHandler) List(c *gin.Context) { + userID := c.GetHeader("X-User-ID") + if userID == "" { + response.BadRequest(c, "user id not found") + return + } + + notifications, err := h.svc.List(c.Request.Context(), userID) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, notifications) +} + +func (h *NotificationHandler) MarkRead(c *gin.Context) { + id := c.Param("id") + userID := c.GetHeader("X-User-ID") + if userID == "" { + response.BadRequest(c, "user id not found") + return + } + + if err := h.svc.MarkRead(c.Request.Context(), id, userID); err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, gin.H{"message": "marked as read"}) +} diff --git a/auth-service/internal/model/company.go b/auth-service/internal/model/company.go new file mode 100644 index 0000000..00b47aa --- /dev/null +++ b/auth-service/internal/model/company.go @@ -0,0 +1,32 @@ +package model + +import "time" + +type Company struct { + ID string `json:"id"` + Name string `json:"name"` + Role string `json:"role"` + Address string `json:"address,omitempty"` + Phone string `json:"phone,omitempty"` + Status int `json:"status"` + CreatedAt time.Time `json:"created_at"` +} + +type CompanyMember struct { + ID string `json:"id"` + CompanyID string `json:"company_id"` + UserID string `json:"user_id"` + Role string `json:"role"` + IsMaster bool `json:"is_master"` + CreatedAt time.Time `json:"created_at"` +} + +type CompanyRelationship struct { + ID string `json:"id"` + PurchaserCompanyID string `json:"purchaser_company_id"` + FactoryCompanyID string `json:"factory_company_id"` + FactoryType string `json:"factory_type"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/auth-service/internal/model/notification.go b/auth-service/internal/model/notification.go new file mode 100644 index 0000000..bd2bc00 --- /dev/null +++ b/auth-service/internal/model/notification.go @@ -0,0 +1,35 @@ +package model + +import "time" + +type Notification struct { + ID string `json:"id" db:"id"` + RecipientID string `json:"recipient_id" db:"recipient_id"` + SenderID *string `json:"sender_id,omitempty" db:"sender_id"` + SenderCompanyID *string `json:"sender_company_id,omitempty" db:"sender_company_id"` + Title string `json:"title" db:"title"` + Content string `json:"content" db:"content"` + Type string `json:"type" db:"type"` + PlanID *string `json:"plan_id,omitempty" db:"plan_id"` + IsRead bool `json:"is_read" db:"is_read"` + ReadAt *time.Time `json:"read_at,omitempty" db:"read_at"` + CreatedAt time.Time `json:"created_at" db:"created_at"` +} + +type ShareLink struct { + ID string `json:"id" db:"id"` + PlanID string `json:"plan_id" db:"plan_id"` + CreatedBy string `json:"created_by" db:"created_by"` + ShortCode string `json:"short_code" db:"short_code"` + FactoryType string `json:"factory_type" db:"factory_type"` + HideSensitive bool `json:"hide_sensitive" db:"hide_sensitive"` + Status string `json:"status" db:"status"` + ExpiresAt *time.Time `json:"expires_at,omitempty" db:"expires_at"` + ClickedAt *time.Time `json:"clicked_at,omitempty" db:"clicked_at"` + ClickCount int `json:"click_count" db:"click_count"` + UsedAt *time.Time `json:"used_at,omitempty" db:"used_at"` + ResponseResult *string `json:"response_result,omitempty" db:"response_result"` + RejectReason *string `json:"reject_reason,omitempty" db:"reject_reason"` + RespondedAt *time.Time `json:"responded_at,omitempty" db:"responded_at"` + CreatedAt time.Time `json:"created_at" db:"created_at"` +} diff --git a/auth-service/internal/model/profile.go b/auth-service/internal/model/profile.go new file mode 100644 index 0000000..471455d --- /dev/null +++ b/auth-service/internal/model/profile.go @@ -0,0 +1,18 @@ +package model + +import "time" + +type Profile struct { + ID string `json:"id"` + Username string `json:"username"` + RealName string `json:"real_name,omitempty"` + Phone string `json:"phone,omitempty"` + Email string `json:"email,omitempty"` + Avatar string `json:"avatar,omitempty"` + Status int `json:"status"` + CompanyID string `json:"company_id,omitempty"` + CompanyName string `json:"company_name,omitempty"` + Role string `json:"role,omitempty"` + IsMaster bool `json:"is_master"` + CreatedAt time.Time `json:"created_at"` +} diff --git a/auth-service/internal/repository/audit_log_repo.go b/auth-service/internal/repository/audit_log_repo.go new file mode 100644 index 0000000..bc62cd8 --- /dev/null +++ b/auth-service/internal/repository/audit_log_repo.go @@ -0,0 +1,41 @@ +package repository + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + + "github.com/google/uuid" +) + +type AuditLogRepo struct { + db *sql.DB +} + +func NewAuditLogRepo(db *sql.DB) *AuditLogRepo { + return &AuditLogRepo{db: db} +} + +func (r *AuditLogRepo) Create(ctx context.Context, userID, companyID, action, resourceType, resourceID, ip string, newValue any) error { + id := uuid.New().String() + + var valueJSON []byte + if newValue != nil { + var err error + valueJSON, err = json.Marshal(newValue) + if err != nil { + return fmt.Errorf("marshal audit value: %w", err) + } + } + + _, err := r.db.ExecContext(ctx, + `INSERT INTO ilm_audit_log (id, user_id, company_id, action, resource_type, resource_id, new_value, ip) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + id, userID, companyID, action, resourceType, resourceID, valueJSON, ip, + ) + if err != nil { + return fmt.Errorf("create audit log: %w", err) + } + return nil +} diff --git a/auth-service/internal/repository/company_repo.go b/auth-service/internal/repository/company_repo.go new file mode 100644 index 0000000..cb64a73 --- /dev/null +++ b/auth-service/internal/repository/company_repo.go @@ -0,0 +1,69 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + + "github.com/muyuqingfeng/iloom/auth-service/internal/model" +) + +type CompanyRepo struct { + db *sql.DB +} + +func NewCompanyRepo(db *sql.DB) *CompanyRepo { + return &CompanyRepo{db: db} +} + +func (r *CompanyRepo) Create(ctx context.Context, c *model.Company) error { + query := `INSERT INTO ilm_company (id, name, type, address, phone, status, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?)` + _, err := r.db.ExecContext(ctx, query, + c.ID, c.Name, c.Role, c.Address, c.Phone, c.Status, c.CreatedAt, + ) + if err != nil { + return fmt.Errorf("insert company: %w", err) + } + return nil +} + +func (r *CompanyRepo) GetByID(ctx context.Context, id string) (*model.Company, error) { + query := `SELECT id, name, type, address, phone, status, created_at FROM ilm_company WHERE id = ?` + c := &model.Company{} + err := r.db.QueryRowContext(ctx, query, id).Scan( + &c.ID, &c.Name, &c.Role, &c.Address, &c.Phone, &c.Status, &c.CreatedAt, + ) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, fmt.Errorf("get company by id: %w", err) + } + return c, nil +} + +func (r *CompanyRepo) ListRelationships(ctx context.Context, companyID string) ([]model.CompanyRelationship, error) { + query := `SELECT id, purchaser_company_id, factory_company_id, factory_type, status, created_at, updated_at + FROM ilm_company_relationship + WHERE purchaser_company_id = ? OR factory_company_id = ? + ORDER BY created_at DESC` + rows, err := r.db.QueryContext(ctx, query, companyID, companyID) + if err != nil { + return nil, fmt.Errorf("list relationships: %w", err) + } + defer rows.Close() + + var rels []model.CompanyRelationship + for rows.Next() { + var rel model.CompanyRelationship + if err := rows.Scan( + &rel.ID, &rel.PurchaserCompanyID, &rel.FactoryCompanyID, + &rel.FactoryType, &rel.Status, &rel.CreatedAt, &rel.UpdatedAt, + ); err != nil { + return nil, fmt.Errorf("scan relationship: %w", err) + } + rels = append(rels, rel) + } + return rels, nil +} diff --git a/auth-service/internal/repository/member_repo.go b/auth-service/internal/repository/member_repo.go new file mode 100644 index 0000000..d5874be --- /dev/null +++ b/auth-service/internal/repository/member_repo.go @@ -0,0 +1,85 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + + "github.com/muyuqingfeng/iloom/auth-service/internal/model" +) + +type MemberRepo struct { + db *sql.DB +} + +func NewMemberRepo(db *sql.DB) *MemberRepo { + return &MemberRepo{db: db} +} + +func (r *MemberRepo) Create(ctx context.Context, m *model.CompanyMember) error { + query := `INSERT INTO ilm_company_member (id, company_id, user_id, role, is_master, created_at) + VALUES (?, ?, ?, ?, ?, ?)` + _, err := r.db.ExecContext(ctx, query, + m.ID, m.CompanyID, m.UserID, m.Role, m.IsMaster, m.CreatedAt, + ) + if err != nil { + return fmt.Errorf("insert member: %w", err) + } + return nil +} + +func (r *MemberRepo) Delete(ctx context.Context, id, companyID string) error { + _, err := r.db.ExecContext(ctx, + `DELETE FROM ilm_company_member WHERE id = ? AND company_id = ?`, + id, companyID, + ) + if err != nil { + return fmt.Errorf("delete member: %w", err) + } + return nil +} + +func (r *MemberRepo) GetByUserAndCompany(ctx context.Context, userID, companyID string) (*model.CompanyMember, error) { + query := `SELECT id, company_id, user_id, role, is_master, created_at + FROM ilm_company_member WHERE user_id = ? AND company_id = ?` + m := &model.CompanyMember{} + err := r.db.QueryRowContext(ctx, query, userID, companyID).Scan( + &m.ID, &m.CompanyID, &m.UserID, &m.Role, &m.IsMaster, &m.CreatedAt, + ) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, fmt.Errorf("get member: %w", err) + } + return m, nil +} + +func (r *MemberRepo) ListByCompany(ctx context.Context, companyID string) ([]model.Profile, error) { + query := `SELECT su.user_id, su.username, su.real_name, su.phone, su.email, su.avatar, su.status, su.created_at, + cm.company_id, COALESCE(c.name, ''), COALESCE(c.type, ''), cm.is_master + FROM ilm_company_member cm + JOIN sys_user su ON su.user_id = cm.user_id AND su.deleted_at IS NULL + LEFT JOIN ilm_company c ON c.id = cm.company_id + WHERE cm.company_id = ? + ORDER BY cm.created_at` + rows, err := r.db.QueryContext(ctx, query, companyID) + if err != nil { + return nil, fmt.Errorf("list members by company: %w", err) + } + defer rows.Close() + + var profiles []model.Profile + for rows.Next() { + var p model.Profile + if err := rows.Scan( + &p.ID, &p.Username, &p.RealName, &p.Phone, &p.Email, + &p.Avatar, &p.Status, &p.CreatedAt, + &p.CompanyID, &p.CompanyName, &p.Role, &p.IsMaster, + ); err != nil { + return nil, fmt.Errorf("scan member profile: %w", err) + } + profiles = append(profiles, p) + } + return profiles, nil +} diff --git a/auth-service/internal/repository/notification_repo.go b/auth-service/internal/repository/notification_repo.go new file mode 100644 index 0000000..2967fb2 --- /dev/null +++ b/auth-service/internal/repository/notification_repo.go @@ -0,0 +1,54 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/muyuqingfeng/iloom/auth-service/internal/model" +) + +type NotificationRepo struct { + db *sql.DB +} + +func NewNotificationRepo(db *sql.DB) *NotificationRepo { + return &NotificationRepo{db: db} +} + +func (r *NotificationRepo) ListByRecipient(ctx context.Context, userID string) ([]model.Notification, error) { + query := `SELECT id, recipient_id, sender_id, sender_company_id, title, content, type, plan_id, is_read, read_at, created_at + FROM ilm_notification WHERE recipient_id = ? ORDER BY created_at DESC` + rows, err := r.db.QueryContext(ctx, query, userID) + if err != nil { + return nil, fmt.Errorf("list notifications: %w", err) + } + defer rows.Close() + + var notifications []model.Notification + for rows.Next() { + var n model.Notification + if err := rows.Scan( + &n.ID, &n.RecipientID, &n.SenderID, &n.SenderCompanyID, + &n.Title, &n.Content, &n.Type, &n.PlanID, + &n.IsRead, &n.ReadAt, &n.CreatedAt, + ); err != nil { + return nil, fmt.Errorf("scan notification: %w", err) + } + notifications = append(notifications, n) + } + return notifications, nil +} + +func (r *NotificationRepo) MarkRead(ctx context.Context, id, userID string) error { + now := time.Now() + _, err := r.db.ExecContext(ctx, + `UPDATE ilm_notification SET is_read = true, read_at = ? WHERE id = ? AND recipient_id = ?`, + now, id, userID, + ) + if err != nil { + return fmt.Errorf("mark notification read: %w", err) + } + return nil +} diff --git a/auth-service/internal/repository/profile_repo.go b/auth-service/internal/repository/profile_repo.go new file mode 100644 index 0000000..8933a21 --- /dev/null +++ b/auth-service/internal/repository/profile_repo.go @@ -0,0 +1,74 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + + "github.com/muyuqingfeng/iloom/auth-service/internal/model" +) + +type ProfileRepo struct { + db *sql.DB +} + +func NewProfileRepo(db *sql.DB) *ProfileRepo { + return &ProfileRepo{db: db} +} + +func (r *ProfileRepo) GetByID(ctx context.Context, userID string) (*model.Profile, error) { + query := `SELECT user_id, username, real_name, phone, email, avatar, status, created_at + FROM sys_user WHERE user_id = ? AND deleted_at IS NULL` + p := &model.Profile{} + err := r.db.QueryRowContext(ctx, query, userID).Scan( + &p.ID, &p.Username, &p.RealName, &p.Phone, &p.Email, + &p.Avatar, &p.Status, &p.CreatedAt, + ) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, fmt.Errorf("get profile by id: %w", err) + } + return p, nil +} + +func (r *ProfileRepo) GetByUsername(ctx context.Context, username string) (*model.Profile, error) { + query := `SELECT user_id, username, real_name, phone, email, avatar, status, created_at + FROM sys_user WHERE username = ? AND deleted_at IS NULL` + p := &model.Profile{} + err := r.db.QueryRowContext(ctx, query, username).Scan( + &p.ID, &p.Username, &p.RealName, &p.Phone, &p.Email, + &p.Avatar, &p.Status, &p.CreatedAt, + ) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, fmt.Errorf("get profile by username: %w", err) + } + return p, nil +} + +func (r *ProfileRepo) GetWithCompany(ctx context.Context, userID string) (*model.Profile, error) { + query := `SELECT su.user_id, su.username, su.real_name, su.phone, su.email, su.avatar, su.status, su.created_at, + COALESCE(cm.company_id, ''), COALESCE(c.name, ''), COALESCE(c.type, ''), COALESCE(cm.is_master, 0) + FROM sys_user su + LEFT JOIN ilm_company_member cm ON cm.user_id = su.user_id + LEFT JOIN ilm_company c ON c.id = cm.company_id + WHERE su.user_id = ? AND su.deleted_at IS NULL + LIMIT 1` + p := &model.Profile{} + err := r.db.QueryRowContext(ctx, query, userID).Scan( + &p.ID, &p.Username, &p.RealName, &p.Phone, &p.Email, + &p.Avatar, &p.Status, &p.CreatedAt, + &p.CompanyID, &p.CompanyName, &p.Role, &p.IsMaster, + ) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, fmt.Errorf("get profile with company: %w", err) + } + return p, nil +} diff --git a/auth-service/internal/repository/share_link_repo.go b/auth-service/internal/repository/share_link_repo.go new file mode 100644 index 0000000..5a91e69 --- /dev/null +++ b/auth-service/internal/repository/share_link_repo.go @@ -0,0 +1,77 @@ +package repository + +import ( + "context" + "crypto/rand" + "database/sql" + "encoding/hex" + "fmt" + "time" + + "github.com/google/uuid" +) + +type ShareLinkRepo struct { + db *sql.DB +} + +func NewShareLinkRepo(db *sql.DB) *ShareLinkRepo { + return &ShareLinkRepo{db: db} +} + +type ShareLink struct { + ID string `json:"id"` + Code string `json:"code"` + CompanyID string `json:"company_id"` + CreatedBy string `json:"created_by"` + ResourceType string `json:"resource_type"` + ResourceID string `json:"resource_id"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` + CreatedAt time.Time `json:"created_at"` +} + +func (r *ShareLinkRepo) Create(ctx context.Context, companyID, createdBy, resourceType, resourceID string, expiresAt *time.Time) (*ShareLink, error) { + id := uuid.New().String() + code := generateShortCode() + + _, err := r.db.ExecContext(ctx, + `INSERT INTO ilm_share_link (id, code, company_id, created_by, resource_type, resource_id, expires_at) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + id, code, companyID, createdBy, resourceType, resourceID, expiresAt, + ) + if err != nil { + return nil, fmt.Errorf("create share link: %w", err) + } + + return &ShareLink{ + ID: id, + Code: code, + CompanyID: companyID, + CreatedBy: createdBy, + ResourceType: resourceType, + ResourceID: resourceID, + ExpiresAt: expiresAt, + CreatedAt: time.Now(), + }, nil +} + +func (r *ShareLinkRepo) GetByCode(ctx context.Context, code string) (*ShareLink, error) { + var sl ShareLink + err := r.db.QueryRowContext(ctx, + `SELECT id, code, company_id, created_by, resource_type, resource_id, expires_at, created_at + FROM ilm_share_link WHERE code = ?`, code, + ).Scan(&sl.ID, &sl.Code, &sl.CompanyID, &sl.CreatedBy, &sl.ResourceType, &sl.ResourceID, &sl.ExpiresAt, &sl.CreatedAt) + if err == sql.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("get share link: %w", err) + } + return &sl, nil +} + +func generateShortCode() string { + b := make([]byte, 6) + rand.Read(b) + return hex.EncodeToString(b) +} diff --git a/auth-service/internal/service/auth_service.go b/auth-service/internal/service/auth_service.go new file mode 100644 index 0000000..565da93 --- /dev/null +++ b/auth-service/internal/service/auth_service.go @@ -0,0 +1,220 @@ +package service + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/muyuqingfeng/iloom/auth-service/internal/model" + "github.com/muyuqingfeng/iloom/auth-service/internal/repository" + "github.com/muyuqingfeng/iloom/shared/pkg/auth" + sgrpc "github.com/muyuqingfeng/iloom/shared/pkg/grpc" +) + +var roleIDMap = map[string]string{ + "purchaser": "r_010", + "textile": "r_011", + "washing": "r_012", +} + +type AuthService struct { + profileRepo *repository.ProfileRepo + compRepo *repository.CompanyRepo + memberRepo *repository.MemberRepo + sysClient *sgrpc.SystemClient + jwt *auth.JWTManager +} + +func NewAuthService( + profileRepo *repository.ProfileRepo, + compRepo *repository.CompanyRepo, + memberRepo *repository.MemberRepo, + sysClient *sgrpc.SystemClient, + jwt *auth.JWTManager, +) *AuthService { + return &AuthService{ + profileRepo: profileRepo, + compRepo: compRepo, + memberRepo: memberRepo, + sysClient: sysClient, + jwt: jwt, + } +} + +type RegisterRequest struct { + Username string `json:"username"` + Password string `json:"password"` + CompanyName string `json:"company_name"` + Role string `json:"role"` +} + +type LoginResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + User *model.Profile `json:"user"` + Company *model.Company `json:"company"` +} + +type ProfileWithCompany struct { + User *model.Profile `json:"user"` + Company *model.Company `json:"company,omitempty"` +} + +func (s *AuthService) Register(ctx context.Context, req RegisterRequest) (*LoginResponse, error) { + roleID, ok := roleIDMap[req.Role] + if !ok { + return nil, errors.New("invalid role") + } + + userID, err := s.sysClient.CreateUser(ctx, req.Username, req.Password, "", "", roleID) + if err != nil { + return nil, fmt.Errorf("create user: %w", err) + } + + now := time.Now() + companyID := uuid.New().String() + company := &model.Company{ + ID: companyID, + Name: req.CompanyName, + Role: req.Role, + Status: 1, + CreatedAt: now, + } + if err := s.compRepo.Create(ctx, company); err != nil { + return nil, fmt.Errorf("create company: %w", err) + } + + member := &model.CompanyMember{ + ID: uuid.New().String(), + CompanyID: companyID, + UserID: userID, + Role: "owner", + IsMaster: true, + CreatedAt: now, + } + if err := s.memberRepo.Create(ctx, member); err != nil { + return nil, fmt.Errorf("create member: %w", err) + } + + profile := &model.Profile{ + ID: userID, + Username: req.Username, + CompanyID: companyID, + CompanyName: req.CompanyName, + Role: req.Role, + IsMaster: true, + CreatedAt: now, + } + + return s.generateTokens(profile, company) +} + +func (s *AuthService) Login(ctx context.Context, username, password string) (*LoginResponse, error) { + result, err := s.sysClient.Login(ctx, username, password, "") + if err != nil { + return nil, errors.New("invalid credentials") + } + + profile, err := s.profileRepo.GetWithCompany(ctx, result.UserID) + if err != nil { + return nil, fmt.Errorf("get profile: %w", err) + } + if profile == nil { + profile = &model.Profile{ + ID: result.UserID, + Username: result.Username, + RealName: result.RealName, + Avatar: result.Avatar, + } + } + + var company *model.Company + if profile.CompanyID != "" { + company, err = s.compRepo.GetByID(ctx, profile.CompanyID) + if err != nil { + return nil, fmt.Errorf("get company: %w", err) + } + } + + return s.generateTokens(profile, company) +} + +func (s *AuthService) RefreshToken(ctx context.Context, refreshToken string) (string, error) { + userID, err := s.jwt.ParseRefreshToken(refreshToken) + if err != nil { + return "", fmt.Errorf("invalid refresh token: %w", err) + } + + profile, err := s.profileRepo.GetWithCompany(ctx, userID) + if err != nil { + return "", fmt.Errorf("get user: %w", err) + } + if profile == nil { + return "", errors.New("user not found") + } + + claims := auth.Claims{ + UserID: profile.ID, + CompanyID: profile.CompanyID, + Role: profile.Role, + IsMaster: profile.IsMaster, + Username: profile.Username, + } + + return s.jwt.GenerateAccessToken(claims) +} + +func (s *AuthService) GetMe(ctx context.Context, userID string) (*ProfileWithCompany, error) { + profile, err := s.profileRepo.GetWithCompany(ctx, userID) + if err != nil { + return nil, fmt.Errorf("get profile: %w", err) + } + if profile == nil { + return nil, errors.New("user not found") + } + + result := &ProfileWithCompany{User: profile} + if profile.CompanyID != "" { + company, err := s.compRepo.GetByID(ctx, profile.CompanyID) + if err != nil { + return nil, fmt.Errorf("get company: %w", err) + } + result.Company = company + } + + return result, nil +} + +func (s *AuthService) generateTokens(profile *model.Profile, company *model.Company) (*LoginResponse, error) { + role := profile.Role + if role == "" && company != nil { + role = company.Role + } + + claims := auth.Claims{ + UserID: profile.ID, + CompanyID: profile.CompanyID, + Role: role, + IsMaster: profile.IsMaster, + Username: profile.Username, + } + + accessToken, err := s.jwt.GenerateAccessToken(claims) + if err != nil { + return nil, fmt.Errorf("generate access token: %w", err) + } + + refreshToken, err := s.jwt.GenerateRefreshToken(profile.ID) + if err != nil { + return nil, fmt.Errorf("generate refresh token: %w", err) + } + + return &LoginResponse{ + AccessToken: accessToken, + RefreshToken: refreshToken, + User: profile, + Company: company, + }, nil +} diff --git a/auth-service/internal/service/company_service.go b/auth-service/internal/service/company_service.go new file mode 100644 index 0000000..d6c9c6a --- /dev/null +++ b/auth-service/internal/service/company_service.go @@ -0,0 +1,33 @@ +package service + +import ( + "context" + "errors" + "fmt" + + "github.com/muyuqingfeng/iloom/auth-service/internal/model" + "github.com/muyuqingfeng/iloom/auth-service/internal/repository" +) + +type CompanyService struct { + repo *repository.CompanyRepo +} + +func NewCompanyService(repo *repository.CompanyRepo) *CompanyService { + return &CompanyService{repo: repo} +} + +func (s *CompanyService) GetByID(ctx context.Context, id string) (*model.Company, error) { + company, err := s.repo.GetByID(ctx, id) + if err != nil { + return nil, fmt.Errorf("get company: %w", err) + } + if company == nil { + return nil, errors.New("company not found") + } + return company, nil +} + +func (s *CompanyService) ListRelationships(ctx context.Context, companyID string) ([]model.CompanyRelationship, error) { + return s.repo.ListRelationships(ctx, companyID) +} diff --git a/auth-service/internal/service/member_service.go b/auth-service/internal/service/member_service.go new file mode 100644 index 0000000..7397dd2 --- /dev/null +++ b/auth-service/internal/service/member_service.go @@ -0,0 +1,78 @@ +package service + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/muyuqingfeng/iloom/auth-service/internal/model" + "github.com/muyuqingfeng/iloom/auth-service/internal/repository" + sgrpc "github.com/muyuqingfeng/iloom/shared/pkg/grpc" +) + +type MemberService struct { + memberRepo *repository.MemberRepo + sysClient *sgrpc.SystemClient +} + +func NewMemberService(memberRepo *repository.MemberRepo, sysClient *sgrpc.SystemClient) *MemberService { + return &MemberService{ + memberRepo: memberRepo, + sysClient: sysClient, + } +} + +type CreateMemberRequest struct { + Username string `json:"username"` + Password string `json:"password"` + RealName string `json:"real_name"` + Phone string `json:"phone"` +} + +func (s *MemberService) List(ctx context.Context, companyID string) ([]model.Profile, error) { + return s.memberRepo.ListByCompany(ctx, companyID) +} + +func (s *MemberService) Create(ctx context.Context, companyID, roleKey string, req CreateMemberRequest) (*model.Profile, error) { + roleID := roleIDMap[roleKey] + + userID, err := s.sysClient.CreateUser(ctx, req.Username, req.Password, req.RealName, req.Phone, roleID) + if err != nil { + return nil, fmt.Errorf("create user: %w", err) + } + + now := time.Now() + member := &model.CompanyMember{ + ID: uuid.New().String(), + CompanyID: companyID, + UserID: userID, + Role: "member", + CreatedAt: now, + } + if err := s.memberRepo.Create(ctx, member); err != nil { + return nil, fmt.Errorf("create member: %w", err) + } + + return &model.Profile{ + ID: userID, + Username: req.Username, + RealName: req.RealName, + Phone: req.Phone, + CompanyID: companyID, + CreatedAt: now, + }, nil +} + +func (s *MemberService) Delete(ctx context.Context, memberID, companyID string) error { + member, err := s.memberRepo.GetByUserAndCompany(ctx, memberID, companyID) + if err != nil { + return fmt.Errorf("get member: %w", err) + } + if member == nil { + return errors.New("member not found") + } + + return s.memberRepo.Delete(ctx, member.ID, companyID) +} diff --git a/auth-service/internal/service/notification_service.go b/auth-service/internal/service/notification_service.go new file mode 100644 index 0000000..7fcc87f --- /dev/null +++ b/auth-service/internal/service/notification_service.go @@ -0,0 +1,24 @@ +package service + +import ( + "context" + + "github.com/muyuqingfeng/iloom/auth-service/internal/model" + "github.com/muyuqingfeng/iloom/auth-service/internal/repository" +) + +type NotificationService struct { + repo *repository.NotificationRepo +} + +func NewNotificationService(repo *repository.NotificationRepo) *NotificationService { + return &NotificationService{repo: repo} +} + +func (s *NotificationService) List(ctx context.Context, userID string) ([]model.Notification, error) { + return s.repo.ListByRecipient(ctx, userID) +} + +func (s *NotificationService) MarkRead(ctx context.Context, id, userID string) error { + return s.repo.MarkRead(ctx, id, userID) +} diff --git a/docker-compose.integrated.yml b/docker-compose.integrated.yml new file mode 100644 index 0000000..f013087 --- /dev/null +++ b/docker-compose.integrated.yml @@ -0,0 +1,128 @@ +# iloom 集成模式 — 连接 muyu-apiserver 的 MySQL 和 gRPC +# 用法:docker compose -f docker-compose.integrated.yml up -d + +services: + gateway: + build: + context: . + dockerfile: gateway/Dockerfile + ports: + - "${GATEWAY_PORT:-8080}:8080" + environment: + - PORT=8080 + - JWT_SECRET=${JWT_SECRET:-muyu-wms-jwt-secret-key-2026} + - AUTH_SERVICE_URL=http://auth-service:8081 + - PURCHASER_SERVICE_URL=http://purchaser-service:8082 + - TEXTILE_SERVICE_URL=http://textile-service:8083 + - WASHING_SERVICE_URL=http://washing-service:8084 + - ALLOW_ORIGINS=${ALLOW_ORIGINS:-http://localhost:3015} + depends_on: + auth-service: + condition: service_healthy + purchaser-service: + condition: service_healthy + textile-service: + condition: service_healthy + washing-service: + condition: service_healthy + networks: + - muyu-net + - default + + auth-service: + build: + context: . + dockerfile: auth-service/Dockerfile + ports: + - "${AUTH_SERVICE_PORT:-8081}:8081" + environment: + - PORT=8081 + - DB_DSN=root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4 + - JWT_SECRET=${JWT_SECRET:-muyu-wms-jwt-secret-key-2026} + - JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-muyu-wms-jwt-refresh-2026} + - MUYU_SYSTEM_RPC_ADDR=muyu-system-rpc:9001 + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8081/health"] + interval: 10s + timeout: 3s + retries: 5 + networks: + - muyu-net + - default + + purchaser-service: + build: + context: . + dockerfile: purchaser-service/Dockerfile + ports: + - "${PURCHASER_SERVICE_PORT:-8082}:8082" + environment: + - PORT=8082 + - DB_DSN=root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4 + - INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret} + - MUYU_INVENTORY_RPC_ADDR=muyu-inventory-rpc:9002 + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8082/health"] + interval: 10s + timeout: 3s + retries: 5 + networks: + - muyu-net + - default + + textile-service: + build: + context: . + dockerfile: textile-service/Dockerfile + ports: + - "${TEXTILE_SERVICE_PORT:-8083}:8083" + environment: + - PORT=8083 + - DB_DSN=root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4 + - INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret} + - PURCHASER_SERVICE_URL=http://purchaser-service:8082 + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8083/health"] + interval: 10s + timeout: 3s + retries: 5 + networks: + - muyu-net + - default + + washing-service: + build: + context: . + dockerfile: washing-service/Dockerfile + ports: + - "${WASHING_SERVICE_PORT:-8084}:8084" + environment: + - PORT=8084 + - DB_DSN=root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4 + - INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret} + - PURCHASER_SERVICE_URL=http://purchaser-service:8082 + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8084/health"] + interval: 10s + timeout: 3s + retries: 5 + networks: + - muyu-net + - default + + frontend: + build: + context: . + dockerfile: iloom-flatten/Dockerfile + ports: + - "${FRONTEND_PORT:-3015}:3015" + depends_on: + gateway: + condition: service_started + networks: + - default + +networks: + muyu-net: + external: true + name: deploy_default diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..931bd3c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,150 @@ +# 使用方式: +# 独立模式(开发):docker compose up +# - 启动本地 MySQL,自动初始化 muyu + iloom 表 +# - gRPC 服务需要另外启动 muyu-apiserver(cd ../muyu-apiserver/deploy && docker compose up) +# +# 集成模式(与 muyu-apiserver 联合): +# 1. cd ../muyu-apiserver/deploy && docker compose up +# 2. 设置环境变量指向 muyu 的服务: +# export DB_DSN="root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4" +# export MUYU_SYSTEM_RPC_ADDR=muyu-system-rpc:9001 +# export MUYU_INVENTORY_RPC_ADDR=muyu-inventory-rpc:9002 +# 3. docker compose --profile integrated up + +services: + mysql: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-muyu2026} + MYSQL_DATABASE: ${MYSQL_DATABASE:-muyu_wms} + TZ: Asia/Shanghai + ports: + - "${MYSQL_PORT:-3306}:3306" + volumes: + - mysql_data:/var/lib/mysql + - ../muyu-apiserver/deploy/mysql/init.sql:/docker-entrypoint-initdb.d/01-muyu.sql + - ./scripts/init-iloom.sql:/docker-entrypoint-initdb.d/02-iloom.sql + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-muyu2026}"] + interval: 5s + timeout: 5s + retries: 10 + + gateway: + build: + context: . + dockerfile: gateway/Dockerfile + ports: + - "${GATEWAY_PORT:-8080}:8080" + environment: + - PORT=8080 + - JWT_SECRET=${JWT_SECRET:-muyu-wms-jwt-secret-key-2026} + - AUTH_SERVICE_URL=http://auth-service:8081 + - PURCHASER_SERVICE_URL=http://purchaser-service:8082 + - TEXTILE_SERVICE_URL=http://textile-service:8083 + - WASHING_SERVICE_URL=http://washing-service:8084 + - ALLOW_ORIGINS=${ALLOW_ORIGINS:-http://localhost:3015} + depends_on: + auth-service: + condition: service_healthy + purchaser-service: + condition: service_healthy + textile-service: + condition: service_healthy + washing-service: + condition: service_healthy + + auth-service: + build: + context: . + dockerfile: auth-service/Dockerfile + ports: + - "${AUTH_SERVICE_PORT:-8081}:8081" + environment: + - PORT=8081 + - DB_DSN=${DB_DSN:-root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4} + - JWT_SECRET=${JWT_SECRET:-muyu-wms-jwt-secret-key-2026} + - JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-muyu-wms-jwt-refresh-2026} + - MUYU_SYSTEM_RPC_ADDR=${MUYU_SYSTEM_RPC_ADDR:-system-rpc:9001} + depends_on: + mysql: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8081/health"] + interval: 10s + timeout: 3s + retries: 3 + + purchaser-service: + build: + context: . + dockerfile: purchaser-service/Dockerfile + ports: + - "${PURCHASER_SERVICE_PORT:-8082}:8082" + environment: + - PORT=8082 + - DB_DSN=${DB_DSN:-root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4} + - INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret} + - MUYU_INVENTORY_RPC_ADDR=${MUYU_INVENTORY_RPC_ADDR:-inventory-rpc:9002} + depends_on: + mysql: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8082/health"] + interval: 10s + timeout: 3s + retries: 3 + + textile-service: + build: + context: . + dockerfile: textile-service/Dockerfile + ports: + - "${TEXTILE_SERVICE_PORT:-8083}:8083" + environment: + - PORT=8083 + - DB_DSN=${DB_DSN:-root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4} + - INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret} + - PURCHASER_SERVICE_URL=http://purchaser-service:8082 + depends_on: + mysql: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8083/health"] + interval: 10s + timeout: 3s + retries: 3 + + washing-service: + build: + context: . + dockerfile: washing-service/Dockerfile + ports: + - "${WASHING_SERVICE_PORT:-8084}:8084" + environment: + - PORT=8084 + - DB_DSN=${DB_DSN:-root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4} + - INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret} + - PURCHASER_SERVICE_URL=http://purchaser-service:8082 + depends_on: + mysql: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8084/health"] + interval: 10s + timeout: 3s + retries: 3 + + frontend: + build: + context: . + dockerfile: iloom-flatten/Dockerfile + ports: + - "${FRONTEND_PORT:-3015}:3015" + depends_on: + gateway: + condition: service_started + +volumes: + mysql_data: diff --git a/docs/architecture.html b/docs/architecture.html new file mode 100644 index 0000000..acb1f4c --- /dev/null +++ b/docs/architecture.html @@ -0,0 +1,1563 @@ + + + + + +iloom × muyu-apiserver 系统架构图 + + + + +
+

iloom × muyu-apiserver 系统架构

+

纺织供应链管理 · 微服务架构 · 点击服务卡片查看详情

+
+ +
+
Frontend
+
Gateway
+
iloom 服务
+
muyu 服务
+
数据层
+
HTTP
+
gRPC
+
SQL
+
+ + + + +
+ + +
+ +
+
+
+
+
+
React SPA
+
:3015
+
+
+
Frontend
+
+ +
+
+
🖥
+
+
muyu-portal
+
:8080 (Nginx)
+
+
+
muyu 前端
+
+
+ + +
+ iloom 微服务集群 +
+
+
+
🔀
+
+
API Gateway
+
:8080
+
+
+
+
JWT 验证
+
CORS
+
路由分发
+
限流
+
+
+ +
+
+
🔐
+
+
Auth Service
+
:8081
+
+
+
认证 · 公司 · 成员
+
+ +
+
+
🛒
+
+
Purchaser Service
+
:8082
+
+
+
采购 · 计划 · 产品
+
+ +
+
+
🧵
+
+
Textile Service
+
:8083
+
+
+
纺织 · 纱线 · 工序
+
+ +
+
+
💧
+
+
Washing Service
+
:8084
+
+
+
水洗 · 成品 · 出库
+
+
+
+ + +
+ muyu-apiserver +
+
+
+
🌐
+
+
muyu Gateway
+
:8888 (REST)
+
+
+
go-zero API
+
+ +
+
+
👤
+
+
System RPC
+
:9001 (gRPC)
+
+
+
+
用户
+
角色
+
菜单
+
权限
+
+
+ +
+
+
📦
+
+
Inventory RPC
+
:9002 (gRPC)
+
+
+
+
产品
+
库存
+
盘点
+
+
+ +
+
+
+
+
+
Redis
+
:6379
+
+
+
+
+
+
🔧
+
+
etcd
+
:2379
+
+
+
+
+
+
+ + +
+ MySQL 8.0 · muyu_wms · :3306 +
+
+

ilm_* 表 (iloom 拥有 · 直接 SQL 读写)

+
    +
  • ilm_company — 公司
  • +
  • ilm_company_member — 成员
  • +
  • ilm_company_relationship — 公司关系
  • +
  • ilm_product_ext — 产品扩展
  • +
  • ilm_production_plan — 生产计划
  • +
  • ilm_plan_factory — 计划工厂
  • +
  • ilm_yarn_ratio — 纱线配比
  • +
  • ilm_process_step — 生产工序
  • +
  • ilm_warehouse — 仓库
  • +
  • ilm_inventory_record — 库存记录
  • +
+
+
+

ilm_* 表 (续)

+
    +
  • ilm_product_inventory — 产品出入库
  • +
  • ilm_product_outbound — 产品出库
  • +
  • ilm_washing_plan — 水洗计划
  • +
  • ilm_washing_step — 水洗工序
  • +
  • ilm_washing_completion — 水洗完工
  • +
  • ilm_finished_product — 成品
  • +
  • ilm_finished_product_inventory — 成品出入库
  • +
  • ilm_yarn_stock — 纱线库存
  • +
  • ilm_yarn_stock_record — 纱线记录
  • +
  • ilm_accounts_payable — 应付账款
  • +
  • ilm_ap_item — 账款明细
  • +
  • ilm_payment — 收付款
  • +
  • ilm_notification — 通知
  • +
  • ilm_share_link — 分享链接
  • +
  • ilm_audit_log — 审计日志
  • +
  • ilm_product_price_history — 产品价格历史
  • +
  • ilm_production_price_history — 生产价格历史
  • +
+
+
+

sys_* / inv_* 表 (muyu 拥有 · 只读, 写走 gRPC)

+
    +
  • sys_user — 用户
  • +
  • sys_role — 角色
  • +
  • sys_user_role — 用户角色绑定
  • +
  • sys_menu — 菜单
  • +
  • sys_role_menu — 角色菜单权限
  • +
  • sys_operation_log — 操作日志
  • +
  • sys_config — 系统配置
  • +
  • inv_product — 产品
  • +
  • inv_stock_check — 盘点
  • +
  • inv_stock_adjust — 调整
  • +
  • casbin_rule — RBAC 策略
  • +
+
+
+
+
+
+ + + + +
+
数据流向 · 接口调用一览
+ + +
+
JWT 认证流程
+
+
+
1
+
+
用户登录
+
Frontend → Gateway
POST /api/v1/auth/login
{username, password}
+
+
+
+
2
+
+
gRPC 验证
+
Auth Service → System RPC
SystemService.Login()
muyu 验证密码
+
+
+
+
3
+
+
查询公司
+
Auth Service → MySQL
sys_user JOIN ilm_company_member
获取 company_id, role
+
+
+
+
4
+
+
签发 Token
+
JWT Claims:
{uid, cid, role, master, uname}
Secret: muyu-wms-jwt-secret-key-2026
+
+
+
+
5
+
+
请求鉴权
+
Gateway 验证 JWT → 注入 Headers
X-User-ID X-Company-ID
X-User-Role X-Is-Master
+
+
+
+
+ + +
服务间调用 · 数据流向
+
+
+
调用方
+ +
目标
+
协议
+
说明
+
+ +
+
+
React SPA
+
+
Gateway :8080
+
HTTP
+
+
所有 API 请求 + WebSocket 升级,Authorization: Bearer <token>
+
+ +
+
+
Gateway
+
+
Auth Service
+
HTTP
+
+
/api/v1/auth/*, /api/v1/common/* → 反向代理到 :8081
+
+ +
+
+
Gateway
+
+
Purchaser Svc
+
HTTP
+
+
/api/v1/purchaser/* → :8082 (role=purchaser)
+
+ +
+
+
Gateway
+
+
Textile Svc
+
HTTP
+
+
/api/v1/textile/* → :8083 (role=textile)
+
+ +
+
+
Gateway
+
+
Washing Svc
+
HTTP
+
+
/api/v1/washing/* → :8084 (role=washing)
+
+ +
+
+
Auth Service
+
+
System RPC
+
gRPC
+
+
Login() 密码验证, CreateUser() 注册, GetUser() 查询
+
+ +
+
+
Purchaser Svc
+
+
Inventory RPC
+
gRPC
+
+
CreateProduct(), GetProduct(), ListProduct() 产品CRUD
+
+ +
+
+
Textile Svc
+
+
Purchaser Svc
+
HTTP
+
+
内部服务调用 — 查询采购商计划、工厂信息
+
+ +
+
+
Washing Svc
+
+
Purchaser Svc
+
HTTP
+
+
内部服务调用 — 查询采购商计划、工厂信息
+
+ +
+
+
iloom 全部服务
+
+
MySQL
+
SQL
+
+
ilm_* 表直接读写;sys_user 只读查询 (JOIN)
+
+ +
+
+
muyu RPC 服务
+
+
MySQL
+
SQL
+
+
sys_*, inv_*, casbin_rule 全权读写
+
+ +
+
+
muyu RPC 服务
+ +
Redis / etcd
+
TCP
+
+
Redis: 缓存 + 会话 | etcd: 服务注册发现
+
+
+
+ + + + +
+
+ +
+
+ + + + diff --git a/gateway/Dockerfile b/gateway/Dockerfile new file mode 100644 index 0000000..be6aab8 --- /dev/null +++ b/gateway/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.24-alpine AS builder +WORKDIR /app +COPY shared/ ./shared/ +COPY gateway/ ./gateway/ +WORKDIR /app/gateway +RUN go build -o /gateway ./cmd/ + +FROM alpine:3.20 +RUN apk add --no-cache wget +COPY --from=builder /gateway /gateway +EXPOSE 8080 +CMD ["/gateway"] diff --git a/gateway/cmd/main.go b/gateway/cmd/main.go new file mode 100644 index 0000000..7e47cb1 --- /dev/null +++ b/gateway/cmd/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/gateway/internal/config" + "github.com/muyuqingfeng/iloom/gateway/internal/middleware" + "github.com/muyuqingfeng/iloom/gateway/internal/proxy" + sharedMW "github.com/muyuqingfeng/iloom/shared/pkg/middleware" +) + +func main() { + cfg := config.Load() + + r := gin.New() + r.Use( + sharedMW.RequestID(), + sharedMW.Logger(), + sharedMW.Recovery(), + sharedMW.CORS(cfg.AllowOrigins), + middleware.RateLimit(100), + ) + + r.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "ok"}) + }) + + v1 := r.Group("/api/v1") + { + // /api/v1/auth/* -> AUTH_SERVICE_URL (no auth required) + v1.Any("/auth/*path", proxy.NewReverseProxy(cfg.AuthServiceURL)) + + // /api/v1/common/* -> AUTH_SERVICE_URL (JWT required) + common := v1.Group("/common", middleware.AuthRequired(cfg.JWTSecret)) + common.Any("/*path", proxy.NewReverseProxy(cfg.AuthServiceURL)) + + // /api/v1/purchaser/* -> PURCHASER_SERVICE_URL (JWT + role=purchaser) + purchaser := v1.Group("/purchaser", + middleware.AuthRequired(cfg.JWTSecret), + middleware.RoleRequired("purchaser"), + ) + purchaser.Any("/*path", proxy.NewReverseProxy(cfg.PurchaserServiceURL)) + + // /api/v1/textile/* -> TEXTILE_SERVICE_URL (JWT + role=textile) + textile := v1.Group("/textile", + middleware.AuthRequired(cfg.JWTSecret), + middleware.RoleRequired("textile"), + ) + textile.Any("/*path", proxy.NewReverseProxy(cfg.TextileServiceURL)) + + // /api/v1/washing/* -> WASHING_SERVICE_URL (JWT + role=washing) + washing := v1.Group("/washing", + middleware.AuthRequired(cfg.JWTSecret), + middleware.RoleRequired("washing"), + ) + washing.Any("/*path", proxy.NewReverseProxy(cfg.WashingServiceURL)) + } + + // WebSocket proxy — route by role + r.GET("/ws/v1/purchaser", proxy.NewWSProxy(cfg.PurchaserServiceURL)) + r.GET("/ws/v1/textile", proxy.NewWSProxy(cfg.TextileServiceURL)) + r.GET("/ws/v1/washing", proxy.NewWSProxy(cfg.WashingServiceURL)) + + addr := ":" + cfg.Port + log.Printf("gateway listening on %s", addr) + if err := r.Run(addr); err != nil { + log.Fatalf("server error: %v", err) + } +} diff --git a/gateway/go.mod b/gateway/go.mod new file mode 100644 index 0000000..ad166b7 --- /dev/null +++ b/gateway/go.mod @@ -0,0 +1,47 @@ +module github.com/muyuqingfeng/iloom/gateway + +go 1.23 + +toolchain go1.24.4 + +require github.com/muyuqingfeng/iloom/shared v0.0.0 + +replace github.com/muyuqingfeng/iloom/shared => ../shared + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/gorilla/websocket v1.5.3 +) + +require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + 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.2.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/gateway/go.sum b/gateway/go.sum new file mode 100644 index 0000000..9010125 --- /dev/null +++ b/gateway/go.sum @@ -0,0 +1,109 @@ +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +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= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +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= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +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/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/gateway/internal/config/config.go b/gateway/internal/config/config.go new file mode 100644 index 0000000..78a40ed --- /dev/null +++ b/gateway/internal/config/config.go @@ -0,0 +1,35 @@ +package config + +import ( + "os" + "strings" +) + +type Config struct { + Port string + JWTSecret string + AuthServiceURL string + PurchaserServiceURL string + TextileServiceURL string + WashingServiceURL string + AllowOrigins []string +} + +func Load() *Config { + return &Config{ + Port: getEnv("PORT", "8080"), + JWTSecret: getEnv("JWT_SECRET", "muyu-wms-jwt-secret-key-2026"), + AuthServiceURL: getEnv("AUTH_SERVICE_URL", "http://localhost:8081"), + PurchaserServiceURL: getEnv("PURCHASER_SERVICE_URL", "http://localhost:8082"), + TextileServiceURL: getEnv("TEXTILE_SERVICE_URL", "http://localhost:8083"), + WashingServiceURL: getEnv("WASHING_SERVICE_URL", "http://localhost:8084"), + AllowOrigins: strings.Split(getEnv("ALLOW_ORIGINS", "*"), ","), + } +} + +func getEnv(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} diff --git a/gateway/internal/middleware/auth.go b/gateway/internal/middleware/auth.go new file mode 100644 index 0000000..39b441c --- /dev/null +++ b/gateway/internal/middleware/auth.go @@ -0,0 +1,65 @@ +package middleware + +import ( + "fmt" + "strings" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "github.com/muyuqingfeng/iloom/shared/pkg/auth" + "github.com/muyuqingfeng/iloom/shared/pkg/response" +) + +func AuthRequired(jwtSecret string) gin.HandlerFunc { + secret := []byte(jwtSecret) + + return func(c *gin.Context) { + header := c.GetHeader("Authorization") + if header == "" { + response.Unauthorized(c, "missing authorization header") + c.Abort() + return + } + + parts := strings.SplitN(header, " ", 2) + if len(parts) != 2 || !strings.EqualFold(parts[0], "bearer") { + response.Unauthorized(c, "invalid authorization format") + c.Abort() + return + } + + tokenStr := parts[1] + token, err := jwt.ParseWithClaims(tokenStr, &auth.Claims{}, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) + } + return secret, nil + }) + if err != nil { + response.Error(c, 401, response.ErrCodeTokenExpired, "token expired or invalid") + c.Abort() + return + } + + claims, ok := token.Claims.(*auth.Claims) + if !ok || !token.Valid { + response.Error(c, 401, response.ErrCodeTokenInvalid, "invalid token") + c.Abort() + return + } + + c.Set("user_id", claims.UserID) + c.Set("company_id", claims.CompanyID) + c.Set("role", claims.Role) + c.Set("is_master", claims.IsMaster) + c.Set("username", claims.Username) + + c.Request.Header.Set("X-User-ID", claims.UserID) + c.Request.Header.Set("X-Company-ID", claims.CompanyID) + c.Request.Header.Set("X-User-Role", claims.Role) + c.Request.Header.Set("X-Is-Master", fmt.Sprintf("%t", claims.IsMaster)) + c.Request.Header.Set("X-Username", claims.Username) + + c.Next() + } +} diff --git a/gateway/internal/middleware/ratelimit.go b/gateway/internal/middleware/ratelimit.go new file mode 100644 index 0000000..2ccb309 --- /dev/null +++ b/gateway/internal/middleware/ratelimit.go @@ -0,0 +1,54 @@ +package middleware + +import ( + "net/http" + "sync" + "time" + + "github.com/gin-gonic/gin" +) + +type bucket struct { + tokens float64 + lastCheck time.Time + mu sync.Mutex +} + +var buckets sync.Map + +func RateLimit(rps int) gin.HandlerFunc { + rate := float64(rps) + + return func(c *gin.Context) { + ip := c.ClientIP() + + val, _ := buckets.LoadOrStore(ip, &bucket{ + tokens: rate, + lastCheck: time.Now(), + }) + b := val.(*bucket) + + b.mu.Lock() + now := time.Now() + elapsed := now.Sub(b.lastCheck).Seconds() + b.lastCheck = now + b.tokens += elapsed * rate + if b.tokens > rate { + b.tokens = rate + } + + if b.tokens < 1 { + b.mu.Unlock() + c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{ + "code": 42900, + "message": "rate limit exceeded", + }) + return + } + + b.tokens-- + b.mu.Unlock() + + c.Next() + } +} diff --git a/gateway/internal/middleware/role_check.go b/gateway/internal/middleware/role_check.go new file mode 100644 index 0000000..eec0ac1 --- /dev/null +++ b/gateway/internal/middleware/role_check.go @@ -0,0 +1,31 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/response" +) + +func RoleRequired(roles ...string) gin.HandlerFunc { + allowed := make(map[string]bool, len(roles)) + for _, r := range roles { + allowed[r] = true + } + + return func(c *gin.Context) { + role, exists := c.Get("role") + if !exists { + response.Forbidden(c, "role not found in context") + c.Abort() + return + } + + roleStr, ok := role.(string) + if !ok || !allowed[roleStr] { + response.Error(c, 403, response.ErrCodeRoleMismatch, "insufficient role permission") + c.Abort() + return + } + + c.Next() + } +} diff --git a/gateway/internal/proxy/reverse_proxy.go b/gateway/internal/proxy/reverse_proxy.go new file mode 100644 index 0000000..cbc82da --- /dev/null +++ b/gateway/internal/proxy/reverse_proxy.go @@ -0,0 +1,34 @@ +package proxy + +import ( + "log" + "net/http" + "net/http/httputil" + "net/url" + + "github.com/gin-gonic/gin" +) + +func NewReverseProxy(target string) gin.HandlerFunc { + targetURL, err := url.Parse(target) + if err != nil { + log.Fatalf("invalid proxy target URL: %s, err: %v", target, err) + } + + proxy := &httputil.ReverseProxy{ + Director: func(req *http.Request) { + req.URL.Scheme = targetURL.Scheme + req.URL.Host = targetURL.Host + req.Host = targetURL.Host + }, + ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { + log.Printf("[proxy] error: %v", err) + w.WriteHeader(http.StatusBadGateway) + _, _ = w.Write([]byte(`{"code":50200,"message":"service unavailable"}`)) + }, + } + + return func(c *gin.Context) { + proxy.ServeHTTP(c.Writer, c.Request) + } +} diff --git a/gateway/internal/proxy/ws_proxy.go b/gateway/internal/proxy/ws_proxy.go new file mode 100644 index 0000000..8ff591a --- /dev/null +++ b/gateway/internal/proxy/ws_proxy.go @@ -0,0 +1,94 @@ +package proxy + +import ( + "io" + "log" + "net/http" + "net/url" + "strings" + + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" +) + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { return true }, +} + +func NewWSProxy(targetURL string) gin.HandlerFunc { + return func(c *gin.Context) { + clientConn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Printf("[ws_proxy] upgrade error: %v", err) + return + } + defer clientConn.Close() + + backendURL, err := buildWSURL(targetURL, c.Request) + if err != nil { + log.Printf("[ws_proxy] url parse error: %v", err) + return + } + + header := http.Header{} + for _, key := range []string{"X-User-ID", "X-Company-ID", "X-User-Role"} { + if v := c.Request.Header.Get(key); v != "" { + header.Set(key, v) + } + } + + backendConn, _, err := websocket.DefaultDialer.Dial(backendURL, header) + if err != nil { + log.Printf("[ws_proxy] dial backend error: %v", err) + return + } + defer backendConn.Close() + + errCh := make(chan error, 2) + + go pumpMessages(clientConn, backendConn, errCh) + go pumpMessages(backendConn, clientConn, errCh) + + <-errCh + } +} + +func pumpMessages(src, dst *websocket.Conn, errCh chan<- error) { + for { + msgType, reader, err := src.NextReader() + if err != nil { + errCh <- err + return + } + writer, err := dst.NextWriter(msgType) + if err != nil { + errCh <- err + return + } + if _, err := io.Copy(writer, reader); err != nil { + errCh <- err + return + } + if err := writer.Close(); err != nil { + errCh <- err + return + } + } +} + +func buildWSURL(target string, req *http.Request) (string, error) { + u, err := url.Parse(target) + if err != nil { + return "", err + } + + scheme := "ws" + if strings.HasPrefix(u.Scheme, "https") { + scheme = "wss" + } + + u.Scheme = scheme + u.Path = req.URL.Path + u.RawQuery = req.URL.RawQuery + return u.String(), nil +} diff --git a/go.work b/go.work new file mode 100644 index 0000000..9ea1276 --- /dev/null +++ b/go.work @@ -0,0 +1,12 @@ +go 1.23 + +toolchain go1.24.4 + +use ( + ./auth-service + ./gateway + ./purchaser-service + ./shared + ./textile-service + ./washing-service +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..3c434d0 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,26 @@ +cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +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/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/iloom-flatten b/iloom-flatten new file mode 160000 index 0000000..3093ae1 --- /dev/null +++ b/iloom-flatten @@ -0,0 +1 @@ +Subproject commit 3093ae18c25f6d654e1c0c3a6cd546c04a5438d1 diff --git a/k8s/00-namespace.yaml b/k8s/00-namespace.yaml new file mode 100644 index 0000000..3f680a5 --- /dev/null +++ b/k8s/00-namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: iloom diff --git a/k8s/02-configmaps/frontend-nginx.yaml b/k8s/02-configmaps/frontend-nginx.yaml new file mode 100644 index 0000000..c1bcb70 --- /dev/null +++ b/k8s/02-configmaps/frontend-nginx.yaml @@ -0,0 +1,44 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: iloom-frontend-nginx + namespace: iloom +data: + default.conf: | + server { + listen 3015; + server_name _; + root /usr/share/nginx/html; + index index.html; + + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript image/svg+xml; + gzip_min_length 256; + + location /api/ { + proxy_pass http://iloom-gateway:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /ws/ { + proxy_pass http://iloom-gateway:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 86400; + } + + location / { + try_files $uri $uri/ /index.html; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 30d; + add_header Cache-Control "public, immutable"; + } + } diff --git a/k8s/02-configmaps/muyu-configs.yaml b/k8s/02-configmaps/muyu-configs.yaml new file mode 100644 index 0000000..bbbd338 --- /dev/null +++ b/k8s/02-configmaps/muyu-configs.yaml @@ -0,0 +1,83 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: muyu-system-config + namespace: iloom +data: + system.yaml: | + Name: system.rpc + ListenOn: 0.0.0.0:9001 + + Etcd: + Hosts: + - etcd:2379 + Key: system.rpc + + DataSource: root:muyu2026@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai + + Cache: + - Host: redis:6379 + + Log: + ServiceName: system-rpc + Mode: console + Level: info +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: muyu-inventory-config + namespace: iloom +data: + inventory.yaml: | + Name: inventory.rpc + ListenOn: 0.0.0.0:9002 + + Etcd: + Hosts: + - etcd:2379 + Key: inventory.rpc + + DataSource: root:muyu2026@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai + + Cache: + - Host: redis:6379 + + Log: + ServiceName: inventory-rpc + Mode: console + Level: info +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: muyu-gateway-config + namespace: iloom +data: + gateway.yaml: | + Name: gateway-api + Host: 0.0.0.0 + Port: 8888 + + Auth: + AccessSecret: muyu-wms-jwt-secret-key-2026 + AccessExpire: 7200 + + SystemRpc: + Etcd: + Hosts: + - etcd:2379 + Key: system.rpc + NonBlock: true + + InventoryRpc: + Etcd: + Hosts: + - etcd:2379 + Key: inventory.rpc + NonBlock: true + + Log: + ServiceName: gateway-api + Mode: console + Level: info diff --git a/k8s/10-infra/etcd.yaml b/k8s/10-infra/etcd.yaml new file mode 100644 index 0000000..a8f12ca --- /dev/null +++ b/k8s/10-infra/etcd.yaml @@ -0,0 +1,89 @@ +apiVersion: v1 +kind: Service +metadata: + name: etcd + namespace: iloom +spec: + clusterIP: None + ports: + - name: client + port: 2379 + targetPort: 2379 + - name: peer + port: 2380 + targetPort: 2380 + selector: + app: etcd +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: etcd + namespace: iloom +spec: + serviceName: etcd + replicas: 1 + selector: + matchLabels: + app: etcd + template: + metadata: + labels: + app: etcd + spec: + imagePullSecrets: + - name: harbor-pull-secret + containers: + - name: etcd + image: harbor-in-k3s.cheverjohn.me/iloom/etcd:v3.5.17 + ports: + - containerPort: 2379 + name: client + - containerPort: 2380 + name: peer + env: + - name: ETCD_NAME + value: "etcd-0" + - name: ETCD_DATA_DIR + value: "/etcd-data" + - name: ETCD_LISTEN_CLIENT_URLS + value: "http://0.0.0.0:2379" + - name: ETCD_ADVERTISE_CLIENT_URLS + value: "http://etcd:2379" + - name: ETCD_LISTEN_PEER_URLS + value: "http://0.0.0.0:2380" + - name: ETCD_INITIAL_ADVERTISE_PEER_URLS + value: "http://etcd-0.etcd:2380" + - name: ETCD_INITIAL_CLUSTER + value: "etcd-0=http://etcd-0.etcd:2380" + - name: ETCD_INITIAL_CLUSTER_STATE + value: "new" + volumeMounts: + - name: etcd-data + mountPath: /etcd-data + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi + readinessProbe: + exec: + command: ["etcdctl", "endpoint", "health", "--endpoints=http://localhost:2379"] + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + exec: + command: ["etcdctl", "endpoint", "health", "--endpoints=http://localhost:2379"] + initialDelaySeconds: 30 + periodSeconds: 30 + volumeClaimTemplates: + - metadata: + name: etcd-data + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: longhorn + resources: + requests: + storage: 1Gi diff --git a/k8s/10-infra/mysql.yaml b/k8s/10-infra/mysql.yaml new file mode 100644 index 0000000..5e5d950 --- /dev/null +++ b/k8s/10-infra/mysql.yaml @@ -0,0 +1,87 @@ +apiVersion: v1 +kind: Service +metadata: + name: mysql + namespace: iloom +spec: + clusterIP: None + ports: + - port: 3306 + targetPort: 3306 + selector: + app: mysql +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: mysql + namespace: iloom +spec: + serviceName: mysql + replicas: 1 + selector: + matchLabels: + app: mysql + template: + metadata: + labels: + app: mysql + spec: + imagePullSecrets: + - name: harbor-pull-secret + containers: + - name: mysql + image: harbor-in-k3s.cheverjohn.me/iloom/mysql:8.0 + ports: + - containerPort: 3306 + env: + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: db-secret + key: MYSQL_ROOT_PASSWORD + - name: MYSQL_DATABASE + valueFrom: + secretKeyRef: + name: db-secret + key: MYSQL_DATABASE + - name: TZ + value: Asia/Shanghai + args: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + volumeMounts: + - name: mysql-data + mountPath: /var/lib/mysql + - name: init-scripts + mountPath: /docker-entrypoint-initdb.d + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + readinessProbe: + exec: + command: ["mysqladmin", "ping", "-h", "localhost"] + initialDelaySeconds: 30 + periodSeconds: 10 + livenessProbe: + exec: + command: ["mysqladmin", "ping", "-h", "localhost"] + initialDelaySeconds: 60 + periodSeconds: 30 + volumes: + - name: init-scripts + configMap: + name: mysql-init-scripts + volumeClaimTemplates: + - metadata: + name: mysql-data + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: longhorn + resources: + requests: + storage: 5Gi diff --git a/k8s/10-infra/redis.yaml b/k8s/10-infra/redis.yaml new file mode 100644 index 0000000..0499858 --- /dev/null +++ b/k8s/10-infra/redis.yaml @@ -0,0 +1,66 @@ +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: iloom +spec: + clusterIP: None + ports: + - port: 6379 + targetPort: 6379 + selector: + app: redis +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis + namespace: iloom +spec: + serviceName: redis + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + imagePullSecrets: + - name: harbor-pull-secret + containers: + - name: redis + image: harbor-in-k3s.cheverjohn.me/iloom/redis:7-alpine + ports: + - containerPort: 6379 + command: ["redis-server", "--appendonly", "yes"] + volumeMounts: + - name: redis-data + mountPath: /data + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi + readinessProbe: + exec: + command: ["redis-cli", "ping"] + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + exec: + command: ["redis-cli", "ping"] + initialDelaySeconds: 15 + periodSeconds: 30 + volumeClaimTemplates: + - metadata: + name: redis-data + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: longhorn + resources: + requests: + storage: 2Gi diff --git a/k8s/20-muyu/gateway.yaml b/k8s/20-muyu/gateway.yaml new file mode 100644 index 0000000..6c2bb51 --- /dev/null +++ b/k8s/20-muyu/gateway.yaml @@ -0,0 +1,66 @@ +apiVersion: v1 +kind: Service +metadata: + name: muyu-gateway + namespace: iloom +spec: + type: ClusterIP + ports: + - port: 8888 + targetPort: 8888 + selector: + app: muyu-gateway +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: muyu-gateway + namespace: iloom +spec: + replicas: 1 + selector: + matchLabels: + app: muyu-gateway + template: + metadata: + labels: + app: muyu-gateway + spec: + imagePullSecrets: + - name: harbor-pull-secret + initContainers: + - name: wait-etcd + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z etcd 2379; do sleep 2; done'] + containers: + - name: gateway + image: harbor-in-k3s.cheverjohn.me/iloom/muyu-gateway:v1 + ports: + - containerPort: 8888 + env: + - name: TZ + value: Asia/Shanghai + volumeMounts: + - name: config + mountPath: /app/etc + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 250m + memory: 128Mi + readinessProbe: + tcpSocket: + port: 8888 + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: 8888 + initialDelaySeconds: 20 + periodSeconds: 30 + volumes: + - name: config + configMap: + name: muyu-gateway-config diff --git a/k8s/20-muyu/inventory-rpc.yaml b/k8s/20-muyu/inventory-rpc.yaml new file mode 100644 index 0000000..11625d7 --- /dev/null +++ b/k8s/20-muyu/inventory-rpc.yaml @@ -0,0 +1,72 @@ +apiVersion: v1 +kind: Service +metadata: + name: muyu-inventory-rpc + namespace: iloom +spec: + type: ClusterIP + ports: + - port: 9002 + targetPort: 9002 + selector: + app: muyu-inventory-rpc +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: muyu-inventory-rpc + namespace: iloom +spec: + replicas: 1 + selector: + matchLabels: + app: muyu-inventory-rpc + template: + metadata: + labels: + app: muyu-inventory-rpc + spec: + imagePullSecrets: + - name: harbor-pull-secret + initContainers: + - name: wait-mysql + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z mysql 3306; do sleep 2; done'] + - name: wait-etcd + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z etcd 2379; do sleep 2; done'] + - name: wait-redis + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z redis 6379; do sleep 2; done'] + containers: + - name: inventory-rpc + image: harbor-in-k3s.cheverjohn.me/iloom/muyu-inventory-rpc:v1 + ports: + - containerPort: 9002 + env: + - name: TZ + value: Asia/Shanghai + volumeMounts: + - name: config + mountPath: /app/etc + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 250m + memory: 128Mi + readinessProbe: + tcpSocket: + port: 9002 + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: 9002 + initialDelaySeconds: 20 + periodSeconds: 30 + volumes: + - name: config + configMap: + name: muyu-inventory-config diff --git a/k8s/20-muyu/system-rpc.yaml b/k8s/20-muyu/system-rpc.yaml new file mode 100644 index 0000000..e0cd7ec --- /dev/null +++ b/k8s/20-muyu/system-rpc.yaml @@ -0,0 +1,72 @@ +apiVersion: v1 +kind: Service +metadata: + name: muyu-system-rpc + namespace: iloom +spec: + type: ClusterIP + ports: + - port: 9001 + targetPort: 9001 + selector: + app: muyu-system-rpc +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: muyu-system-rpc + namespace: iloom +spec: + replicas: 1 + selector: + matchLabels: + app: muyu-system-rpc + template: + metadata: + labels: + app: muyu-system-rpc + spec: + imagePullSecrets: + - name: harbor-pull-secret + initContainers: + - name: wait-mysql + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z mysql 3306; do sleep 2; done'] + - name: wait-etcd + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z etcd 2379; do sleep 2; done'] + - name: wait-redis + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z redis 6379; do sleep 2; done'] + containers: + - name: system-rpc + image: harbor-in-k3s.cheverjohn.me/iloom/muyu-system-rpc:v1 + ports: + - containerPort: 9001 + env: + - name: TZ + value: Asia/Shanghai + volumeMounts: + - name: config + mountPath: /app/etc + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 250m + memory: 128Mi + readinessProbe: + tcpSocket: + port: 9001 + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: 9001 + initialDelaySeconds: 20 + periodSeconds: 30 + volumes: + - name: config + configMap: + name: muyu-system-config diff --git a/k8s/30-iloom/auth-service.yaml b/k8s/30-iloom/auth-service.yaml new file mode 100644 index 0000000..5f37c90 --- /dev/null +++ b/k8s/30-iloom/auth-service.yaml @@ -0,0 +1,81 @@ +apiVersion: v1 +kind: Service +metadata: + name: iloom-auth-service + namespace: iloom +spec: + type: ClusterIP + ports: + - port: 8081 + targetPort: 8081 + selector: + app: iloom-auth-service +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: iloom-auth-service + namespace: iloom +spec: + replicas: 1 + selector: + matchLabels: + app: iloom-auth-service + template: + metadata: + labels: + app: iloom-auth-service + spec: + imagePullSecrets: + - name: harbor-pull-secret + initContainers: + - name: wait-mysql + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z mysql 3306; do sleep 2; done'] + - name: wait-muyu-system + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z muyu-system-rpc 9001; do sleep 2; done'] + containers: + - name: auth-service + image: harbor-in-k3s.cheverjohn.me/iloom/iloom-auth-service:v1 + ports: + - containerPort: 8081 + env: + - name: PORT + value: "8081" + - name: DB_DSN + valueFrom: + secretKeyRef: + name: db-secret + key: DB_DSN + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: app-secret + key: JWT_SECRET + - name: JWT_REFRESH_SECRET + valueFrom: + secretKeyRef: + name: app-secret + key: JWT_REFRESH_SECRET + - name: MUYU_SYSTEM_RPC_ADDR + value: "muyu-system-rpc:9001" + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 250m + memory: 128Mi + readinessProbe: + httpGet: + path: /health + port: 8081 + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health + port: 8081 + initialDelaySeconds: 20 + periodSeconds: 30 diff --git a/k8s/30-iloom/frontend.yaml b/k8s/30-iloom/frontend.yaml new file mode 100644 index 0000000..1d2feab --- /dev/null +++ b/k8s/30-iloom/frontend.yaml @@ -0,0 +1,61 @@ +apiVersion: v1 +kind: Service +metadata: + name: iloom-frontend + namespace: iloom +spec: + type: ClusterIP + ports: + - port: 3015 + targetPort: 3015 + selector: + app: iloom-frontend +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: iloom-frontend + namespace: iloom +spec: + replicas: 1 + selector: + matchLabels: + app: iloom-frontend + template: + metadata: + labels: + app: iloom-frontend + spec: + imagePullSecrets: + - name: harbor-pull-secret + containers: + - name: frontend + image: harbor-in-k3s.cheverjohn.me/iloom/iloom-frontend:v1 + ports: + - containerPort: 3015 + volumeMounts: + - name: nginx-config + mountPath: /etc/nginx/conf.d + resources: + requests: + cpu: 25m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + readinessProbe: + httpGet: + path: / + port: 3015 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: / + port: 3015 + initialDelaySeconds: 10 + periodSeconds: 30 + volumes: + - name: nginx-config + configMap: + name: iloom-frontend-nginx diff --git a/k8s/30-iloom/gateway.yaml b/k8s/30-iloom/gateway.yaml new file mode 100644 index 0000000..204e547 --- /dev/null +++ b/k8s/30-iloom/gateway.yaml @@ -0,0 +1,85 @@ +apiVersion: v1 +kind: Service +metadata: + name: iloom-gateway + namespace: iloom +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: 8080 + selector: + app: iloom-gateway +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: iloom-gateway + namespace: iloom +spec: + replicas: 1 + selector: + matchLabels: + app: iloom-gateway + template: + metadata: + labels: + app: iloom-gateway + spec: + imagePullSecrets: + - name: harbor-pull-secret + initContainers: + - name: wait-auth + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z iloom-auth-service 8081; do sleep 2; done'] + - name: wait-purchaser + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z iloom-purchaser-service 8082; do sleep 2; done'] + - name: wait-textile + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z iloom-textile-service 8083; do sleep 2; done'] + - name: wait-washing + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z iloom-washing-service 8084; do sleep 2; done'] + containers: + - name: gateway + image: harbor-in-k3s.cheverjohn.me/iloom/iloom-gateway:v1 + ports: + - containerPort: 8080 + env: + - name: PORT + value: "8080" + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: app-secret + key: JWT_SECRET + - name: AUTH_SERVICE_URL + value: "http://iloom-auth-service:8081" + - name: PURCHASER_SERVICE_URL + value: "http://iloom-purchaser-service:8082" + - name: TEXTILE_SERVICE_URL + value: "http://iloom-textile-service:8083" + - name: WASHING_SERVICE_URL + value: "http://iloom-washing-service:8084" + - name: ALLOW_ORIGINS + value: "https://iloom.cheverjohn.me" + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 250m + memory: 128Mi + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 20 + periodSeconds: 30 diff --git a/k8s/30-iloom/purchaser-service.yaml b/k8s/30-iloom/purchaser-service.yaml new file mode 100644 index 0000000..450188b --- /dev/null +++ b/k8s/30-iloom/purchaser-service.yaml @@ -0,0 +1,76 @@ +apiVersion: v1 +kind: Service +metadata: + name: iloom-purchaser-service + namespace: iloom +spec: + type: ClusterIP + ports: + - port: 8082 + targetPort: 8082 + selector: + app: iloom-purchaser-service +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: iloom-purchaser-service + namespace: iloom +spec: + replicas: 1 + selector: + matchLabels: + app: iloom-purchaser-service + template: + metadata: + labels: + app: iloom-purchaser-service + spec: + imagePullSecrets: + - name: harbor-pull-secret + initContainers: + - name: wait-mysql + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z mysql 3306; do sleep 2; done'] + - name: wait-muyu-inventory + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z muyu-inventory-rpc 9002; do sleep 2; done'] + containers: + - name: purchaser-service + image: harbor-in-k3s.cheverjohn.me/iloom/iloom-purchaser-service:v1 + ports: + - containerPort: 8082 + env: + - name: PORT + value: "8082" + - name: DB_DSN + valueFrom: + secretKeyRef: + name: db-secret + key: DB_DSN + - name: INTERNAL_SERVICE_KEY + valueFrom: + secretKeyRef: + name: app-secret + key: INTERNAL_SERVICE_KEY + - name: MUYU_INVENTORY_RPC_ADDR + value: "muyu-inventory-rpc:9002" + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 250m + memory: 128Mi + readinessProbe: + httpGet: + path: /health + port: 8082 + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health + port: 8082 + initialDelaySeconds: 20 + periodSeconds: 30 diff --git a/k8s/30-iloom/textile-service.yaml b/k8s/30-iloom/textile-service.yaml new file mode 100644 index 0000000..706392b --- /dev/null +++ b/k8s/30-iloom/textile-service.yaml @@ -0,0 +1,73 @@ +apiVersion: v1 +kind: Service +metadata: + name: iloom-textile-service + namespace: iloom +spec: + type: ClusterIP + ports: + - port: 8083 + targetPort: 8083 + selector: + app: iloom-textile-service +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: iloom-textile-service + namespace: iloom +spec: + replicas: 1 + selector: + matchLabels: + app: iloom-textile-service + template: + metadata: + labels: + app: iloom-textile-service + spec: + imagePullSecrets: + - name: harbor-pull-secret + initContainers: + - name: wait-mysql + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z mysql 3306; do sleep 2; done'] + containers: + - name: textile-service + image: harbor-in-k3s.cheverjohn.me/iloom/iloom-textile-service:v1 + ports: + - containerPort: 8083 + env: + - name: PORT + value: "8083" + - name: DB_DSN + valueFrom: + secretKeyRef: + name: db-secret + key: DB_DSN + - name: INTERNAL_SERVICE_KEY + valueFrom: + secretKeyRef: + name: app-secret + key: INTERNAL_SERVICE_KEY + - name: PURCHASER_SERVICE_URL + value: "http://iloom-purchaser-service:8082" + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 250m + memory: 128Mi + readinessProbe: + httpGet: + path: /health + port: 8083 + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health + port: 8083 + initialDelaySeconds: 20 + periodSeconds: 30 diff --git a/k8s/30-iloom/washing-service.yaml b/k8s/30-iloom/washing-service.yaml new file mode 100644 index 0000000..5f39893 --- /dev/null +++ b/k8s/30-iloom/washing-service.yaml @@ -0,0 +1,73 @@ +apiVersion: v1 +kind: Service +metadata: + name: iloom-washing-service + namespace: iloom +spec: + type: ClusterIP + ports: + - port: 8084 + targetPort: 8084 + selector: + app: iloom-washing-service +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: iloom-washing-service + namespace: iloom +spec: + replicas: 1 + selector: + matchLabels: + app: iloom-washing-service + template: + metadata: + labels: + app: iloom-washing-service + spec: + imagePullSecrets: + - name: harbor-pull-secret + initContainers: + - name: wait-mysql + image: harbor-in-k3s.cheverjohn.me/iloom/busybox:1.36 + command: ['sh', '-c', 'until nc -z mysql 3306; do sleep 2; done'] + containers: + - name: washing-service + image: harbor-in-k3s.cheverjohn.me/iloom/iloom-washing-service:v1 + ports: + - containerPort: 8084 + env: + - name: PORT + value: "8084" + - name: DB_DSN + valueFrom: + secretKeyRef: + name: db-secret + key: DB_DSN + - name: INTERNAL_SERVICE_KEY + valueFrom: + secretKeyRef: + name: app-secret + key: INTERNAL_SERVICE_KEY + - name: PURCHASER_SERVICE_URL + value: "http://iloom-purchaser-service:8082" + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 250m + memory: 128Mi + readinessProbe: + httpGet: + path: /health + port: 8084 + initialDelaySeconds: 10 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health + port: 8084 + initialDelaySeconds: 20 + periodSeconds: 30 diff --git a/k8s/40-ingress.yaml b/k8s/40-ingress.yaml new file mode 100644 index 0000000..b23facd --- /dev/null +++ b/k8s/40-ingress.yaml @@ -0,0 +1,33 @@ +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: iloom-frontend + namespace: iloom +spec: + entryPoints: + - websecure + routes: + - match: Host(`iloom.cheverjohn.me`) + kind: Rule + services: + - name: iloom-frontend + port: 3015 + tls: + certResolver: letsencrypt +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: muyu-gateway + namespace: iloom +spec: + entryPoints: + - websecure + routes: + - match: Host(`muyu-api.cheverjohn.me`) + kind: Rule + services: + - name: muyu-gateway + port: 8888 + tls: + certResolver: letsencrypt diff --git a/purchaser-service/Dockerfile b/purchaser-service/Dockerfile new file mode 100644 index 0000000..25a000d --- /dev/null +++ b/purchaser-service/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.24-alpine AS builder +WORKDIR /app +COPY shared/ ./shared/ +COPY purchaser-service/ ./purchaser-service/ +WORKDIR /app/purchaser-service +RUN go build -o /purchaser-service ./cmd/ + +FROM alpine:3.20 +RUN apk add --no-cache wget +COPY --from=builder /purchaser-service /purchaser-service +EXPOSE 8082 +CMD ["/purchaser-service"] diff --git a/purchaser-service/cmd/main.go b/purchaser-service/cmd/main.go new file mode 100644 index 0000000..aadbd40 --- /dev/null +++ b/purchaser-service/cmd/main.go @@ -0,0 +1,102 @@ +package main + +import ( + "log" + "os" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/config" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/handler" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/repository" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/service" + "github.com/muyuqingfeng/iloom/shared/pkg/database" + "github.com/muyuqingfeng/iloom/shared/pkg/middleware" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + "github.com/muyuqingfeng/iloom/shared/pkg/websocket" +) + +func main() { + cfg := config.Load() + + db, err := database.NewDBFromDSN(cfg.DBDSN) + if err != nil { + log.Fatalf("failed to connect database: %v", err) + } + defer db.Close() + + wsHub := websocket.NewHub() + go wsHub.Run() + + planRepo := repository.NewPlanRepo(db) + productRepo := repository.NewProductRepo(db) + apRepo := repository.NewAPRepo(db) + factoryRepo := repository.NewFactoryRepo(db) + washingPlanRepo := repository.NewWashingPlanRepo(db) + + planSvc := service.NewPlanService(planRepo, db) + productSvc := service.NewProductService(productRepo, db) + apSvc := service.NewAPService(apRepo, db) + factorySvc := service.NewFactoryService(factoryRepo) + washingPlanSvc := service.NewWashingPlanService(washingPlanRepo) + dashboardSvc := service.NewDashboardService(planRepo, productRepo, apRepo) + + planHandler := handler.NewPlanHandler(planSvc, wsHub) + productHandler := handler.NewProductHandler(productSvc, wsHub) + apHandler := handler.NewAPHandler(apSvc) + factoryHandler := handler.NewFactoryHandler(factorySvc) + washingPlanHandler := handler.NewWashingPlanHandler(washingPlanSvc) + dashboardHandler := handler.NewDashboardHandler(dashboardSvc) + + if os.Getenv("GIN_MODE") == "" { + gin.SetMode(gin.ReleaseMode) + } + r := gin.New() + r.Use(middleware.Recovery(), middleware.Logger(), middleware.RequestID()) + + r.GET("/health", func(c *gin.Context) { + response.OK(c, gin.H{"status": "ok", "service": "purchaser-service"}) + }) + + api := r.Group("/api/v1/purchaser") + { + api.GET("/dashboard/stats", dashboardHandler.Stats) + + api.GET("/plans", planHandler.List) + api.POST("/plans", planHandler.Create) + api.GET("/plans/:id", planHandler.Get) + api.PUT("/plans/:id", planHandler.Update) + api.DELETE("/plans/:id", planHandler.Delete) + api.POST("/plans/:id/factories", planHandler.AssignFactory) + api.GET("/plans/:id/steps", planHandler.GetSteps) + api.GET("/plans/:id/inventory", planHandler.GetInventory) + + api.GET("/products", productHandler.List) + api.POST("/products", productHandler.Create) + api.PUT("/products/:id", productHandler.Update) + api.DELETE("/products/:id", productHandler.Delete) + api.POST("/products/:id/inbound", productHandler.Inbound) + api.POST("/products/:id/outbound", productHandler.Outbound) + api.GET("/products/:id/records", productHandler.Records) + api.PUT("/products/:id/price", productHandler.UpdatePrice) + api.GET("/products/:id/price-history", productHandler.PriceHistory) + + api.GET("/factories", factoryHandler.List) + + api.GET("/accounts-payable", apHandler.List) + api.POST("/accounts-payable/:id/pay", apHandler.Pay) + + api.POST("/washing-plans", washingPlanHandler.Create) + api.GET("/washing-plans", washingPlanHandler.List) + } + + r.GET("/ws/v1/purchaser", handler.HandleWebSocket(wsHub)) + + port := cfg.Port + if port == "" { + port = "8082" + } + log.Printf("purchaser-service listening on :%s", port) + if err := r.Run(":" + port); err != nil { + log.Fatalf("failed to start server: %v", err) + } +} diff --git a/purchaser-service/go.mod b/purchaser-service/go.mod new file mode 100644 index 0000000..225d8e6 --- /dev/null +++ b/purchaser-service/go.mod @@ -0,0 +1,45 @@ +module github.com/muyuqingfeng/iloom/purchaser-service + +go 1.23 + +toolchain go1.24.4 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.3 + github.com/muyuqingfeng/iloom/shared v0.0.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + 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.2.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/muyuqingfeng/iloom/shared => ../shared diff --git a/purchaser-service/go.sum b/purchaser-service/go.sum new file mode 100644 index 0000000..64bfbd0 --- /dev/null +++ b/purchaser-service/go.sum @@ -0,0 +1,97 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +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= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +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= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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/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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/purchaser-service/internal/config/config.go b/purchaser-service/internal/config/config.go new file mode 100644 index 0000000..297e2e1 --- /dev/null +++ b/purchaser-service/internal/config/config.go @@ -0,0 +1,26 @@ +package config + +import "os" + +type Config struct { + Port string + DBDSN string + InternalServiceKey string + InventoryRPCAddr string +} + +func Load() *Config { + return &Config{ + Port: getEnv("PORT", "8082"), + DBDSN: getEnv("DB_DSN", "root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?parseTime=true&charset=utf8mb4"), + InternalServiceKey: getEnv("INTERNAL_SERVICE_KEY", "dev-internal-key"), + InventoryRPCAddr: getEnv("MUYU_INVENTORY_RPC_ADDR", "127.0.0.1:9002"), + } +} + +func getEnv(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} diff --git a/purchaser-service/internal/handler/accounts_payable.go b/purchaser-service/internal/handler/accounts_payable.go new file mode 100644 index 0000000..9fa14c7 --- /dev/null +++ b/purchaser-service/internal/handler/accounts_payable.go @@ -0,0 +1,62 @@ +package handler + +import ( + "log" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/service" + "github.com/muyuqingfeng/iloom/shared/pkg/response" +) + +type APHandler struct { + svc *service.APService +} + +func NewAPHandler(svc *service.APService) *APHandler { + return &APHandler{svc: svc} +} + +type PayRequest struct { + Amount float64 `json:"amount" binding:"required,gt=0"` +} + +func (h *APHandler) List(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + list, err := h.svc.List(c.Request.Context(), companyID) + if err != nil { + log.Printf("list accounts payable error: %v", err) + response.InternalError(c, "failed to list accounts payable") + return + } + + response.OK(c, list) +} + +func (h *APHandler) Pay(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + apID := c.Param("id") + + var req PayRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + if err := h.svc.Pay(c.Request.Context(), apID, companyID, req.Amount); err != nil { + log.Printf("pay error: %v", err) + response.InternalError(c, "failed to process payment") + return + } + + response.OK(c, nil) +} diff --git a/purchaser-service/internal/handler/dashboard.go b/purchaser-service/internal/handler/dashboard.go new file mode 100644 index 0000000..bc749de --- /dev/null +++ b/purchaser-service/internal/handler/dashboard.go @@ -0,0 +1,34 @@ +package handler + +import ( + "log" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/service" + "github.com/muyuqingfeng/iloom/shared/pkg/response" +) + +type DashboardHandler struct { + svc *service.DashboardService +} + +func NewDashboardHandler(svc *service.DashboardService) *DashboardHandler { + return &DashboardHandler{svc: svc} +} + +func (h *DashboardHandler) Stats(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + stats, err := h.svc.Stats(c.Request.Context(), companyID) + if err != nil { + log.Printf("get dashboard stats error: %v", err) + response.InternalError(c, "failed to get stats") + return + } + + response.OK(c, stats) +} diff --git a/purchaser-service/internal/handler/factory.go b/purchaser-service/internal/handler/factory.go new file mode 100644 index 0000000..701c3be --- /dev/null +++ b/purchaser-service/internal/handler/factory.go @@ -0,0 +1,34 @@ +package handler + +import ( + "log" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/service" + "github.com/muyuqingfeng/iloom/shared/pkg/response" +) + +type FactoryHandler struct { + svc *service.FactoryService +} + +func NewFactoryHandler(svc *service.FactoryService) *FactoryHandler { + return &FactoryHandler{svc: svc} +} + +func (h *FactoryHandler) List(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + factories, err := h.svc.List(c.Request.Context(), companyID) + if err != nil { + log.Printf("list factories error: %v", err) + response.InternalError(c, "failed to list factories") + return + } + + response.OK(c, factories) +} diff --git a/purchaser-service/internal/handler/plan.go b/purchaser-service/internal/handler/plan.go new file mode 100644 index 0000000..47f4b5e --- /dev/null +++ b/purchaser-service/internal/handler/plan.go @@ -0,0 +1,274 @@ +package handler + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/service" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + "github.com/muyuqingfeng/iloom/shared/pkg/types" + "github.com/muyuqingfeng/iloom/shared/pkg/websocket" +) + +type PlanHandler struct { + svc *service.PlanService + wsHub *websocket.Hub +} + +func NewPlanHandler(svc *service.PlanService, wsHub *websocket.Hub) *PlanHandler { + return &PlanHandler{svc: svc, wsHub: wsHub} +} + +type CreatePlanReq struct { + ProductName string `json:"product_name" binding:"required"` + FabricCode string `json:"fabric_code" binding:"required"` + Color string `json:"color" binding:"required"` + ColorCode string `json:"color_code"` + TargetQuantity float64 `json:"target_quantity" binding:"required,gt=0"` + ProductionPrice *float64 `json:"production_price"` + YarnUsagePerMeter *float64 `json:"yarn_usage_per_meter"` + Remark string `json:"remark"` + YarnRatios []CreateYarnRatioReq `json:"yarn_ratios"` +} + +type CreateYarnRatioReq struct { + YarnName string `json:"yarn_name" binding:"required"` + YarnType string `json:"yarn_type"` + Ratio float64 `json:"ratio" binding:"required"` + AmountPerMeter float64 `json:"amount_per_meter"` + TotalAmount float64 `json:"total_amount"` +} + +type UpdatePlanReq struct { + ProductName string `json:"product_name"` + FabricCode string `json:"fabric_code"` + Color string `json:"color"` + ColorCode string `json:"color_code"` + TargetQuantity float64 `json:"target_quantity"` + ProductionPrice *float64 `json:"production_price"` + YarnUsagePerMeter *float64 `json:"yarn_usage_per_meter"` + Status string `json:"status"` + Remark string `json:"remark"` +} + +type AssignFactoryReq struct { + FactoryID string `json:"factory_id" binding:"required"` + FactoryType string `json:"factory_type" binding:"required"` +} + +func (h *PlanHandler) List(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + var page types.Pagination + if err := c.ShouldBindQuery(&page); err != nil { + response.BadRequest(c, "invalid pagination") + return + } + page.Normalize() + + status := c.Query("status") + + plans, total, err := h.svc.List(c.Request.Context(), companyID, page, status) + if err != nil { + log.Printf("list plans error: %v", err) + response.InternalError(c, "failed to list plans") + return + } + + response.OKWithMeta(c, plans, &response.Meta{ + Page: page.Page, + PageSize: page.PageSize, + Total: total, + HasMore: page.Page*page.PageSize < total, + }) +} + +func (h *PlanHandler) Create(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + if companyID == "" || userID == "" { + response.BadRequest(c, "missing user or company id") + return + } + + var req CreatePlanReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + svcReq := service.CreatePlanRequest{ + ProductName: req.ProductName, + FabricCode: req.FabricCode, + Color: req.Color, + ColorCode: req.ColorCode, + TargetQuantity: req.TargetQuantity, + ProductionPrice: req.ProductionPrice, + YarnUsagePerMeter: req.YarnUsagePerMeter, + Remark: req.Remark, + } + for _, yr := range req.YarnRatios { + svcReq.YarnRatios = append(svcReq.YarnRatios, service.CreateYarnRatio{ + YarnName: yr.YarnName, + YarnType: yr.YarnType, + Ratio: yr.Ratio, + AmountPerMeter: yr.AmountPerMeter, + TotalAmount: yr.TotalAmount, + }) + } + + plan, err := h.svc.Create(c.Request.Context(), companyID, userID, svcReq) + if err != nil { + log.Printf("create plan error: %v", err) + response.InternalError(c, "failed to create plan") + return + } + + h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ + Table: "production_plans", + Action: "INSERT", + }) + + c.JSON(http.StatusCreated, response.Response{ + Code: 0, + Message: "success", + Data: plan, + }) +} + +func (h *PlanHandler) Get(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + id := c.Param("id") + plan, err := h.svc.GetByID(c.Request.Context(), id, companyID) + if err != nil { + log.Printf("get plan error: %v", err) + response.NotFound(c, "plan not found") + return + } + + response.OK(c, plan) +} + +func (h *PlanHandler) Update(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + id := c.Param("id") + + var req UpdatePlanReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + svcReq := service.UpdatePlanRequest{ + ProductName: req.ProductName, + FabricCode: req.FabricCode, + Color: req.Color, + ColorCode: req.ColorCode, + TargetQuantity: req.TargetQuantity, + ProductionPrice: req.ProductionPrice, + YarnUsagePerMeter: req.YarnUsagePerMeter, + Status: req.Status, + Remark: req.Remark, + } + + if err := h.svc.Update(c.Request.Context(), id, companyID, svcReq); err != nil { + log.Printf("update plan error: %v", err) + response.InternalError(c, "failed to update plan") + return + } + + h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ + Table: "production_plans", + Action: "UPDATE", + }) + + response.OK(c, nil) +} + +func (h *PlanHandler) Delete(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + id := c.Param("id") + if err := h.svc.Delete(c.Request.Context(), id, companyID); err != nil { + log.Printf("delete plan error: %v", err) + response.InternalError(c, "failed to delete plan") + return + } + + h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ + Table: "production_plans", + Action: "DELETE", + }) + + response.OK(c, nil) +} + +func (h *PlanHandler) AssignFactory(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + planID := c.Param("id") + + var req AssignFactoryReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + if err := h.svc.AssignFactory(c.Request.Context(), planID, req.FactoryID, req.FactoryType); err != nil { + log.Printf("assign factory error: %v", err) + response.InternalError(c, "failed to assign factory") + return + } + + h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ + Table: "plan_factories", + Action: "INSERT", + }) + + response.OK(c, nil) +} + +func (h *PlanHandler) GetSteps(c *gin.Context) { + planID := c.Param("id") + steps, err := h.svc.GetSteps(c.Request.Context(), planID) + if err != nil { + log.Printf("get steps error: %v", err) + response.InternalError(c, "failed to get steps") + return + } + response.OK(c, steps) +} + +func (h *PlanHandler) GetInventory(c *gin.Context) { + planID := c.Param("id") + records, err := h.svc.GetInventory(c.Request.Context(), planID) + if err != nil { + log.Printf("get inventory error: %v", err) + response.InternalError(c, "failed to get inventory") + return + } + response.OK(c, records) +} diff --git a/purchaser-service/internal/handler/product.go b/purchaser-service/internal/handler/product.go new file mode 100644 index 0000000..1c98afe --- /dev/null +++ b/purchaser-service/internal/handler/product.go @@ -0,0 +1,319 @@ +package handler + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/service" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + "github.com/muyuqingfeng/iloom/shared/pkg/websocket" +) + +type ProductHandler struct { + svc *service.ProductService + wsHub *websocket.Hub +} + +func NewProductHandler(svc *service.ProductService, wsHub *websocket.Hub) *ProductHandler { + return &ProductHandler{svc: svc, wsHub: wsHub} +} + +type CreateProductReq struct { + ProductName string `json:"product_name" binding:"required"` + FabricCode string `json:"fabric_code" binding:"required"` + Color string `json:"color" binding:"required"` + ColorCode string `json:"color_code"` + Weight float64 `json:"weight"` + YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"` + ProductionPrice *float64 `json:"production_price"` + YarnTypes []string `json:"yarn_types"` + YarnRatios []float64 `json:"yarn_ratios"` +} + +type UpdateProductReq struct { + ProductName string `json:"product_name"` + FabricCode string `json:"fabric_code"` + Color string `json:"color"` + ColorCode string `json:"color_code"` + Weight float64 `json:"weight"` + YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"` + ProductionPrice *float64 `json:"production_price"` + YarnTypes []string `json:"yarn_types"` + YarnRatios []float64 `json:"yarn_ratios"` +} + +type InboundReq struct { + BatchNo string `json:"batch_no" binding:"required"` + Rolls int `json:"rolls" binding:"required,gt=0"` + Meters float64 `json:"meters" binding:"required,gt=0"` + Notes string `json:"notes"` + WarehouseID string `json:"warehouse_id"` +} + +type OutboundReq struct { + BatchNo string `json:"batch_no"` + Rolls int `json:"rolls" binding:"required,gt=0"` + Meters float64 `json:"meters" binding:"required,gt=0"` + OutboundType string `json:"outbound_type"` + Notes string `json:"notes"` + RecipientCompanyID string `json:"recipient_company_id"` + RecipientCompanyName string `json:"recipient_company_name"` + SourceBatchID string `json:"source_batch_id"` + WashingPlanID string `json:"washing_plan_id"` +} + +type UpdatePriceReq struct { + NewPrice float64 `json:"new_price" binding:"required,gt=0"` + Reason string `json:"reason"` +} + +func (h *ProductHandler) List(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + products, err := h.svc.List(c.Request.Context(), companyID) + if err != nil { + log.Printf("list products error: %v", err) + response.InternalError(c, "failed to list products") + return + } + response.OK(c, products) +} + +func (h *ProductHandler) Create(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + var req CreateProductReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + svcReq := service.CreateProductRequest{ + ProductName: req.ProductName, + FabricCode: req.FabricCode, + Color: req.Color, + ColorCode: req.ColorCode, + Weight: req.Weight, + YarnUsagePerMeter: req.YarnUsagePerMeter, + ProductionPrice: req.ProductionPrice, + YarnTypes: req.YarnTypes, + YarnRatios: req.YarnRatios, + } + + product, err := h.svc.Create(c.Request.Context(), companyID, svcReq) + if err != nil { + log.Printf("create product error: %v", err) + response.InternalError(c, "failed to create product") + return + } + + h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ + Table: "products", + Action: "INSERT", + }) + + c.JSON(http.StatusCreated, response.Response{ + Code: 0, + Message: "success", + Data: product, + }) +} + +func (h *ProductHandler) Update(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + id := c.Param("id") + + var req UpdateProductReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + svcReq := service.UpdateProductRequest{ + ProductName: req.ProductName, + FabricCode: req.FabricCode, + Color: req.Color, + ColorCode: req.ColorCode, + Weight: req.Weight, + YarnUsagePerMeter: req.YarnUsagePerMeter, + ProductionPrice: req.ProductionPrice, + YarnTypes: req.YarnTypes, + YarnRatios: req.YarnRatios, + } + + if err := h.svc.Update(c.Request.Context(), id, companyID, svcReq); err != nil { + log.Printf("update product error: %v", err) + response.InternalError(c, "failed to update product") + return + } + + h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ + Table: "products", + Action: "UPDATE", + }) + + response.OK(c, nil) +} + +func (h *ProductHandler) Delete(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + id := c.Param("id") + if err := h.svc.Delete(c.Request.Context(), id, companyID); err != nil { + log.Printf("delete product error: %v", err) + response.InternalError(c, "failed to delete product") + return + } + + h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ + Table: "products", + Action: "DELETE", + }) + + response.OK(c, nil) +} + +func (h *ProductHandler) Inbound(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + if companyID == "" || userID == "" { + response.BadRequest(c, "missing user or company id") + return + } + + productID := c.Param("id") + + var req InboundReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + svcReq := service.InboundRequest{ + BatchNo: req.BatchNo, + Rolls: req.Rolls, + Meters: req.Meters, + Notes: req.Notes, + WarehouseID: req.WarehouseID, + } + + if err := h.svc.Inbound(c.Request.Context(), productID, companyID, userID, svcReq); err != nil { + log.Printf("inbound error: %v", err) + response.InternalError(c, "failed to inbound") + return + } + + h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ + Table: "product_inventory_records", + Action: "INSERT", + }) + + response.OK(c, nil) +} + +func (h *ProductHandler) Outbound(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + if companyID == "" || userID == "" { + response.BadRequest(c, "missing user or company id") + return + } + + productID := c.Param("id") + + var req OutboundReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + svcReq := service.OutboundRequest{ + BatchNo: req.BatchNo, + Rolls: req.Rolls, + Meters: req.Meters, + OutboundType: req.OutboundType, + Notes: req.Notes, + RecipientCompanyID: req.RecipientCompanyID, + RecipientCompanyName: req.RecipientCompanyName, + SourceBatchID: req.SourceBatchID, + WashingPlanID: req.WashingPlanID, + } + + if err := h.svc.Outbound(c.Request.Context(), productID, companyID, userID, svcReq); err != nil { + log.Printf("outbound error: %v", err) + response.InternalError(c, "failed to outbound") + return + } + + h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ + Table: "product_outbound_records", + Action: "INSERT", + }) + + response.OK(c, nil) +} + +func (h *ProductHandler) Records(c *gin.Context) { + productID := c.Param("id") + records, err := h.svc.Records(c.Request.Context(), productID) + if err != nil { + log.Printf("get records error: %v", err) + response.InternalError(c, "failed to get records") + return + } + response.OK(c, records) +} + +func (h *ProductHandler) UpdatePrice(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + if companyID == "" || userID == "" { + response.BadRequest(c, "missing user or company id") + return + } + + productID := c.Param("id") + + var req UpdatePriceReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + if err := h.svc.UpdatePrice(c.Request.Context(), productID, companyID, userID, req.NewPrice, req.Reason); err != nil { + log.Printf("update price error: %v", err) + response.InternalError(c, "failed to update price") + return + } + + response.OK(c, nil) +} + +func (h *ProductHandler) PriceHistory(c *gin.Context) { + productID := c.Param("id") + history, err := h.svc.PriceHistory(c.Request.Context(), productID) + if err != nil { + log.Printf("get price history error: %v", err) + response.InternalError(c, "failed to get price history") + return + } + response.OK(c, history) +} diff --git a/purchaser-service/internal/handler/washing_plan.go b/purchaser-service/internal/handler/washing_plan.go new file mode 100644 index 0000000..96673a7 --- /dev/null +++ b/purchaser-service/internal/handler/washing_plan.go @@ -0,0 +1,81 @@ +package handler + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/service" + "github.com/muyuqingfeng/iloom/shared/pkg/response" +) + +type WashingPlanHandler struct { + svc *service.WashingPlanService +} + +func NewWashingPlanHandler(svc *service.WashingPlanService) *WashingPlanHandler { + return &WashingPlanHandler{svc: svc} +} + +type CreateWashingPlanReq struct { + ProductID string `json:"product_id" binding:"required"` + WashingFactoryID string `json:"washing_factory_id"` + PlannedMeters float64 `json:"planned_meters" binding:"required,gt=0"` + WashingPrice float64 `json:"washing_price" binding:"required,gt=0"` + EstimatedShrinkageRate float64 `json:"estimated_shrinkage_rate"` + Notes string `json:"notes"` +} + +func (h *WashingPlanHandler) Create(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + if companyID == "" || userID == "" { + response.BadRequest(c, "missing user or company id") + return + } + + var req CreateWashingPlanReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + svcReq := service.CreateWashingPlanRequest{ + ProductID: req.ProductID, + WashingFactoryID: req.WashingFactoryID, + PlannedMeters: req.PlannedMeters, + WashingPrice: req.WashingPrice, + EstimatedShrinkageRate: req.EstimatedShrinkageRate, + Notes: req.Notes, + } + + wp, err := h.svc.Create(c.Request.Context(), companyID, userID, svcReq) + if err != nil { + log.Printf("create washing plan error: %v", err) + response.InternalError(c, "failed to create washing plan") + return + } + + c.JSON(http.StatusCreated, response.Response{ + Code: 0, + Message: "success", + Data: wp, + }) +} + +func (h *WashingPlanHandler) List(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + plans, err := h.svc.List(c.Request.Context(), companyID) + if err != nil { + log.Printf("list washing plans error: %v", err) + response.InternalError(c, "failed to list washing plans") + return + } + + response.OK(c, plans) +} diff --git a/purchaser-service/internal/handler/ws.go b/purchaser-service/internal/handler/ws.go new file mode 100644 index 0000000..03a3b25 --- /dev/null +++ b/purchaser-service/internal/handler/ws.go @@ -0,0 +1,48 @@ +package handler + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + gorillaWS "github.com/gorilla/websocket" + "github.com/muyuqingfeng/iloom/shared/pkg/websocket" +) + +var upgrader = gorillaWS.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func HandleWebSocket(hub *websocket.Hub) gin.HandlerFunc { + return func(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + companyID = c.Query("company_id") + } + userID := c.GetHeader("X-User-ID") + if userID == "" { + userID = c.Query("user_id") + } + + if companyID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "missing company_id"}) + return + } + + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Printf("websocket upgrade error: %v", err) + return + } + + client := websocket.NewClient(hub, conn, companyID, userID) + client.Register() + + go client.WritePump() + go client.ReadPump() + } +} diff --git a/purchaser-service/internal/model/accounts_payable.go b/purchaser-service/internal/model/accounts_payable.go new file mode 100644 index 0000000..9b6374c --- /dev/null +++ b/purchaser-service/internal/model/accounts_payable.go @@ -0,0 +1,46 @@ +package model + +import "time" + +type AccountsPayable struct { + ID string `json:"id" db:"id"` + PlanID string `json:"plan_id" db:"plan_id"` + PurchaserID string `json:"purchaser_id" db:"purchaser_id"` + TextileFactoryID string `json:"textile_factory_id" db:"textile_factory_id"` + PricePerMeter float64 `json:"price_per_meter" db:"price_per_meter"` + TotalQuantity float64 `json:"total_quantity" db:"total_quantity"` + TotalAmount float64 `json:"total_amount" db:"total_amount"` + PaidQuantity float64 `json:"paid_quantity" db:"paid_quantity"` + PaidAmount float64 `json:"paid_amount" db:"paid_amount"` + UnpaidQuantity float64 `json:"unpaid_quantity" db:"unpaid_quantity"` + UnpaidAmount float64 `json:"unpaid_amount" db:"unpaid_amount"` + Status string `json:"status" db:"status"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` + + // Aggregated + PlanCode string `json:"plan_code,omitempty"` + FactoryName string `json:"factory_name,omitempty"` +} + +type WashingPlan struct { + ID string `json:"id" db:"id"` + PlanCode string `json:"plan_code" db:"plan_code"` + CompanyID string `json:"company_id" db:"company_id"` + ProductID string `json:"product_id" db:"product_id"` + WashingFactoryID *string `json:"washing_factory_id,omitempty" db:"washing_factory_id"` + PlannedMeters float64 `json:"planned_meters" db:"planned_meters"` + WashingPrice float64 `json:"washing_price" db:"washing_price"` + EstimatedShrinkageRate float64 `json:"estimated_shrinkage_rate" db:"estimated_shrinkage_rate"` + EstimatedWashedMeters *float64 `json:"estimated_washed_meters,omitempty" db:"estimated_washed_meters"` + EstimatedTotalCost *float64 `json:"estimated_total_cost,omitempty" db:"estimated_total_cost"` + ActualShrinkageRate *float64 `json:"actual_shrinkage_rate,omitempty" db:"actual_shrinkage_rate"` + ActualWashedMeters *float64 `json:"actual_washed_meters,omitempty" db:"actual_washed_meters"` + ActualTotalCost *float64 `json:"actual_total_cost,omitempty" db:"actual_total_cost"` + WashingDate *time.Time `json:"washing_date,omitempty" db:"washing_date"` + Status string `json:"status" db:"status"` + Notes *string `json:"notes,omitempty" db:"notes"` + CreatedBy *string `json:"created_by,omitempty" db:"created_by"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} diff --git a/purchaser-service/internal/model/company.go b/purchaser-service/internal/model/company.go new file mode 100644 index 0000000..47f9be0 --- /dev/null +++ b/purchaser-service/internal/model/company.go @@ -0,0 +1,16 @@ +package model + +import "time" + +type CompanyRelationship struct { + ID string `json:"id" db:"id"` + PurchaserCompanyID string `json:"purchaser_company_id" db:"purchaser_company_id"` + FactoryCompanyID string `json:"factory_company_id" db:"factory_company_id"` + FactoryType string `json:"factory_type" db:"factory_type"` + Status string `json:"status" db:"status"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` + + // Aggregated + FactoryName string `json:"factory_name,omitempty"` +} diff --git a/purchaser-service/internal/model/plan.go b/purchaser-service/internal/model/plan.go new file mode 100644 index 0000000..90f01e3 --- /dev/null +++ b/purchaser-service/internal/model/plan.go @@ -0,0 +1,84 @@ +package model + +import "time" + +type ProductionPlan struct { + ID string `json:"id" db:"id"` + PlanCode string `json:"plan_code" db:"plan_code"` + PurchaserID string `json:"purchaser_id" db:"purchaser_id"` + ProductName string `json:"product_name" db:"product_name"` + FabricCode string `json:"fabric_code" db:"fabric_code"` + Color string `json:"color" db:"color"` + ColorCode string `json:"color_code" db:"color_code"` + TargetQuantity float64 `json:"target_quantity" db:"target_quantity"` + CompletedQuantity float64 `json:"completed_quantity" db:"completed_quantity"` + ProductionPrice *float64 `json:"production_price,omitempty" db:"production_price"` + YarnUsagePerMeter *float64 `json:"yarn_usage_per_meter,omitempty" db:"yarn_usage_per_meter"` + Status string `json:"status" db:"status"` + Remark *string `json:"remark,omitempty" db:"remark"` + StartTime *time.Time `json:"start_time,omitempty" db:"start_time"` + CreatedBy *string `json:"created_by,omitempty" db:"created_by"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` + + // Aggregated fields + YarnRatios []YarnRatio `json:"yarn_ratios,omitempty"` + ProcessSteps []ProcessStep `json:"process_steps,omitempty"` + Factories []PlanFactory `json:"factories,omitempty"` + InventoryRecords []InventoryRecord `json:"inventory_records,omitempty"` +} + +type YarnRatio struct { + ID string `json:"id" db:"id"` + PlanID string `json:"plan_id" db:"plan_id"` + YarnName string `json:"yarn_name" db:"yarn_name"` + YarnType *string `json:"yarn_type,omitempty" db:"yarn_type"` + Ratio float64 `json:"ratio" db:"ratio"` + AmountPerMeter float64 `json:"amount_per_meter" db:"amount_per_meter"` + TotalAmount float64 `json:"total_amount" db:"total_amount"` + CompanyID *string `json:"company_id,omitempty" db:"company_id"` + CreatedAt time.Time `json:"created_at" db:"created_at"` +} + +type PlanFactory struct { + ID string `json:"id" db:"id"` + PlanID string `json:"plan_id" db:"plan_id"` + FactoryID string `json:"factory_id" db:"factory_id"` + FactoryType string `json:"factory_type" db:"factory_type"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + + // Aggregated + FactoryName string `json:"factory_name,omitempty"` +} + +type ProcessStep struct { + ID string `json:"id" db:"id"` + PlanID string `json:"plan_id" db:"plan_id"` + StepType string `json:"step_type" db:"step_type"` + Status string `json:"status" db:"status"` + Notes *string `json:"notes,omitempty" db:"notes"` + OperatorID *string `json:"operator_id,omitempty" db:"operator_id"` + CompanyID *string `json:"company_id,omitempty" db:"company_id"` + Timestamp *time.Time `json:"timestamp,omitempty" db:"timestamp"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + + // Aggregated + OperatorName string `json:"operator_name,omitempty"` +} + +type InventoryRecord struct { + ID string `json:"id" db:"id"` + PlanID string `json:"plan_id" db:"plan_id"` + WarehouseID string `json:"warehouse_id" db:"warehouse_id"` + Quantity float64 `json:"quantity" db:"quantity"` + Rolls *int `json:"rolls,omitempty" db:"rolls"` + PricePerMeter *float64 `json:"price_per_meter,omitempty" db:"price_per_meter"` + PriceNote *string `json:"price_note,omitempty" db:"price_note"` + WarehouseLocation *string `json:"warehouse_location,omitempty" db:"warehouse_location"` + OperatorID *string `json:"operator_id,omitempty" db:"operator_id"` + CompanyID *string `json:"company_id,omitempty" db:"company_id"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + + // Aggregated + OperatorName string `json:"operator_name,omitempty"` +} diff --git a/purchaser-service/internal/model/product.go b/purchaser-service/internal/model/product.go new file mode 100644 index 0000000..224e3c4 --- /dev/null +++ b/purchaser-service/internal/model/product.go @@ -0,0 +1,66 @@ +package model + +import "time" + +type Product struct { + ID string `json:"id" db:"id"` + CompanyID string `json:"company_id" db:"company_id"` + ProductName string `json:"product_name" db:"product_name"` + FabricCode string `json:"fabric_code" db:"fabric_code"` + Color string `json:"color" db:"color"` + ColorCode string `json:"color_code" db:"color_code"` + Weight float64 `json:"weight" db:"weight"` + WarpWeight *float64 `json:"warp_weight,omitempty" db:"warp_weight"` + WeftWeight *float64 `json:"weft_weight,omitempty" db:"weft_weight"` + TotalWeight *float64 `json:"total_weight,omitempty" db:"total_weight"` + YarnUsagePerMeter float64 `json:"yarn_usage_per_meter" db:"yarn_usage_per_meter"` + ProductionPrice *float64 `json:"production_price,omitempty" db:"production_price"` + TotalStock float64 `json:"total_stock" db:"total_stock"` + RawFabricMeters float64 `json:"raw_fabric_meters" db:"raw_fabric_meters"` + RawFabricRolls int `json:"raw_fabric_rolls" db:"raw_fabric_rolls"` + ImageURL *string `json:"image_url,omitempty" db:"image_url"` + YarnTypes []string `json:"yarn_types,omitempty" db:"yarn_types"` + YarnRatios []float64 `json:"yarn_ratios,omitempty" db:"yarn_ratios"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +type ProductInventoryRecord struct { + ID string `json:"id" db:"id"` + ProductID string `json:"product_id" db:"product_id"` + BatchNo string `json:"batch_no" db:"batch_no"` + Rolls int `json:"rolls" db:"rolls"` + Meters float64 `json:"meters" db:"meters"` + Notes *string `json:"notes,omitempty" db:"notes"` + OperatorID *string `json:"operator_id,omitempty" db:"operator_id"` + CompanyID *string `json:"company_id,omitempty" db:"company_id"` + WarehouseID *string `json:"warehouse_id,omitempty" db:"warehouse_id"` + CreatedAt time.Time `json:"created_at" db:"created_at"` +} + +type ProductOutboundRecord struct { + ID string `json:"id" db:"id"` + ProductID string `json:"product_id" db:"product_id"` + BatchNo string `json:"batch_no" db:"batch_no"` + Rolls int `json:"rolls" db:"rolls"` + Meters float64 `json:"meters" db:"meters"` + OutboundType string `json:"outbound_type" db:"outbound_type"` + Notes *string `json:"notes,omitempty" db:"notes"` + OperatorID *string `json:"operator_id,omitempty" db:"operator_id"` + CompanyID *string `json:"company_id,omitempty" db:"company_id"` + RecipientCompanyID *string `json:"recipient_company_id,omitempty" db:"recipient_company_id"` + RecipientCompanyName *string `json:"recipient_company_name,omitempty" db:"recipient_company_name"` + SourceBatchID *string `json:"source_batch_id,omitempty" db:"source_batch_id"` + WashingPlanID *string `json:"washing_plan_id,omitempty" db:"washing_plan_id"` + CreatedAt time.Time `json:"created_at" db:"created_at"` +} + +type ProductPriceHistory struct { + ID string `json:"id" db:"id"` + ProductID string `json:"product_id" db:"product_id"` + OldPrice *float64 `json:"old_price,omitempty" db:"old_price"` + NewPrice float64 `json:"new_price" db:"new_price"` + Reason *string `json:"reason,omitempty" db:"reason"` + ChangedBy *string `json:"changed_by,omitempty" db:"changed_by"` + CreatedAt time.Time `json:"created_at" db:"created_at"` +} diff --git a/purchaser-service/internal/repository/ap_repo.go b/purchaser-service/internal/repository/ap_repo.go new file mode 100644 index 0000000..a369e1a --- /dev/null +++ b/purchaser-service/internal/repository/ap_repo.go @@ -0,0 +1,86 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + + "github.com/muyuqingfeng/iloom/purchaser-service/internal/model" +) + +type APRepo struct { + db *sql.DB +} + +func NewAPRepo(db *sql.DB) *APRepo { + return &APRepo{db: db} +} + +func (r *APRepo) List(ctx context.Context, companyID string) ([]model.AccountsPayable, error) { + query := `SELECT ap.id, ap.plan_id, ap.purchaser_id, ap.textile_factory_id, + ap.price_per_meter, ap.total_quantity, ap.total_amount, + ap.paid_quantity, ap.paid_amount, ap.unpaid_quantity, ap.unpaid_amount, + ap.status, ap.created_at, ap.updated_at, + COALESCE(pp.plan_code, '') as plan_code, + COALESCE(c.name, '') as factory_name + FROM ilm_accounts_payable ap + LEFT JOIN ilm_production_plan pp ON pp.id = ap.plan_id + LEFT JOIN ilm_company c ON c.id = ap.textile_factory_id + WHERE ap.purchaser_id = ? + ORDER BY ap.created_at DESC` + + rows, err := r.db.QueryContext(ctx, query, companyID) + if err != nil { + return nil, fmt.Errorf("list accounts payable: %w", err) + } + defer rows.Close() + + var list []model.AccountsPayable + for rows.Next() { + var ap model.AccountsPayable + if err := rows.Scan( + &ap.ID, &ap.PlanID, &ap.PurchaserID, &ap.TextileFactoryID, + &ap.PricePerMeter, &ap.TotalQuantity, &ap.TotalAmount, + &ap.PaidQuantity, &ap.PaidAmount, &ap.UnpaidQuantity, &ap.UnpaidAmount, + &ap.Status, &ap.CreatedAt, &ap.UpdatedAt, + &ap.PlanCode, &ap.FactoryName, + ); err != nil { + return nil, fmt.Errorf("scan accounts payable: %w", err) + } + list = append(list, ap) + } + return list, nil +} + +func (r *APRepo) Pay(ctx context.Context, tx *sql.Tx, apID string, amount float64) error { + query := `UPDATE ilm_accounts_payable SET + paid_amount = paid_amount + ?, + unpaid_amount = unpaid_amount - ?, + status = CASE WHEN unpaid_amount - ? <= 0 THEN 'completed' ELSE status END, + updated_at = NOW() + WHERE id = ? AND unpaid_amount >= ?` + result, err := tx.ExecContext(ctx, query, amount, amount, amount, apID, amount) + if err != nil { + return fmt.Errorf("pay: %w", err) + } + rowsAffected, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("check rows affected: %w", err) + } + if rowsAffected == 0 { + return fmt.Errorf("payment failed: insufficient unpaid amount or record not found") + } + return nil +} + +func (r *APRepo) TotalUnpaid(ctx context.Context, companyID string) (float64, error) { + var total float64 + err := r.db.QueryRowContext(ctx, + `SELECT COALESCE(SUM(unpaid_amount), 0) FROM ilm_accounts_payable WHERE purchaser_id = ?`, + companyID, + ).Scan(&total) + if err != nil { + return 0, fmt.Errorf("total unpaid: %w", err) + } + return total, nil +} diff --git a/purchaser-service/internal/repository/factory_repo.go b/purchaser-service/internal/repository/factory_repo.go new file mode 100644 index 0000000..e8f5373 --- /dev/null +++ b/purchaser-service/internal/repository/factory_repo.go @@ -0,0 +1,47 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + + "github.com/muyuqingfeng/iloom/purchaser-service/internal/model" +) + +type FactoryRepo struct { + db *sql.DB +} + +func NewFactoryRepo(db *sql.DB) *FactoryRepo { + return &FactoryRepo{db: db} +} + +func (r *FactoryRepo) ListRelationships(ctx context.Context, companyID string) ([]model.CompanyRelationship, error) { + query := `SELECT cr.id, cr.purchaser_company_id, cr.factory_company_id, + cr.factory_type, cr.status, cr.created_at, cr.updated_at, + COALESCE(c.name, '') as factory_name + FROM ilm_company_relationship cr + LEFT JOIN ilm_company c ON c.id = cr.factory_company_id + WHERE cr.purchaser_company_id = ? AND cr.status = 'active' + ORDER BY cr.created_at DESC` + + rows, err := r.db.QueryContext(ctx, query, companyID) + if err != nil { + return nil, fmt.Errorf("list relationships: %w", err) + } + defer rows.Close() + + var rels []model.CompanyRelationship + for rows.Next() { + var rel model.CompanyRelationship + if err := rows.Scan( + &rel.ID, &rel.PurchaserCompanyID, &rel.FactoryCompanyID, + &rel.FactoryType, &rel.Status, &rel.CreatedAt, &rel.UpdatedAt, + &rel.FactoryName, + ); err != nil { + return nil, fmt.Errorf("scan relationship: %w", err) + } + rels = append(rels, rel) + } + return rels, nil +} diff --git a/purchaser-service/internal/repository/plan_repo.go b/purchaser-service/internal/repository/plan_repo.go new file mode 100644 index 0000000..209437e --- /dev/null +++ b/purchaser-service/internal/repository/plan_repo.go @@ -0,0 +1,324 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/model" + "github.com/muyuqingfeng/iloom/shared/pkg/types" +) + +type PlanRepo struct { + db *sql.DB +} + +func NewPlanRepo(db *sql.DB) *PlanRepo { + return &PlanRepo{db: db} +} + +func (r *PlanRepo) List(ctx context.Context, companyID string, p types.Pagination, status string) ([]model.ProductionPlan, int, error) { + countQuery := `SELECT COUNT(*) FROM ilm_production_plan WHERE purchaser_id = ?` + args := []interface{}{companyID} + + if status != "" { + countQuery += " AND status = ?" + args = append(args, status) + } + + var total int + if err := r.db.QueryRowContext(ctx, countQuery, args...).Scan(&total); err != nil { + return nil, 0, fmt.Errorf("count plans: %w", err) + } + + query := `SELECT id, plan_code, purchaser_id, product_name, fabric_code, color, color_code, + target_quantity, completed_quantity, production_price, yarn_usage_per_meter, + status, remark, start_time, created_by, created_at, updated_at + FROM ilm_production_plan WHERE purchaser_id = ?` + + queryArgs := []interface{}{companyID} + + if status != "" { + query += " AND status = ?" + queryArgs = append(queryArgs, status) + } + + query += " ORDER BY created_at DESC" + query += " LIMIT ? OFFSET ?" + queryArgs = append(queryArgs, p.PageSize, p.Offset()) + + rows, err := r.db.QueryContext(ctx, query, queryArgs...) + if err != nil { + return nil, 0, fmt.Errorf("list plans: %w", err) + } + defer rows.Close() + + var plans []model.ProductionPlan + for rows.Next() { + var plan model.ProductionPlan + if err := rows.Scan( + &plan.ID, &plan.PlanCode, &plan.PurchaserID, &plan.ProductName, + &plan.FabricCode, &plan.Color, &plan.ColorCode, + &plan.TargetQuantity, &plan.CompletedQuantity, + &plan.ProductionPrice, &plan.YarnUsagePerMeter, + &plan.Status, &plan.Remark, &plan.StartTime, + &plan.CreatedBy, &plan.CreatedAt, &plan.UpdatedAt, + ); err != nil { + return nil, 0, fmt.Errorf("scan plan: %w", err) + } + plans = append(plans, plan) + } + return plans, total, nil +} + +func (r *PlanRepo) Create(ctx context.Context, tx *sql.Tx, plan *model.ProductionPlan) error { + query := `INSERT INTO ilm_production_plan + (id, plan_code, purchaser_id, product_name, fabric_code, color, color_code, + target_quantity, completed_quantity, production_price, yarn_usage_per_meter, + status, remark, start_time, created_by, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)` + _, err := tx.ExecContext(ctx, query, + plan.ID, plan.PlanCode, plan.PurchaserID, plan.ProductName, + plan.FabricCode, plan.Color, plan.ColorCode, + plan.TargetQuantity, plan.CompletedQuantity, + plan.ProductionPrice, plan.YarnUsagePerMeter, + plan.Status, plan.Remark, plan.StartTime, + plan.CreatedBy, plan.CreatedAt, plan.UpdatedAt, + ) + if err != nil { + return fmt.Errorf("insert plan: %w", err) + } + return nil +} + +func (r *PlanRepo) CreateYarnRatios(ctx context.Context, tx *sql.Tx, ratios []model.YarnRatio) error { + for _, ratio := range ratios { + query := `INSERT INTO ilm_yarn_ratio + (id, plan_id, yarn_name, yarn_type, ratio, amount_per_meter, total_amount, company_id, created_at) + VALUES (?,?,?,?,?,?,?,?,?)` + _, err := tx.ExecContext(ctx, query, + ratio.ID, ratio.PlanID, ratio.YarnName, ratio.YarnType, + ratio.Ratio, ratio.AmountPerMeter, ratio.TotalAmount, + ratio.CompanyID, ratio.CreatedAt, + ) + if err != nil { + return fmt.Errorf("insert yarn ratio: %w", err) + } + } + return nil +} + +func (r *PlanRepo) GetByID(ctx context.Context, id string) (*model.ProductionPlan, error) { + query := `SELECT id, plan_code, purchaser_id, product_name, fabric_code, color, color_code, + target_quantity, completed_quantity, production_price, yarn_usage_per_meter, + status, remark, start_time, created_by, created_at, updated_at + FROM ilm_production_plan WHERE id = ?` + + plan := &model.ProductionPlan{} + err := r.db.QueryRowContext(ctx, query, id).Scan( + &plan.ID, &plan.PlanCode, &plan.PurchaserID, &plan.ProductName, + &plan.FabricCode, &plan.Color, &plan.ColorCode, + &plan.TargetQuantity, &plan.CompletedQuantity, + &plan.ProductionPrice, &plan.YarnUsagePerMeter, + &plan.Status, &plan.Remark, &plan.StartTime, + &plan.CreatedBy, &plan.CreatedAt, &plan.UpdatedAt, + ) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, fmt.Errorf("get plan by id: %w", err) + } + + // Load yarn ratios + yarnRows, err := r.db.QueryContext(ctx, + `SELECT id, plan_id, yarn_name, yarn_type, ratio, amount_per_meter, total_amount, company_id, created_at + FROM ilm_yarn_ratio WHERE plan_id = ? ORDER BY created_at`, id) + if err != nil { + return nil, fmt.Errorf("get yarn ratios: %w", err) + } + defer yarnRows.Close() + + for yarnRows.Next() { + var yr model.YarnRatio + if err := yarnRows.Scan( + &yr.ID, &yr.PlanID, &yr.YarnName, &yr.YarnType, + &yr.Ratio, &yr.AmountPerMeter, &yr.TotalAmount, + &yr.CompanyID, &yr.CreatedAt, + ); err != nil { + return nil, fmt.Errorf("scan yarn ratio: %w", err) + } + plan.YarnRatios = append(plan.YarnRatios, yr) + } + + // Load factories + factoryRows, err := r.db.QueryContext(ctx, + `SELECT pf.id, pf.plan_id, pf.factory_id, pf.factory_type, pf.created_at, + COALESCE(c.name, '') as factory_name + FROM ilm_plan_factory pf + LEFT JOIN ilm_company c ON c.id = pf.factory_id + WHERE pf.plan_id = ? ORDER BY pf.created_at`, id) + if err != nil { + return nil, fmt.Errorf("get plan factories: %w", err) + } + defer factoryRows.Close() + + for factoryRows.Next() { + var pf model.PlanFactory + if err := factoryRows.Scan( + &pf.ID, &pf.PlanID, &pf.FactoryID, &pf.FactoryType, + &pf.CreatedAt, &pf.FactoryName, + ); err != nil { + return nil, fmt.Errorf("scan plan factory: %w", err) + } + plan.Factories = append(plan.Factories, pf) + } + + return plan, nil +} + +func (r *PlanRepo) Update(ctx context.Context, plan *model.ProductionPlan) error { + query := `UPDATE ilm_production_plan SET + product_name = ?, fabric_code = ?, color = ?, color_code = ?, + target_quantity = ?, production_price = ?, yarn_usage_per_meter = ?, + status = ?, remark = ?, updated_at = ? + WHERE id = ?` + _, err := r.db.ExecContext(ctx, query, + plan.ProductName, plan.FabricCode, plan.Color, plan.ColorCode, + plan.TargetQuantity, plan.ProductionPrice, plan.YarnUsagePerMeter, + plan.Status, plan.Remark, time.Now(), + plan.ID, + ) + if err != nil { + return fmt.Errorf("update plan: %w", err) + } + return nil +} + +func (r *PlanRepo) Delete(ctx context.Context, id, companyID string) error { + _, err := r.db.ExecContext(ctx, + `DELETE FROM ilm_production_plan WHERE id = ? AND purchaser_id = ?`, + id, companyID, + ) + if err != nil { + return fmt.Errorf("delete plan: %w", err) + } + return nil +} + +func (r *PlanRepo) AssignFactory(ctx context.Context, planID, factoryID, factoryType string) error { + id := uuid.New().String() + _, err := r.db.ExecContext(ctx, + `INSERT INTO ilm_plan_factory (id, plan_id, factory_id, factory_type, created_at) + VALUES (?, ?, ?, ?, ?)`, + id, planID, factoryID, factoryType, time.Now(), + ) + if err != nil { + return fmt.Errorf("assign factory: %w", err) + } + return nil +} + +func (r *PlanRepo) GetSteps(ctx context.Context, planID string) ([]model.ProcessStep, error) { + rows, err := r.db.QueryContext(ctx, + `SELECT ps.id, ps.plan_id, ps.step_type, ps.status, ps.notes, + ps.operator_id, ps.company_id, ps.timestamp, ps.created_at, + COALESCE(su.real_name, '') as operator_name + FROM ilm_process_step ps + LEFT JOIN sys_user su ON su.user_id = ps.operator_id + WHERE ps.plan_id = ? ORDER BY ps.created_at`, planID) + if err != nil { + return nil, fmt.Errorf("get steps: %w", err) + } + defer rows.Close() + + var steps []model.ProcessStep + for rows.Next() { + var s model.ProcessStep + if err := rows.Scan( + &s.ID, &s.PlanID, &s.StepType, &s.Status, &s.Notes, + &s.OperatorID, &s.CompanyID, &s.Timestamp, &s.CreatedAt, + &s.OperatorName, + ); err != nil { + return nil, fmt.Errorf("scan step: %w", err) + } + steps = append(steps, s) + } + return steps, nil +} + +func (r *PlanRepo) GetInventory(ctx context.Context, planID string) ([]model.InventoryRecord, error) { + rows, err := r.db.QueryContext(ctx, + `SELECT ir.id, ir.plan_id, ir.warehouse_id, ir.quantity, ir.rolls, + ir.price_per_meter, ir.price_note, ir.warehouse_location, + ir.operator_id, ir.company_id, ir.created_at, + COALESCE(su.real_name, '') as operator_name + FROM ilm_inventory_record ir + LEFT JOIN sys_user su ON su.user_id = ir.operator_id + WHERE ir.plan_id = ? ORDER BY ir.created_at DESC`, planID) + if err != nil { + return nil, fmt.Errorf("get inventory: %w", err) + } + defer rows.Close() + + var records []model.InventoryRecord + for rows.Next() { + var rec model.InventoryRecord + if err := rows.Scan( + &rec.ID, &rec.PlanID, &rec.WarehouseID, &rec.Quantity, &rec.Rolls, + &rec.PricePerMeter, &rec.PriceNote, &rec.WarehouseLocation, + &rec.OperatorID, &rec.CompanyID, &rec.CreatedAt, + &rec.OperatorName, + ); err != nil { + return nil, fmt.Errorf("scan inventory record: %w", err) + } + records = append(records, rec) + } + return records, nil +} + +func (r *PlanRepo) CountByStatus(ctx context.Context, companyID string) (total, producing, completed int, err error) { + query := `SELECT + COUNT(*), + SUM(CASE WHEN status = 'producing' THEN 1 ELSE 0 END), + SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) + FROM ilm_production_plan WHERE purchaser_id = ?` + err = r.db.QueryRowContext(ctx, query, companyID).Scan(&total, &producing, &completed) + if err != nil { + return 0, 0, 0, fmt.Errorf("count by status: %w", err) + } + return +} + +func (r *PlanRepo) Recent(ctx context.Context, companyID string, limit int) ([]model.ProductionPlan, error) { + query := `SELECT id, plan_code, purchaser_id, product_name, fabric_code, color, color_code, + target_quantity, completed_quantity, production_price, yarn_usage_per_meter, + status, remark, start_time, created_by, created_at, updated_at + FROM ilm_production_plan WHERE purchaser_id = ? + ORDER BY created_at DESC LIMIT ?` + + rows, err := r.db.QueryContext(ctx, query, companyID, limit) + if err != nil { + return nil, fmt.Errorf("recent plans: %w", err) + } + defer rows.Close() + + var plans []model.ProductionPlan + for rows.Next() { + var plan model.ProductionPlan + if err := rows.Scan( + &plan.ID, &plan.PlanCode, &plan.PurchaserID, &plan.ProductName, + &plan.FabricCode, &plan.Color, &plan.ColorCode, + &plan.TargetQuantity, &plan.CompletedQuantity, + &plan.ProductionPrice, &plan.YarnUsagePerMeter, + &plan.Status, &plan.Remark, &plan.StartTime, + &plan.CreatedBy, &plan.CreatedAt, &plan.UpdatedAt, + ); err != nil { + return nil, fmt.Errorf("scan recent plan: %w", err) + } + plans = append(plans, plan) + } + return plans, nil +} diff --git a/purchaser-service/internal/repository/product_repo.go b/purchaser-service/internal/repository/product_repo.go new file mode 100644 index 0000000..dcf5896 --- /dev/null +++ b/purchaser-service/internal/repository/product_repo.go @@ -0,0 +1,264 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/model" +) + +type ProductRepo struct { + db *sql.DB +} + +func NewProductRepo(db *sql.DB) *ProductRepo { + return &ProductRepo{db: db} +} + +func (r *ProductRepo) List(ctx context.Context, companyID string) ([]model.Product, error) { + query := `SELECT id, company_id, product_name, fabric_code, color, color_code, + weight, warp_weight, weft_weight, total_weight, yarn_usage_per_meter, + production_price, total_stock, raw_fabric_meters, raw_fabric_rolls, + image_url, yarn_types, yarn_ratios, created_at, updated_at + FROM products WHERE company_id = ? ORDER BY created_at DESC` + + rows, err := r.db.QueryContext(ctx, query, companyID) + if err != nil { + return nil, fmt.Errorf("list products: %w", err) + } + defer rows.Close() + + var products []model.Product + for rows.Next() { + var p model.Product + if err := rows.Scan( + &p.ID, &p.CompanyID, &p.ProductName, &p.FabricCode, &p.Color, &p.ColorCode, + &p.Weight, &p.WarpWeight, &p.WeftWeight, &p.TotalWeight, + &p.YarnUsagePerMeter, &p.ProductionPrice, + &p.TotalStock, &p.RawFabricMeters, &p.RawFabricRolls, + &p.ImageURL, &p.YarnTypes, &p.YarnRatios, + &p.CreatedAt, &p.UpdatedAt, + ); err != nil { + return nil, fmt.Errorf("scan product: %w", err) + } + products = append(products, p) + } + return products, nil +} + +func (r *ProductRepo) Create(ctx context.Context, p *model.Product) error { + query := `INSERT INTO products + (id, company_id, product_name, fabric_code, color, color_code, + weight, warp_weight, weft_weight, total_weight, yarn_usage_per_meter, + production_price, total_stock, raw_fabric_meters, raw_fabric_rolls, + image_url, yarn_types, yarn_ratios, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)` + _, err := r.db.ExecContext(ctx, query, + p.ID, p.CompanyID, p.ProductName, p.FabricCode, p.Color, p.ColorCode, + p.Weight, p.WarpWeight, p.WeftWeight, p.TotalWeight, + p.YarnUsagePerMeter, p.ProductionPrice, + p.TotalStock, p.RawFabricMeters, p.RawFabricRolls, + p.ImageURL, p.YarnTypes, p.YarnRatios, + p.CreatedAt, p.UpdatedAt, + ) + if err != nil { + return fmt.Errorf("insert product: %w", err) + } + return nil +} + +func (r *ProductRepo) Update(ctx context.Context, p *model.Product) error { + query := `UPDATE products SET + product_name = ?, fabric_code = ?, color = ?, color_code = ?, + weight = ?, warp_weight = ?, weft_weight = ?, total_weight = ?, + yarn_usage_per_meter = ?, production_price = ?, + image_url = ?, yarn_types = ?, yarn_ratios = ?, updated_at = ? + WHERE id = ?` + _, err := r.db.ExecContext(ctx, query, + p.ProductName, p.FabricCode, p.Color, p.ColorCode, + p.Weight, p.WarpWeight, p.WeftWeight, p.TotalWeight, + p.YarnUsagePerMeter, p.ProductionPrice, + p.ImageURL, p.YarnTypes, p.YarnRatios, time.Now(), + p.ID, + ) + if err != nil { + return fmt.Errorf("update product: %w", err) + } + return nil +} + +func (r *ProductRepo) Delete(ctx context.Context, id, companyID string) error { + _, err := r.db.ExecContext(ctx, + `DELETE FROM products WHERE id = ? AND company_id = ?`, + id, companyID, + ) + if err != nil { + return fmt.Errorf("delete product: %w", err) + } + return nil +} + +func (r *ProductRepo) Inbound(ctx context.Context, tx *sql.Tx, rec *model.ProductInventoryRecord) error { + query := `INSERT INTO ilm_product_inventory + (id, product_id, batch_no, rolls, meters, notes, operator_id, company_id, warehouse_id, created_at) + VALUES (?,?,?,?,?,?,?,?,?,?)` + _, err := tx.ExecContext(ctx, query, + rec.ID, rec.ProductID, rec.BatchNo, rec.Rolls, rec.Meters, + rec.Notes, rec.OperatorID, rec.CompanyID, rec.WarehouseID, rec.CreatedAt, + ) + if err != nil { + return fmt.Errorf("insert inbound record: %w", err) + } + return nil +} + +func (r *ProductRepo) UpdateStock(ctx context.Context, tx *sql.Tx, productID string, deltaMeters float64, deltaRolls int) error { + query := `UPDATE products SET + total_stock = total_stock + ?, + raw_fabric_meters = raw_fabric_meters + ?, + raw_fabric_rolls = raw_fabric_rolls + ?, + updated_at = ? + WHERE id = ?` + _, err := tx.ExecContext(ctx, query, deltaMeters, deltaMeters, deltaRolls, time.Now(), productID) + if err != nil { + return fmt.Errorf("update stock: %w", err) + } + return nil +} + +func (r *ProductRepo) Outbound(ctx context.Context, tx *sql.Tx, rec *model.ProductOutboundRecord) error { + query := `INSERT INTO ilm_product_outbound + (id, product_id, batch_no, rolls, meters, outbound_type, notes, + operator_id, company_id, recipient_company_id, recipient_company_name, + source_batch_id, washing_plan_id, created_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)` + _, err := tx.ExecContext(ctx, query, + rec.ID, rec.ProductID, rec.BatchNo, rec.Rolls, rec.Meters, + rec.OutboundType, rec.Notes, + rec.OperatorID, rec.CompanyID, + rec.RecipientCompanyID, rec.RecipientCompanyName, + rec.SourceBatchID, rec.WashingPlanID, rec.CreatedAt, + ) + if err != nil { + return fmt.Errorf("insert outbound record: %w", err) + } + return nil +} + +func (r *ProductRepo) Records(ctx context.Context, productID string) ([]model.ProductInventoryRecord, error) { + query := `SELECT id, product_id, batch_no, rolls, meters, notes, + operator_id, company_id, warehouse_id, created_at + FROM ilm_product_inventory + WHERE product_id = ? ORDER BY created_at DESC` + rows, err := r.db.QueryContext(ctx, query, productID) + if err != nil { + return nil, fmt.Errorf("list records: %w", err) + } + defer rows.Close() + + var records []model.ProductInventoryRecord + for rows.Next() { + var rec model.ProductInventoryRecord + if err := rows.Scan( + &rec.ID, &rec.ProductID, &rec.BatchNo, &rec.Rolls, &rec.Meters, + &rec.Notes, &rec.OperatorID, &rec.CompanyID, &rec.WarehouseID, &rec.CreatedAt, + ); err != nil { + return nil, fmt.Errorf("scan record: %w", err) + } + records = append(records, rec) + } + return records, nil +} + +func (r *ProductRepo) UpdatePrice(ctx context.Context, tx *sql.Tx, productID string, newPrice float64) error { + _, err := tx.ExecContext(ctx, + `UPDATE products SET production_price = ?, updated_at = ? WHERE id = ?`, + newPrice, time.Now(), productID, + ) + if err != nil { + return fmt.Errorf("update price: %w", err) + } + return nil +} + +func (r *ProductRepo) GetPrice(ctx context.Context, productID string) (*float64, error) { + var price *float64 + err := r.db.QueryRowContext(ctx, + `SELECT production_price FROM products WHERE id = ?`, productID, + ).Scan(&price) + if err != nil { + return nil, fmt.Errorf("get price: %w", err) + } + return price, nil +} + +func (r *ProductRepo) InsertPriceHistory(ctx context.Context, tx *sql.Tx, productID string, oldPrice *float64, newPrice float64, reason, changedBy string) error { + id := uuid.New().String() + var reasonPtr *string + if reason != "" { + reasonPtr = &reason + } + var changedByPtr *string + if changedBy != "" { + changedByPtr = &changedBy + } + _, err := tx.ExecContext(ctx, + `INSERT INTO ilm_product_price_history + (id, product_id, old_price, new_price, reason, changed_by, created_at) + VALUES (?,?,?,?,?,?,?)`, + id, productID, oldPrice, newPrice, reasonPtr, changedByPtr, time.Now(), + ) + if err != nil { + return fmt.Errorf("insert price history: %w", err) + } + return nil +} + +func (r *ProductRepo) PriceHistory(ctx context.Context, productID string) ([]model.ProductPriceHistory, error) { + query := `SELECT id, product_id, old_price, new_price, reason, changed_by, created_at + FROM ilm_product_price_history + WHERE product_id = ? ORDER BY created_at DESC` + rows, err := r.db.QueryContext(ctx, query, productID) + if err != nil { + return nil, fmt.Errorf("list price history: %w", err) + } + defer rows.Close() + + var history []model.ProductPriceHistory + for rows.Next() { + var h model.ProductPriceHistory + if err := rows.Scan( + &h.ID, &h.ProductID, &h.OldPrice, &h.NewPrice, + &h.Reason, &h.ChangedBy, &h.CreatedAt, + ); err != nil { + return nil, fmt.Errorf("scan price history: %w", err) + } + history = append(history, h) + } + return history, nil +} + +func (r *ProductRepo) Count(ctx context.Context, companyID string) (int, error) { + var count int + err := r.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM products WHERE company_id = ?`, companyID, + ).Scan(&count) + if err != nil { + return 0, fmt.Errorf("count products: %w", err) + } + return count, nil +} + +func (r *ProductRepo) TotalStock(ctx context.Context, companyID string) (float64, error) { + var total float64 + err := r.db.QueryRowContext(ctx, + `SELECT COALESCE(SUM(total_stock), 0) FROM products WHERE company_id = ?`, companyID, + ).Scan(&total) + if err != nil { + return 0, fmt.Errorf("total stock: %w", err) + } + return total, nil +} diff --git a/purchaser-service/internal/repository/washing_plan_repo.go b/purchaser-service/internal/repository/washing_plan_repo.go new file mode 100644 index 0000000..daa431c --- /dev/null +++ b/purchaser-service/internal/repository/washing_plan_repo.go @@ -0,0 +1,86 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/muyuqingfeng/iloom/purchaser-service/internal/model" +) + +type WashingPlanRepo struct { + db *sql.DB +} + +func NewWashingPlanRepo(db *sql.DB) *WashingPlanRepo { + return &WashingPlanRepo{db: db} +} + +func (r *WashingPlanRepo) Create(ctx context.Context, wp *model.WashingPlan) error { + query := `INSERT INTO ilm_washing_plan + (id, plan_code, company_id, product_id, washing_factory_id, + planned_meters, washing_price, estimated_shrinkage_rate, + estimated_washed_meters, estimated_total_cost, + status, notes, created_by, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)` + _, err := r.db.ExecContext(ctx, query, + wp.ID, wp.PlanCode, wp.CompanyID, wp.ProductID, wp.WashingFactoryID, + wp.PlannedMeters, wp.WashingPrice, wp.EstimatedShrinkageRate, + wp.EstimatedWashedMeters, wp.EstimatedTotalCost, + wp.Status, wp.Notes, wp.CreatedBy, wp.CreatedAt, wp.UpdatedAt, + ) + if err != nil { + return fmt.Errorf("insert washing plan: %w", err) + } + return nil +} + +func (r *WashingPlanRepo) List(ctx context.Context, companyID string) ([]model.WashingPlan, error) { + query := `SELECT id, plan_code, company_id, product_id, washing_factory_id, + planned_meters, washing_price, estimated_shrinkage_rate, + estimated_washed_meters, estimated_total_cost, + actual_shrinkage_rate, actual_washed_meters, actual_total_cost, + washing_date, status, notes, created_by, created_at, updated_at + FROM ilm_washing_plan WHERE company_id = ? + ORDER BY created_at DESC` + + rows, err := r.db.QueryContext(ctx, query, companyID) + if err != nil { + return nil, fmt.Errorf("list washing plans: %w", err) + } + defer rows.Close() + + var plans []model.WashingPlan + for rows.Next() { + var wp model.WashingPlan + if err := rows.Scan( + &wp.ID, &wp.PlanCode, &wp.CompanyID, &wp.ProductID, &wp.WashingFactoryID, + &wp.PlannedMeters, &wp.WashingPrice, &wp.EstimatedShrinkageRate, + &wp.EstimatedWashedMeters, &wp.EstimatedTotalCost, + &wp.ActualShrinkageRate, &wp.ActualWashedMeters, &wp.ActualTotalCost, + &wp.WashingDate, &wp.Status, &wp.Notes, &wp.CreatedBy, + &wp.CreatedAt, &wp.UpdatedAt, + ); err != nil { + return nil, fmt.Errorf("scan washing plan: %w", err) + } + plans = append(plans, wp) + } + return plans, nil +} + +func (r *WashingPlanRepo) GeneratePlanCode(ctx context.Context) (string, error) { + today := time.Now().Format("20060102") + prefix := "WP" + today + + var count int + err := r.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM ilm_washing_plan WHERE plan_code LIKE ?`, + prefix+"%", + ).Scan(&count) + if err != nil { + return "", fmt.Errorf("generate plan code: %w", err) + } + + return fmt.Sprintf("%s%03d", prefix, count+1), nil +} diff --git a/purchaser-service/internal/service/ap_service.go b/purchaser-service/internal/service/ap_service.go new file mode 100644 index 0000000..e38d3c7 --- /dev/null +++ b/purchaser-service/internal/service/ap_service.go @@ -0,0 +1,41 @@ +package service + +import ( + "context" + "database/sql" + "fmt" + + "github.com/muyuqingfeng/iloom/purchaser-service/internal/model" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/repository" +) + +type APService struct { + repo *repository.APRepo + db *sql.DB +} + +func NewAPService(repo *repository.APRepo, db *sql.DB) *APService { + return &APService{repo: repo, db: db} +} + +func (s *APService) List(ctx context.Context, companyID string) ([]model.AccountsPayable, error) { + return s.repo.List(ctx, companyID) +} + +func (s *APService) Pay(ctx context.Context, apID, companyID string, amount float64) error { + if amount <= 0 { + return fmt.Errorf("amount must be positive") + } + + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("begin tx: %w", err) + } + defer tx.Rollback() + + if err := s.repo.Pay(ctx, tx, apID, amount); err != nil { + return err + } + + return tx.Commit() +} diff --git a/purchaser-service/internal/service/dashboard_service.go b/purchaser-service/internal/service/dashboard_service.go new file mode 100644 index 0000000..a0635dd --- /dev/null +++ b/purchaser-service/internal/service/dashboard_service.go @@ -0,0 +1,69 @@ +package service + +import ( + "context" + + "github.com/muyuqingfeng/iloom/purchaser-service/internal/model" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/repository" +) + +type DashboardStats struct { + TotalPlans int `json:"total_plans"` + ProducingPlans int `json:"producing_plans"` + CompletedPlans int `json:"completed_plans"` + TotalProducts int `json:"total_products"` + TotalStock float64 `json:"total_stock"` + UnpaidAmount float64 `json:"unpaid_amount"` + RecentPlans []model.ProductionPlan `json:"recent_plans"` +} + +type DashboardService struct { + planRepo *repository.PlanRepo + productRepo *repository.ProductRepo + apRepo *repository.APRepo +} + +func NewDashboardService(planRepo *repository.PlanRepo, productRepo *repository.ProductRepo, apRepo *repository.APRepo) *DashboardService { + return &DashboardService{ + planRepo: planRepo, + productRepo: productRepo, + apRepo: apRepo, + } +} + +func (s *DashboardService) Stats(ctx context.Context, companyID string) (*DashboardStats, error) { + total, producing, completed, err := s.planRepo.CountByStatus(ctx, companyID) + if err != nil { + return nil, err + } + + productCount, err := s.productRepo.Count(ctx, companyID) + if err != nil { + return nil, err + } + + totalStock, err := s.productRepo.TotalStock(ctx, companyID) + if err != nil { + return nil, err + } + + unpaid, err := s.apRepo.TotalUnpaid(ctx, companyID) + if err != nil { + return nil, err + } + + recentPlans, err := s.planRepo.Recent(ctx, companyID, 5) + if err != nil { + return nil, err + } + + return &DashboardStats{ + TotalPlans: total, + ProducingPlans: producing, + CompletedPlans: completed, + TotalProducts: productCount, + TotalStock: totalStock, + UnpaidAmount: unpaid, + RecentPlans: recentPlans, + }, nil +} diff --git a/purchaser-service/internal/service/factory_service.go b/purchaser-service/internal/service/factory_service.go new file mode 100644 index 0000000..1448aca --- /dev/null +++ b/purchaser-service/internal/service/factory_service.go @@ -0,0 +1,20 @@ +package service + +import ( + "context" + + "github.com/muyuqingfeng/iloom/purchaser-service/internal/model" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/repository" +) + +type FactoryService struct { + repo *repository.FactoryRepo +} + +func NewFactoryService(repo *repository.FactoryRepo) *FactoryService { + return &FactoryService{repo: repo} +} + +func (s *FactoryService) List(ctx context.Context, companyID string) ([]model.CompanyRelationship, error) { + return s.repo.ListRelationships(ctx, companyID) +} diff --git a/purchaser-service/internal/service/plan_service.go b/purchaser-service/internal/service/plan_service.go new file mode 100644 index 0000000..f9b585f --- /dev/null +++ b/purchaser-service/internal/service/plan_service.go @@ -0,0 +1,213 @@ +package service + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/model" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/repository" + "github.com/muyuqingfeng/iloom/shared/pkg/types" +) + +type PlanService struct { + repo *repository.PlanRepo + db *sql.DB +} + +func NewPlanService(repo *repository.PlanRepo, db *sql.DB) *PlanService { + return &PlanService{repo: repo, db: db} +} + +type CreatePlanRequest struct { + ProductName string `json:"product_name"` + FabricCode string `json:"fabric_code"` + Color string `json:"color"` + ColorCode string `json:"color_code"` + TargetQuantity float64 `json:"target_quantity"` + ProductionPrice *float64 `json:"production_price"` + YarnUsagePerMeter *float64 `json:"yarn_usage_per_meter"` + Remark string `json:"remark"` + YarnRatios []CreateYarnRatio `json:"yarn_ratios"` +} + +type CreateYarnRatio struct { + YarnName string `json:"yarn_name"` + YarnType string `json:"yarn_type"` + Ratio float64 `json:"ratio"` + AmountPerMeter float64 `json:"amount_per_meter"` + TotalAmount float64 `json:"total_amount"` +} + +type UpdatePlanRequest struct { + ProductName string `json:"product_name"` + FabricCode string `json:"fabric_code"` + Color string `json:"color"` + ColorCode string `json:"color_code"` + TargetQuantity float64 `json:"target_quantity"` + ProductionPrice *float64 `json:"production_price"` + YarnUsagePerMeter *float64 `json:"yarn_usage_per_meter"` + Status string `json:"status"` + Remark string `json:"remark"` +} + +func (s *PlanService) List(ctx context.Context, companyID string, page types.Pagination, status string) ([]model.ProductionPlan, int, error) { + page.Normalize() + return s.repo.List(ctx, companyID, page, status) +} + +func (s *PlanService) Create(ctx context.Context, companyID, userID string, req CreatePlanRequest) (*model.ProductionPlan, error) { + now := time.Now() + planCode := fmt.Sprintf("PP%s%03d", now.Format("20060102"), now.UnixMilli()%1000) + + var remarkPtr *string + if req.Remark != "" { + remarkPtr = &req.Remark + } + + plan := &model.ProductionPlan{ + ID: uuid.New().String(), + PlanCode: planCode, + PurchaserID: companyID, + ProductName: req.ProductName, + FabricCode: req.FabricCode, + Color: req.Color, + ColorCode: req.ColorCode, + TargetQuantity: req.TargetQuantity, + CompletedQuantity: 0, + ProductionPrice: req.ProductionPrice, + YarnUsagePerMeter: req.YarnUsagePerMeter, + Status: string(types.PlanPending), + Remark: remarkPtr, + CreatedBy: &userID, + CreatedAt: now, + UpdatedAt: now, + } + + var yarnRatios []model.YarnRatio + for _, yr := range req.YarnRatios { + var yarnTypePtr *string + if yr.YarnType != "" { + yarnTypePtr = &yr.YarnType + } + yarnRatios = append(yarnRatios, model.YarnRatio{ + ID: uuid.New().String(), + PlanID: plan.ID, + YarnName: yr.YarnName, + YarnType: yarnTypePtr, + Ratio: yr.Ratio, + AmountPerMeter: yr.AmountPerMeter, + TotalAmount: yr.TotalAmount, + CompanyID: &companyID, + CreatedAt: now, + }) + } + + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, fmt.Errorf("begin tx: %w", err) + } + defer tx.Rollback() + + if err := s.repo.Create(ctx, tx, plan); err != nil { + return nil, err + } + + if len(yarnRatios) > 0 { + if err := s.repo.CreateYarnRatios(ctx, tx, yarnRatios); err != nil { + return nil, err + } + } + + if err := tx.Commit(); err != nil { + return nil, fmt.Errorf("commit tx: %w", err) + } + + plan.YarnRatios = yarnRatios + return plan, nil +} + +func (s *PlanService) GetByID(ctx context.Context, id, companyID string) (*model.ProductionPlan, error) { + plan, err := s.repo.GetByID(ctx, id) + if err != nil { + return nil, err + } + if plan == nil { + return nil, fmt.Errorf("plan not found") + } + if plan.PurchaserID != companyID { + return nil, fmt.Errorf("plan not found") + } + + steps, err := s.repo.GetSteps(ctx, id) + if err != nil { + return nil, err + } + plan.ProcessSteps = steps + + inventory, err := s.repo.GetInventory(ctx, id) + if err != nil { + return nil, err + } + plan.InventoryRecords = inventory + + return plan, nil +} + +func (s *PlanService) Update(ctx context.Context, id, companyID string, req UpdatePlanRequest) error { + plan, err := s.repo.GetByID(ctx, id) + if err != nil { + return err + } + if plan == nil || plan.PurchaserID != companyID { + return fmt.Errorf("plan not found") + } + + if req.ProductName != "" { + plan.ProductName = req.ProductName + } + if req.FabricCode != "" { + plan.FabricCode = req.FabricCode + } + if req.Color != "" { + plan.Color = req.Color + } + if req.ColorCode != "" { + plan.ColorCode = req.ColorCode + } + if req.TargetQuantity > 0 { + plan.TargetQuantity = req.TargetQuantity + } + if req.ProductionPrice != nil { + plan.ProductionPrice = req.ProductionPrice + } + if req.YarnUsagePerMeter != nil { + plan.YarnUsagePerMeter = req.YarnUsagePerMeter + } + if req.Status != "" { + plan.Status = req.Status + } + if req.Remark != "" { + plan.Remark = &req.Remark + } + + return s.repo.Update(ctx, plan) +} + +func (s *PlanService) Delete(ctx context.Context, id, companyID string) error { + return s.repo.Delete(ctx, id, companyID) +} + +func (s *PlanService) AssignFactory(ctx context.Context, planID, factoryID, factoryType string) error { + return s.repo.AssignFactory(ctx, planID, factoryID, factoryType) +} + +func (s *PlanService) GetSteps(ctx context.Context, planID string) ([]model.ProcessStep, error) { + return s.repo.GetSteps(ctx, planID) +} + +func (s *PlanService) GetInventory(ctx context.Context, planID string) ([]model.InventoryRecord, error) { + return s.repo.GetInventory(ctx, planID) +} diff --git a/purchaser-service/internal/service/product_service.go b/purchaser-service/internal/service/product_service.go new file mode 100644 index 0000000..144188f --- /dev/null +++ b/purchaser-service/internal/service/product_service.go @@ -0,0 +1,244 @@ +package service + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/model" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/repository" +) + +type ProductService struct { + repo *repository.ProductRepo + db *sql.DB +} + +func NewProductService(repo *repository.ProductRepo, db *sql.DB) *ProductService { + return &ProductService{repo: repo, db: db} +} + +type CreateProductRequest struct { + ProductName string `json:"product_name"` + FabricCode string `json:"fabric_code"` + Color string `json:"color"` + ColorCode string `json:"color_code"` + Weight float64 `json:"weight"` + YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"` + ProductionPrice *float64 `json:"production_price"` + YarnTypes []string `json:"yarn_types"` + YarnRatios []float64 `json:"yarn_ratios"` +} + +type UpdateProductRequest struct { + ProductName string `json:"product_name"` + FabricCode string `json:"fabric_code"` + Color string `json:"color"` + ColorCode string `json:"color_code"` + Weight float64 `json:"weight"` + YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"` + ProductionPrice *float64 `json:"production_price"` + YarnTypes []string `json:"yarn_types"` + YarnRatios []float64 `json:"yarn_ratios"` +} + +type InboundRequest struct { + BatchNo string `json:"batch_no"` + Rolls int `json:"rolls"` + Meters float64 `json:"meters"` + Notes string `json:"notes"` + WarehouseID string `json:"warehouse_id"` +} + +type OutboundRequest struct { + BatchNo string `json:"batch_no"` + Rolls int `json:"rolls"` + Meters float64 `json:"meters"` + OutboundType string `json:"outbound_type"` + Notes string `json:"notes"` + RecipientCompanyID string `json:"recipient_company_id"` + RecipientCompanyName string `json:"recipient_company_name"` + SourceBatchID string `json:"source_batch_id"` + WashingPlanID string `json:"washing_plan_id"` +} + +func (s *ProductService) List(ctx context.Context, companyID string) ([]model.Product, error) { + return s.repo.List(ctx, companyID) +} + +func (s *ProductService) Create(ctx context.Context, companyID string, req CreateProductRequest) (*model.Product, error) { + now := time.Now() + p := &model.Product{ + ID: uuid.New().String(), + CompanyID: companyID, + ProductName: req.ProductName, + FabricCode: req.FabricCode, + Color: req.Color, + ColorCode: req.ColorCode, + Weight: req.Weight, + YarnUsagePerMeter: req.YarnUsagePerMeter, + ProductionPrice: req.ProductionPrice, + TotalStock: 0, + RawFabricMeters: 0, + RawFabricRolls: 0, + YarnTypes: req.YarnTypes, + YarnRatios: req.YarnRatios, + CreatedAt: now, + UpdatedAt: now, + } + + if err := s.repo.Create(ctx, p); err != nil { + return nil, err + } + return p, nil +} + +func (s *ProductService) Update(ctx context.Context, id, companyID string, req UpdateProductRequest) error { + p := &model.Product{ + ID: id, + ProductName: req.ProductName, + FabricCode: req.FabricCode, + Color: req.Color, + ColorCode: req.ColorCode, + Weight: req.Weight, + YarnUsagePerMeter: req.YarnUsagePerMeter, + ProductionPrice: req.ProductionPrice, + YarnTypes: req.YarnTypes, + YarnRatios: req.YarnRatios, + } + return s.repo.Update(ctx, p) +} + +func (s *ProductService) Delete(ctx context.Context, id, companyID string) error { + return s.repo.Delete(ctx, id, companyID) +} + +func (s *ProductService) Inbound(ctx context.Context, productID, companyID, userID string, req InboundRequest) error { + now := time.Now() + var notesPtr *string + if req.Notes != "" { + notesPtr = &req.Notes + } + var warehousePtr *string + if req.WarehouseID != "" { + warehousePtr = &req.WarehouseID + } + + rec := &model.ProductInventoryRecord{ + ID: uuid.New().String(), + ProductID: productID, + BatchNo: req.BatchNo, + Rolls: req.Rolls, + Meters: req.Meters, + Notes: notesPtr, + OperatorID: &userID, + CompanyID: &companyID, + WarehouseID: warehousePtr, + CreatedAt: now, + } + + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("begin tx: %w", err) + } + defer tx.Rollback() + + if err := s.repo.Inbound(ctx, tx, rec); err != nil { + return err + } + if err := s.repo.UpdateStock(ctx, tx, productID, req.Meters, req.Rolls); err != nil { + return err + } + + return tx.Commit() +} + +func (s *ProductService) Outbound(ctx context.Context, productID, companyID, userID string, req OutboundRequest) error { + now := time.Now() + var notesPtr *string + if req.Notes != "" { + notesPtr = &req.Notes + } + var recipientIDPtr, recipientNamePtr, sourceBatchPtr, washingPlanPtr *string + if req.RecipientCompanyID != "" { + recipientIDPtr = &req.RecipientCompanyID + } + if req.RecipientCompanyName != "" { + recipientNamePtr = &req.RecipientCompanyName + } + if req.SourceBatchID != "" { + sourceBatchPtr = &req.SourceBatchID + } + if req.WashingPlanID != "" { + washingPlanPtr = &req.WashingPlanID + } + + outboundType := req.OutboundType + if outboundType == "" { + outboundType = "normal" + } + + rec := &model.ProductOutboundRecord{ + ID: uuid.New().String(), + ProductID: productID, + BatchNo: req.BatchNo, + Rolls: req.Rolls, + Meters: req.Meters, + OutboundType: outboundType, + Notes: notesPtr, + OperatorID: &userID, + CompanyID: &companyID, + RecipientCompanyID: recipientIDPtr, + RecipientCompanyName: recipientNamePtr, + SourceBatchID: sourceBatchPtr, + WashingPlanID: washingPlanPtr, + CreatedAt: now, + } + + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("begin tx: %w", err) + } + defer tx.Rollback() + + if err := s.repo.Outbound(ctx, tx, rec); err != nil { + return err + } + if err := s.repo.UpdateStock(ctx, tx, productID, -req.Meters, -req.Rolls); err != nil { + return err + } + + return tx.Commit() +} + +func (s *ProductService) Records(ctx context.Context, productID string) ([]model.ProductInventoryRecord, error) { + return s.repo.Records(ctx, productID) +} + +func (s *ProductService) UpdatePrice(ctx context.Context, productID, companyID, userID string, newPrice float64, reason string) error { + oldPrice, err := s.repo.GetPrice(ctx, productID) + if err != nil { + return err + } + + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("begin tx: %w", err) + } + defer tx.Rollback() + + if err := s.repo.UpdatePrice(ctx, tx, productID, newPrice); err != nil { + return err + } + if err := s.repo.InsertPriceHistory(ctx, tx, productID, oldPrice, newPrice, reason, userID); err != nil { + return err + } + + return tx.Commit() +} + +func (s *ProductService) PriceHistory(ctx context.Context, productID string) ([]model.ProductPriceHistory, error) { + return s.repo.PriceHistory(ctx, productID) +} diff --git a/purchaser-service/internal/service/washing_plan_service.go b/purchaser-service/internal/service/washing_plan_service.go new file mode 100644 index 0000000..8dadee0 --- /dev/null +++ b/purchaser-service/internal/service/washing_plan_service.go @@ -0,0 +1,76 @@ +package service + +import ( + "context" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/model" + "github.com/muyuqingfeng/iloom/purchaser-service/internal/repository" +) + +type WashingPlanService struct { + repo *repository.WashingPlanRepo +} + +func NewWashingPlanService(repo *repository.WashingPlanRepo) *WashingPlanService { + return &WashingPlanService{repo: repo} +} + +type CreateWashingPlanRequest struct { + ProductID string `json:"product_id" binding:"required"` + WashingFactoryID string `json:"washing_factory_id"` + PlannedMeters float64 `json:"planned_meters" binding:"required,gt=0"` + WashingPrice float64 `json:"washing_price" binding:"required,gt=0"` + EstimatedShrinkageRate float64 `json:"estimated_shrinkage_rate"` + Notes string `json:"notes"` +} + +func (s *WashingPlanService) Create(ctx context.Context, companyID, userID string, req CreateWashingPlanRequest) (*model.WashingPlan, error) { + planCode, err := s.repo.GeneratePlanCode(ctx) + if err != nil { + return nil, err + } + + now := time.Now() + + estimatedWashedMeters := req.PlannedMeters * (1 - req.EstimatedShrinkageRate/100) + estimatedTotalCost := req.PlannedMeters * req.WashingPrice + + var washingFactoryPtr *string + if req.WashingFactoryID != "" { + washingFactoryPtr = &req.WashingFactoryID + } + var notesPtr *string + if req.Notes != "" { + notesPtr = &req.Notes + } + + wp := &model.WashingPlan{ + ID: uuid.New().String(), + PlanCode: planCode, + CompanyID: companyID, + ProductID: req.ProductID, + WashingFactoryID: washingFactoryPtr, + PlannedMeters: req.PlannedMeters, + WashingPrice: req.WashingPrice, + EstimatedShrinkageRate: req.EstimatedShrinkageRate, + EstimatedWashedMeters: &estimatedWashedMeters, + EstimatedTotalCost: &estimatedTotalCost, + Status: "pending", + Notes: notesPtr, + CreatedBy: &userID, + CreatedAt: now, + UpdatedAt: now, + } + + if err := s.repo.Create(ctx, wp); err != nil { + return nil, fmt.Errorf("create washing plan: %w", err) + } + return wp, nil +} + +func (s *WashingPlanService) List(ctx context.Context, companyID string) ([]model.WashingPlan, error) { + return s.repo.List(ctx, companyID) +} diff --git a/scripts/init-iloom.sql b/scripts/init-iloom.sql new file mode 100644 index 0000000..9009922 --- /dev/null +++ b/scripts/init-iloom.sql @@ -0,0 +1,463 @@ +-- iloom Business Tables for muyu_wms Database +-- All tables use ilm_ prefix to coexist with muyu-apiserver tables +-- This script should be run AFTER muyu's init.sql + +USE `muyu_wms`; + +-- ==================== iloom Roles & Permissions ==================== + +INSERT IGNORE INTO `sys_role` (`role_id`, `role_name`, `role_key`, `role_desc`, `sort_order`, `status`) VALUES +('r_010', '采购商', 'purchaser', 'iloom purchaser role', 10, 1), +('r_011', '纺织厂', 'textile', 'iloom textile factory role', 11, 1), +('r_012', '水洗厂', 'washing', 'iloom washing factory role', 12, 1); + +INSERT IGNORE INTO `casbin_rule` (`ptype`, `v0`, `v1`, `v2`) VALUES +('p', 'purchaser', '/api/v1/purchaser/*', '*'), +('p', 'purchaser', '/api/v1/common/*', '*'), +('p', 'purchaser', '/api/v1/auth/*', '*'), +('p', 'textile', '/api/v1/textile/*', '*'), +('p', 'textile', '/api/v1/common/*', '*'), +('p', 'textile', '/api/v1/auth/*', '*'), +('p', 'washing', '/api/v1/washing/*', '*'), +('p', 'washing', '/api/v1/common/*', '*'), +('p', 'washing', '/api/v1/auth/*', '*'); + +-- ==================== Company & Multi-tenancy ==================== + +CREATE TABLE IF NOT EXISTS `ilm_company` ( + `id` VARCHAR(36) NOT NULL, + `name` VARCHAR(100) NOT NULL, + `type` VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'purchaser|textile|washing', + `address` VARCHAR(255) NOT NULL DEFAULT '', + `phone` VARCHAR(20) NOT NULL DEFAULT '', + `contact_person` VARCHAR(50) NOT NULL DEFAULT '', + `status` TINYINT NOT NULL DEFAULT 1, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_type` (`type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_company_member` ( + `id` VARCHAR(36) NOT NULL, + `company_id` VARCHAR(36) NOT NULL, + `user_id` VARCHAR(32) NOT NULL, + `role` VARCHAR(20) NOT NULL DEFAULT 'member' COMMENT 'owner|admin|member', + `is_master` TINYINT NOT NULL DEFAULT 0, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_company_user` (`company_id`, `user_id`), + KEY `idx_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_company_relationship` ( + `id` VARCHAR(36) NOT NULL, + `from_company_id` VARCHAR(36) NOT NULL, + `to_company_id` VARCHAR(36) NOT NULL, + `relationship_type` VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'supplier|customer|partner', + `status` VARCHAR(20) NOT NULL DEFAULT 'active', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_relationship` (`from_company_id`, `to_company_id`, `relationship_type`), + KEY `idx_to_company` (`to_company_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Product Extension ==================== + +CREATE TABLE IF NOT EXISTS `ilm_product_ext` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `product_id` VARCHAR(32) NOT NULL, + `company_id` VARCHAR(36) NOT NULL, + `fabric_code` VARCHAR(50) NOT NULL DEFAULT '', + `color_code` VARCHAR(50) NOT NULL DEFAULT '', + `yarn_usage_per_meter` DECIMAL(10,4) DEFAULT NULL, + `current_price` DECIMAL(10,2) DEFAULT NULL, + `warp_weight` DECIMAL(10,2) DEFAULT NULL, + `weft_weight` DECIMAL(10,2) DEFAULT NULL, + `total_weight` DECIMAL(10,2) DEFAULT NULL, + `stock_meters` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `stock_rolls` INT NOT NULL DEFAULT 0, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_product_company` (`product_id`, `company_id`), + KEY `idx_company` (`company_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Production Plans ==================== + +CREATE TABLE IF NOT EXISTS `ilm_production_plan` ( + `id` VARCHAR(36) NOT NULL, + `plan_code` VARCHAR(50) NOT NULL, + `purchaser_id` VARCHAR(36) NOT NULL COMMENT 'company_id of purchaser', + `product_name` VARCHAR(100) NOT NULL DEFAULT '', + `fabric_code` VARCHAR(50) NOT NULL DEFAULT '', + `color` VARCHAR(50) NOT NULL DEFAULT '', + `color_code` VARCHAR(50) NOT NULL DEFAULT '', + `target_quantity` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `completed_quantity` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `production_price` DECIMAL(10,2) DEFAULT NULL, + `yarn_usage_per_meter` DECIMAL(10,4) DEFAULT NULL, + `status` VARCHAR(20) NOT NULL DEFAULT 'draft' COMMENT 'draft|producing|completed|cancelled', + `remark` TEXT, + `start_time` DATETIME DEFAULT NULL, + `created_by` VARCHAR(32) NOT NULL DEFAULT '', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_plan_code` (`plan_code`), + KEY `idx_purchaser` (`purchaser_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_plan_factory` ( + `id` VARCHAR(36) NOT NULL, + `plan_id` VARCHAR(36) NOT NULL, + `factory_id` VARCHAR(36) NOT NULL, + `factory_type` VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'textile|washing', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_plan` (`plan_id`), + KEY `idx_factory` (`factory_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_yarn_ratio` ( + `id` VARCHAR(36) NOT NULL, + `plan_id` VARCHAR(36) NOT NULL, + `yarn_name` VARCHAR(100) NOT NULL DEFAULT '', + `yarn_type` VARCHAR(50) NOT NULL DEFAULT '', + `ratio` DECIMAL(5,2) NOT NULL DEFAULT 0.00, + `amount_per_meter` DECIMAL(10,4) DEFAULT NULL, + `total_amount` DECIMAL(12,2) DEFAULT NULL, + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_plan` (`plan_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Production Process ==================== + +CREATE TABLE IF NOT EXISTS `ilm_process_step` ( + `id` VARCHAR(36) NOT NULL, + `plan_id` VARCHAR(36) NOT NULL, + `step_type` VARCHAR(30) NOT NULL DEFAULT '' COMMENT 'warping|sizing|weaving|inspection|finishing', + `status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending|in_progress|completed|rejected', + `notes` TEXT, + `operator_id` VARCHAR(32) NOT NULL DEFAULT '', + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `timestamp` DATETIME DEFAULT NULL, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_plan` (`plan_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Warehousing ==================== + +CREATE TABLE IF NOT EXISTS `ilm_warehouse` ( + `id` VARCHAR(36) NOT NULL, + `name` VARCHAR(100) NOT NULL, + `location` VARCHAR(255) NOT NULL DEFAULT '', + `company_id` VARCHAR(36) NOT NULL, + `capacity` DECIMAL(12,2) DEFAULT NULL, + `status` TINYINT NOT NULL DEFAULT 1, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_company` (`company_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_inventory_record` ( + `id` VARCHAR(36) NOT NULL, + `plan_id` VARCHAR(36) NOT NULL DEFAULT '', + `warehouse_id` VARCHAR(36) NOT NULL DEFAULT '', + `quantity` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `rolls` INT DEFAULT NULL, + `price_per_meter` DECIMAL(10,2) DEFAULT NULL, + `price_note` TEXT, + `warehouse_location` VARCHAR(100) NOT NULL DEFAULT '', + `operator_id` VARCHAR(32) NOT NULL DEFAULT '', + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `record_type` VARCHAR(20) NOT NULL DEFAULT 'inbound' COMMENT 'inbound|outbound', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_plan` (`plan_id`), + KEY `idx_company` (`company_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Product Inventory (Purchaser) ==================== + +CREATE TABLE IF NOT EXISTS `ilm_product_inventory` ( + `id` VARCHAR(36) NOT NULL, + `product_id` VARCHAR(32) NOT NULL DEFAULT '', + `batch_no` VARCHAR(50) NOT NULL DEFAULT '', + `rolls` INT NOT NULL DEFAULT 0, + `meters` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `record_type` VARCHAR(20) NOT NULL DEFAULT 'inbound' COMMENT 'inbound|outbound', + `notes` TEXT, + `warehouse_id` VARCHAR(36) NOT NULL DEFAULT '', + `plan_code` VARCHAR(50) NOT NULL DEFAULT '', + `factory_id` VARCHAR(36) NOT NULL DEFAULT '', + `source` VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'self|textile', + `price_per_meter` DECIMAL(10,2) DEFAULT NULL, + `operator_id` VARCHAR(32) NOT NULL DEFAULT '', + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_product` (`product_id`), + KEY `idx_company` (`company_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_product_outbound` ( + `id` VARCHAR(36) NOT NULL, + `product_id` VARCHAR(32) NOT NULL DEFAULT '', + `batch_no` VARCHAR(50) NOT NULL DEFAULT '', + `rolls` INT NOT NULL DEFAULT 0, + `meters` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `outbound_type` VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'sale|washing|transfer', + `notes` TEXT, + `washing_plan_id` VARCHAR(36) NOT NULL DEFAULT '', + `recipient_company_id` VARCHAR(36) NOT NULL DEFAULT '', + `operator_id` VARCHAR(32) NOT NULL DEFAULT '', + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_product` (`product_id`), + KEY `idx_company` (`company_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Price History ==================== + +CREATE TABLE IF NOT EXISTS `ilm_product_price_history` ( + `id` VARCHAR(36) NOT NULL, + `product_id` VARCHAR(32) NOT NULL DEFAULT '', + `price` DECIMAL(10,2) NOT NULL DEFAULT 0.00, + `reason` TEXT, + `changed_by` VARCHAR(32) NOT NULL DEFAULT '', + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_product` (`product_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_production_price_history` ( + `id` VARCHAR(36) NOT NULL, + `plan_id` VARCHAR(36) NOT NULL DEFAULT '', + `price` DECIMAL(10,2) NOT NULL DEFAULT 0.00, + `reason` TEXT, + `changed_by` VARCHAR(32) NOT NULL DEFAULT '', + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_plan` (`plan_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Accounts Payable ==================== + +CREATE TABLE IF NOT EXISTS `ilm_accounts_payable` ( + `id` VARCHAR(36) NOT NULL, + `purchaser_id` VARCHAR(36) NOT NULL, + `factory_id` VARCHAR(36) NOT NULL, + `factory_type` VARCHAR(20) NOT NULL DEFAULT '', + `total_amount` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `paid_amount` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending|partial|paid', + `notes` TEXT, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_purchaser` (`purchaser_id`), + KEY `idx_factory` (`factory_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_ap_item` ( + `id` VARCHAR(36) NOT NULL, + `payable_id` VARCHAR(36) NOT NULL, + `plan_id` VARCHAR(36) NOT NULL DEFAULT '', + `amount` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `description` TEXT, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_payable` (`payable_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Washing Plans ==================== + +CREATE TABLE IF NOT EXISTS `ilm_washing_plan` ( + `id` VARCHAR(36) NOT NULL, + `plan_code` VARCHAR(50) NOT NULL DEFAULT '', + `purchaser_id` VARCHAR(36) NOT NULL, + `washing_factory_id` VARCHAR(36) NOT NULL DEFAULT '', + `product_name` VARCHAR(100) NOT NULL DEFAULT '', + `fabric_code` VARCHAR(50) NOT NULL DEFAULT '', + `color` VARCHAR(50) NOT NULL DEFAULT '', + `quantity` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `unit_price` DECIMAL(10,2) DEFAULT NULL, + `status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending|accepted|processing|completed|rejected', + `notes` TEXT, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_purchaser` (`purchaser_id`), + KEY `idx_washing_factory` (`washing_factory_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Washing Process ==================== + +CREATE TABLE IF NOT EXISTS `ilm_washing_step` ( + `id` VARCHAR(36) NOT NULL, + `washing_plan_id` VARCHAR(36) NOT NULL, + `step_type` VARCHAR(30) NOT NULL DEFAULT '', + `status` VARCHAR(20) NOT NULL DEFAULT 'pending', + `notes` TEXT, + `operator_id` VARCHAR(32) NOT NULL DEFAULT '', + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `timestamp` DATETIME DEFAULT NULL, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_washing_plan` (`washing_plan_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_washing_completion` ( + `id` VARCHAR(36) NOT NULL, + `washing_plan_id` VARCHAR(36) NOT NULL, + `completed_quantity` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `quality_notes` TEXT, + `operator_id` VARCHAR(32) NOT NULL DEFAULT '', + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `completed_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_washing_plan` (`washing_plan_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Finished Products ==================== + +CREATE TABLE IF NOT EXISTS `ilm_finished_product` ( + `id` VARCHAR(36) NOT NULL, + `product_name` VARCHAR(100) NOT NULL DEFAULT '', + `fabric_code` VARCHAR(50) NOT NULL DEFAULT '', + `color` VARCHAR(50) NOT NULL DEFAULT '', + `stock_meters` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `stock_rolls` INT NOT NULL DEFAULT 0, + `company_id` VARCHAR(36) NOT NULL, + `washing_plan_id` VARCHAR(36) NOT NULL DEFAULT '', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_company` (`company_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_finished_product_inventory` ( + `id` VARCHAR(36) NOT NULL, + `finished_product_id` VARCHAR(36) NOT NULL, + `batch_no` VARCHAR(50) NOT NULL DEFAULT '', + `rolls` INT NOT NULL DEFAULT 0, + `meters` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `record_type` VARCHAR(20) NOT NULL DEFAULT 'inbound', + `notes` TEXT, + `operator_id` VARCHAR(32) NOT NULL DEFAULT '', + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_finished_product` (`finished_product_id`), + KEY `idx_company` (`company_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Yarn Stock ==================== + +CREATE TABLE IF NOT EXISTS `ilm_yarn_stock` ( + `id` VARCHAR(36) NOT NULL, + `yarn_name` VARCHAR(100) NOT NULL DEFAULT '', + `yarn_type` VARCHAR(50) NOT NULL DEFAULT '', + `current_stock` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `unit` VARCHAR(20) NOT NULL DEFAULT 'kg', + `company_id` VARCHAR(36) NOT NULL, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_company` (`company_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_yarn_stock_record` ( + `id` VARCHAR(36) NOT NULL, + `yarn_stock_id` VARCHAR(36) NOT NULL, + `quantity` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `record_type` VARCHAR(20) NOT NULL DEFAULT 'in' COMMENT 'in|out', + `notes` TEXT, + `operator_id` VARCHAR(32) NOT NULL DEFAULT '', + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_yarn_stock` (`yarn_stock_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Payments ==================== + +CREATE TABLE IF NOT EXISTS `ilm_payment` ( + `id` VARCHAR(36) NOT NULL, + `payer_company_id` VARCHAR(36) NOT NULL, + `payee_company_id` VARCHAR(36) NOT NULL, + `amount` DECIMAL(12,2) NOT NULL DEFAULT 0.00, + `payment_type` VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'production|washing', + `plan_id` VARCHAR(36) NOT NULL DEFAULT '', + `status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending|confirmed|rejected', + `notes` TEXT, + `confirmed_at` DATETIME DEFAULT NULL, + `confirmed_by` VARCHAR(32) NOT NULL DEFAULT '', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_payer` (`payer_company_id`), + KEY `idx_payee` (`payee_company_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ==================== Notifications & Misc ==================== + +CREATE TABLE IF NOT EXISTS `ilm_notification` ( + `id` VARCHAR(36) NOT NULL, + `user_id` VARCHAR(32) NOT NULL, + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `title` VARCHAR(200) NOT NULL DEFAULT '', + `message` TEXT, + `type` VARCHAR(30) NOT NULL DEFAULT 'info' COMMENT 'info|warning|success|error', + `is_read` TINYINT NOT NULL DEFAULT 0, + `link` VARCHAR(255) NOT NULL DEFAULT '', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_user_unread` (`user_id`, `is_read`), + KEY `idx_company` (`company_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_share_link` ( + `id` VARCHAR(36) NOT NULL, + `code` VARCHAR(20) NOT NULL, + `company_id` VARCHAR(36) NOT NULL, + `created_by` VARCHAR(32) NOT NULL DEFAULT '', + `resource_type` VARCHAR(30) NOT NULL DEFAULT '', + `resource_id` VARCHAR(36) NOT NULL DEFAULT '', + `expires_at` DATETIME DEFAULT NULL, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_code` (`code`), + KEY `idx_company` (`company_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `ilm_audit_log` ( + `id` VARCHAR(36) NOT NULL, + `user_id` VARCHAR(32) NOT NULL DEFAULT '', + `company_id` VARCHAR(36) NOT NULL DEFAULT '', + `action` VARCHAR(50) NOT NULL DEFAULT '', + `resource_type` VARCHAR(50) NOT NULL DEFAULT '', + `resource_id` VARCHAR(36) NOT NULL DEFAULT '', + `old_value` JSON DEFAULT NULL, + `new_value` JSON DEFAULT NULL, + `ip` VARCHAR(50) NOT NULL DEFAULT '', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_user` (`user_id`), + KEY `idx_company` (`company_id`), + KEY `idx_created_at` (`created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/scripts/init-schemas.sql b/scripts/init-schemas.sql new file mode 100644 index 0000000..7027b22 --- /dev/null +++ b/scripts/init-schemas.sql @@ -0,0 +1,639 @@ +-- iloom 微服务数据库初始化脚本 +-- 创建 5 个 Schema,迁移表结构,建立跨 Schema 视图 + +-- ============================================= +-- 1. 创建 Schema +-- ============================================= +CREATE SCHEMA IF NOT EXISTS common; +CREATE SCHEMA IF NOT EXISTS purchaser; +CREATE SCHEMA IF NOT EXISTS textile; +CREATE SCHEMA IF NOT EXISTS washing; +CREATE SCHEMA IF NOT EXISTS audit; + +-- ============================================= +-- 2. 创建枚举类型(在 public schema,所有 schema 共享) +-- ============================================= +DO $$ BEGIN + CREATE TYPE app_role AS ENUM ('purchaser', 'textile', 'washing'); +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; + +DO $$ BEGIN + CREATE TYPE factory_type AS ENUM ('textile', 'washing'); +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; + +DO $$ BEGIN + CREATE TYPE plan_status AS ENUM ('pending', 'producing', 'completed'); +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; + +DO $$ BEGIN + CREATE TYPE step_status AS ENUM ('pending', 'active', 'completed', 'rejected'); +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; + +DO $$ BEGIN + CREATE TYPE step_type AS ENUM ('confirm', 'yarn_purchase', 'dyeing', 'machine_start', 'fabric_warehouse'); +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; + +DO $$ BEGIN + CREATE TYPE warehouse_type AS ENUM ('raw_fabric', 'fabric', 'finished', 'yarn'); +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; + +DO $$ BEGIN + CREATE TYPE payment_status AS ENUM ('pending', 'completed'); +EXCEPTION WHEN duplicate_object THEN NULL; +END $$; + +-- ============================================= +-- 3. common Schema 表 +-- ============================================= + +CREATE TABLE IF NOT EXISTS common.companies ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + role app_role NOT NULL, + address TEXT, + contact_phone TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS common.profiles ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL, + phone TEXT, + company_id UUID REFERENCES common.companies(id), + is_master BOOLEAN NOT NULL DEFAULT true, + master_id UUID REFERENCES common.profiles(id), + display_name TEXT, + image_url TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS common.company_members ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + company_id UUID NOT NULL REFERENCES common.companies(id), + user_id UUID NOT NULL REFERENCES common.profiles(id), + role TEXT NOT NULL DEFAULT 'member', + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS common.company_relationships ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + purchaser_company_id UUID NOT NULL REFERENCES common.companies(id), + factory_company_id UUID NOT NULL REFERENCES common.companies(id), + factory_type factory_type NOT NULL, + status TEXT NOT NULL DEFAULT 'active', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS common.notifications ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + recipient_id UUID NOT NULL, + sender_id UUID, + sender_company_id UUID REFERENCES common.companies(id), + title TEXT NOT NULL, + content TEXT NOT NULL, + type TEXT NOT NULL, + plan_id UUID, + is_read BOOLEAN DEFAULT false, + read_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS common.share_links ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL, + created_by UUID NOT NULL, + short_code TEXT NOT NULL UNIQUE, + factory_type TEXT NOT NULL, + hide_sensitive BOOLEAN DEFAULT false, + status TEXT NOT NULL DEFAULT 'pending', + expires_at TIMESTAMPTZ, + clicked_at TIMESTAMPTZ, + click_count INT DEFAULT 0, + used_at TIMESTAMPTZ, + response_result TEXT, + reject_reason TEXT, + responded_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS common.user_feedback ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID, + type TEXT NOT NULL, + content TEXT NOT NULL, + contact TEXT, + status TEXT NOT NULL DEFAULT 'pending', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS common.crash_reports ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID, + error_message TEXT NOT NULL, + error_stack TEXT, + component_stack TEXT, + url TEXT, + user_agent TEXT, + user_description TEXT, + status TEXT NOT NULL DEFAULT 'new', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- ============================================= +-- 4. purchaser Schema 表 +-- ============================================= + +CREATE TABLE IF NOT EXISTS purchaser.production_plans ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_code TEXT NOT NULL, + purchaser_id UUID NOT NULL REFERENCES common.companies(id), + product_name TEXT NOT NULL, + fabric_code TEXT NOT NULL, + color TEXT NOT NULL, + color_code TEXT NOT NULL DEFAULT '', + target_quantity NUMERIC NOT NULL DEFAULT 0, + completed_quantity NUMERIC NOT NULL DEFAULT 0, + production_price NUMERIC, + yarn_usage_per_meter NUMERIC, + status plan_status NOT NULL DEFAULT 'pending', + remark TEXT, + start_time TIMESTAMPTZ, + created_by UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS purchaser.plan_factories ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES purchaser.production_plans(id) ON DELETE CASCADE, + factory_id UUID NOT NULL REFERENCES common.companies(id), + factory_type factory_type NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS purchaser.yarn_ratios ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES purchaser.production_plans(id) ON DELETE CASCADE, + yarn_name TEXT NOT NULL, + yarn_type TEXT, + ratio NUMERIC NOT NULL DEFAULT 0, + amount_per_meter NUMERIC NOT NULL DEFAULT 0, + total_amount NUMERIC NOT NULL DEFAULT 0, + company_id UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS purchaser.products ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + company_id UUID NOT NULL REFERENCES common.companies(id), + product_name TEXT NOT NULL, + fabric_code TEXT NOT NULL, + color TEXT NOT NULL, + color_code TEXT NOT NULL DEFAULT '', + weight NUMERIC NOT NULL DEFAULT 0, + warp_weight NUMERIC, + weft_weight NUMERIC, + total_weight NUMERIC, + yarn_usage_per_meter NUMERIC NOT NULL DEFAULT 0, + production_price NUMERIC, + total_stock NUMERIC NOT NULL DEFAULT 0, + raw_fabric_meters NUMERIC NOT NULL DEFAULT 0, + raw_fabric_rolls INT NOT NULL DEFAULT 0, + image_url TEXT, + yarn_types TEXT[], + yarn_ratios NUMERIC[], + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS purchaser.product_inventory_records ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + product_id UUID NOT NULL REFERENCES purchaser.products(id) ON DELETE CASCADE, + batch_no TEXT NOT NULL, + rolls INT NOT NULL DEFAULT 0, + meters NUMERIC NOT NULL DEFAULT 0, + notes TEXT, + operator_id UUID, + company_id UUID, + warehouse_id UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS purchaser.product_outbound_records ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + product_id UUID NOT NULL REFERENCES purchaser.products(id) ON DELETE CASCADE, + batch_no TEXT NOT NULL DEFAULT '', + rolls INT NOT NULL DEFAULT 0, + meters NUMERIC NOT NULL DEFAULT 0, + outbound_type TEXT NOT NULL DEFAULT 'normal', + notes TEXT, + operator_id UUID, + company_id UUID, + recipient_company_id UUID, + recipient_company_name TEXT, + source_batch_id UUID, + washing_plan_id UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS purchaser.product_yarn_ratios ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + product_id UUID NOT NULL REFERENCES purchaser.products(id) ON DELETE CASCADE, + yarn_name TEXT NOT NULL, + yarn_type TEXT, + ratio NUMERIC NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS purchaser.product_price_history ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + product_id UUID NOT NULL REFERENCES purchaser.products(id) ON DELETE CASCADE, + company_id UUID NOT NULL, + old_price NUMERIC NOT NULL DEFAULT 0, + new_price NUMERIC NOT NULL DEFAULT 0, + change_amount NUMERIC NOT NULL DEFAULT 0, + change_reason TEXT, + operator_id UUID, + operator_name TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS purchaser.production_price_history ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES purchaser.production_plans(id) ON DELETE CASCADE, + old_price NUMERIC NOT NULL DEFAULT 0, + new_price NUMERIC NOT NULL DEFAULT 0, + change_amount NUMERIC NOT NULL DEFAULT 0, + change_reason TEXT, + operator_id UUID, + operator_name TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS purchaser.accounts_payable ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES purchaser.production_plans(id), + purchaser_id UUID NOT NULL REFERENCES common.companies(id), + textile_factory_id UUID NOT NULL REFERENCES common.companies(id), + price_per_meter NUMERIC NOT NULL DEFAULT 0, + total_quantity NUMERIC NOT NULL DEFAULT 0, + total_amount NUMERIC NOT NULL DEFAULT 0, + paid_quantity NUMERIC NOT NULL DEFAULT 0, + paid_amount NUMERIC NOT NULL DEFAULT 0, + unpaid_quantity NUMERIC NOT NULL DEFAULT 0, + unpaid_amount NUMERIC NOT NULL DEFAULT 0, + status TEXT NOT NULL DEFAULT 'pending', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS purchaser.accounts_payable_items ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + accounts_payable_id UUID NOT NULL REFERENCES purchaser.accounts_payable(id) ON DELETE CASCADE, + inventory_record_id UUID, + payment_id UUID, + quantity NUMERIC NOT NULL DEFAULT 0, + price_per_meter NUMERIC NOT NULL DEFAULT 0, + amount NUMERIC NOT NULL DEFAULT 0, + rolls INT, + status TEXT NOT NULL DEFAULT 'pending', + paid_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS purchaser.washing_plans ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_code TEXT NOT NULL, + company_id UUID NOT NULL REFERENCES common.companies(id), + product_id UUID NOT NULL REFERENCES purchaser.products(id), + washing_factory_id UUID REFERENCES common.companies(id), + planned_meters NUMERIC NOT NULL DEFAULT 0, + washing_price NUMERIC NOT NULL DEFAULT 0, + estimated_shrinkage_rate NUMERIC NOT NULL DEFAULT 0, + estimated_washed_meters NUMERIC, + estimated_total_cost NUMERIC, + actual_shrinkage_rate NUMERIC, + actual_washed_meters NUMERIC, + actual_total_cost NUMERIC, + washing_date TIMESTAMPTZ, + status TEXT NOT NULL DEFAULT 'pending', + notes TEXT, + created_by UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- ============================================= +-- 5. textile Schema 表 +-- ============================================= + +CREATE TABLE IF NOT EXISTS textile.plan_process_steps ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES purchaser.production_plans(id) ON DELETE CASCADE, + step_type step_type NOT NULL, + status step_status NOT NULL DEFAULT 'pending', + notes TEXT, + operator_id UUID, + company_id UUID, + timestamp TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS textile.warehouses ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + company_id UUID NOT NULL REFERENCES common.companies(id), + name TEXT NOT NULL, + type warehouse_type NOT NULL, + location TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS textile.inventory_records ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES purchaser.production_plans(id), + warehouse_id UUID NOT NULL REFERENCES textile.warehouses(id), + quantity NUMERIC NOT NULL DEFAULT 0, + rolls INT, + price_per_meter NUMERIC, + price_note TEXT, + warehouse_location TEXT, + operator_id UUID, + company_id UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS textile.yarn_stock ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + company_id UUID NOT NULL REFERENCES common.companies(id), + name TEXT NOT NULL, + spec TEXT, + quantity NUMERIC NOT NULL DEFAULT 0, + unit TEXT NOT NULL DEFAULT 'kg', + min_stock NUMERIC NOT NULL DEFAULT 0, + warehouse_id UUID REFERENCES textile.warehouses(id), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS textile.yarn_stock_records ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + yarn_stock_id UUID REFERENCES textile.yarn_stock(id), + company_id UUID NOT NULL REFERENCES common.companies(id), + record_type TEXT NOT NULL, + quantity NUMERIC NOT NULL DEFAULT 0, + unit TEXT NOT NULL DEFAULT 'kg', + batch_no TEXT, + plan_id UUID, + notes TEXT, + operator_id UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS textile.payments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES purchaser.production_plans(id), + from_company_id UUID NOT NULL REFERENCES common.companies(id), + to_company_id UUID NOT NULL REFERENCES common.companies(id), + amount NUMERIC NOT NULL DEFAULT 0, + quantity NUMERIC NOT NULL DEFAULT 0, + price_per_meter NUMERIC NOT NULL DEFAULT 0, + status payment_status NOT NULL DEFAULT 'pending', + paid_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- ============================================= +-- 6. washing Schema 表 +-- ============================================= + +CREATE TABLE IF NOT EXISTS washing.washing_process_steps ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + washing_plan_id UUID NOT NULL REFERENCES purchaser.washing_plans(id) ON DELETE CASCADE, + company_id UUID NOT NULL REFERENCES common.companies(id), + step_type TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + notes TEXT, + operator_id UUID, + timestamp TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS washing.washing_plan_completions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + washing_plan_id UUID NOT NULL REFERENCES purchaser.washing_plans(id), + company_id UUID NOT NULL REFERENCES common.companies(id), + actual_shrinkage_rate NUMERIC NOT NULL, + actual_washed_meters NUMERIC NOT NULL, + actual_washing_cost NUMERIC, + unit_cost NUMERIC, + rolls INT NOT NULL DEFAULT 0, + washing_date DATE NOT NULL, + notes TEXT, + completed_by UUID, + completed_at TIMESTAMPTZ NOT NULL DEFAULT now(), + finished_product_id UUID, + auto_imported BOOLEAN DEFAULT false, + warehouse_id UUID, + warehouse_location TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS washing.finished_products ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + company_id UUID NOT NULL REFERENCES common.companies(id), + product_name TEXT NOT NULL, + fabric_code TEXT NOT NULL, + color TEXT NOT NULL, + color_code TEXT, + weight NUMERIC, + shrinkage_rate NUMERIC NOT NULL DEFAULT 0, + washed_meters NUMERIC NOT NULL DEFAULT 0, + stock_meters NUMERIC NOT NULL DEFAULT 0, + stock_rolls INT NOT NULL DEFAULT 0, + rolls INT, + washing_cost NUMERIC, + unit_cost NUMERIC, + status TEXT NOT NULL DEFAULT 'active', + notes TEXT, + product_id UUID, + washing_plan_id UUID, + warehouse_id UUID, + warehouse_location TEXT, + created_by UUID, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS washing.finished_product_inventory_records ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + finished_product_id UUID NOT NULL REFERENCES washing.finished_products(id) ON DELETE CASCADE, + company_id UUID NOT NULL REFERENCES common.companies(id), + record_type TEXT NOT NULL, + rolls INT NOT NULL DEFAULT 0, + meters NUMERIC NOT NULL DEFAULT 0, + batch_no TEXT, + notes TEXT, + operator_id UUID, + warehouse_id UUID, + warehouse_location TEXT, + washing_completion_id UUID, + related_order_id UUID, + related_order_type TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- ============================================= +-- 7. audit Schema 表 +-- ============================================= + +CREATE TABLE IF NOT EXISTS audit.audit_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID, + company_id UUID, + action TEXT NOT NULL, + resource_type TEXT NOT NULL, + resource_id UUID, + details JSONB, + ip_address TEXT, + user_agent TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS audit.data_audit_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + table_name TEXT NOT NULL, + record_id TEXT NOT NULL, + operation TEXT NOT NULL, + old_values JSONB, + new_values JSONB, + changed_by UUID, + error_message TEXT, + validation_passed BOOLEAN NOT NULL DEFAULT true, + changed_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- ============================================= +-- 8. 跨 Schema 只读视图 +-- ============================================= + +-- textile-service 需要读取分配给自己的计划 +CREATE OR REPLACE VIEW textile.v_assigned_plans AS +SELECT + pp.id, pp.plan_code, pp.product_name, pp.fabric_code, + pp.color, pp.color_code, pp.target_quantity, pp.completed_quantity, + pp.production_price, pp.yarn_usage_per_meter, pp.status, + pp.remark, pp.start_time, pp.created_at, pp.updated_at, + pf.factory_id, pf.id AS plan_factory_id, + c.name AS purchaser_name +FROM purchaser.production_plans pp +JOIN purchaser.plan_factories pf ON pp.id = pf.plan_id +JOIN common.companies c ON pp.purchaser_id = c.id +WHERE pf.factory_type = 'textile'; + +-- washing-service 需要读取分配给自己的水洗计划 +CREATE OR REPLACE VIEW washing.v_assigned_washing_plans AS +SELECT + wp.id, wp.plan_code, wp.company_id, wp.product_id, + wp.washing_factory_id, wp.planned_meters, wp.washing_price, + wp.estimated_shrinkage_rate, wp.estimated_washed_meters, + wp.estimated_total_cost, wp.actual_shrinkage_rate, + wp.actual_washed_meters, wp.actual_total_cost, + wp.washing_date, wp.status, wp.notes, wp.created_at, wp.updated_at, + p.product_name, p.fabric_code, p.color, p.color_code, + c.name AS purchaser_name +FROM purchaser.washing_plans wp +LEFT JOIN purchaser.products p ON wp.product_id = p.id +LEFT JOIN common.companies c ON wp.company_id = c.id; + +-- purchaser-service 需要读取计划的流程步骤 +CREATE OR REPLACE VIEW purchaser.v_plan_steps AS +SELECT + ps.id, ps.plan_id, ps.step_type, ps.status, + ps.notes, ps.operator_id, ps.company_id, ps.timestamp, ps.created_at, + pr.display_name AS operator_name +FROM textile.plan_process_steps ps +LEFT JOIN common.profiles pr ON ps.operator_id = pr.id; + +-- purchaser-service 需要读取入库记录 +CREATE OR REPLACE VIEW purchaser.v_plan_inventory AS +SELECT + ir.id, ir.plan_id, ir.warehouse_id, ir.quantity, ir.rolls, + ir.price_per_meter, ir.price_note, ir.warehouse_location, + ir.operator_id, ir.company_id, ir.created_at, + w.name AS warehouse_name, + pr.display_name AS operator_name +FROM textile.inventory_records ir +LEFT JOIN textile.warehouses w ON ir.warehouse_id = w.id +LEFT JOIN common.profiles pr ON ir.operator_id = pr.id; + +-- ============================================= +-- 9. 索引 +-- ============================================= + +CREATE INDEX IF NOT EXISTS idx_profiles_username ON common.profiles(username); +CREATE INDEX IF NOT EXISTS idx_profiles_company ON common.profiles(company_id); +CREATE INDEX IF NOT EXISTS idx_company_members_company ON common.company_members(company_id); +CREATE INDEX IF NOT EXISTS idx_company_relationships_purchaser ON common.company_relationships(purchaser_company_id); +CREATE INDEX IF NOT EXISTS idx_company_relationships_factory ON common.company_relationships(factory_company_id); +CREATE INDEX IF NOT EXISTS idx_notifications_recipient ON common.notifications(recipient_id); +CREATE INDEX IF NOT EXISTS idx_share_links_code ON common.share_links(short_code); + +CREATE INDEX IF NOT EXISTS idx_plans_purchaser ON purchaser.production_plans(purchaser_id); +CREATE INDEX IF NOT EXISTS idx_plans_status ON purchaser.production_plans(status); +CREATE INDEX IF NOT EXISTS idx_plan_factories_plan ON purchaser.plan_factories(plan_id); +CREATE INDEX IF NOT EXISTS idx_plan_factories_factory ON purchaser.plan_factories(factory_id); +CREATE INDEX IF NOT EXISTS idx_yarn_ratios_plan ON purchaser.yarn_ratios(plan_id); +CREATE INDEX IF NOT EXISTS idx_products_company ON purchaser.products(company_id); +CREATE INDEX IF NOT EXISTS idx_product_inventory_product ON purchaser.product_inventory_records(product_id); +CREATE INDEX IF NOT EXISTS idx_ap_purchaser ON purchaser.accounts_payable(purchaser_id); +CREATE INDEX IF NOT EXISTS idx_washing_plans_company ON purchaser.washing_plans(company_id); +CREATE INDEX IF NOT EXISTS idx_washing_plans_factory ON purchaser.washing_plans(washing_factory_id); + +CREATE INDEX IF NOT EXISTS idx_process_steps_plan ON textile.plan_process_steps(plan_id); +CREATE INDEX IF NOT EXISTS idx_inventory_plan ON textile.inventory_records(plan_id); +CREATE INDEX IF NOT EXISTS idx_yarn_stock_company ON textile.yarn_stock(company_id); +CREATE INDEX IF NOT EXISTS idx_yarn_records_stock ON textile.yarn_stock_records(yarn_stock_id); +CREATE INDEX IF NOT EXISTS idx_payments_to ON textile.payments(to_company_id); + +CREATE INDEX IF NOT EXISTS idx_washing_steps_plan ON washing.washing_process_steps(washing_plan_id); +CREATE INDEX IF NOT EXISTS idx_completions_plan ON washing.washing_plan_completions(washing_plan_id); +CREATE INDEX IF NOT EXISTS idx_finished_products_company ON washing.finished_products(company_id); +CREATE INDEX IF NOT EXISTS idx_fp_inventory_product ON washing.finished_product_inventory_records(finished_product_id); + +-- ============================================= +-- 10. 权限授予 +-- ============================================= + +GRANT USAGE ON SCHEMA common TO iloom; +GRANT USAGE ON SCHEMA purchaser TO iloom; +GRANT USAGE ON SCHEMA textile TO iloom; +GRANT USAGE ON SCHEMA washing TO iloom; +GRANT USAGE ON SCHEMA audit TO iloom; + +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA common TO iloom; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA purchaser TO iloom; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA textile TO iloom; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA washing TO iloom; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA audit TO iloom; + +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA common TO iloom; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA purchaser TO iloom; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA textile TO iloom; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA washing TO iloom; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA audit TO iloom; + +ALTER DEFAULT PRIVILEGES IN SCHEMA common GRANT ALL ON TABLES TO iloom; +ALTER DEFAULT PRIVILEGES IN SCHEMA purchaser GRANT ALL ON TABLES TO iloom; +ALTER DEFAULT PRIVILEGES IN SCHEMA textile GRANT ALL ON TABLES TO iloom; +ALTER DEFAULT PRIVILEGES IN SCHEMA washing GRANT ALL ON TABLES TO iloom; +ALTER DEFAULT PRIVILEGES IN SCHEMA audit GRANT ALL ON TABLES TO iloom; diff --git a/shared/cmd/bcryptcheck/main.go b/shared/cmd/bcryptcheck/main.go new file mode 100644 index 0000000..54350c9 --- /dev/null +++ b/shared/cmd/bcryptcheck/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "golang.org/x/crypto/bcrypt" +) + +func main() { + hash := "$2a$10$S5XPWRXUnexAt9KJw2CefeHMa6aude7j5dm5qgWM1YMQAAwRgsGXa" + passwords := []string{"admin123", "muyu2026", "admin", "123456", "password", "Aa123456"} + for _, p := range passwords { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(p)) + if err == nil { + fmt.Printf("MATCH: %s\n", p) + } else { + fmt.Printf("%-12s → %v\n", p, err) + } + } +} diff --git a/shared/go.mod b/shared/go.mod new file mode 100644 index 0000000..6ba76d9 --- /dev/null +++ b/shared/go.mod @@ -0,0 +1,45 @@ +module github.com/muyuqingfeng/iloom/shared + +go 1.23 + +toolchain go1.24.4 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/go-sql-driver/mysql v1.8.1 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.3 + golang.org/x/crypto v0.33.0 + google.golang.org/grpc v1.72.0 + google.golang.org/protobuf v1.36.5 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + 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.2.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/shared/go.sum b/shared/go.sum new file mode 100644 index 0000000..f08ac69 --- /dev/null +++ b/shared/go.sum @@ -0,0 +1,119 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +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= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +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= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +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/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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/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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +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/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= +google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/shared/pkg/auth/claims.go b/shared/pkg/auth/claims.go new file mode 100644 index 0000000..c49687c --- /dev/null +++ b/shared/pkg/auth/claims.go @@ -0,0 +1,12 @@ +package auth + +import "github.com/golang-jwt/jwt/v5" + +type Claims struct { + jwt.RegisteredClaims + UserID string `json:"uid"` + CompanyID string `json:"cid"` + Role string `json:"role"` + IsMaster bool `json:"master"` + Username string `json:"uname"` +} diff --git a/shared/pkg/auth/jwt.go b/shared/pkg/auth/jwt.go new file mode 100644 index 0000000..6745a57 --- /dev/null +++ b/shared/pkg/auth/jwt.go @@ -0,0 +1,91 @@ +package auth + +import ( + "fmt" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +const ( + defaultAccessTTL = 7200 * time.Second + refreshTTL = 30 * 24 * time.Hour +) + +type JWTManager struct { + secret []byte + refreshSecret []byte + accessTTL time.Duration + refreshTTL time.Duration +} + +func NewJWTManager(secret, refreshSecret string) *JWTManager { + return &JWTManager{ + secret: []byte(secret), + refreshSecret: []byte(refreshSecret), + accessTTL: defaultAccessTTL, + refreshTTL: refreshTTL, + } +} + +func (m *JWTManager) GenerateAccessToken(claims Claims) (string, error) { + now := time.Now() + claims.RegisteredClaims = jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(now.Add(m.accessTTL)), + IssuedAt: jwt.NewNumericDate(now), + Subject: claims.UserID, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(m.secret) +} + +func (m *JWTManager) GenerateRefreshToken(userID string) (string, error) { + now := time.Now() + claims := jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(now.Add(m.refreshTTL)), + IssuedAt: jwt.NewNumericDate(now), + Subject: userID, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(m.refreshSecret) +} + +func (m *JWTManager) ParseAccessToken(tokenStr string) (*Claims, error) { + token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) + } + return m.secret, nil + }) + if err != nil { + return nil, fmt.Errorf("parse access token: %w", err) + } + + claims, ok := token.Claims.(*Claims) + if !ok || !token.Valid { + return nil, fmt.Errorf("invalid access token") + } + + return claims, nil +} + +func (m *JWTManager) ParseRefreshToken(tokenStr string) (string, error) { + token, err := jwt.ParseWithClaims(tokenStr, &jwt.RegisteredClaims{}, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) + } + return m.refreshSecret, nil + }) + if err != nil { + return "", fmt.Errorf("parse refresh token: %w", err) + } + + claims, ok := token.Claims.(*jwt.RegisteredClaims) + if !ok || !token.Valid { + return "", fmt.Errorf("invalid refresh token") + } + + return claims.Subject, nil +} diff --git a/shared/pkg/auth/password.go b/shared/pkg/auth/password.go new file mode 100644 index 0000000..7b1b67d --- /dev/null +++ b/shared/pkg/auth/password.go @@ -0,0 +1,15 @@ +package auth + +import "golang.org/x/crypto/bcrypt" + +func HashPassword(password string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(hash), nil +} + +func CheckPassword(password, hash string) bool { + return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil +} diff --git a/shared/pkg/database/mysql.go b/shared/pkg/database/mysql.go new file mode 100644 index 0000000..82d294c --- /dev/null +++ b/shared/pkg/database/mysql.go @@ -0,0 +1,71 @@ +package database + +import ( + "context" + "database/sql" + "fmt" + "time" + + _ "github.com/go-sql-driver/mysql" +) + +type Config struct { + Host string + Port int + User string + Password string + DBName string + MaxConns int + MinConns int +} + +func (c Config) DSN() string { + return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&charset=utf8mb4&collation=utf8mb4_unicode_ci&loc=Local", + c.User, c.Password, c.Host, c.Port, c.DBName) +} + +func NewDB(cfg Config) (*sql.DB, error) { + db, err := sql.Open("mysql", cfg.DSN()) + if err != nil { + return nil, fmt.Errorf("open database: %w", err) + } + + if cfg.MaxConns > 0 { + db.SetMaxOpenConns(cfg.MaxConns) + } + if cfg.MinConns > 0 { + db.SetMaxIdleConns(cfg.MinConns) + } + db.SetConnMaxLifetime(30 * time.Minute) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if err := db.PingContext(ctx); err != nil { + db.Close() + return nil, fmt.Errorf("ping database: %w", err) + } + + return db, nil +} + +func NewDBFromDSN(dsn string) (*sql.DB, error) { + db, err := sql.Open("mysql", dsn) + if err != nil { + return nil, fmt.Errorf("open database: %w", err) + } + + db.SetMaxOpenConns(25) + db.SetMaxIdleConns(5) + db.SetConnMaxLifetime(30 * time.Minute) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if err := db.PingContext(ctx); err != nil { + db.Close() + return nil, fmt.Errorf("ping database: %w", err) + } + + return db, nil +} diff --git a/shared/pkg/database/postgres.go b/shared/pkg/database/postgres.go new file mode 100644 index 0000000..636bab8 --- /dev/null +++ b/shared/pkg/database/postgres.go @@ -0,0 +1 @@ +package database diff --git a/shared/pkg/database/tx.go b/shared/pkg/database/tx.go new file mode 100644 index 0000000..83e854c --- /dev/null +++ b/shared/pkg/database/tx.go @@ -0,0 +1,27 @@ +package database + +import ( + "context" + "database/sql" + "fmt" +) + +func WithTx(ctx context.Context, db *sql.DB, fn func(tx *sql.Tx) error) error { + tx, err := db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("begin tx: %w", err) + } + + if err := fn(tx); err != nil { + if rbErr := tx.Rollback(); rbErr != nil { + return fmt.Errorf("rollback failed: %v (original: %w)", rbErr, err) + } + return err + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("commit tx: %w", err) + } + + return nil +} diff --git a/shared/pkg/event/bus.go b/shared/pkg/event/bus.go new file mode 100644 index 0000000..722e820 --- /dev/null +++ b/shared/pkg/event/bus.go @@ -0,0 +1,7 @@ +package event + +type EventBus interface { + Publish(topic string, payload interface{}) error + Subscribe(topic string, handler func(payload []byte)) error + Close() error +} diff --git a/shared/pkg/event/pg_notify.go b/shared/pkg/event/pg_notify.go new file mode 100644 index 0000000..0e4b82e --- /dev/null +++ b/shared/pkg/event/pg_notify.go @@ -0,0 +1 @@ +package event diff --git a/shared/pkg/grpc/client.go b/shared/pkg/grpc/client.go new file mode 100644 index 0000000..3d0d19f --- /dev/null +++ b/shared/pkg/grpc/client.go @@ -0,0 +1,18 @@ +package grpc + +import ( + "fmt" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +// Dial creates a gRPC client connection to the given address. +// Uses insecure credentials (no TLS) for internal service communication. +func Dial(addr string) (*grpc.ClientConn, error) { + conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, fmt.Errorf("grpc dial %s: %w", addr, err) + } + return conn, nil +} diff --git a/shared/pkg/grpc/inventory_client.go b/shared/pkg/grpc/inventory_client.go new file mode 100644 index 0000000..503bdff --- /dev/null +++ b/shared/pkg/grpc/inventory_client.go @@ -0,0 +1,64 @@ +package grpc + +import ( + "context" + "fmt" + + "google.golang.org/grpc" + + pb "github.com/muyuqingfeng/iloom/shared/pkg/grpc/pb/inventory" +) + +// InventoryClient wraps the generated InventoryServiceClient with convenience methods. +type InventoryClient struct { + conn *grpc.ClientConn + Raw pb.InventoryServiceClient +} + +// NewInventoryClient creates an InventoryClient connected to the given address. +func NewInventoryClient(addr string) (*InventoryClient, error) { + conn, err := Dial(addr) + if err != nil { + return nil, err + } + return &InventoryClient{ + conn: conn, + Raw: pb.NewInventoryServiceClient(conn), + }, nil +} + +// Close closes the underlying gRPC connection. +func (c *InventoryClient) Close() error { + return c.conn.Close() +} + +// CreateProduct creates a new product and returns the product ID. +func (c *InventoryClient) CreateProduct(ctx context.Context, req *pb.CreateProductReq) (string, error) { + resp, err := c.Raw.CreateProduct(ctx, req) + if err != nil { + return "", fmt.Errorf("inventory.CreateProduct: %w", err) + } + return resp.Id, nil +} + +// GetProduct returns product info by product ID. +func (c *InventoryClient) GetProduct(ctx context.Context, productID string) (*pb.ProductInfo, error) { + resp, err := c.Raw.GetProduct(ctx, &pb.GetProductReq{ProductId: productID}) + if err != nil { + return nil, fmt.Errorf("inventory.GetProduct: %w", err) + } + return resp, nil +} + +// ListProduct returns a paginated list of products with optional filters. +func (c *InventoryClient) ListProduct(ctx context.Context, page, pageSize int64, productName string) ([]*pb.ProductInfo, int64, error) { + resp, err := c.Raw.ListProduct(ctx, &pb.ListProductReq{ + Page: page, + PageSize: pageSize, + ProductName: productName, + }) + if err != nil { + return nil, 0, fmt.Errorf("inventory.ListProduct: %w", err) + } + return resp.List, resp.Total, nil +} diff --git a/shared/pkg/grpc/pb/inventory/inventory.pb.go b/shared/pkg/grpc/pb/inventory/inventory.pb.go new file mode 100644 index 0000000..2326f43 --- /dev/null +++ b/shared/pkg/grpc/pb/inventory/inventory.pb.go @@ -0,0 +1,3709 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.20.3 +// source: proto/inventory/inventory.proto + +package inventory + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ProductInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + ProductName string `protobuf:"bytes,2,opt,name=product_name,json=productName,proto3" json:"product_name,omitempty"` + ImageUrl string `protobuf:"bytes,3,opt,name=image_url,json=imageUrl,proto3" json:"image_url,omitempty"` + Spec string `protobuf:"bytes,4,opt,name=spec,proto3" json:"spec,omitempty"` + Color string `protobuf:"bytes,5,opt,name=color,proto3" json:"color,omitempty"` + UnitPieces int64 `protobuf:"varint,6,opt,name=unit_pieces,json=unitPieces,proto3" json:"unit_pieces,omitempty"` + UnitRolls int64 `protobuf:"varint,7,opt,name=unit_rolls,json=unitRolls,proto3" json:"unit_rolls,omitempty"` + StockQuantity string `protobuf:"bytes,8,opt,name=stock_quantity,json=stockQuantity,proto3" json:"stock_quantity,omitempty"` + Location string `protobuf:"bytes,9,opt,name=location,proto3" json:"location,omitempty"` + CostPrice string `protobuf:"bytes,10,opt,name=cost_price,json=costPrice,proto3" json:"cost_price,omitempty"` + SalesPrice string `protobuf:"bytes,11,opt,name=sales_price,json=salesPrice,proto3" json:"sales_price,omitempty"` + Remark string `protobuf:"bytes,12,opt,name=remark,proto3" json:"remark,omitempty"` + Status int64 `protobuf:"varint,13,opt,name=status,proto3" json:"status,omitempty"` + CreatedAt string `protobuf:"bytes,14,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt string `protobuf:"bytes,15,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` +} + +func (x *ProductInfo) Reset() { + *x = ProductInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProductInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProductInfo) ProtoMessage() {} + +func (x *ProductInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProductInfo.ProtoReflect.Descriptor instead. +func (*ProductInfo) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{0} +} + +func (x *ProductInfo) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *ProductInfo) GetProductName() string { + if x != nil { + return x.ProductName + } + return "" +} + +func (x *ProductInfo) GetImageUrl() string { + if x != nil { + return x.ImageUrl + } + return "" +} + +func (x *ProductInfo) GetSpec() string { + if x != nil { + return x.Spec + } + return "" +} + +func (x *ProductInfo) GetColor() string { + if x != nil { + return x.Color + } + return "" +} + +func (x *ProductInfo) GetUnitPieces() int64 { + if x != nil { + return x.UnitPieces + } + return 0 +} + +func (x *ProductInfo) GetUnitRolls() int64 { + if x != nil { + return x.UnitRolls + } + return 0 +} + +func (x *ProductInfo) GetStockQuantity() string { + if x != nil { + return x.StockQuantity + } + return "" +} + +func (x *ProductInfo) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *ProductInfo) GetCostPrice() string { + if x != nil { + return x.CostPrice + } + return "" +} + +func (x *ProductInfo) GetSalesPrice() string { + if x != nil { + return x.SalesPrice + } + return "" +} + +func (x *ProductInfo) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +func (x *ProductInfo) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *ProductInfo) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +func (x *ProductInfo) GetUpdatedAt() string { + if x != nil { + return x.UpdatedAt + } + return "" +} + +type CreateProductReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductName string `protobuf:"bytes,1,opt,name=product_name,json=productName,proto3" json:"product_name,omitempty"` + ImageUrl string `protobuf:"bytes,2,opt,name=image_url,json=imageUrl,proto3" json:"image_url,omitempty"` + Spec string `protobuf:"bytes,3,opt,name=spec,proto3" json:"spec,omitempty"` + Color string `protobuf:"bytes,4,opt,name=color,proto3" json:"color,omitempty"` + UnitPieces int64 `protobuf:"varint,5,opt,name=unit_pieces,json=unitPieces,proto3" json:"unit_pieces,omitempty"` + UnitRolls int64 `protobuf:"varint,6,opt,name=unit_rolls,json=unitRolls,proto3" json:"unit_rolls,omitempty"` + StockQuantity string `protobuf:"bytes,7,opt,name=stock_quantity,json=stockQuantity,proto3" json:"stock_quantity,omitempty"` + Location string `protobuf:"bytes,8,opt,name=location,proto3" json:"location,omitempty"` + CostPrice string `protobuf:"bytes,9,opt,name=cost_price,json=costPrice,proto3" json:"cost_price,omitempty"` + SalesPrice string `protobuf:"bytes,10,opt,name=sales_price,json=salesPrice,proto3" json:"sales_price,omitempty"` + Remark string `protobuf:"bytes,11,opt,name=remark,proto3" json:"remark,omitempty"` +} + +func (x *CreateProductReq) Reset() { + *x = CreateProductReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateProductReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateProductReq) ProtoMessage() {} + +func (x *CreateProductReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateProductReq.ProtoReflect.Descriptor instead. +func (*CreateProductReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateProductReq) GetProductName() string { + if x != nil { + return x.ProductName + } + return "" +} + +func (x *CreateProductReq) GetImageUrl() string { + if x != nil { + return x.ImageUrl + } + return "" +} + +func (x *CreateProductReq) GetSpec() string { + if x != nil { + return x.Spec + } + return "" +} + +func (x *CreateProductReq) GetColor() string { + if x != nil { + return x.Color + } + return "" +} + +func (x *CreateProductReq) GetUnitPieces() int64 { + if x != nil { + return x.UnitPieces + } + return 0 +} + +func (x *CreateProductReq) GetUnitRolls() int64 { + if x != nil { + return x.UnitRolls + } + return 0 +} + +func (x *CreateProductReq) GetStockQuantity() string { + if x != nil { + return x.StockQuantity + } + return "" +} + +func (x *CreateProductReq) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *CreateProductReq) GetCostPrice() string { + if x != nil { + return x.CostPrice + } + return "" +} + +func (x *CreateProductReq) GetSalesPrice() string { + if x != nil { + return x.SalesPrice + } + return "" +} + +func (x *CreateProductReq) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +type UpdateProductReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + ProductName string `protobuf:"bytes,2,opt,name=product_name,json=productName,proto3" json:"product_name,omitempty"` + ImageUrl string `protobuf:"bytes,3,opt,name=image_url,json=imageUrl,proto3" json:"image_url,omitempty"` + Spec string `protobuf:"bytes,4,opt,name=spec,proto3" json:"spec,omitempty"` + Color string `protobuf:"bytes,5,opt,name=color,proto3" json:"color,omitempty"` + UnitPieces int64 `protobuf:"varint,6,opt,name=unit_pieces,json=unitPieces,proto3" json:"unit_pieces,omitempty"` + UnitRolls int64 `protobuf:"varint,7,opt,name=unit_rolls,json=unitRolls,proto3" json:"unit_rolls,omitempty"` + StockQuantity string `protobuf:"bytes,8,opt,name=stock_quantity,json=stockQuantity,proto3" json:"stock_quantity,omitempty"` + Location string `protobuf:"bytes,9,opt,name=location,proto3" json:"location,omitempty"` + CostPrice string `protobuf:"bytes,10,opt,name=cost_price,json=costPrice,proto3" json:"cost_price,omitempty"` + SalesPrice string `protobuf:"bytes,11,opt,name=sales_price,json=salesPrice,proto3" json:"sales_price,omitempty"` + Remark string `protobuf:"bytes,12,opt,name=remark,proto3" json:"remark,omitempty"` +} + +func (x *UpdateProductReq) Reset() { + *x = UpdateProductReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateProductReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateProductReq) ProtoMessage() {} + +func (x *UpdateProductReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateProductReq.ProtoReflect.Descriptor instead. +func (*UpdateProductReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{2} +} + +func (x *UpdateProductReq) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *UpdateProductReq) GetProductName() string { + if x != nil { + return x.ProductName + } + return "" +} + +func (x *UpdateProductReq) GetImageUrl() string { + if x != nil { + return x.ImageUrl + } + return "" +} + +func (x *UpdateProductReq) GetSpec() string { + if x != nil { + return x.Spec + } + return "" +} + +func (x *UpdateProductReq) GetColor() string { + if x != nil { + return x.Color + } + return "" +} + +func (x *UpdateProductReq) GetUnitPieces() int64 { + if x != nil { + return x.UnitPieces + } + return 0 +} + +func (x *UpdateProductReq) GetUnitRolls() int64 { + if x != nil { + return x.UnitRolls + } + return 0 +} + +func (x *UpdateProductReq) GetStockQuantity() string { + if x != nil { + return x.StockQuantity + } + return "" +} + +func (x *UpdateProductReq) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *UpdateProductReq) GetCostPrice() string { + if x != nil { + return x.CostPrice + } + return "" +} + +func (x *UpdateProductReq) GetSalesPrice() string { + if x != nil { + return x.SalesPrice + } + return "" +} + +func (x *UpdateProductReq) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +type DeleteProductReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` +} + +func (x *DeleteProductReq) Reset() { + *x = DeleteProductReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteProductReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteProductReq) ProtoMessage() {} + +func (x *DeleteProductReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteProductReq.ProtoReflect.Descriptor instead. +func (*DeleteProductReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{3} +} + +func (x *DeleteProductReq) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +type GetProductReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` +} + +func (x *GetProductReq) Reset() { + *x = GetProductReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetProductReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetProductReq) ProtoMessage() {} + +func (x *GetProductReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetProductReq.ProtoReflect.Descriptor instead. +func (*GetProductReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{4} +} + +func (x *GetProductReq) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +type ListProductReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Page int64 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + PageSize int64 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + ProductName string `protobuf:"bytes,3,opt,name=product_name,json=productName,proto3" json:"product_name,omitempty"` + Spec string `protobuf:"bytes,4,opt,name=spec,proto3" json:"spec,omitempty"` + Color string `protobuf:"bytes,5,opt,name=color,proto3" json:"color,omitempty"` + Location string `protobuf:"bytes,6,opt,name=location,proto3" json:"location,omitempty"` + Status int64 `protobuf:"varint,7,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *ListProductReq) Reset() { + *x = ListProductReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListProductReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProductReq) ProtoMessage() {} + +func (x *ListProductReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProductReq.ProtoReflect.Descriptor instead. +func (*ListProductReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{5} +} + +func (x *ListProductReq) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListProductReq) GetPageSize() int64 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListProductReq) GetProductName() string { + if x != nil { + return x.ProductName + } + return "" +} + +func (x *ListProductReq) GetSpec() string { + if x != nil { + return x.Spec + } + return "" +} + +func (x *ListProductReq) GetColor() string { + if x != nil { + return x.Color + } + return "" +} + +func (x *ListProductReq) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *ListProductReq) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +type ListProductResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + List []*ProductInfo `protobuf:"bytes,2,rep,name=list,proto3" json:"list,omitempty"` +} + +func (x *ListProductResp) Reset() { + *x = ListProductResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListProductResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProductResp) ProtoMessage() {} + +func (x *ListProductResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProductResp.ProtoReflect.Descriptor instead. +func (*ListProductResp) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{6} +} + +func (x *ListProductResp) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ListProductResp) GetList() []*ProductInfo { + if x != nil { + return x.List + } + return nil +} + +type ImportProductReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Products []*CreateProductReq `protobuf:"bytes,1,rep,name=products,proto3" json:"products,omitempty"` + FileName string `protobuf:"bytes,2,opt,name=file_name,json=fileName,proto3" json:"file_name,omitempty"` + Operator string `protobuf:"bytes,3,opt,name=operator,proto3" json:"operator,omitempty"` +} + +func (x *ImportProductReq) Reset() { + *x = ImportProductReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImportProductReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImportProductReq) ProtoMessage() {} + +func (x *ImportProductReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImportProductReq.ProtoReflect.Descriptor instead. +func (*ImportProductReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{7} +} + +func (x *ImportProductReq) GetProducts() []*CreateProductReq { + if x != nil { + return x.Products + } + return nil +} + +func (x *ImportProductReq) GetFileName() string { + if x != nil { + return x.FileName + } + return "" +} + +func (x *ImportProductReq) GetOperator() string { + if x != nil { + return x.Operator + } + return "" +} + +type ImportProductResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TotalCount int64 `protobuf:"varint,1,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"` + SuccessCount int64 `protobuf:"varint,2,opt,name=success_count,json=successCount,proto3" json:"success_count,omitempty"` + FailCount int64 `protobuf:"varint,3,opt,name=fail_count,json=failCount,proto3" json:"fail_count,omitempty"` + ErrorMsg string `protobuf:"bytes,4,opt,name=error_msg,json=errorMsg,proto3" json:"error_msg,omitempty"` + ImportId string `protobuf:"bytes,5,opt,name=import_id,json=importId,proto3" json:"import_id,omitempty"` +} + +func (x *ImportProductResp) Reset() { + *x = ImportProductResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImportProductResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImportProductResp) ProtoMessage() {} + +func (x *ImportProductResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImportProductResp.ProtoReflect.Descriptor instead. +func (*ImportProductResp) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{8} +} + +func (x *ImportProductResp) GetTotalCount() int64 { + if x != nil { + return x.TotalCount + } + return 0 +} + +func (x *ImportProductResp) GetSuccessCount() int64 { + if x != nil { + return x.SuccessCount + } + return 0 +} + +func (x *ImportProductResp) GetFailCount() int64 { + if x != nil { + return x.FailCount + } + return 0 +} + +func (x *ImportProductResp) GetErrorMsg() string { + if x != nil { + return x.ErrorMsg + } + return "" +} + +func (x *ImportProductResp) GetImportId() string { + if x != nil { + return x.ImportId + } + return "" +} + +type ListImportLogReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Page int64 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + PageSize int64 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` +} + +func (x *ListImportLogReq) Reset() { + *x = ListImportLogReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListImportLogReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListImportLogReq) ProtoMessage() {} + +func (x *ListImportLogReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListImportLogReq.ProtoReflect.Descriptor instead. +func (*ListImportLogReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{9} +} + +func (x *ListImportLogReq) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListImportLogReq) GetPageSize() int64 { + if x != nil { + return x.PageSize + } + return 0 +} + +type ListImportLogResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + List []*ImportLogInfo `protobuf:"bytes,2,rep,name=list,proto3" json:"list,omitempty"` +} + +func (x *ListImportLogResp) Reset() { + *x = ListImportLogResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListImportLogResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListImportLogResp) ProtoMessage() {} + +func (x *ListImportLogResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListImportLogResp.ProtoReflect.Descriptor instead. +func (*ListImportLogResp) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{10} +} + +func (x *ListImportLogResp) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ListImportLogResp) GetList() []*ImportLogInfo { + if x != nil { + return x.List + } + return nil +} + +type ImportLogInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ImportId string `protobuf:"bytes,1,opt,name=import_id,json=importId,proto3" json:"import_id,omitempty"` + FileName string `protobuf:"bytes,2,opt,name=file_name,json=fileName,proto3" json:"file_name,omitempty"` + TotalCount int64 `protobuf:"varint,3,opt,name=total_count,json=totalCount,proto3" json:"total_count,omitempty"` + SuccessCount int64 `protobuf:"varint,4,opt,name=success_count,json=successCount,proto3" json:"success_count,omitempty"` + FailCount int64 `protobuf:"varint,5,opt,name=fail_count,json=failCount,proto3" json:"fail_count,omitempty"` + ErrorMsg string `protobuf:"bytes,6,opt,name=error_msg,json=errorMsg,proto3" json:"error_msg,omitempty"` + Operator string `protobuf:"bytes,7,opt,name=operator,proto3" json:"operator,omitempty"` + CreatedAt string `protobuf:"bytes,8,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` +} + +func (x *ImportLogInfo) Reset() { + *x = ImportLogInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImportLogInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImportLogInfo) ProtoMessage() {} + +func (x *ImportLogInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImportLogInfo.ProtoReflect.Descriptor instead. +func (*ImportLogInfo) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{11} +} + +func (x *ImportLogInfo) GetImportId() string { + if x != nil { + return x.ImportId + } + return "" +} + +func (x *ImportLogInfo) GetFileName() string { + if x != nil { + return x.FileName + } + return "" +} + +func (x *ImportLogInfo) GetTotalCount() int64 { + if x != nil { + return x.TotalCount + } + return 0 +} + +func (x *ImportLogInfo) GetSuccessCount() int64 { + if x != nil { + return x.SuccessCount + } + return 0 +} + +func (x *ImportLogInfo) GetFailCount() int64 { + if x != nil { + return x.FailCount + } + return 0 +} + +func (x *ImportLogInfo) GetErrorMsg() string { + if x != nil { + return x.ErrorMsg + } + return "" +} + +func (x *ImportLogInfo) GetOperator() string { + if x != nil { + return x.Operator + } + return "" +} + +func (x *ImportLogInfo) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +type StockSummaryReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *StockSummaryReq) Reset() { + *x = StockSummaryReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StockSummaryReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StockSummaryReq) ProtoMessage() {} + +func (x *StockSummaryReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StockSummaryReq.ProtoReflect.Descriptor instead. +func (*StockSummaryReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{12} +} + +type StockSummaryResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductCount int64 `protobuf:"varint,1,opt,name=product_count,json=productCount,proto3" json:"product_count,omitempty"` + TotalPieces int64 `protobuf:"varint,2,opt,name=total_pieces,json=totalPieces,proto3" json:"total_pieces,omitempty"` + TotalRolls int64 `protobuf:"varint,3,opt,name=total_rolls,json=totalRolls,proto3" json:"total_rolls,omitempty"` + TotalCostValue string `protobuf:"bytes,4,opt,name=total_cost_value,json=totalCostValue,proto3" json:"total_cost_value,omitempty"` + TotalSalesValue string `protobuf:"bytes,5,opt,name=total_sales_value,json=totalSalesValue,proto3" json:"total_sales_value,omitempty"` +} + +func (x *StockSummaryResp) Reset() { + *x = StockSummaryResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StockSummaryResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StockSummaryResp) ProtoMessage() {} + +func (x *StockSummaryResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StockSummaryResp.ProtoReflect.Descriptor instead. +func (*StockSummaryResp) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{13} +} + +func (x *StockSummaryResp) GetProductCount() int64 { + if x != nil { + return x.ProductCount + } + return 0 +} + +func (x *StockSummaryResp) GetTotalPieces() int64 { + if x != nil { + return x.TotalPieces + } + return 0 +} + +func (x *StockSummaryResp) GetTotalRolls() int64 { + if x != nil { + return x.TotalRolls + } + return 0 +} + +func (x *StockSummaryResp) GetTotalCostValue() string { + if x != nil { + return x.TotalCostValue + } + return "" +} + +func (x *StockSummaryResp) GetTotalSalesValue() string { + if x != nil { + return x.TotalSalesValue + } + return "" +} + +type StockGroupReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + GroupBy string `protobuf:"bytes,1,opt,name=group_by,json=groupBy,proto3" json:"group_by,omitempty"` +} + +func (x *StockGroupReq) Reset() { + *x = StockGroupReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StockGroupReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StockGroupReq) ProtoMessage() {} + +func (x *StockGroupReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StockGroupReq.ProtoReflect.Descriptor instead. +func (*StockGroupReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{14} +} + +func (x *StockGroupReq) GetGroupBy() string { + if x != nil { + return x.GroupBy + } + return "" +} + +type StockGroupItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Count int64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"` + Quantity string `protobuf:"bytes,3,opt,name=quantity,proto3" json:"quantity,omitempty"` +} + +func (x *StockGroupItem) Reset() { + *x = StockGroupItem{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StockGroupItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StockGroupItem) ProtoMessage() {} + +func (x *StockGroupItem) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StockGroupItem.ProtoReflect.Descriptor instead. +func (*StockGroupItem) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{15} +} + +func (x *StockGroupItem) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *StockGroupItem) GetCount() int64 { + if x != nil { + return x.Count + } + return 0 +} + +func (x *StockGroupItem) GetQuantity() string { + if x != nil { + return x.Quantity + } + return "" +} + +type StockGroupResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + List []*StockGroupItem `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` +} + +func (x *StockGroupResp) Reset() { + *x = StockGroupResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StockGroupResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StockGroupResp) ProtoMessage() {} + +func (x *StockGroupResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StockGroupResp.ProtoReflect.Descriptor instead. +func (*StockGroupResp) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{16} +} + +func (x *StockGroupResp) GetList() []*StockGroupItem { + if x != nil { + return x.List + } + return nil +} + +type StockCheckInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CheckId string `protobuf:"bytes,1,opt,name=check_id,json=checkId,proto3" json:"check_id,omitempty"` + CheckNo string `protobuf:"bytes,2,opt,name=check_no,json=checkNo,proto3" json:"check_no,omitempty"` + CheckDate string `protobuf:"bytes,3,opt,name=check_date,json=checkDate,proto3" json:"check_date,omitempty"` + Checker string `protobuf:"bytes,4,opt,name=checker,proto3" json:"checker,omitempty"` + Status int64 `protobuf:"varint,5,opt,name=status,proto3" json:"status,omitempty"` + Remark string `protobuf:"bytes,6,opt,name=remark,proto3" json:"remark,omitempty"` + CreatedAt string `protobuf:"bytes,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt string `protobuf:"bytes,8,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + Details []*StockCheckDetailInfo `protobuf:"bytes,9,rep,name=details,proto3" json:"details,omitempty"` +} + +func (x *StockCheckInfo) Reset() { + *x = StockCheckInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StockCheckInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StockCheckInfo) ProtoMessage() {} + +func (x *StockCheckInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StockCheckInfo.ProtoReflect.Descriptor instead. +func (*StockCheckInfo) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{17} +} + +func (x *StockCheckInfo) GetCheckId() string { + if x != nil { + return x.CheckId + } + return "" +} + +func (x *StockCheckInfo) GetCheckNo() string { + if x != nil { + return x.CheckNo + } + return "" +} + +func (x *StockCheckInfo) GetCheckDate() string { + if x != nil { + return x.CheckDate + } + return "" +} + +func (x *StockCheckInfo) GetChecker() string { + if x != nil { + return x.Checker + } + return "" +} + +func (x *StockCheckInfo) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *StockCheckInfo) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +func (x *StockCheckInfo) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +func (x *StockCheckInfo) GetUpdatedAt() string { + if x != nil { + return x.UpdatedAt + } + return "" +} + +func (x *StockCheckInfo) GetDetails() []*StockCheckDetailInfo { + if x != nil { + return x.Details + } + return nil +} + +type StockCheckDetailInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DetailId string `protobuf:"bytes,1,opt,name=detail_id,json=detailId,proto3" json:"detail_id,omitempty"` + CheckId string `protobuf:"bytes,2,opt,name=check_id,json=checkId,proto3" json:"check_id,omitempty"` + ProductId string `protobuf:"bytes,3,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + ProductName string `protobuf:"bytes,4,opt,name=product_name,json=productName,proto3" json:"product_name,omitempty"` + SystemQuantity string `protobuf:"bytes,5,opt,name=system_quantity,json=systemQuantity,proto3" json:"system_quantity,omitempty"` + ActualQuantity string `protobuf:"bytes,6,opt,name=actual_quantity,json=actualQuantity,proto3" json:"actual_quantity,omitempty"` + DiffQuantity string `protobuf:"bytes,7,opt,name=diff_quantity,json=diffQuantity,proto3" json:"diff_quantity,omitempty"` + DiffAmount string `protobuf:"bytes,8,opt,name=diff_amount,json=diffAmount,proto3" json:"diff_amount,omitempty"` + Remark string `protobuf:"bytes,9,opt,name=remark,proto3" json:"remark,omitempty"` +} + +func (x *StockCheckDetailInfo) Reset() { + *x = StockCheckDetailInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StockCheckDetailInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StockCheckDetailInfo) ProtoMessage() {} + +func (x *StockCheckDetailInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StockCheckDetailInfo.ProtoReflect.Descriptor instead. +func (*StockCheckDetailInfo) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{18} +} + +func (x *StockCheckDetailInfo) GetDetailId() string { + if x != nil { + return x.DetailId + } + return "" +} + +func (x *StockCheckDetailInfo) GetCheckId() string { + if x != nil { + return x.CheckId + } + return "" +} + +func (x *StockCheckDetailInfo) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *StockCheckDetailInfo) GetProductName() string { + if x != nil { + return x.ProductName + } + return "" +} + +func (x *StockCheckDetailInfo) GetSystemQuantity() string { + if x != nil { + return x.SystemQuantity + } + return "" +} + +func (x *StockCheckDetailInfo) GetActualQuantity() string { + if x != nil { + return x.ActualQuantity + } + return "" +} + +func (x *StockCheckDetailInfo) GetDiffQuantity() string { + if x != nil { + return x.DiffQuantity + } + return "" +} + +func (x *StockCheckDetailInfo) GetDiffAmount() string { + if x != nil { + return x.DiffAmount + } + return "" +} + +func (x *StockCheckDetailInfo) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +type CreateStockCheckReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CheckDate string `protobuf:"bytes,1,opt,name=check_date,json=checkDate,proto3" json:"check_date,omitempty"` + Checker string `protobuf:"bytes,2,opt,name=checker,proto3" json:"checker,omitempty"` + Remark string `protobuf:"bytes,3,opt,name=remark,proto3" json:"remark,omitempty"` + Details []*StockCheckDetailReq `protobuf:"bytes,4,rep,name=details,proto3" json:"details,omitempty"` +} + +func (x *CreateStockCheckReq) Reset() { + *x = CreateStockCheckReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateStockCheckReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateStockCheckReq) ProtoMessage() {} + +func (x *CreateStockCheckReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateStockCheckReq.ProtoReflect.Descriptor instead. +func (*CreateStockCheckReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{19} +} + +func (x *CreateStockCheckReq) GetCheckDate() string { + if x != nil { + return x.CheckDate + } + return "" +} + +func (x *CreateStockCheckReq) GetChecker() string { + if x != nil { + return x.Checker + } + return "" +} + +func (x *CreateStockCheckReq) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +func (x *CreateStockCheckReq) GetDetails() []*StockCheckDetailReq { + if x != nil { + return x.Details + } + return nil +} + +type StockCheckDetailReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + ActualQuantity string `protobuf:"bytes,2,opt,name=actual_quantity,json=actualQuantity,proto3" json:"actual_quantity,omitempty"` + Remark string `protobuf:"bytes,3,opt,name=remark,proto3" json:"remark,omitempty"` +} + +func (x *StockCheckDetailReq) Reset() { + *x = StockCheckDetailReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StockCheckDetailReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StockCheckDetailReq) ProtoMessage() {} + +func (x *StockCheckDetailReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StockCheckDetailReq.ProtoReflect.Descriptor instead. +func (*StockCheckDetailReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{20} +} + +func (x *StockCheckDetailReq) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *StockCheckDetailReq) GetActualQuantity() string { + if x != nil { + return x.ActualQuantity + } + return "" +} + +func (x *StockCheckDetailReq) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +type UpdateStockCheckReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CheckId string `protobuf:"bytes,1,opt,name=check_id,json=checkId,proto3" json:"check_id,omitempty"` + Remark string `protobuf:"bytes,2,opt,name=remark,proto3" json:"remark,omitempty"` + Details []*StockCheckDetailReq `protobuf:"bytes,3,rep,name=details,proto3" json:"details,omitempty"` +} + +func (x *UpdateStockCheckReq) Reset() { + *x = UpdateStockCheckReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateStockCheckReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateStockCheckReq) ProtoMessage() {} + +func (x *UpdateStockCheckReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateStockCheckReq.ProtoReflect.Descriptor instead. +func (*UpdateStockCheckReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{21} +} + +func (x *UpdateStockCheckReq) GetCheckId() string { + if x != nil { + return x.CheckId + } + return "" +} + +func (x *UpdateStockCheckReq) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +func (x *UpdateStockCheckReq) GetDetails() []*StockCheckDetailReq { + if x != nil { + return x.Details + } + return nil +} + +type ConfirmStockCheckReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CheckId string `protobuf:"bytes,1,opt,name=check_id,json=checkId,proto3" json:"check_id,omitempty"` + Operator string `protobuf:"bytes,2,opt,name=operator,proto3" json:"operator,omitempty"` +} + +func (x *ConfirmStockCheckReq) Reset() { + *x = ConfirmStockCheckReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfirmStockCheckReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfirmStockCheckReq) ProtoMessage() {} + +func (x *ConfirmStockCheckReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfirmStockCheckReq.ProtoReflect.Descriptor instead. +func (*ConfirmStockCheckReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{22} +} + +func (x *ConfirmStockCheckReq) GetCheckId() string { + if x != nil { + return x.CheckId + } + return "" +} + +func (x *ConfirmStockCheckReq) GetOperator() string { + if x != nil { + return x.Operator + } + return "" +} + +type GetStockCheckReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CheckId string `protobuf:"bytes,1,opt,name=check_id,json=checkId,proto3" json:"check_id,omitempty"` +} + +func (x *GetStockCheckReq) Reset() { + *x = GetStockCheckReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetStockCheckReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetStockCheckReq) ProtoMessage() {} + +func (x *GetStockCheckReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetStockCheckReq.ProtoReflect.Descriptor instead. +func (*GetStockCheckReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{23} +} + +func (x *GetStockCheckReq) GetCheckId() string { + if x != nil { + return x.CheckId + } + return "" +} + +type ListStockCheckReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Page int64 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + PageSize int64 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + CheckNo string `protobuf:"bytes,3,opt,name=check_no,json=checkNo,proto3" json:"check_no,omitempty"` + Status int64 `protobuf:"varint,4,opt,name=status,proto3" json:"status,omitempty"` + StartDate string `protobuf:"bytes,5,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"` + EndDate string `protobuf:"bytes,6,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"` +} + +func (x *ListStockCheckReq) Reset() { + *x = ListStockCheckReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListStockCheckReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListStockCheckReq) ProtoMessage() {} + +func (x *ListStockCheckReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListStockCheckReq.ProtoReflect.Descriptor instead. +func (*ListStockCheckReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{24} +} + +func (x *ListStockCheckReq) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListStockCheckReq) GetPageSize() int64 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListStockCheckReq) GetCheckNo() string { + if x != nil { + return x.CheckNo + } + return "" +} + +func (x *ListStockCheckReq) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *ListStockCheckReq) GetStartDate() string { + if x != nil { + return x.StartDate + } + return "" +} + +func (x *ListStockCheckReq) GetEndDate() string { + if x != nil { + return x.EndDate + } + return "" +} + +type ListStockCheckResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + List []*StockCheckInfo `protobuf:"bytes,2,rep,name=list,proto3" json:"list,omitempty"` +} + +func (x *ListStockCheckResp) Reset() { + *x = ListStockCheckResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListStockCheckResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListStockCheckResp) ProtoMessage() {} + +func (x *ListStockCheckResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListStockCheckResp.ProtoReflect.Descriptor instead. +func (*ListStockCheckResp) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{25} +} + +func (x *ListStockCheckResp) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ListStockCheckResp) GetList() []*StockCheckInfo { + if x != nil { + return x.List + } + return nil +} + +type StockAdjustInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AdjustId string `protobuf:"bytes,1,opt,name=adjust_id,json=adjustId,proto3" json:"adjust_id,omitempty"` + AdjustNo string `protobuf:"bytes,2,opt,name=adjust_no,json=adjustNo,proto3" json:"adjust_no,omitempty"` + AdjustDate string `protobuf:"bytes,3,opt,name=adjust_date,json=adjustDate,proto3" json:"adjust_date,omitempty"` + AdjustReason string `protobuf:"bytes,4,opt,name=adjust_reason,json=adjustReason,proto3" json:"adjust_reason,omitempty"` + Operator string `protobuf:"bytes,5,opt,name=operator,proto3" json:"operator,omitempty"` + Approver string `protobuf:"bytes,6,opt,name=approver,proto3" json:"approver,omitempty"` + Status int64 `protobuf:"varint,7,opt,name=status,proto3" json:"status,omitempty"` + Remark string `protobuf:"bytes,8,opt,name=remark,proto3" json:"remark,omitempty"` + CreatedAt string `protobuf:"bytes,9,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt string `protobuf:"bytes,10,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + Details []*StockAdjustDetailInfo `protobuf:"bytes,11,rep,name=details,proto3" json:"details,omitempty"` +} + +func (x *StockAdjustInfo) Reset() { + *x = StockAdjustInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StockAdjustInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StockAdjustInfo) ProtoMessage() {} + +func (x *StockAdjustInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StockAdjustInfo.ProtoReflect.Descriptor instead. +func (*StockAdjustInfo) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{26} +} + +func (x *StockAdjustInfo) GetAdjustId() string { + if x != nil { + return x.AdjustId + } + return "" +} + +func (x *StockAdjustInfo) GetAdjustNo() string { + if x != nil { + return x.AdjustNo + } + return "" +} + +func (x *StockAdjustInfo) GetAdjustDate() string { + if x != nil { + return x.AdjustDate + } + return "" +} + +func (x *StockAdjustInfo) GetAdjustReason() string { + if x != nil { + return x.AdjustReason + } + return "" +} + +func (x *StockAdjustInfo) GetOperator() string { + if x != nil { + return x.Operator + } + return "" +} + +func (x *StockAdjustInfo) GetApprover() string { + if x != nil { + return x.Approver + } + return "" +} + +func (x *StockAdjustInfo) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *StockAdjustInfo) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +func (x *StockAdjustInfo) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +func (x *StockAdjustInfo) GetUpdatedAt() string { + if x != nil { + return x.UpdatedAt + } + return "" +} + +func (x *StockAdjustInfo) GetDetails() []*StockAdjustDetailInfo { + if x != nil { + return x.Details + } + return nil +} + +type StockAdjustDetailInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DetailId string `protobuf:"bytes,1,opt,name=detail_id,json=detailId,proto3" json:"detail_id,omitempty"` + AdjustId string `protobuf:"bytes,2,opt,name=adjust_id,json=adjustId,proto3" json:"adjust_id,omitempty"` + ProductId string `protobuf:"bytes,3,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + ProductName string `protobuf:"bytes,4,opt,name=product_name,json=productName,proto3" json:"product_name,omitempty"` + BeforeQuantity string `protobuf:"bytes,5,opt,name=before_quantity,json=beforeQuantity,proto3" json:"before_quantity,omitempty"` + AdjustQuantity string `protobuf:"bytes,6,opt,name=adjust_quantity,json=adjustQuantity,proto3" json:"adjust_quantity,omitempty"` + AfterQuantity string `protobuf:"bytes,7,opt,name=after_quantity,json=afterQuantity,proto3" json:"after_quantity,omitempty"` + Remark string `protobuf:"bytes,8,opt,name=remark,proto3" json:"remark,omitempty"` +} + +func (x *StockAdjustDetailInfo) Reset() { + *x = StockAdjustDetailInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StockAdjustDetailInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StockAdjustDetailInfo) ProtoMessage() {} + +func (x *StockAdjustDetailInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StockAdjustDetailInfo.ProtoReflect.Descriptor instead. +func (*StockAdjustDetailInfo) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{27} +} + +func (x *StockAdjustDetailInfo) GetDetailId() string { + if x != nil { + return x.DetailId + } + return "" +} + +func (x *StockAdjustDetailInfo) GetAdjustId() string { + if x != nil { + return x.AdjustId + } + return "" +} + +func (x *StockAdjustDetailInfo) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *StockAdjustDetailInfo) GetProductName() string { + if x != nil { + return x.ProductName + } + return "" +} + +func (x *StockAdjustDetailInfo) GetBeforeQuantity() string { + if x != nil { + return x.BeforeQuantity + } + return "" +} + +func (x *StockAdjustDetailInfo) GetAdjustQuantity() string { + if x != nil { + return x.AdjustQuantity + } + return "" +} + +func (x *StockAdjustDetailInfo) GetAfterQuantity() string { + if x != nil { + return x.AfterQuantity + } + return "" +} + +func (x *StockAdjustDetailInfo) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +type CreateStockAdjustReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AdjustDate string `protobuf:"bytes,1,opt,name=adjust_date,json=adjustDate,proto3" json:"adjust_date,omitempty"` + AdjustReason string `protobuf:"bytes,2,opt,name=adjust_reason,json=adjustReason,proto3" json:"adjust_reason,omitempty"` + Operator string `protobuf:"bytes,3,opt,name=operator,proto3" json:"operator,omitempty"` + Remark string `protobuf:"bytes,4,opt,name=remark,proto3" json:"remark,omitempty"` + Details []*StockAdjustDetailReq `protobuf:"bytes,5,rep,name=details,proto3" json:"details,omitempty"` +} + +func (x *CreateStockAdjustReq) Reset() { + *x = CreateStockAdjustReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateStockAdjustReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateStockAdjustReq) ProtoMessage() {} + +func (x *CreateStockAdjustReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateStockAdjustReq.ProtoReflect.Descriptor instead. +func (*CreateStockAdjustReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{28} +} + +func (x *CreateStockAdjustReq) GetAdjustDate() string { + if x != nil { + return x.AdjustDate + } + return "" +} + +func (x *CreateStockAdjustReq) GetAdjustReason() string { + if x != nil { + return x.AdjustReason + } + return "" +} + +func (x *CreateStockAdjustReq) GetOperator() string { + if x != nil { + return x.Operator + } + return "" +} + +func (x *CreateStockAdjustReq) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +func (x *CreateStockAdjustReq) GetDetails() []*StockAdjustDetailReq { + if x != nil { + return x.Details + } + return nil +} + +type StockAdjustDetailReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProductId string `protobuf:"bytes,1,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"` + AdjustQuantity string `protobuf:"bytes,2,opt,name=adjust_quantity,json=adjustQuantity,proto3" json:"adjust_quantity,omitempty"` + Remark string `protobuf:"bytes,3,opt,name=remark,proto3" json:"remark,omitempty"` +} + +func (x *StockAdjustDetailReq) Reset() { + *x = StockAdjustDetailReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StockAdjustDetailReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StockAdjustDetailReq) ProtoMessage() {} + +func (x *StockAdjustDetailReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StockAdjustDetailReq.ProtoReflect.Descriptor instead. +func (*StockAdjustDetailReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{29} +} + +func (x *StockAdjustDetailReq) GetProductId() string { + if x != nil { + return x.ProductId + } + return "" +} + +func (x *StockAdjustDetailReq) GetAdjustQuantity() string { + if x != nil { + return x.AdjustQuantity + } + return "" +} + +func (x *StockAdjustDetailReq) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +type ApproveStockAdjustReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AdjustId string `protobuf:"bytes,1,opt,name=adjust_id,json=adjustId,proto3" json:"adjust_id,omitempty"` + Approver string `protobuf:"bytes,2,opt,name=approver,proto3" json:"approver,omitempty"` + Action int64 `protobuf:"varint,3,opt,name=action,proto3" json:"action,omitempty"` +} + +func (x *ApproveStockAdjustReq) Reset() { + *x = ApproveStockAdjustReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ApproveStockAdjustReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApproveStockAdjustReq) ProtoMessage() {} + +func (x *ApproveStockAdjustReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApproveStockAdjustReq.ProtoReflect.Descriptor instead. +func (*ApproveStockAdjustReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{30} +} + +func (x *ApproveStockAdjustReq) GetAdjustId() string { + if x != nil { + return x.AdjustId + } + return "" +} + +func (x *ApproveStockAdjustReq) GetApprover() string { + if x != nil { + return x.Approver + } + return "" +} + +func (x *ApproveStockAdjustReq) GetAction() int64 { + if x != nil { + return x.Action + } + return 0 +} + +type GetStockAdjustReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AdjustId string `protobuf:"bytes,1,opt,name=adjust_id,json=adjustId,proto3" json:"adjust_id,omitempty"` +} + +func (x *GetStockAdjustReq) Reset() { + *x = GetStockAdjustReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetStockAdjustReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetStockAdjustReq) ProtoMessage() {} + +func (x *GetStockAdjustReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetStockAdjustReq.ProtoReflect.Descriptor instead. +func (*GetStockAdjustReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{31} +} + +func (x *GetStockAdjustReq) GetAdjustId() string { + if x != nil { + return x.AdjustId + } + return "" +} + +type ListStockAdjustReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Page int64 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + PageSize int64 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + AdjustNo string `protobuf:"bytes,3,opt,name=adjust_no,json=adjustNo,proto3" json:"adjust_no,omitempty"` + AdjustReason string `protobuf:"bytes,4,opt,name=adjust_reason,json=adjustReason,proto3" json:"adjust_reason,omitempty"` + Status int64 `protobuf:"varint,5,opt,name=status,proto3" json:"status,omitempty"` + StartDate string `protobuf:"bytes,6,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"` + EndDate string `protobuf:"bytes,7,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"` +} + +func (x *ListStockAdjustReq) Reset() { + *x = ListStockAdjustReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListStockAdjustReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListStockAdjustReq) ProtoMessage() {} + +func (x *ListStockAdjustReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[32] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListStockAdjustReq.ProtoReflect.Descriptor instead. +func (*ListStockAdjustReq) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{32} +} + +func (x *ListStockAdjustReq) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListStockAdjustReq) GetPageSize() int64 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListStockAdjustReq) GetAdjustNo() string { + if x != nil { + return x.AdjustNo + } + return "" +} + +func (x *ListStockAdjustReq) GetAdjustReason() string { + if x != nil { + return x.AdjustReason + } + return "" +} + +func (x *ListStockAdjustReq) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *ListStockAdjustReq) GetStartDate() string { + if x != nil { + return x.StartDate + } + return "" +} + +func (x *ListStockAdjustReq) GetEndDate() string { + if x != nil { + return x.EndDate + } + return "" +} + +type ListStockAdjustResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + List []*StockAdjustInfo `protobuf:"bytes,2,rep,name=list,proto3" json:"list,omitempty"` +} + +func (x *ListStockAdjustResp) Reset() { + *x = ListStockAdjustResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListStockAdjustResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListStockAdjustResp) ProtoMessage() {} + +func (x *ListStockAdjustResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[33] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListStockAdjustResp.ProtoReflect.Descriptor instead. +func (*ListStockAdjustResp) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{33} +} + +func (x *ListStockAdjustResp) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ListStockAdjustResp) GetList() []*StockAdjustInfo { + if x != nil { + return x.List + } + return nil +} + +type IdResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *IdResp) Reset() { + *x = IdResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IdResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IdResp) ProtoMessage() {} + +func (x *IdResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[34] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IdResp.ProtoReflect.Descriptor instead. +func (*IdResp) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{34} +} + +func (x *IdResp) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_inventory_inventory_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_proto_inventory_inventory_proto_msgTypes[35] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_proto_inventory_inventory_proto_rawDescGZIP(), []int{35} +} + +var File_proto_inventory_inventory_proto protoreflect.FileDescriptor + +var file_proto_inventory_inventory_proto_rawDesc = []byte{ + 0x0a, 0x1f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, + 0x79, 0x2f, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x09, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x22, 0xc7, 0x03, 0x0a, + 0x0b, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x73, + 0x70, 0x65, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, + 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x70, 0x69, + 0x65, 0x63, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x75, 0x6e, 0x69, 0x74, + 0x50, 0x69, 0x65, 0x63, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x72, + 0x6f, 0x6c, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x6e, 0x69, 0x74, + 0x52, 0x6f, 0x6c, 0x6c, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x5f, 0x71, + 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, + 0x74, 0x6f, 0x63, 0x6b, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x08, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x73, 0x74, + 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, + 0x73, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x61, 0x6c, 0x65, 0x73, + 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x61, + 0x6c, 0x65, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x61, + 0x72, 0x6b, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0xd7, 0x02, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x12, 0x21, 0x0a, 0x0c, 0x70, + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, + 0x0a, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x73, + 0x70, 0x65, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, + 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x70, 0x69, + 0x65, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x75, 0x6e, 0x69, 0x74, + 0x50, 0x69, 0x65, 0x63, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x72, + 0x6f, 0x6c, 0x6c, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x6e, 0x69, 0x74, + 0x52, 0x6f, 0x6c, 0x6c, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x5f, 0x71, + 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, + 0x74, 0x6f, 0x63, 0x6b, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x08, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x73, 0x74, + 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, + 0x73, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x61, 0x6c, 0x65, 0x73, + 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x61, + 0x6c, 0x65, 0x73, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x61, + 0x72, 0x6b, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, + 0x22, 0xf6, 0x02, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x52, 0x65, 0x71, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, + 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x1f, + 0x0a, 0x0b, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x70, 0x69, 0x65, 0x63, 0x65, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0a, 0x75, 0x6e, 0x69, 0x74, 0x50, 0x69, 0x65, 0x63, 0x65, 0x73, 0x12, + 0x1d, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x72, 0x6f, 0x6c, 0x6c, 0x73, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x6e, 0x69, 0x74, 0x52, 0x6f, 0x6c, 0x6c, 0x73, 0x12, 0x25, + 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x5f, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x51, 0x75, 0x61, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x73, 0x74, 0x50, 0x72, 0x69, 0x63, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x50, 0x72, 0x69, 0x63, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x22, 0x31, 0x0a, 0x10, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x12, 0x1d, 0x0a, + 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x22, 0x2e, 0x0a, 0x0d, + 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x12, 0x1d, 0x0a, + 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x22, 0xc2, 0x01, 0x0a, + 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x12, + 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, + 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x12, 0x1a, 0x0a, + 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x22, 0x53, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x2a, 0x0a, 0x04, 0x6c, 0x69, + 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, + 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x84, 0x01, 0x0a, 0x10, 0x49, 0x6d, 0x70, 0x6f, 0x72, + 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x12, 0x37, 0x0a, 0x08, 0x70, + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x22, 0xb2, 0x01, + 0x0a, 0x11, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x75, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x61, 0x69, + 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x66, + 0x61, 0x69, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, + 0x49, 0x64, 0x22, 0x43, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, + 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, + 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x57, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x12, 0x2c, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x49, 0x6d, 0x70, + 0x6f, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, + 0x22, 0x86, 0x02, 0x0a, 0x0d, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x49, 0x64, 0x12, + 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x23, 0x0a, + 0x0d, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x43, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x61, 0x69, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x66, 0x61, 0x69, 0x6c, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x1a, + 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x74, 0x6f, + 0x63, 0x6b, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x22, 0xd1, 0x01, 0x0a, + 0x10, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, + 0x70, 0x69, 0x65, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x50, 0x69, 0x65, 0x63, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x5f, 0x72, 0x6f, 0x6c, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x52, 0x6f, 0x6c, 0x6c, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x73, 0x61, + 0x6c, 0x65, 0x73, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0x2a, 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, + 0x71, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x62, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x42, 0x79, 0x22, 0x56, 0x0a, 0x0e, + 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x22, 0x3f, 0x0a, 0x0e, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2d, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, + 0x2e, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x52, + 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0xa8, 0x02, 0x0a, 0x0e, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x6e, 0x6f, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x4e, 0x6f, 0x12, 0x1d, + 0x0a, 0x0a, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, + 0x72, 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x22, 0xc0, 0x02, 0x0a, 0x14, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x71, 0x75, + 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x27, 0x0a, 0x0f, + 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x51, 0x75, 0x61, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x69, 0x66, 0x66, 0x5f, 0x71, 0x75, + 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x69, + 0x66, 0x66, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x69, + 0x66, 0x66, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x64, 0x69, 0x66, 0x66, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, + 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x6d, + 0x61, 0x72, 0x6b, 0x22, 0xa0, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, + 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x12, 0x1d, 0x0a, 0x0a, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x44, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x38, 0x0a, 0x07, + 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, + 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x52, 0x07, 0x64, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x75, 0x0a, 0x13, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x12, 0x1d, 0x0a, + 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, + 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x51, 0x75, 0x61, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x22, 0x82, 0x01, + 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x71, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x38, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x6e, 0x76, 0x65, + 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x22, 0x4d, 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x53, 0x74, 0x6f, + 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, + 0x72, 0x22, 0x2d, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x71, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x64, + 0x22, 0xb1, 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, + 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x5f, 0x6e, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x4e, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, + 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, + 0x44, 0x61, 0x74, 0x65, 0x22, 0x59, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x63, + 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x12, 0x2d, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x63, 0x6b, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, + 0xf3, 0x02, 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x4e, 0x6f, 0x12, 0x1f, 0x0a, + 0x0b, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x52, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, + 0x1a, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x1d, 0x0a, 0x0a, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x3a, 0x0a, 0x07, 0x64, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x69, 0x6e, 0x76, + 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, + 0x73, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x64, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0xa4, 0x02, 0x0a, 0x15, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, + 0x64, 0x6a, 0x75, 0x73, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, + 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, + 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x62, + 0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x51, 0x75, 0x61, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, 0x71, + 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, + 0x64, 0x6a, 0x75, 0x73, 0x74, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x25, 0x0a, + 0x0e, 0x61, 0x66, 0x74, 0x65, 0x72, 0x5f, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x51, 0x75, 0x61, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x22, 0xcb, 0x01, 0x0a, + 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, + 0x73, 0x74, 0x52, 0x65, 0x71, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, + 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x64, 0x6a, 0x75, + 0x73, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, + 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, + 0x64, 0x6a, 0x75, 0x73, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, + 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x12, + 0x39, 0x0a, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1f, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x6f, + 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, 0x65, + 0x71, 0x52, 0x07, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x76, 0x0a, 0x14, 0x53, 0x74, + 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x52, + 0x65, 0x71, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, + 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, 0x71, 0x75, 0x61, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x64, 0x6a, 0x75, + 0x73, 0x74, 0x51, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, + 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x6d, 0x61, + 0x72, 0x6b, 0x22, 0x68, 0x0a, 0x15, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x53, 0x74, 0x6f, + 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x52, 0x65, 0x71, 0x12, 0x1b, 0x0a, 0x09, 0x61, + 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x72, + 0x6f, 0x76, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x70, 0x70, 0x72, + 0x6f, 0x76, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x30, 0x0a, 0x11, + 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x49, 0x64, 0x22, 0xd9, + 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, + 0x73, 0x74, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, + 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, + 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, + 0x5f, 0x6e, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6a, 0x75, 0x73, + 0x74, 0x4e, 0x6f, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x5f, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x64, 0x6a, 0x75, + 0x73, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, + 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x22, 0x5b, 0x0a, 0x13, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x2e, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, + 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x18, 0x0a, 0x06, 0x49, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0xa2, 0x0a, 0x0a, 0x10, 0x49, + 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0x3f, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x12, 0x1b, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, + 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x12, 0x3e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x12, 0x1b, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x10, + 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x12, 0x3e, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x12, 0x1b, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x10, + 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x12, 0x3e, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, 0x18, + 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, + 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x12, + 0x19, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x1a, 0x2e, 0x69, 0x6e, 0x76, + 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x4b, 0x0a, 0x0e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, + 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, + 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, + 0x79, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x12, 0x4a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, + 0x74, 0x4c, 0x6f, 0x67, 0x12, 0x1b, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, + 0x71, 0x1a, 0x1c, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x12, + 0x4a, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x53, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x12, 0x1a, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, + 0x74, 0x6f, 0x63, 0x6b, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1b, + 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x63, 0x6b, + 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x44, 0x0a, 0x0d, 0x47, + 0x65, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x18, 0x2e, 0x69, + 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, + 0x72, 0x79, 0x2e, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x65, 0x73, + 0x70, 0x12, 0x45, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x63, 0x6b, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1e, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, + 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, + 0x79, 0x2e, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x44, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1e, 0x2e, 0x69, + 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, + 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x1a, 0x10, 0x2e, 0x69, + 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x46, + 0x0a, 0x11, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x12, 0x1f, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x52, 0x65, 0x71, 0x1a, 0x10, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x47, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, + 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1b, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, + 0x6f, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x52, 0x65, 0x71, 0x1a, 0x19, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, + 0x2e, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, + 0x4d, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x12, 0x1c, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x1a, + 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x12, 0x47, + 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, + 0x75, 0x73, 0x74, 0x12, 0x1f, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, + 0x74, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, + 0x2e, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x48, 0x0a, 0x12, 0x41, 0x70, 0x70, 0x72, 0x6f, + 0x76, 0x65, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x12, 0x20, 0x2e, + 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, + 0x65, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x52, 0x65, 0x71, 0x1a, + 0x10, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x12, 0x4a, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, + 0x75, 0x73, 0x74, 0x12, 0x1c, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, + 0x47, 0x65, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x1a, 0x1a, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x53, 0x74, + 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x50, 0x0a, + 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, + 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x52, 0x65, 0x71, 0x1a, + 0x1e, 0x2e, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x42, + 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x75, + 0x79, 0x75, 0x71, 0x69, 0x6e, 0x67, 0x66, 0x65, 0x6e, 0x67, 0x2f, 0x69, 0x6c, 0x6f, 0x6f, 0x6d, + 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67, 0x72, 0x70, 0x63, + 0x2f, 0x70, 0x62, 0x2f, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_inventory_inventory_proto_rawDescOnce sync.Once + file_proto_inventory_inventory_proto_rawDescData = file_proto_inventory_inventory_proto_rawDesc +) + +func file_proto_inventory_inventory_proto_rawDescGZIP() []byte { + file_proto_inventory_inventory_proto_rawDescOnce.Do(func() { + file_proto_inventory_inventory_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_inventory_inventory_proto_rawDescData) + }) + return file_proto_inventory_inventory_proto_rawDescData +} + +var file_proto_inventory_inventory_proto_msgTypes = make([]protoimpl.MessageInfo, 36) +var file_proto_inventory_inventory_proto_goTypes = []interface{}{ + (*ProductInfo)(nil), // 0: inventory.ProductInfo + (*CreateProductReq)(nil), // 1: inventory.CreateProductReq + (*UpdateProductReq)(nil), // 2: inventory.UpdateProductReq + (*DeleteProductReq)(nil), // 3: inventory.DeleteProductReq + (*GetProductReq)(nil), // 4: inventory.GetProductReq + (*ListProductReq)(nil), // 5: inventory.ListProductReq + (*ListProductResp)(nil), // 6: inventory.ListProductResp + (*ImportProductReq)(nil), // 7: inventory.ImportProductReq + (*ImportProductResp)(nil), // 8: inventory.ImportProductResp + (*ListImportLogReq)(nil), // 9: inventory.ListImportLogReq + (*ListImportLogResp)(nil), // 10: inventory.ListImportLogResp + (*ImportLogInfo)(nil), // 11: inventory.ImportLogInfo + (*StockSummaryReq)(nil), // 12: inventory.StockSummaryReq + (*StockSummaryResp)(nil), // 13: inventory.StockSummaryResp + (*StockGroupReq)(nil), // 14: inventory.StockGroupReq + (*StockGroupItem)(nil), // 15: inventory.StockGroupItem + (*StockGroupResp)(nil), // 16: inventory.StockGroupResp + (*StockCheckInfo)(nil), // 17: inventory.StockCheckInfo + (*StockCheckDetailInfo)(nil), // 18: inventory.StockCheckDetailInfo + (*CreateStockCheckReq)(nil), // 19: inventory.CreateStockCheckReq + (*StockCheckDetailReq)(nil), // 20: inventory.StockCheckDetailReq + (*UpdateStockCheckReq)(nil), // 21: inventory.UpdateStockCheckReq + (*ConfirmStockCheckReq)(nil), // 22: inventory.ConfirmStockCheckReq + (*GetStockCheckReq)(nil), // 23: inventory.GetStockCheckReq + (*ListStockCheckReq)(nil), // 24: inventory.ListStockCheckReq + (*ListStockCheckResp)(nil), // 25: inventory.ListStockCheckResp + (*StockAdjustInfo)(nil), // 26: inventory.StockAdjustInfo + (*StockAdjustDetailInfo)(nil), // 27: inventory.StockAdjustDetailInfo + (*CreateStockAdjustReq)(nil), // 28: inventory.CreateStockAdjustReq + (*StockAdjustDetailReq)(nil), // 29: inventory.StockAdjustDetailReq + (*ApproveStockAdjustReq)(nil), // 30: inventory.ApproveStockAdjustReq + (*GetStockAdjustReq)(nil), // 31: inventory.GetStockAdjustReq + (*ListStockAdjustReq)(nil), // 32: inventory.ListStockAdjustReq + (*ListStockAdjustResp)(nil), // 33: inventory.ListStockAdjustResp + (*IdResp)(nil), // 34: inventory.IdResp + (*Empty)(nil), // 35: inventory.Empty +} +var file_proto_inventory_inventory_proto_depIdxs = []int32{ + 0, // 0: inventory.ListProductResp.list:type_name -> inventory.ProductInfo + 1, // 1: inventory.ImportProductReq.products:type_name -> inventory.CreateProductReq + 11, // 2: inventory.ListImportLogResp.list:type_name -> inventory.ImportLogInfo + 15, // 3: inventory.StockGroupResp.list:type_name -> inventory.StockGroupItem + 18, // 4: inventory.StockCheckInfo.details:type_name -> inventory.StockCheckDetailInfo + 20, // 5: inventory.CreateStockCheckReq.details:type_name -> inventory.StockCheckDetailReq + 20, // 6: inventory.UpdateStockCheckReq.details:type_name -> inventory.StockCheckDetailReq + 17, // 7: inventory.ListStockCheckResp.list:type_name -> inventory.StockCheckInfo + 27, // 8: inventory.StockAdjustInfo.details:type_name -> inventory.StockAdjustDetailInfo + 29, // 9: inventory.CreateStockAdjustReq.details:type_name -> inventory.StockAdjustDetailReq + 26, // 10: inventory.ListStockAdjustResp.list:type_name -> inventory.StockAdjustInfo + 1, // 11: inventory.InventoryService.CreateProduct:input_type -> inventory.CreateProductReq + 2, // 12: inventory.InventoryService.UpdateProduct:input_type -> inventory.UpdateProductReq + 3, // 13: inventory.InventoryService.DeleteProduct:input_type -> inventory.DeleteProductReq + 4, // 14: inventory.InventoryService.GetProduct:input_type -> inventory.GetProductReq + 5, // 15: inventory.InventoryService.ListProduct:input_type -> inventory.ListProductReq + 7, // 16: inventory.InventoryService.ImportProducts:input_type -> inventory.ImportProductReq + 9, // 17: inventory.InventoryService.ListImportLog:input_type -> inventory.ListImportLogReq + 12, // 18: inventory.InventoryService.GetStockSummary:input_type -> inventory.StockSummaryReq + 14, // 19: inventory.InventoryService.GetStockGroup:input_type -> inventory.StockGroupReq + 19, // 20: inventory.InventoryService.CreateStockCheck:input_type -> inventory.CreateStockCheckReq + 21, // 21: inventory.InventoryService.UpdateStockCheck:input_type -> inventory.UpdateStockCheckReq + 22, // 22: inventory.InventoryService.ConfirmStockCheck:input_type -> inventory.ConfirmStockCheckReq + 23, // 23: inventory.InventoryService.GetStockCheck:input_type -> inventory.GetStockCheckReq + 24, // 24: inventory.InventoryService.ListStockCheck:input_type -> inventory.ListStockCheckReq + 28, // 25: inventory.InventoryService.CreateStockAdjust:input_type -> inventory.CreateStockAdjustReq + 30, // 26: inventory.InventoryService.ApproveStockAdjust:input_type -> inventory.ApproveStockAdjustReq + 31, // 27: inventory.InventoryService.GetStockAdjust:input_type -> inventory.GetStockAdjustReq + 32, // 28: inventory.InventoryService.ListStockAdjust:input_type -> inventory.ListStockAdjustReq + 34, // 29: inventory.InventoryService.CreateProduct:output_type -> inventory.IdResp + 35, // 30: inventory.InventoryService.UpdateProduct:output_type -> inventory.Empty + 35, // 31: inventory.InventoryService.DeleteProduct:output_type -> inventory.Empty + 0, // 32: inventory.InventoryService.GetProduct:output_type -> inventory.ProductInfo + 6, // 33: inventory.InventoryService.ListProduct:output_type -> inventory.ListProductResp + 8, // 34: inventory.InventoryService.ImportProducts:output_type -> inventory.ImportProductResp + 10, // 35: inventory.InventoryService.ListImportLog:output_type -> inventory.ListImportLogResp + 13, // 36: inventory.InventoryService.GetStockSummary:output_type -> inventory.StockSummaryResp + 16, // 37: inventory.InventoryService.GetStockGroup:output_type -> inventory.StockGroupResp + 34, // 38: inventory.InventoryService.CreateStockCheck:output_type -> inventory.IdResp + 35, // 39: inventory.InventoryService.UpdateStockCheck:output_type -> inventory.Empty + 35, // 40: inventory.InventoryService.ConfirmStockCheck:output_type -> inventory.Empty + 17, // 41: inventory.InventoryService.GetStockCheck:output_type -> inventory.StockCheckInfo + 25, // 42: inventory.InventoryService.ListStockCheck:output_type -> inventory.ListStockCheckResp + 34, // 43: inventory.InventoryService.CreateStockAdjust:output_type -> inventory.IdResp + 35, // 44: inventory.InventoryService.ApproveStockAdjust:output_type -> inventory.Empty + 26, // 45: inventory.InventoryService.GetStockAdjust:output_type -> inventory.StockAdjustInfo + 33, // 46: inventory.InventoryService.ListStockAdjust:output_type -> inventory.ListStockAdjustResp + 29, // [29:47] is the sub-list for method output_type + 11, // [11:29] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name +} + +func init() { file_proto_inventory_inventory_proto_init() } +func file_proto_inventory_inventory_proto_init() { + if File_proto_inventory_inventory_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_inventory_inventory_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProductInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateProductReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateProductReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteProductReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetProductReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListProductReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListProductResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImportProductReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImportProductResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListImportLogReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListImportLogResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImportLogInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StockSummaryReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StockSummaryResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StockGroupReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StockGroupItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StockGroupResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StockCheckInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StockCheckDetailInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateStockCheckReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StockCheckDetailReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateStockCheckReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfirmStockCheckReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStockCheckReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListStockCheckReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListStockCheckResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StockAdjustInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StockAdjustDetailInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateStockAdjustReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StockAdjustDetailReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ApproveStockAdjustReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetStockAdjustReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListStockAdjustReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListStockAdjustResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IdResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_inventory_inventory_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Empty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_inventory_inventory_proto_rawDesc, + NumEnums: 0, + NumMessages: 36, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_inventory_inventory_proto_goTypes, + DependencyIndexes: file_proto_inventory_inventory_proto_depIdxs, + MessageInfos: file_proto_inventory_inventory_proto_msgTypes, + }.Build() + File_proto_inventory_inventory_proto = out.File + file_proto_inventory_inventory_proto_rawDesc = nil + file_proto_inventory_inventory_proto_goTypes = nil + file_proto_inventory_inventory_proto_depIdxs = nil +} diff --git a/shared/pkg/grpc/pb/inventory/inventory_grpc.pb.go b/shared/pkg/grpc/pb/inventory/inventory_grpc.pb.go new file mode 100644 index 0000000..b47f6ad --- /dev/null +++ b/shared/pkg/grpc/pb/inventory/inventory_grpc.pb.go @@ -0,0 +1,727 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.20.3 +// source: proto/inventory/inventory.proto + +package inventory + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// InventoryServiceClient is the client API for InventoryService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type InventoryServiceClient interface { + // Product + CreateProduct(ctx context.Context, in *CreateProductReq, opts ...grpc.CallOption) (*IdResp, error) + UpdateProduct(ctx context.Context, in *UpdateProductReq, opts ...grpc.CallOption) (*Empty, error) + DeleteProduct(ctx context.Context, in *DeleteProductReq, opts ...grpc.CallOption) (*Empty, error) + GetProduct(ctx context.Context, in *GetProductReq, opts ...grpc.CallOption) (*ProductInfo, error) + ListProduct(ctx context.Context, in *ListProductReq, opts ...grpc.CallOption) (*ListProductResp, error) + // Import + ImportProducts(ctx context.Context, in *ImportProductReq, opts ...grpc.CallOption) (*ImportProductResp, error) + ListImportLog(ctx context.Context, in *ListImportLogReq, opts ...grpc.CallOption) (*ListImportLogResp, error) + // Stock Statistics + GetStockSummary(ctx context.Context, in *StockSummaryReq, opts ...grpc.CallOption) (*StockSummaryResp, error) + GetStockGroup(ctx context.Context, in *StockGroupReq, opts ...grpc.CallOption) (*StockGroupResp, error) + // Stock Check + CreateStockCheck(ctx context.Context, in *CreateStockCheckReq, opts ...grpc.CallOption) (*IdResp, error) + UpdateStockCheck(ctx context.Context, in *UpdateStockCheckReq, opts ...grpc.CallOption) (*Empty, error) + ConfirmStockCheck(ctx context.Context, in *ConfirmStockCheckReq, opts ...grpc.CallOption) (*Empty, error) + GetStockCheck(ctx context.Context, in *GetStockCheckReq, opts ...grpc.CallOption) (*StockCheckInfo, error) + ListStockCheck(ctx context.Context, in *ListStockCheckReq, opts ...grpc.CallOption) (*ListStockCheckResp, error) + // Stock Adjust + CreateStockAdjust(ctx context.Context, in *CreateStockAdjustReq, opts ...grpc.CallOption) (*IdResp, error) + ApproveStockAdjust(ctx context.Context, in *ApproveStockAdjustReq, opts ...grpc.CallOption) (*Empty, error) + GetStockAdjust(ctx context.Context, in *GetStockAdjustReq, opts ...grpc.CallOption) (*StockAdjustInfo, error) + ListStockAdjust(ctx context.Context, in *ListStockAdjustReq, opts ...grpc.CallOption) (*ListStockAdjustResp, error) +} + +type inventoryServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewInventoryServiceClient(cc grpc.ClientConnInterface) InventoryServiceClient { + return &inventoryServiceClient{cc} +} + +func (c *inventoryServiceClient) CreateProduct(ctx context.Context, in *CreateProductReq, opts ...grpc.CallOption) (*IdResp, error) { + out := new(IdResp) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/CreateProduct", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) UpdateProduct(ctx context.Context, in *UpdateProductReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/UpdateProduct", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) DeleteProduct(ctx context.Context, in *DeleteProductReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/DeleteProduct", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) GetProduct(ctx context.Context, in *GetProductReq, opts ...grpc.CallOption) (*ProductInfo, error) { + out := new(ProductInfo) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/GetProduct", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) ListProduct(ctx context.Context, in *ListProductReq, opts ...grpc.CallOption) (*ListProductResp, error) { + out := new(ListProductResp) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/ListProduct", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) ImportProducts(ctx context.Context, in *ImportProductReq, opts ...grpc.CallOption) (*ImportProductResp, error) { + out := new(ImportProductResp) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/ImportProducts", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) ListImportLog(ctx context.Context, in *ListImportLogReq, opts ...grpc.CallOption) (*ListImportLogResp, error) { + out := new(ListImportLogResp) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/ListImportLog", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) GetStockSummary(ctx context.Context, in *StockSummaryReq, opts ...grpc.CallOption) (*StockSummaryResp, error) { + out := new(StockSummaryResp) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/GetStockSummary", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) GetStockGroup(ctx context.Context, in *StockGroupReq, opts ...grpc.CallOption) (*StockGroupResp, error) { + out := new(StockGroupResp) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/GetStockGroup", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) CreateStockCheck(ctx context.Context, in *CreateStockCheckReq, opts ...grpc.CallOption) (*IdResp, error) { + out := new(IdResp) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/CreateStockCheck", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) UpdateStockCheck(ctx context.Context, in *UpdateStockCheckReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/UpdateStockCheck", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) ConfirmStockCheck(ctx context.Context, in *ConfirmStockCheckReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/ConfirmStockCheck", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) GetStockCheck(ctx context.Context, in *GetStockCheckReq, opts ...grpc.CallOption) (*StockCheckInfo, error) { + out := new(StockCheckInfo) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/GetStockCheck", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) ListStockCheck(ctx context.Context, in *ListStockCheckReq, opts ...grpc.CallOption) (*ListStockCheckResp, error) { + out := new(ListStockCheckResp) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/ListStockCheck", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) CreateStockAdjust(ctx context.Context, in *CreateStockAdjustReq, opts ...grpc.CallOption) (*IdResp, error) { + out := new(IdResp) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/CreateStockAdjust", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) ApproveStockAdjust(ctx context.Context, in *ApproveStockAdjustReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/ApproveStockAdjust", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) GetStockAdjust(ctx context.Context, in *GetStockAdjustReq, opts ...grpc.CallOption) (*StockAdjustInfo, error) { + out := new(StockAdjustInfo) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/GetStockAdjust", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *inventoryServiceClient) ListStockAdjust(ctx context.Context, in *ListStockAdjustReq, opts ...grpc.CallOption) (*ListStockAdjustResp, error) { + out := new(ListStockAdjustResp) + err := c.cc.Invoke(ctx, "/inventory.InventoryService/ListStockAdjust", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// InventoryServiceServer is the server API for InventoryService service. +// All implementations must embed UnimplementedInventoryServiceServer +// for forward compatibility +type InventoryServiceServer interface { + // Product + CreateProduct(context.Context, *CreateProductReq) (*IdResp, error) + UpdateProduct(context.Context, *UpdateProductReq) (*Empty, error) + DeleteProduct(context.Context, *DeleteProductReq) (*Empty, error) + GetProduct(context.Context, *GetProductReq) (*ProductInfo, error) + ListProduct(context.Context, *ListProductReq) (*ListProductResp, error) + // Import + ImportProducts(context.Context, *ImportProductReq) (*ImportProductResp, error) + ListImportLog(context.Context, *ListImportLogReq) (*ListImportLogResp, error) + // Stock Statistics + GetStockSummary(context.Context, *StockSummaryReq) (*StockSummaryResp, error) + GetStockGroup(context.Context, *StockGroupReq) (*StockGroupResp, error) + // Stock Check + CreateStockCheck(context.Context, *CreateStockCheckReq) (*IdResp, error) + UpdateStockCheck(context.Context, *UpdateStockCheckReq) (*Empty, error) + ConfirmStockCheck(context.Context, *ConfirmStockCheckReq) (*Empty, error) + GetStockCheck(context.Context, *GetStockCheckReq) (*StockCheckInfo, error) + ListStockCheck(context.Context, *ListStockCheckReq) (*ListStockCheckResp, error) + // Stock Adjust + CreateStockAdjust(context.Context, *CreateStockAdjustReq) (*IdResp, error) + ApproveStockAdjust(context.Context, *ApproveStockAdjustReq) (*Empty, error) + GetStockAdjust(context.Context, *GetStockAdjustReq) (*StockAdjustInfo, error) + ListStockAdjust(context.Context, *ListStockAdjustReq) (*ListStockAdjustResp, error) + mustEmbedUnimplementedInventoryServiceServer() +} + +// UnimplementedInventoryServiceServer must be embedded to have forward compatible implementations. +type UnimplementedInventoryServiceServer struct { +} + +func (UnimplementedInventoryServiceServer) CreateProduct(context.Context, *CreateProductReq) (*IdResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateProduct not implemented") +} +func (UnimplementedInventoryServiceServer) UpdateProduct(context.Context, *UpdateProductReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateProduct not implemented") +} +func (UnimplementedInventoryServiceServer) DeleteProduct(context.Context, *DeleteProductReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteProduct not implemented") +} +func (UnimplementedInventoryServiceServer) GetProduct(context.Context, *GetProductReq) (*ProductInfo, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetProduct not implemented") +} +func (UnimplementedInventoryServiceServer) ListProduct(context.Context, *ListProductReq) (*ListProductResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListProduct not implemented") +} +func (UnimplementedInventoryServiceServer) ImportProducts(context.Context, *ImportProductReq) (*ImportProductResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ImportProducts not implemented") +} +func (UnimplementedInventoryServiceServer) ListImportLog(context.Context, *ListImportLogReq) (*ListImportLogResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListImportLog not implemented") +} +func (UnimplementedInventoryServiceServer) GetStockSummary(context.Context, *StockSummaryReq) (*StockSummaryResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStockSummary not implemented") +} +func (UnimplementedInventoryServiceServer) GetStockGroup(context.Context, *StockGroupReq) (*StockGroupResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStockGroup not implemented") +} +func (UnimplementedInventoryServiceServer) CreateStockCheck(context.Context, *CreateStockCheckReq) (*IdResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateStockCheck not implemented") +} +func (UnimplementedInventoryServiceServer) UpdateStockCheck(context.Context, *UpdateStockCheckReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateStockCheck not implemented") +} +func (UnimplementedInventoryServiceServer) ConfirmStockCheck(context.Context, *ConfirmStockCheckReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConfirmStockCheck not implemented") +} +func (UnimplementedInventoryServiceServer) GetStockCheck(context.Context, *GetStockCheckReq) (*StockCheckInfo, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStockCheck not implemented") +} +func (UnimplementedInventoryServiceServer) ListStockCheck(context.Context, *ListStockCheckReq) (*ListStockCheckResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListStockCheck not implemented") +} +func (UnimplementedInventoryServiceServer) CreateStockAdjust(context.Context, *CreateStockAdjustReq) (*IdResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateStockAdjust not implemented") +} +func (UnimplementedInventoryServiceServer) ApproveStockAdjust(context.Context, *ApproveStockAdjustReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ApproveStockAdjust not implemented") +} +func (UnimplementedInventoryServiceServer) GetStockAdjust(context.Context, *GetStockAdjustReq) (*StockAdjustInfo, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStockAdjust not implemented") +} +func (UnimplementedInventoryServiceServer) ListStockAdjust(context.Context, *ListStockAdjustReq) (*ListStockAdjustResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListStockAdjust not implemented") +} +func (UnimplementedInventoryServiceServer) mustEmbedUnimplementedInventoryServiceServer() {} + +// UnsafeInventoryServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to InventoryServiceServer will +// result in compilation errors. +type UnsafeInventoryServiceServer interface { + mustEmbedUnimplementedInventoryServiceServer() +} + +func RegisterInventoryServiceServer(s grpc.ServiceRegistrar, srv InventoryServiceServer) { + s.RegisterService(&InventoryService_ServiceDesc, srv) +} + +func _InventoryService_CreateProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateProductReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).CreateProduct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/CreateProduct", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).CreateProduct(ctx, req.(*CreateProductReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_UpdateProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateProductReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).UpdateProduct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/UpdateProduct", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).UpdateProduct(ctx, req.(*UpdateProductReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_DeleteProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteProductReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).DeleteProduct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/DeleteProduct", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).DeleteProduct(ctx, req.(*DeleteProductReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_GetProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetProductReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).GetProduct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/GetProduct", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).GetProduct(ctx, req.(*GetProductReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_ListProduct_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListProductReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).ListProduct(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/ListProduct", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).ListProduct(ctx, req.(*ListProductReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_ImportProducts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ImportProductReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).ImportProducts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/ImportProducts", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).ImportProducts(ctx, req.(*ImportProductReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_ListImportLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListImportLogReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).ListImportLog(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/ListImportLog", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).ListImportLog(ctx, req.(*ListImportLogReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_GetStockSummary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StockSummaryReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).GetStockSummary(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/GetStockSummary", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).GetStockSummary(ctx, req.(*StockSummaryReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_GetStockGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StockGroupReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).GetStockGroup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/GetStockGroup", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).GetStockGroup(ctx, req.(*StockGroupReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_CreateStockCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateStockCheckReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).CreateStockCheck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/CreateStockCheck", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).CreateStockCheck(ctx, req.(*CreateStockCheckReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_UpdateStockCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateStockCheckReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).UpdateStockCheck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/UpdateStockCheck", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).UpdateStockCheck(ctx, req.(*UpdateStockCheckReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_ConfirmStockCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ConfirmStockCheckReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).ConfirmStockCheck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/ConfirmStockCheck", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).ConfirmStockCheck(ctx, req.(*ConfirmStockCheckReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_GetStockCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetStockCheckReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).GetStockCheck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/GetStockCheck", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).GetStockCheck(ctx, req.(*GetStockCheckReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_ListStockCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListStockCheckReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).ListStockCheck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/ListStockCheck", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).ListStockCheck(ctx, req.(*ListStockCheckReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_CreateStockAdjust_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateStockAdjustReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).CreateStockAdjust(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/CreateStockAdjust", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).CreateStockAdjust(ctx, req.(*CreateStockAdjustReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_ApproveStockAdjust_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApproveStockAdjustReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).ApproveStockAdjust(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/ApproveStockAdjust", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).ApproveStockAdjust(ctx, req.(*ApproveStockAdjustReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_GetStockAdjust_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetStockAdjustReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).GetStockAdjust(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/GetStockAdjust", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).GetStockAdjust(ctx, req.(*GetStockAdjustReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _InventoryService_ListStockAdjust_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListStockAdjustReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(InventoryServiceServer).ListStockAdjust(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/inventory.InventoryService/ListStockAdjust", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(InventoryServiceServer).ListStockAdjust(ctx, req.(*ListStockAdjustReq)) + } + return interceptor(ctx, in, info, handler) +} + +// InventoryService_ServiceDesc is the grpc.ServiceDesc for InventoryService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var InventoryService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "inventory.InventoryService", + HandlerType: (*InventoryServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateProduct", + Handler: _InventoryService_CreateProduct_Handler, + }, + { + MethodName: "UpdateProduct", + Handler: _InventoryService_UpdateProduct_Handler, + }, + { + MethodName: "DeleteProduct", + Handler: _InventoryService_DeleteProduct_Handler, + }, + { + MethodName: "GetProduct", + Handler: _InventoryService_GetProduct_Handler, + }, + { + MethodName: "ListProduct", + Handler: _InventoryService_ListProduct_Handler, + }, + { + MethodName: "ImportProducts", + Handler: _InventoryService_ImportProducts_Handler, + }, + { + MethodName: "ListImportLog", + Handler: _InventoryService_ListImportLog_Handler, + }, + { + MethodName: "GetStockSummary", + Handler: _InventoryService_GetStockSummary_Handler, + }, + { + MethodName: "GetStockGroup", + Handler: _InventoryService_GetStockGroup_Handler, + }, + { + MethodName: "CreateStockCheck", + Handler: _InventoryService_CreateStockCheck_Handler, + }, + { + MethodName: "UpdateStockCheck", + Handler: _InventoryService_UpdateStockCheck_Handler, + }, + { + MethodName: "ConfirmStockCheck", + Handler: _InventoryService_ConfirmStockCheck_Handler, + }, + { + MethodName: "GetStockCheck", + Handler: _InventoryService_GetStockCheck_Handler, + }, + { + MethodName: "ListStockCheck", + Handler: _InventoryService_ListStockCheck_Handler, + }, + { + MethodName: "CreateStockAdjust", + Handler: _InventoryService_CreateStockAdjust_Handler, + }, + { + MethodName: "ApproveStockAdjust", + Handler: _InventoryService_ApproveStockAdjust_Handler, + }, + { + MethodName: "GetStockAdjust", + Handler: _InventoryService_GetStockAdjust_Handler, + }, + { + MethodName: "ListStockAdjust", + Handler: _InventoryService_ListStockAdjust_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/inventory/inventory.proto", +} diff --git a/shared/pkg/grpc/pb/system/system.pb.go b/shared/pkg/grpc/pb/system/system.pb.go new file mode 100644 index 0000000..2ee5c69 --- /dev/null +++ b/shared/pkg/grpc/pb/system/system.pb.go @@ -0,0 +1,3627 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.20.3 +// source: proto/system/system.proto + +package system + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type LoginReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` + Ip string `protobuf:"bytes,3,opt,name=ip,proto3" json:"ip,omitempty"` +} + +func (x *LoginReq) Reset() { + *x = LoginReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginReq) ProtoMessage() {} + +func (x *LoginReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginReq.ProtoReflect.Descriptor instead. +func (*LoginReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{0} +} + +func (x *LoginReq) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *LoginReq) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *LoginReq) GetIp() string { + if x != nil { + return x.Ip + } + return "" +} + +type LoginResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` + RealName string `protobuf:"bytes,3,opt,name=real_name,json=realName,proto3" json:"real_name,omitempty"` + Avatar string `protobuf:"bytes,4,opt,name=avatar,proto3" json:"avatar,omitempty"` + RoleKey string `protobuf:"bytes,5,opt,name=role_key,json=roleKey,proto3" json:"role_key,omitempty"` + RoleName string `protobuf:"bytes,6,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` +} + +func (x *LoginResp) Reset() { + *x = LoginResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginResp) ProtoMessage() {} + +func (x *LoginResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginResp.ProtoReflect.Descriptor instead. +func (*LoginResp) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{1} +} + +func (x *LoginResp) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *LoginResp) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *LoginResp) GetRealName() string { + if x != nil { + return x.RealName + } + return "" +} + +func (x *LoginResp) GetAvatar() string { + if x != nil { + return x.Avatar + } + return "" +} + +func (x *LoginResp) GetRoleKey() string { + if x != nil { + return x.RoleKey + } + return "" +} + +func (x *LoginResp) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +type ChangePasswordReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + OldPassword string `protobuf:"bytes,2,opt,name=old_password,json=oldPassword,proto3" json:"old_password,omitempty"` + NewPassword string `protobuf:"bytes,3,opt,name=new_password,json=newPassword,proto3" json:"new_password,omitempty"` +} + +func (x *ChangePasswordReq) Reset() { + *x = ChangePasswordReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChangePasswordReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChangePasswordReq) ProtoMessage() {} + +func (x *ChangePasswordReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChangePasswordReq.ProtoReflect.Descriptor instead. +func (*ChangePasswordReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{2} +} + +func (x *ChangePasswordReq) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *ChangePasswordReq) GetOldPassword() string { + if x != nil { + return x.OldPassword + } + return "" +} + +func (x *ChangePasswordReq) GetNewPassword() string { + if x != nil { + return x.NewPassword + } + return "" +} + +type UserInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` + RealName string `protobuf:"bytes,3,opt,name=real_name,json=realName,proto3" json:"real_name,omitempty"` + Phone string `protobuf:"bytes,4,opt,name=phone,proto3" json:"phone,omitempty"` + Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` + Avatar string `protobuf:"bytes,6,opt,name=avatar,proto3" json:"avatar,omitempty"` + Status int64 `protobuf:"varint,7,opt,name=status,proto3" json:"status,omitempty"` + RoleId string `protobuf:"bytes,8,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` + RoleName string `protobuf:"bytes,9,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + LastLoginTime string `protobuf:"bytes,10,opt,name=last_login_time,json=lastLoginTime,proto3" json:"last_login_time,omitempty"` + CreatedAt string `protobuf:"bytes,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt string `protobuf:"bytes,12,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` +} + +func (x *UserInfo) Reset() { + *x = UserInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UserInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserInfo) ProtoMessage() {} + +func (x *UserInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UserInfo.ProtoReflect.Descriptor instead. +func (*UserInfo) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{3} +} + +func (x *UserInfo) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *UserInfo) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *UserInfo) GetRealName() string { + if x != nil { + return x.RealName + } + return "" +} + +func (x *UserInfo) GetPhone() string { + if x != nil { + return x.Phone + } + return "" +} + +func (x *UserInfo) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *UserInfo) GetAvatar() string { + if x != nil { + return x.Avatar + } + return "" +} + +func (x *UserInfo) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *UserInfo) GetRoleId() string { + if x != nil { + return x.RoleId + } + return "" +} + +func (x *UserInfo) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +func (x *UserInfo) GetLastLoginTime() string { + if x != nil { + return x.LastLoginTime + } + return "" +} + +func (x *UserInfo) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +func (x *UserInfo) GetUpdatedAt() string { + if x != nil { + return x.UpdatedAt + } + return "" +} + +type CreateUserReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` + RealName string `protobuf:"bytes,3,opt,name=real_name,json=realName,proto3" json:"real_name,omitempty"` + Phone string `protobuf:"bytes,4,opt,name=phone,proto3" json:"phone,omitempty"` + Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"` + RoleId string `protobuf:"bytes,6,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` +} + +func (x *CreateUserReq) Reset() { + *x = CreateUserReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateUserReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateUserReq) ProtoMessage() {} + +func (x *CreateUserReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateUserReq.ProtoReflect.Descriptor instead. +func (*CreateUserReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{4} +} + +func (x *CreateUserReq) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *CreateUserReq) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *CreateUserReq) GetRealName() string { + if x != nil { + return x.RealName + } + return "" +} + +func (x *CreateUserReq) GetPhone() string { + if x != nil { + return x.Phone + } + return "" +} + +func (x *CreateUserReq) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *CreateUserReq) GetRoleId() string { + if x != nil { + return x.RoleId + } + return "" +} + +type UpdateUserReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + RealName string `protobuf:"bytes,2,opt,name=real_name,json=realName,proto3" json:"real_name,omitempty"` + Phone string `protobuf:"bytes,3,opt,name=phone,proto3" json:"phone,omitempty"` + Email string `protobuf:"bytes,4,opt,name=email,proto3" json:"email,omitempty"` + RoleId string `protobuf:"bytes,5,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` + Avatar string `protobuf:"bytes,6,opt,name=avatar,proto3" json:"avatar,omitempty"` +} + +func (x *UpdateUserReq) Reset() { + *x = UpdateUserReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateUserReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateUserReq) ProtoMessage() {} + +func (x *UpdateUserReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateUserReq.ProtoReflect.Descriptor instead. +func (*UpdateUserReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{5} +} + +func (x *UpdateUserReq) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *UpdateUserReq) GetRealName() string { + if x != nil { + return x.RealName + } + return "" +} + +func (x *UpdateUserReq) GetPhone() string { + if x != nil { + return x.Phone + } + return "" +} + +func (x *UpdateUserReq) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *UpdateUserReq) GetRoleId() string { + if x != nil { + return x.RoleId + } + return "" +} + +func (x *UpdateUserReq) GetAvatar() string { + if x != nil { + return x.Avatar + } + return "" +} + +type DeleteUserReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` +} + +func (x *DeleteUserReq) Reset() { + *x = DeleteUserReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteUserReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUserReq) ProtoMessage() {} + +func (x *DeleteUserReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteUserReq.ProtoReflect.Descriptor instead. +func (*DeleteUserReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{6} +} + +func (x *DeleteUserReq) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type GetUserReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` +} + +func (x *GetUserReq) Reset() { + *x = GetUserReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetUserReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetUserReq) ProtoMessage() {} + +func (x *GetUserReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetUserReq.ProtoReflect.Descriptor instead. +func (*GetUserReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{7} +} + +func (x *GetUserReq) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type ListUserReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Page int64 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + PageSize int64 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"` + RealName string `protobuf:"bytes,4,opt,name=real_name,json=realName,proto3" json:"real_name,omitempty"` + Phone string `protobuf:"bytes,5,opt,name=phone,proto3" json:"phone,omitempty"` + Status int64 `protobuf:"varint,6,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *ListUserReq) Reset() { + *x = ListUserReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListUserReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListUserReq) ProtoMessage() {} + +func (x *ListUserReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListUserReq.ProtoReflect.Descriptor instead. +func (*ListUserReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{8} +} + +func (x *ListUserReq) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListUserReq) GetPageSize() int64 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListUserReq) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *ListUserReq) GetRealName() string { + if x != nil { + return x.RealName + } + return "" +} + +func (x *ListUserReq) GetPhone() string { + if x != nil { + return x.Phone + } + return "" +} + +func (x *ListUserReq) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +type ListUserResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + List []*UserInfo `protobuf:"bytes,2,rep,name=list,proto3" json:"list,omitempty"` +} + +func (x *ListUserResp) Reset() { + *x = ListUserResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListUserResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListUserResp) ProtoMessage() {} + +func (x *ListUserResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListUserResp.ProtoReflect.Descriptor instead. +func (*ListUserResp) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{9} +} + +func (x *ListUserResp) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ListUserResp) GetList() []*UserInfo { + if x != nil { + return x.List + } + return nil +} + +type UpdateUserStatusReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Status int64 `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *UpdateUserStatusReq) Reset() { + *x = UpdateUserStatusReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateUserStatusReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateUserStatusReq) ProtoMessage() {} + +func (x *UpdateUserStatusReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateUserStatusReq.ProtoReflect.Descriptor instead. +func (*UpdateUserStatusReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{10} +} + +func (x *UpdateUserStatusReq) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *UpdateUserStatusReq) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +type RoleInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RoleId string `protobuf:"bytes,1,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` + RoleName string `protobuf:"bytes,2,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + RoleKey string `protobuf:"bytes,3,opt,name=role_key,json=roleKey,proto3" json:"role_key,omitempty"` + RoleDesc string `protobuf:"bytes,4,opt,name=role_desc,json=roleDesc,proto3" json:"role_desc,omitempty"` + SortOrder int64 `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + Status int64 `protobuf:"varint,6,opt,name=status,proto3" json:"status,omitempty"` + MenuIds []string `protobuf:"bytes,7,rep,name=menu_ids,json=menuIds,proto3" json:"menu_ids,omitempty"` + CreatedAt string `protobuf:"bytes,8,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt string `protobuf:"bytes,9,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` +} + +func (x *RoleInfo) Reset() { + *x = RoleInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RoleInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoleInfo) ProtoMessage() {} + +func (x *RoleInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoleInfo.ProtoReflect.Descriptor instead. +func (*RoleInfo) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{11} +} + +func (x *RoleInfo) GetRoleId() string { + if x != nil { + return x.RoleId + } + return "" +} + +func (x *RoleInfo) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +func (x *RoleInfo) GetRoleKey() string { + if x != nil { + return x.RoleKey + } + return "" +} + +func (x *RoleInfo) GetRoleDesc() string { + if x != nil { + return x.RoleDesc + } + return "" +} + +func (x *RoleInfo) GetSortOrder() int64 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *RoleInfo) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *RoleInfo) GetMenuIds() []string { + if x != nil { + return x.MenuIds + } + return nil +} + +func (x *RoleInfo) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +func (x *RoleInfo) GetUpdatedAt() string { + if x != nil { + return x.UpdatedAt + } + return "" +} + +type CreateRoleReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RoleName string `protobuf:"bytes,1,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + RoleKey string `protobuf:"bytes,2,opt,name=role_key,json=roleKey,proto3" json:"role_key,omitempty"` + RoleDesc string `protobuf:"bytes,3,opt,name=role_desc,json=roleDesc,proto3" json:"role_desc,omitempty"` + SortOrder int64 `protobuf:"varint,4,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` +} + +func (x *CreateRoleReq) Reset() { + *x = CreateRoleReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateRoleReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateRoleReq) ProtoMessage() {} + +func (x *CreateRoleReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateRoleReq.ProtoReflect.Descriptor instead. +func (*CreateRoleReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{12} +} + +func (x *CreateRoleReq) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +func (x *CreateRoleReq) GetRoleKey() string { + if x != nil { + return x.RoleKey + } + return "" +} + +func (x *CreateRoleReq) GetRoleDesc() string { + if x != nil { + return x.RoleDesc + } + return "" +} + +func (x *CreateRoleReq) GetSortOrder() int64 { + if x != nil { + return x.SortOrder + } + return 0 +} + +type UpdateRoleReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RoleId string `protobuf:"bytes,1,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` + RoleName string `protobuf:"bytes,2,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + RoleKey string `protobuf:"bytes,3,opt,name=role_key,json=roleKey,proto3" json:"role_key,omitempty"` + RoleDesc string `protobuf:"bytes,4,opt,name=role_desc,json=roleDesc,proto3" json:"role_desc,omitempty"` + SortOrder int64 `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` +} + +func (x *UpdateRoleReq) Reset() { + *x = UpdateRoleReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateRoleReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRoleReq) ProtoMessage() {} + +func (x *UpdateRoleReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRoleReq.ProtoReflect.Descriptor instead. +func (*UpdateRoleReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{13} +} + +func (x *UpdateRoleReq) GetRoleId() string { + if x != nil { + return x.RoleId + } + return "" +} + +func (x *UpdateRoleReq) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +func (x *UpdateRoleReq) GetRoleKey() string { + if x != nil { + return x.RoleKey + } + return "" +} + +func (x *UpdateRoleReq) GetRoleDesc() string { + if x != nil { + return x.RoleDesc + } + return "" +} + +func (x *UpdateRoleReq) GetSortOrder() int64 { + if x != nil { + return x.SortOrder + } + return 0 +} + +type DeleteRoleReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RoleId string `protobuf:"bytes,1,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` +} + +func (x *DeleteRoleReq) Reset() { + *x = DeleteRoleReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteRoleReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRoleReq) ProtoMessage() {} + +func (x *DeleteRoleReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRoleReq.ProtoReflect.Descriptor instead. +func (*DeleteRoleReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{14} +} + +func (x *DeleteRoleReq) GetRoleId() string { + if x != nil { + return x.RoleId + } + return "" +} + +type GetRoleReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RoleId string `protobuf:"bytes,1,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` +} + +func (x *GetRoleReq) Reset() { + *x = GetRoleReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRoleReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoleReq) ProtoMessage() {} + +func (x *GetRoleReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRoleReq.ProtoReflect.Descriptor instead. +func (*GetRoleReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{15} +} + +func (x *GetRoleReq) GetRoleId() string { + if x != nil { + return x.RoleId + } + return "" +} + +type ListRoleReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Page int64 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + PageSize int64 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + RoleName string `protobuf:"bytes,3,opt,name=role_name,json=roleName,proto3" json:"role_name,omitempty"` + Status int64 `protobuf:"varint,4,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *ListRoleReq) Reset() { + *x = ListRoleReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRoleReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoleReq) ProtoMessage() {} + +func (x *ListRoleReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRoleReq.ProtoReflect.Descriptor instead. +func (*ListRoleReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{16} +} + +func (x *ListRoleReq) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListRoleReq) GetPageSize() int64 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListRoleReq) GetRoleName() string { + if x != nil { + return x.RoleName + } + return "" +} + +func (x *ListRoleReq) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +type ListRoleResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + List []*RoleInfo `protobuf:"bytes,2,rep,name=list,proto3" json:"list,omitempty"` +} + +func (x *ListRoleResp) Reset() { + *x = ListRoleResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRoleResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRoleResp) ProtoMessage() {} + +func (x *ListRoleResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRoleResp.ProtoReflect.Descriptor instead. +func (*ListRoleResp) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{17} +} + +func (x *ListRoleResp) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ListRoleResp) GetList() []*RoleInfo { + if x != nil { + return x.List + } + return nil +} + +type SetRolePermissionsReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RoleId string `protobuf:"bytes,1,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` + MenuIds []string `protobuf:"bytes,2,rep,name=menu_ids,json=menuIds,proto3" json:"menu_ids,omitempty"` +} + +func (x *SetRolePermissionsReq) Reset() { + *x = SetRolePermissionsReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetRolePermissionsReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetRolePermissionsReq) ProtoMessage() {} + +func (x *SetRolePermissionsReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetRolePermissionsReq.ProtoReflect.Descriptor instead. +func (*SetRolePermissionsReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{18} +} + +func (x *SetRolePermissionsReq) GetRoleId() string { + if x != nil { + return x.RoleId + } + return "" +} + +func (x *SetRolePermissionsReq) GetMenuIds() []string { + if x != nil { + return x.MenuIds + } + return nil +} + +type MenuInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MenuId string `protobuf:"bytes,1,opt,name=menu_id,json=menuId,proto3" json:"menu_id,omitempty"` + ParentId string `protobuf:"bytes,2,opt,name=parent_id,json=parentId,proto3" json:"parent_id,omitempty"` + MenuName string `protobuf:"bytes,3,opt,name=menu_name,json=menuName,proto3" json:"menu_name,omitempty"` + MenuType int64 `protobuf:"varint,4,opt,name=menu_type,json=menuType,proto3" json:"menu_type,omitempty"` + Path string `protobuf:"bytes,5,opt,name=path,proto3" json:"path,omitempty"` + Component string `protobuf:"bytes,6,opt,name=component,proto3" json:"component,omitempty"` + Permission string `protobuf:"bytes,7,opt,name=permission,proto3" json:"permission,omitempty"` + Icon string `protobuf:"bytes,8,opt,name=icon,proto3" json:"icon,omitempty"` + SortOrder int64 `protobuf:"varint,9,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + Visible int64 `protobuf:"varint,10,opt,name=visible,proto3" json:"visible,omitempty"` + Status int64 `protobuf:"varint,11,opt,name=status,proto3" json:"status,omitempty"` + Children []*MenuInfo `protobuf:"bytes,12,rep,name=children,proto3" json:"children,omitempty"` + CreatedAt string `protobuf:"bytes,13,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt string `protobuf:"bytes,14,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` +} + +func (x *MenuInfo) Reset() { + *x = MenuInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MenuInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MenuInfo) ProtoMessage() {} + +func (x *MenuInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MenuInfo.ProtoReflect.Descriptor instead. +func (*MenuInfo) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{19} +} + +func (x *MenuInfo) GetMenuId() string { + if x != nil { + return x.MenuId + } + return "" +} + +func (x *MenuInfo) GetParentId() string { + if x != nil { + return x.ParentId + } + return "" +} + +func (x *MenuInfo) GetMenuName() string { + if x != nil { + return x.MenuName + } + return "" +} + +func (x *MenuInfo) GetMenuType() int64 { + if x != nil { + return x.MenuType + } + return 0 +} + +func (x *MenuInfo) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *MenuInfo) GetComponent() string { + if x != nil { + return x.Component + } + return "" +} + +func (x *MenuInfo) GetPermission() string { + if x != nil { + return x.Permission + } + return "" +} + +func (x *MenuInfo) GetIcon() string { + if x != nil { + return x.Icon + } + return "" +} + +func (x *MenuInfo) GetSortOrder() int64 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *MenuInfo) GetVisible() int64 { + if x != nil { + return x.Visible + } + return 0 +} + +func (x *MenuInfo) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *MenuInfo) GetChildren() []*MenuInfo { + if x != nil { + return x.Children + } + return nil +} + +func (x *MenuInfo) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +func (x *MenuInfo) GetUpdatedAt() string { + if x != nil { + return x.UpdatedAt + } + return "" +} + +type CreateMenuReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ParentId string `protobuf:"bytes,1,opt,name=parent_id,json=parentId,proto3" json:"parent_id,omitempty"` + MenuName string `protobuf:"bytes,2,opt,name=menu_name,json=menuName,proto3" json:"menu_name,omitempty"` + MenuType int64 `protobuf:"varint,3,opt,name=menu_type,json=menuType,proto3" json:"menu_type,omitempty"` + Path string `protobuf:"bytes,4,opt,name=path,proto3" json:"path,omitempty"` + Component string `protobuf:"bytes,5,opt,name=component,proto3" json:"component,omitempty"` + Permission string `protobuf:"bytes,6,opt,name=permission,proto3" json:"permission,omitempty"` + Icon string `protobuf:"bytes,7,opt,name=icon,proto3" json:"icon,omitempty"` + SortOrder int64 `protobuf:"varint,8,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` +} + +func (x *CreateMenuReq) Reset() { + *x = CreateMenuReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateMenuReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateMenuReq) ProtoMessage() {} + +func (x *CreateMenuReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateMenuReq.ProtoReflect.Descriptor instead. +func (*CreateMenuReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{20} +} + +func (x *CreateMenuReq) GetParentId() string { + if x != nil { + return x.ParentId + } + return "" +} + +func (x *CreateMenuReq) GetMenuName() string { + if x != nil { + return x.MenuName + } + return "" +} + +func (x *CreateMenuReq) GetMenuType() int64 { + if x != nil { + return x.MenuType + } + return 0 +} + +func (x *CreateMenuReq) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *CreateMenuReq) GetComponent() string { + if x != nil { + return x.Component + } + return "" +} + +func (x *CreateMenuReq) GetPermission() string { + if x != nil { + return x.Permission + } + return "" +} + +func (x *CreateMenuReq) GetIcon() string { + if x != nil { + return x.Icon + } + return "" +} + +func (x *CreateMenuReq) GetSortOrder() int64 { + if x != nil { + return x.SortOrder + } + return 0 +} + +type UpdateMenuReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MenuId string `protobuf:"bytes,1,opt,name=menu_id,json=menuId,proto3" json:"menu_id,omitempty"` + ParentId string `protobuf:"bytes,2,opt,name=parent_id,json=parentId,proto3" json:"parent_id,omitempty"` + MenuName string `protobuf:"bytes,3,opt,name=menu_name,json=menuName,proto3" json:"menu_name,omitempty"` + MenuType int64 `protobuf:"varint,4,opt,name=menu_type,json=menuType,proto3" json:"menu_type,omitempty"` + Path string `protobuf:"bytes,5,opt,name=path,proto3" json:"path,omitempty"` + Component string `protobuf:"bytes,6,opt,name=component,proto3" json:"component,omitempty"` + Permission string `protobuf:"bytes,7,opt,name=permission,proto3" json:"permission,omitempty"` + Icon string `protobuf:"bytes,8,opt,name=icon,proto3" json:"icon,omitempty"` + SortOrder int64 `protobuf:"varint,9,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + Visible int64 `protobuf:"varint,10,opt,name=visible,proto3" json:"visible,omitempty"` +} + +func (x *UpdateMenuReq) Reset() { + *x = UpdateMenuReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateMenuReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateMenuReq) ProtoMessage() {} + +func (x *UpdateMenuReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateMenuReq.ProtoReflect.Descriptor instead. +func (*UpdateMenuReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{21} +} + +func (x *UpdateMenuReq) GetMenuId() string { + if x != nil { + return x.MenuId + } + return "" +} + +func (x *UpdateMenuReq) GetParentId() string { + if x != nil { + return x.ParentId + } + return "" +} + +func (x *UpdateMenuReq) GetMenuName() string { + if x != nil { + return x.MenuName + } + return "" +} + +func (x *UpdateMenuReq) GetMenuType() int64 { + if x != nil { + return x.MenuType + } + return 0 +} + +func (x *UpdateMenuReq) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *UpdateMenuReq) GetComponent() string { + if x != nil { + return x.Component + } + return "" +} + +func (x *UpdateMenuReq) GetPermission() string { + if x != nil { + return x.Permission + } + return "" +} + +func (x *UpdateMenuReq) GetIcon() string { + if x != nil { + return x.Icon + } + return "" +} + +func (x *UpdateMenuReq) GetSortOrder() int64 { + if x != nil { + return x.SortOrder + } + return 0 +} + +func (x *UpdateMenuReq) GetVisible() int64 { + if x != nil { + return x.Visible + } + return 0 +} + +type DeleteMenuReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MenuId string `protobuf:"bytes,1,opt,name=menu_id,json=menuId,proto3" json:"menu_id,omitempty"` +} + +func (x *DeleteMenuReq) Reset() { + *x = DeleteMenuReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteMenuReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteMenuReq) ProtoMessage() {} + +func (x *DeleteMenuReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteMenuReq.ProtoReflect.Descriptor instead. +func (*DeleteMenuReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{22} +} + +func (x *DeleteMenuReq) GetMenuId() string { + if x != nil { + return x.MenuId + } + return "" +} + +type GetMenuReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MenuId string `protobuf:"bytes,1,opt,name=menu_id,json=menuId,proto3" json:"menu_id,omitempty"` +} + +func (x *GetMenuReq) Reset() { + *x = GetMenuReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetMenuReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetMenuReq) ProtoMessage() {} + +func (x *GetMenuReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetMenuReq.ProtoReflect.Descriptor instead. +func (*GetMenuReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{23} +} + +func (x *GetMenuReq) GetMenuId() string { + if x != nil { + return x.MenuId + } + return "" +} + +type ListMenuReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ListMenuReq) Reset() { + *x = ListMenuReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListMenuReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListMenuReq) ProtoMessage() {} + +func (x *ListMenuReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListMenuReq.ProtoReflect.Descriptor instead. +func (*ListMenuReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{24} +} + +type ListMenuResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + List []*MenuInfo `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` +} + +func (x *ListMenuResp) Reset() { + *x = ListMenuResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListMenuResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListMenuResp) ProtoMessage() {} + +func (x *ListMenuResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListMenuResp.ProtoReflect.Descriptor instead. +func (*ListMenuResp) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{25} +} + +func (x *ListMenuResp) GetList() []*MenuInfo { + if x != nil { + return x.List + } + return nil +} + +type OperationLogInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LogId string `protobuf:"bytes,1,opt,name=log_id,json=logId,proto3" json:"log_id,omitempty"` + UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"` + Module string `protobuf:"bytes,4,opt,name=module,proto3" json:"module,omitempty"` + Operation string `protobuf:"bytes,5,opt,name=operation,proto3" json:"operation,omitempty"` + Method string `protobuf:"bytes,6,opt,name=method,proto3" json:"method,omitempty"` + Path string `protobuf:"bytes,7,opt,name=path,proto3" json:"path,omitempty"` + Ip string `protobuf:"bytes,8,opt,name=ip,proto3" json:"ip,omitempty"` + Duration int64 `protobuf:"varint,9,opt,name=duration,proto3" json:"duration,omitempty"` + Status int64 `protobuf:"varint,10,opt,name=status,proto3" json:"status,omitempty"` + CreatedAt string `protobuf:"bytes,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` +} + +func (x *OperationLogInfo) Reset() { + *x = OperationLogInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OperationLogInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OperationLogInfo) ProtoMessage() {} + +func (x *OperationLogInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OperationLogInfo.ProtoReflect.Descriptor instead. +func (*OperationLogInfo) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{26} +} + +func (x *OperationLogInfo) GetLogId() string { + if x != nil { + return x.LogId + } + return "" +} + +func (x *OperationLogInfo) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *OperationLogInfo) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *OperationLogInfo) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *OperationLogInfo) GetOperation() string { + if x != nil { + return x.Operation + } + return "" +} + +func (x *OperationLogInfo) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *OperationLogInfo) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *OperationLogInfo) GetIp() string { + if x != nil { + return x.Ip + } + return "" +} + +func (x *OperationLogInfo) GetDuration() int64 { + if x != nil { + return x.Duration + } + return 0 +} + +func (x *OperationLogInfo) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *OperationLogInfo) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +type CreateLogReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` + Module string `protobuf:"bytes,3,opt,name=module,proto3" json:"module,omitempty"` + Operation string `protobuf:"bytes,4,opt,name=operation,proto3" json:"operation,omitempty"` + Method string `protobuf:"bytes,5,opt,name=method,proto3" json:"method,omitempty"` + Path string `protobuf:"bytes,6,opt,name=path,proto3" json:"path,omitempty"` + RequestBody string `protobuf:"bytes,7,opt,name=request_body,json=requestBody,proto3" json:"request_body,omitempty"` + ResponseBody string `protobuf:"bytes,8,opt,name=response_body,json=responseBody,proto3" json:"response_body,omitempty"` + Ip string `protobuf:"bytes,9,opt,name=ip,proto3" json:"ip,omitempty"` + UserAgent string `protobuf:"bytes,10,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"` + Duration int64 `protobuf:"varint,11,opt,name=duration,proto3" json:"duration,omitempty"` + Status int64 `protobuf:"varint,12,opt,name=status,proto3" json:"status,omitempty"` +} + +func (x *CreateLogReq) Reset() { + *x = CreateLogReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateLogReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateLogReq) ProtoMessage() {} + +func (x *CreateLogReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateLogReq.ProtoReflect.Descriptor instead. +func (*CreateLogReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{27} +} + +func (x *CreateLogReq) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *CreateLogReq) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *CreateLogReq) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *CreateLogReq) GetOperation() string { + if x != nil { + return x.Operation + } + return "" +} + +func (x *CreateLogReq) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *CreateLogReq) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *CreateLogReq) GetRequestBody() string { + if x != nil { + return x.RequestBody + } + return "" +} + +func (x *CreateLogReq) GetResponseBody() string { + if x != nil { + return x.ResponseBody + } + return "" +} + +func (x *CreateLogReq) GetIp() string { + if x != nil { + return x.Ip + } + return "" +} + +func (x *CreateLogReq) GetUserAgent() string { + if x != nil { + return x.UserAgent + } + return "" +} + +func (x *CreateLogReq) GetDuration() int64 { + if x != nil { + return x.Duration + } + return 0 +} + +func (x *CreateLogReq) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +type ListLogReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Page int64 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + PageSize int64 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"` + Module string `protobuf:"bytes,4,opt,name=module,proto3" json:"module,omitempty"` + Operation string `protobuf:"bytes,5,opt,name=operation,proto3" json:"operation,omitempty"` + StartTime string `protobuf:"bytes,6,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"` + EndTime string `protobuf:"bytes,7,opt,name=end_time,json=endTime,proto3" json:"end_time,omitempty"` +} + +func (x *ListLogReq) Reset() { + *x = ListLogReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListLogReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListLogReq) ProtoMessage() {} + +func (x *ListLogReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListLogReq.ProtoReflect.Descriptor instead. +func (*ListLogReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{28} +} + +func (x *ListLogReq) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListLogReq) GetPageSize() int64 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListLogReq) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *ListLogReq) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *ListLogReq) GetOperation() string { + if x != nil { + return x.Operation + } + return "" +} + +func (x *ListLogReq) GetStartTime() string { + if x != nil { + return x.StartTime + } + return "" +} + +func (x *ListLogReq) GetEndTime() string { + if x != nil { + return x.EndTime + } + return "" +} + +type ListLogResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + List []*OperationLogInfo `protobuf:"bytes,2,rep,name=list,proto3" json:"list,omitempty"` +} + +func (x *ListLogResp) Reset() { + *x = ListLogResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListLogResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListLogResp) ProtoMessage() {} + +func (x *ListLogResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListLogResp.ProtoReflect.Descriptor instead. +func (*ListLogResp) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{29} +} + +func (x *ListLogResp) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ListLogResp) GetList() []*OperationLogInfo { + if x != nil { + return x.List + } + return nil +} + +type ConfigInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ConfigId string `protobuf:"bytes,1,opt,name=config_id,json=configId,proto3" json:"config_id,omitempty"` + ConfigKey string `protobuf:"bytes,2,opt,name=config_key,json=configKey,proto3" json:"config_key,omitempty"` + ConfigValue string `protobuf:"bytes,3,opt,name=config_value,json=configValue,proto3" json:"config_value,omitempty"` + ConfigName string `protobuf:"bytes,4,opt,name=config_name,json=configName,proto3" json:"config_name,omitempty"` + ConfigGroup string `protobuf:"bytes,5,opt,name=config_group,json=configGroup,proto3" json:"config_group,omitempty"` + Remark string `protobuf:"bytes,6,opt,name=remark,proto3" json:"remark,omitempty"` + UpdatedAt string `protobuf:"bytes,7,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` +} + +func (x *ConfigInfo) Reset() { + *x = ConfigInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfigInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigInfo) ProtoMessage() {} + +func (x *ConfigInfo) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfigInfo.ProtoReflect.Descriptor instead. +func (*ConfigInfo) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{30} +} + +func (x *ConfigInfo) GetConfigId() string { + if x != nil { + return x.ConfigId + } + return "" +} + +func (x *ConfigInfo) GetConfigKey() string { + if x != nil { + return x.ConfigKey + } + return "" +} + +func (x *ConfigInfo) GetConfigValue() string { + if x != nil { + return x.ConfigValue + } + return "" +} + +func (x *ConfigInfo) GetConfigName() string { + if x != nil { + return x.ConfigName + } + return "" +} + +func (x *ConfigInfo) GetConfigGroup() string { + if x != nil { + return x.ConfigGroup + } + return "" +} + +func (x *ConfigInfo) GetRemark() string { + if x != nil { + return x.Remark + } + return "" +} + +func (x *ConfigInfo) GetUpdatedAt() string { + if x != nil { + return x.UpdatedAt + } + return "" +} + +type ListConfigReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ConfigGroup string `protobuf:"bytes,1,opt,name=config_group,json=configGroup,proto3" json:"config_group,omitempty"` +} + +func (x *ListConfigReq) Reset() { + *x = ListConfigReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListConfigReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConfigReq) ProtoMessage() {} + +func (x *ListConfigReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConfigReq.ProtoReflect.Descriptor instead. +func (*ListConfigReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{31} +} + +func (x *ListConfigReq) GetConfigGroup() string { + if x != nil { + return x.ConfigGroup + } + return "" +} + +type ListConfigResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + List []*ConfigInfo `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` +} + +func (x *ListConfigResp) Reset() { + *x = ListConfigResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListConfigResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConfigResp) ProtoMessage() {} + +func (x *ListConfigResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[32] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConfigResp.ProtoReflect.Descriptor instead. +func (*ListConfigResp) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{32} +} + +func (x *ListConfigResp) GetList() []*ConfigInfo { + if x != nil { + return x.List + } + return nil +} + +type UpdateConfigReq struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ConfigKey string `protobuf:"bytes,1,opt,name=config_key,json=configKey,proto3" json:"config_key,omitempty"` + ConfigValue string `protobuf:"bytes,2,opt,name=config_value,json=configValue,proto3" json:"config_value,omitempty"` +} + +func (x *UpdateConfigReq) Reset() { + *x = UpdateConfigReq{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateConfigReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateConfigReq) ProtoMessage() {} + +func (x *UpdateConfigReq) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[33] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateConfigReq.ProtoReflect.Descriptor instead. +func (*UpdateConfigReq) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{33} +} + +func (x *UpdateConfigReq) GetConfigKey() string { + if x != nil { + return x.ConfigKey + } + return "" +} + +func (x *UpdateConfigReq) GetConfigValue() string { + if x != nil { + return x.ConfigValue + } + return "" +} + +type IdResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *IdResp) Reset() { + *x = IdResp{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IdResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IdResp) ProtoMessage() {} + +func (x *IdResp) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[34] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IdResp.ProtoReflect.Descriptor instead. +func (*IdResp) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{34} +} + +func (x *IdResp) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type Empty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *Empty) Reset() { + *x = Empty{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_system_system_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Empty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Empty) ProtoMessage() {} + +func (x *Empty) ProtoReflect() protoreflect.Message { + mi := &file_proto_system_system_proto_msgTypes[35] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Empty.ProtoReflect.Descriptor instead. +func (*Empty) Descriptor() ([]byte, []int) { + return file_proto_system_system_proto_rawDescGZIP(), []int{35} +} + +var File_proto_system_system_proto protoreflect.FileDescriptor + +var file_proto_system_system_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2f, 0x73, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x22, 0x52, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x12, + 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x22, 0xad, 0x01, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, + 0x61, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, + 0x65, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, + 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, + 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, + 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, + 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x72, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x6c, 0x64, 0x5f, 0x70, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x6c, 0x64, + 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x6e, 0x65, 0x77, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0xd4, 0x02, 0x0a, 0x08, + 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x72, 0x65, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, + 0x6f, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, + 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0f, + 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x22, 0xa9, 0x01, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x72, 0x65, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x6f, + 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x22, 0xa2, + 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x61, + 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, + 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x61, + 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, + 0x74, 0x61, 0x72, 0x22, 0x28, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x25, 0x0a, + 0x0a, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x22, 0xa5, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x67, + 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, + 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4a, 0x0a, 0x0c, + 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x12, 0x24, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x10, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x46, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x12, + 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x22, 0x88, 0x02, 0x0a, 0x08, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, + 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1b, + 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x12, 0x1d, 0x0a, 0x0a, 0x73, + 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x6e, 0x75, 0x49, 0x64, 0x73, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x83, 0x01, 0x0a, 0x0d, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x12, 0x1b, 0x0a, + 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, + 0x6c, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, + 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x64, 0x65, + 0x73, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x44, 0x65, + 0x73, 0x63, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x22, 0x9c, 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, + 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6c, + 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6c, + 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x64, 0x65, 0x73, + 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x44, 0x65, 0x73, + 0x63, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x22, 0x28, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, + 0x71, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x22, 0x25, 0x0a, 0x0a, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, + 0x64, 0x22, 0x73, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, + 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, + 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4a, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, + 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x24, 0x0a, 0x04, + 0x6c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x6c, 0x69, + 0x73, 0x74, 0x22, 0x4b, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x72, + 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, + 0x6c, 0x65, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x69, 0x64, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x6e, 0x75, 0x49, 0x64, 0x73, 0x22, + 0x9d, 0x03, 0x0a, 0x08, 0x4d, 0x65, 0x6e, 0x75, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x07, + 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, + 0x65, 0x6e, 0x75, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, + 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x6e, 0x75, 0x4e, 0x61, 0x6d, 0x65, 0x12, + 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x08, 0x6d, 0x65, 0x6e, 0x75, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, + 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x1e, + 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, + 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, + 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x2c, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, + 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x4d, + 0x65, 0x6e, 0x75, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, + 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0e, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, + 0xeb, 0x01, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x6e, 0x75, 0x52, 0x65, + 0x71, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1b, + 0x0a, 0x09, 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x6d, 0x65, 0x6e, 0x75, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, + 0x65, 0x6e, 0x75, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, + 0x6d, 0x65, 0x6e, 0x75, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x09, + 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, + 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x9e, 0x02, + 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x6e, 0x75, 0x52, 0x65, 0x71, 0x12, + 0x17, 0x0a, 0x07, 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x6d, 0x65, 0x6e, 0x75, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x6e, 0x75, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6d, 0x65, 0x6e, 0x75, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, + 0x61, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, + 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, + 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x22, 0x28, + 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x65, 0x6e, 0x75, 0x52, 0x65, 0x71, 0x12, + 0x17, 0x0a, 0x07, 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x6d, 0x65, 0x6e, 0x75, 0x49, 0x64, 0x22, 0x25, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4d, + 0x65, 0x6e, 0x75, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x6e, 0x75, 0x49, 0x64, 0x22, + 0x0d, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6e, 0x75, 0x52, 0x65, 0x71, 0x22, 0x34, + 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6e, 0x75, 0x52, 0x65, 0x73, 0x70, 0x12, 0x24, + 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x73, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x4d, 0x65, 0x6e, 0x75, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, + 0x6c, 0x69, 0x73, 0x74, 0x22, 0xa3, 0x02, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x4c, 0x6f, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x0a, 0x06, 0x6c, 0x6f, 0x67, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x6f, 0x67, 0x49, 0x64, + 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, + 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, + 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0xd0, 0x02, 0x0a, 0x0c, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x12, + 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, + 0x74, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, + 0x64, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, + 0x65, 0x72, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x75, 0x73, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xc9, 0x01, + 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, + 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, + 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1a, 0x0a, + 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, 0x64, + 0x75, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x19, + 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x51, 0x0a, 0x0b, 0x4c, 0x69, 0x73, + 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x2c, + 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, + 0x6f, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0xe6, 0x01, 0x0a, + 0x0a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x16, + 0x0a, 0x06, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x32, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x22, 0x38, 0x0a, 0x0e, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x12, 0x26, 0x0a, 0x04, 0x6c, + 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x6c, + 0x69, 0x73, 0x74, 0x22, 0x53, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x18, 0x0a, 0x06, 0x49, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x32, 0xf9, 0x09, 0x0a, 0x0d, + 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2c, 0x0a, + 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x10, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x12, 0x3a, 0x0a, 0x0e, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x19, 0x2e, + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x33, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x15, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x0e, 0x2e, 0x73, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x32, 0x0a, 0x0a, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x15, 0x2e, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x1a, 0x0d, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x12, 0x32, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x15, + 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x2f, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, + 0x12, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x65, 0x71, 0x1a, 0x10, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x55, 0x73, 0x65, + 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x35, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x12, 0x13, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x3e, 0x0a, 0x10, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x1b, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x33, 0x0a, 0x0a, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x15, 0x2e, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, + 0x71, 0x1a, 0x0e, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x49, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x12, 0x32, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, + 0x15, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, + 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x32, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x6f, 0x6c, 0x65, 0x12, 0x15, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x2f, 0x0a, 0x07, 0x47, 0x65, 0x74, + 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x12, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x10, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x35, 0x0a, 0x08, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x13, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x73, 0x79, + 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x12, 0x42, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x2e, 0x53, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x33, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, + 0x65, 0x6e, 0x75, 0x12, 0x15, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x4d, 0x65, 0x6e, 0x75, 0x52, 0x65, 0x71, 0x1a, 0x0e, 0x2e, 0x73, 0x79, 0x73, + 0x74, 0x65, 0x6d, 0x2e, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x32, 0x0a, 0x0a, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x6e, 0x75, 0x12, 0x15, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x6e, 0x75, 0x52, 0x65, 0x71, 0x1a, + 0x0d, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x32, + 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x65, 0x6e, 0x75, 0x12, 0x15, 0x2e, 0x73, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x65, 0x6e, 0x75, + 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0x2f, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x6e, 0x75, 0x12, 0x12, 0x2e, + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x65, 0x6e, 0x75, 0x52, 0x65, + 0x71, 0x1a, 0x10, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x4d, 0x65, 0x6e, 0x75, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x35, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6e, 0x75, 0x12, + 0x13, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6e, + 0x75, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x4d, 0x65, 0x6e, 0x75, 0x52, 0x65, 0x73, 0x70, 0x12, 0x39, 0x0a, 0x12, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x67, + 0x12, 0x14, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x6f, 0x67, 0x12, 0x12, 0x2e, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x52, 0x65, + 0x73, 0x70, 0x12, 0x3b, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x15, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x12, + 0x36, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x17, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x75, 0x79, 0x75, 0x71, 0x69, 0x6e, 0x67, 0x66, 0x65, + 0x6e, 0x67, 0x2f, 0x69, 0x6c, 0x6f, 0x6f, 0x6d, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, + 0x70, 0x6b, 0x67, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x62, 0x2f, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_system_system_proto_rawDescOnce sync.Once + file_proto_system_system_proto_rawDescData = file_proto_system_system_proto_rawDesc +) + +func file_proto_system_system_proto_rawDescGZIP() []byte { + file_proto_system_system_proto_rawDescOnce.Do(func() { + file_proto_system_system_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_system_system_proto_rawDescData) + }) + return file_proto_system_system_proto_rawDescData +} + +var file_proto_system_system_proto_msgTypes = make([]protoimpl.MessageInfo, 36) +var file_proto_system_system_proto_goTypes = []interface{}{ + (*LoginReq)(nil), // 0: system.LoginReq + (*LoginResp)(nil), // 1: system.LoginResp + (*ChangePasswordReq)(nil), // 2: system.ChangePasswordReq + (*UserInfo)(nil), // 3: system.UserInfo + (*CreateUserReq)(nil), // 4: system.CreateUserReq + (*UpdateUserReq)(nil), // 5: system.UpdateUserReq + (*DeleteUserReq)(nil), // 6: system.DeleteUserReq + (*GetUserReq)(nil), // 7: system.GetUserReq + (*ListUserReq)(nil), // 8: system.ListUserReq + (*ListUserResp)(nil), // 9: system.ListUserResp + (*UpdateUserStatusReq)(nil), // 10: system.UpdateUserStatusReq + (*RoleInfo)(nil), // 11: system.RoleInfo + (*CreateRoleReq)(nil), // 12: system.CreateRoleReq + (*UpdateRoleReq)(nil), // 13: system.UpdateRoleReq + (*DeleteRoleReq)(nil), // 14: system.DeleteRoleReq + (*GetRoleReq)(nil), // 15: system.GetRoleReq + (*ListRoleReq)(nil), // 16: system.ListRoleReq + (*ListRoleResp)(nil), // 17: system.ListRoleResp + (*SetRolePermissionsReq)(nil), // 18: system.SetRolePermissionsReq + (*MenuInfo)(nil), // 19: system.MenuInfo + (*CreateMenuReq)(nil), // 20: system.CreateMenuReq + (*UpdateMenuReq)(nil), // 21: system.UpdateMenuReq + (*DeleteMenuReq)(nil), // 22: system.DeleteMenuReq + (*GetMenuReq)(nil), // 23: system.GetMenuReq + (*ListMenuReq)(nil), // 24: system.ListMenuReq + (*ListMenuResp)(nil), // 25: system.ListMenuResp + (*OperationLogInfo)(nil), // 26: system.OperationLogInfo + (*CreateLogReq)(nil), // 27: system.CreateLogReq + (*ListLogReq)(nil), // 28: system.ListLogReq + (*ListLogResp)(nil), // 29: system.ListLogResp + (*ConfigInfo)(nil), // 30: system.ConfigInfo + (*ListConfigReq)(nil), // 31: system.ListConfigReq + (*ListConfigResp)(nil), // 32: system.ListConfigResp + (*UpdateConfigReq)(nil), // 33: system.UpdateConfigReq + (*IdResp)(nil), // 34: system.IdResp + (*Empty)(nil), // 35: system.Empty +} +var file_proto_system_system_proto_depIdxs = []int32{ + 3, // 0: system.ListUserResp.list:type_name -> system.UserInfo + 11, // 1: system.ListRoleResp.list:type_name -> system.RoleInfo + 19, // 2: system.MenuInfo.children:type_name -> system.MenuInfo + 19, // 3: system.ListMenuResp.list:type_name -> system.MenuInfo + 26, // 4: system.ListLogResp.list:type_name -> system.OperationLogInfo + 30, // 5: system.ListConfigResp.list:type_name -> system.ConfigInfo + 0, // 6: system.SystemService.Login:input_type -> system.LoginReq + 2, // 7: system.SystemService.ChangePassword:input_type -> system.ChangePasswordReq + 4, // 8: system.SystemService.CreateUser:input_type -> system.CreateUserReq + 5, // 9: system.SystemService.UpdateUser:input_type -> system.UpdateUserReq + 6, // 10: system.SystemService.DeleteUser:input_type -> system.DeleteUserReq + 7, // 11: system.SystemService.GetUser:input_type -> system.GetUserReq + 8, // 12: system.SystemService.ListUser:input_type -> system.ListUserReq + 10, // 13: system.SystemService.UpdateUserStatus:input_type -> system.UpdateUserStatusReq + 12, // 14: system.SystemService.CreateRole:input_type -> system.CreateRoleReq + 13, // 15: system.SystemService.UpdateRole:input_type -> system.UpdateRoleReq + 14, // 16: system.SystemService.DeleteRole:input_type -> system.DeleteRoleReq + 15, // 17: system.SystemService.GetRole:input_type -> system.GetRoleReq + 16, // 18: system.SystemService.ListRole:input_type -> system.ListRoleReq + 18, // 19: system.SystemService.SetRolePermissions:input_type -> system.SetRolePermissionsReq + 20, // 20: system.SystemService.CreateMenu:input_type -> system.CreateMenuReq + 21, // 21: system.SystemService.UpdateMenu:input_type -> system.UpdateMenuReq + 22, // 22: system.SystemService.DeleteMenu:input_type -> system.DeleteMenuReq + 23, // 23: system.SystemService.GetMenu:input_type -> system.GetMenuReq + 24, // 24: system.SystemService.ListMenu:input_type -> system.ListMenuReq + 27, // 25: system.SystemService.CreateOperationLog:input_type -> system.CreateLogReq + 28, // 26: system.SystemService.ListOperationLog:input_type -> system.ListLogReq + 31, // 27: system.SystemService.ListConfig:input_type -> system.ListConfigReq + 33, // 28: system.SystemService.UpdateConfig:input_type -> system.UpdateConfigReq + 1, // 29: system.SystemService.Login:output_type -> system.LoginResp + 35, // 30: system.SystemService.ChangePassword:output_type -> system.Empty + 34, // 31: system.SystemService.CreateUser:output_type -> system.IdResp + 35, // 32: system.SystemService.UpdateUser:output_type -> system.Empty + 35, // 33: system.SystemService.DeleteUser:output_type -> system.Empty + 3, // 34: system.SystemService.GetUser:output_type -> system.UserInfo + 9, // 35: system.SystemService.ListUser:output_type -> system.ListUserResp + 35, // 36: system.SystemService.UpdateUserStatus:output_type -> system.Empty + 34, // 37: system.SystemService.CreateRole:output_type -> system.IdResp + 35, // 38: system.SystemService.UpdateRole:output_type -> system.Empty + 35, // 39: system.SystemService.DeleteRole:output_type -> system.Empty + 11, // 40: system.SystemService.GetRole:output_type -> system.RoleInfo + 17, // 41: system.SystemService.ListRole:output_type -> system.ListRoleResp + 35, // 42: system.SystemService.SetRolePermissions:output_type -> system.Empty + 34, // 43: system.SystemService.CreateMenu:output_type -> system.IdResp + 35, // 44: system.SystemService.UpdateMenu:output_type -> system.Empty + 35, // 45: system.SystemService.DeleteMenu:output_type -> system.Empty + 19, // 46: system.SystemService.GetMenu:output_type -> system.MenuInfo + 25, // 47: system.SystemService.ListMenu:output_type -> system.ListMenuResp + 35, // 48: system.SystemService.CreateOperationLog:output_type -> system.Empty + 29, // 49: system.SystemService.ListOperationLog:output_type -> system.ListLogResp + 32, // 50: system.SystemService.ListConfig:output_type -> system.ListConfigResp + 35, // 51: system.SystemService.UpdateConfig:output_type -> system.Empty + 29, // [29:52] is the sub-list for method output_type + 6, // [6:29] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_proto_system_system_proto_init() } +func file_proto_system_system_proto_init() { + if File_proto_system_system_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_system_system_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChangePasswordReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UserInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateUserReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateUserReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteUserReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetUserReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListUserReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListUserResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateUserStatusReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RoleInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateRoleReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateRoleReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteRoleReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRoleReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRoleReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRoleResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetRolePermissionsReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MenuInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateMenuReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateMenuReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteMenuReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetMenuReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListMenuReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListMenuResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OperationLogInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateLogReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListLogReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListLogResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfigInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListConfigReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListConfigResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateConfigReq); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IdResp); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_system_system_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Empty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_system_system_proto_rawDesc, + NumEnums: 0, + NumMessages: 36, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_system_system_proto_goTypes, + DependencyIndexes: file_proto_system_system_proto_depIdxs, + MessageInfos: file_proto_system_system_proto_msgTypes, + }.Build() + File_proto_system_system_proto = out.File + file_proto_system_system_proto_rawDesc = nil + file_proto_system_system_proto_goTypes = nil + file_proto_system_system_proto_depIdxs = nil +} diff --git a/shared/pkg/grpc/pb/system/system_grpc.pb.go b/shared/pkg/grpc/pb/system/system_grpc.pb.go new file mode 100644 index 0000000..39af153 --- /dev/null +++ b/shared/pkg/grpc/pb/system/system_grpc.pb.go @@ -0,0 +1,909 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.20.3 +// source: proto/system/system.proto + +package system + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// SystemServiceClient is the client API for SystemService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type SystemServiceClient interface { + // Auth + Login(ctx context.Context, in *LoginReq, opts ...grpc.CallOption) (*LoginResp, error) + ChangePassword(ctx context.Context, in *ChangePasswordReq, opts ...grpc.CallOption) (*Empty, error) + // User + CreateUser(ctx context.Context, in *CreateUserReq, opts ...grpc.CallOption) (*IdResp, error) + UpdateUser(ctx context.Context, in *UpdateUserReq, opts ...grpc.CallOption) (*Empty, error) + DeleteUser(ctx context.Context, in *DeleteUserReq, opts ...grpc.CallOption) (*Empty, error) + GetUser(ctx context.Context, in *GetUserReq, opts ...grpc.CallOption) (*UserInfo, error) + ListUser(ctx context.Context, in *ListUserReq, opts ...grpc.CallOption) (*ListUserResp, error) + UpdateUserStatus(ctx context.Context, in *UpdateUserStatusReq, opts ...grpc.CallOption) (*Empty, error) + // Role + CreateRole(ctx context.Context, in *CreateRoleReq, opts ...grpc.CallOption) (*IdResp, error) + UpdateRole(ctx context.Context, in *UpdateRoleReq, opts ...grpc.CallOption) (*Empty, error) + DeleteRole(ctx context.Context, in *DeleteRoleReq, opts ...grpc.CallOption) (*Empty, error) + GetRole(ctx context.Context, in *GetRoleReq, opts ...grpc.CallOption) (*RoleInfo, error) + ListRole(ctx context.Context, in *ListRoleReq, opts ...grpc.CallOption) (*ListRoleResp, error) + SetRolePermissions(ctx context.Context, in *SetRolePermissionsReq, opts ...grpc.CallOption) (*Empty, error) + // Menu + CreateMenu(ctx context.Context, in *CreateMenuReq, opts ...grpc.CallOption) (*IdResp, error) + UpdateMenu(ctx context.Context, in *UpdateMenuReq, opts ...grpc.CallOption) (*Empty, error) + DeleteMenu(ctx context.Context, in *DeleteMenuReq, opts ...grpc.CallOption) (*Empty, error) + GetMenu(ctx context.Context, in *GetMenuReq, opts ...grpc.CallOption) (*MenuInfo, error) + ListMenu(ctx context.Context, in *ListMenuReq, opts ...grpc.CallOption) (*ListMenuResp, error) + // Operation Log + CreateOperationLog(ctx context.Context, in *CreateLogReq, opts ...grpc.CallOption) (*Empty, error) + ListOperationLog(ctx context.Context, in *ListLogReq, opts ...grpc.CallOption) (*ListLogResp, error) + // Config + ListConfig(ctx context.Context, in *ListConfigReq, opts ...grpc.CallOption) (*ListConfigResp, error) + UpdateConfig(ctx context.Context, in *UpdateConfigReq, opts ...grpc.CallOption) (*Empty, error) +} + +type systemServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewSystemServiceClient(cc grpc.ClientConnInterface) SystemServiceClient { + return &systemServiceClient{cc} +} + +func (c *systemServiceClient) Login(ctx context.Context, in *LoginReq, opts ...grpc.CallOption) (*LoginResp, error) { + out := new(LoginResp) + err := c.cc.Invoke(ctx, "/system.SystemService/Login", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) ChangePassword(ctx context.Context, in *ChangePasswordReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/system.SystemService/ChangePassword", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) CreateUser(ctx context.Context, in *CreateUserReq, opts ...grpc.CallOption) (*IdResp, error) { + out := new(IdResp) + err := c.cc.Invoke(ctx, "/system.SystemService/CreateUser", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) UpdateUser(ctx context.Context, in *UpdateUserReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/system.SystemService/UpdateUser", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) DeleteUser(ctx context.Context, in *DeleteUserReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/system.SystemService/DeleteUser", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) GetUser(ctx context.Context, in *GetUserReq, opts ...grpc.CallOption) (*UserInfo, error) { + out := new(UserInfo) + err := c.cc.Invoke(ctx, "/system.SystemService/GetUser", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) ListUser(ctx context.Context, in *ListUserReq, opts ...grpc.CallOption) (*ListUserResp, error) { + out := new(ListUserResp) + err := c.cc.Invoke(ctx, "/system.SystemService/ListUser", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) UpdateUserStatus(ctx context.Context, in *UpdateUserStatusReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/system.SystemService/UpdateUserStatus", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) CreateRole(ctx context.Context, in *CreateRoleReq, opts ...grpc.CallOption) (*IdResp, error) { + out := new(IdResp) + err := c.cc.Invoke(ctx, "/system.SystemService/CreateRole", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) UpdateRole(ctx context.Context, in *UpdateRoleReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/system.SystemService/UpdateRole", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) DeleteRole(ctx context.Context, in *DeleteRoleReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/system.SystemService/DeleteRole", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) GetRole(ctx context.Context, in *GetRoleReq, opts ...grpc.CallOption) (*RoleInfo, error) { + out := new(RoleInfo) + err := c.cc.Invoke(ctx, "/system.SystemService/GetRole", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) ListRole(ctx context.Context, in *ListRoleReq, opts ...grpc.CallOption) (*ListRoleResp, error) { + out := new(ListRoleResp) + err := c.cc.Invoke(ctx, "/system.SystemService/ListRole", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) SetRolePermissions(ctx context.Context, in *SetRolePermissionsReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/system.SystemService/SetRolePermissions", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) CreateMenu(ctx context.Context, in *CreateMenuReq, opts ...grpc.CallOption) (*IdResp, error) { + out := new(IdResp) + err := c.cc.Invoke(ctx, "/system.SystemService/CreateMenu", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) UpdateMenu(ctx context.Context, in *UpdateMenuReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/system.SystemService/UpdateMenu", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) DeleteMenu(ctx context.Context, in *DeleteMenuReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/system.SystemService/DeleteMenu", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) GetMenu(ctx context.Context, in *GetMenuReq, opts ...grpc.CallOption) (*MenuInfo, error) { + out := new(MenuInfo) + err := c.cc.Invoke(ctx, "/system.SystemService/GetMenu", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) ListMenu(ctx context.Context, in *ListMenuReq, opts ...grpc.CallOption) (*ListMenuResp, error) { + out := new(ListMenuResp) + err := c.cc.Invoke(ctx, "/system.SystemService/ListMenu", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) CreateOperationLog(ctx context.Context, in *CreateLogReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/system.SystemService/CreateOperationLog", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) ListOperationLog(ctx context.Context, in *ListLogReq, opts ...grpc.CallOption) (*ListLogResp, error) { + out := new(ListLogResp) + err := c.cc.Invoke(ctx, "/system.SystemService/ListOperationLog", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) ListConfig(ctx context.Context, in *ListConfigReq, opts ...grpc.CallOption) (*ListConfigResp, error) { + out := new(ListConfigResp) + err := c.cc.Invoke(ctx, "/system.SystemService/ListConfig", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) UpdateConfig(ctx context.Context, in *UpdateConfigReq, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := c.cc.Invoke(ctx, "/system.SystemService/UpdateConfig", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// SystemServiceServer is the server API for SystemService service. +// All implementations must embed UnimplementedSystemServiceServer +// for forward compatibility +type SystemServiceServer interface { + // Auth + Login(context.Context, *LoginReq) (*LoginResp, error) + ChangePassword(context.Context, *ChangePasswordReq) (*Empty, error) + // User + CreateUser(context.Context, *CreateUserReq) (*IdResp, error) + UpdateUser(context.Context, *UpdateUserReq) (*Empty, error) + DeleteUser(context.Context, *DeleteUserReq) (*Empty, error) + GetUser(context.Context, *GetUserReq) (*UserInfo, error) + ListUser(context.Context, *ListUserReq) (*ListUserResp, error) + UpdateUserStatus(context.Context, *UpdateUserStatusReq) (*Empty, error) + // Role + CreateRole(context.Context, *CreateRoleReq) (*IdResp, error) + UpdateRole(context.Context, *UpdateRoleReq) (*Empty, error) + DeleteRole(context.Context, *DeleteRoleReq) (*Empty, error) + GetRole(context.Context, *GetRoleReq) (*RoleInfo, error) + ListRole(context.Context, *ListRoleReq) (*ListRoleResp, error) + SetRolePermissions(context.Context, *SetRolePermissionsReq) (*Empty, error) + // Menu + CreateMenu(context.Context, *CreateMenuReq) (*IdResp, error) + UpdateMenu(context.Context, *UpdateMenuReq) (*Empty, error) + DeleteMenu(context.Context, *DeleteMenuReq) (*Empty, error) + GetMenu(context.Context, *GetMenuReq) (*MenuInfo, error) + ListMenu(context.Context, *ListMenuReq) (*ListMenuResp, error) + // Operation Log + CreateOperationLog(context.Context, *CreateLogReq) (*Empty, error) + ListOperationLog(context.Context, *ListLogReq) (*ListLogResp, error) + // Config + ListConfig(context.Context, *ListConfigReq) (*ListConfigResp, error) + UpdateConfig(context.Context, *UpdateConfigReq) (*Empty, error) + mustEmbedUnimplementedSystemServiceServer() +} + +// UnimplementedSystemServiceServer must be embedded to have forward compatible implementations. +type UnimplementedSystemServiceServer struct { +} + +func (UnimplementedSystemServiceServer) Login(context.Context, *LoginReq) (*LoginResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") +} +func (UnimplementedSystemServiceServer) ChangePassword(context.Context, *ChangePasswordReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChangePassword not implemented") +} +func (UnimplementedSystemServiceServer) CreateUser(context.Context, *CreateUserReq) (*IdResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateUser not implemented") +} +func (UnimplementedSystemServiceServer) UpdateUser(context.Context, *UpdateUserReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateUser not implemented") +} +func (UnimplementedSystemServiceServer) DeleteUser(context.Context, *DeleteUserReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteUser not implemented") +} +func (UnimplementedSystemServiceServer) GetUser(context.Context, *GetUserReq) (*UserInfo, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented") +} +func (UnimplementedSystemServiceServer) ListUser(context.Context, *ListUserReq) (*ListUserResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListUser not implemented") +} +func (UnimplementedSystemServiceServer) UpdateUserStatus(context.Context, *UpdateUserStatusReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateUserStatus not implemented") +} +func (UnimplementedSystemServiceServer) CreateRole(context.Context, *CreateRoleReq) (*IdResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateRole not implemented") +} +func (UnimplementedSystemServiceServer) UpdateRole(context.Context, *UpdateRoleReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateRole not implemented") +} +func (UnimplementedSystemServiceServer) DeleteRole(context.Context, *DeleteRoleReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteRole not implemented") +} +func (UnimplementedSystemServiceServer) GetRole(context.Context, *GetRoleReq) (*RoleInfo, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRole not implemented") +} +func (UnimplementedSystemServiceServer) ListRole(context.Context, *ListRoleReq) (*ListRoleResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListRole not implemented") +} +func (UnimplementedSystemServiceServer) SetRolePermissions(context.Context, *SetRolePermissionsReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetRolePermissions not implemented") +} +func (UnimplementedSystemServiceServer) CreateMenu(context.Context, *CreateMenuReq) (*IdResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateMenu not implemented") +} +func (UnimplementedSystemServiceServer) UpdateMenu(context.Context, *UpdateMenuReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateMenu not implemented") +} +func (UnimplementedSystemServiceServer) DeleteMenu(context.Context, *DeleteMenuReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteMenu not implemented") +} +func (UnimplementedSystemServiceServer) GetMenu(context.Context, *GetMenuReq) (*MenuInfo, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetMenu not implemented") +} +func (UnimplementedSystemServiceServer) ListMenu(context.Context, *ListMenuReq) (*ListMenuResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListMenu not implemented") +} +func (UnimplementedSystemServiceServer) CreateOperationLog(context.Context, *CreateLogReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateOperationLog not implemented") +} +func (UnimplementedSystemServiceServer) ListOperationLog(context.Context, *ListLogReq) (*ListLogResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListOperationLog not implemented") +} +func (UnimplementedSystemServiceServer) ListConfig(context.Context, *ListConfigReq) (*ListConfigResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListConfig not implemented") +} +func (UnimplementedSystemServiceServer) UpdateConfig(context.Context, *UpdateConfigReq) (*Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateConfig not implemented") +} +func (UnimplementedSystemServiceServer) mustEmbedUnimplementedSystemServiceServer() {} + +// UnsafeSystemServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to SystemServiceServer will +// result in compilation errors. +type UnsafeSystemServiceServer interface { + mustEmbedUnimplementedSystemServiceServer() +} + +func RegisterSystemServiceServer(s grpc.ServiceRegistrar, srv SystemServiceServer) { + s.RegisterService(&SystemService_ServiceDesc, srv) +} + +func _SystemService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LoginReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).Login(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/Login", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).Login(ctx, req.(*LoginReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_ChangePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ChangePasswordReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).ChangePassword(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/ChangePassword", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).ChangePassword(ctx, req.(*ChangePasswordReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_CreateUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateUserReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).CreateUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/CreateUser", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).CreateUser(ctx, req.(*CreateUserReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_UpdateUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateUserReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).UpdateUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/UpdateUser", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).UpdateUser(ctx, req.(*UpdateUserReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_DeleteUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteUserReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).DeleteUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/DeleteUser", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).DeleteUser(ctx, req.(*DeleteUserReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_GetUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetUserReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).GetUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/GetUser", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).GetUser(ctx, req.(*GetUserReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_ListUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListUserReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).ListUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/ListUser", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).ListUser(ctx, req.(*ListUserReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_UpdateUserStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateUserStatusReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).UpdateUserStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/UpdateUserStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).UpdateUserStatus(ctx, req.(*UpdateUserStatusReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_CreateRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateRoleReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).CreateRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/CreateRole", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).CreateRole(ctx, req.(*CreateRoleReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_UpdateRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateRoleReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).UpdateRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/UpdateRole", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).UpdateRole(ctx, req.(*UpdateRoleReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_DeleteRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteRoleReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).DeleteRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/DeleteRole", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).DeleteRole(ctx, req.(*DeleteRoleReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_GetRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRoleReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).GetRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/GetRole", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).GetRole(ctx, req.(*GetRoleReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_ListRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRoleReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).ListRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/ListRole", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).ListRole(ctx, req.(*ListRoleReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_SetRolePermissions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetRolePermissionsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).SetRolePermissions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/SetRolePermissions", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).SetRolePermissions(ctx, req.(*SetRolePermissionsReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_CreateMenu_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateMenuReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).CreateMenu(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/CreateMenu", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).CreateMenu(ctx, req.(*CreateMenuReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_UpdateMenu_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateMenuReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).UpdateMenu(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/UpdateMenu", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).UpdateMenu(ctx, req.(*UpdateMenuReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_DeleteMenu_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteMenuReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).DeleteMenu(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/DeleteMenu", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).DeleteMenu(ctx, req.(*DeleteMenuReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_GetMenu_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetMenuReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).GetMenu(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/GetMenu", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).GetMenu(ctx, req.(*GetMenuReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_ListMenu_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListMenuReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).ListMenu(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/ListMenu", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).ListMenu(ctx, req.(*ListMenuReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_CreateOperationLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateLogReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).CreateOperationLog(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/CreateOperationLog", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).CreateOperationLog(ctx, req.(*CreateLogReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_ListOperationLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListLogReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).ListOperationLog(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/ListOperationLog", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).ListOperationLog(ctx, req.(*ListLogReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_ListConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListConfigReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).ListConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/ListConfig", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).ListConfig(ctx, req.(*ListConfigReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_UpdateConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateConfigReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).UpdateConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/system.SystemService/UpdateConfig", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).UpdateConfig(ctx, req.(*UpdateConfigReq)) + } + return interceptor(ctx, in, info, handler) +} + +// SystemService_ServiceDesc is the grpc.ServiceDesc for SystemService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var SystemService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "system.SystemService", + HandlerType: (*SystemServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Login", + Handler: _SystemService_Login_Handler, + }, + { + MethodName: "ChangePassword", + Handler: _SystemService_ChangePassword_Handler, + }, + { + MethodName: "CreateUser", + Handler: _SystemService_CreateUser_Handler, + }, + { + MethodName: "UpdateUser", + Handler: _SystemService_UpdateUser_Handler, + }, + { + MethodName: "DeleteUser", + Handler: _SystemService_DeleteUser_Handler, + }, + { + MethodName: "GetUser", + Handler: _SystemService_GetUser_Handler, + }, + { + MethodName: "ListUser", + Handler: _SystemService_ListUser_Handler, + }, + { + MethodName: "UpdateUserStatus", + Handler: _SystemService_UpdateUserStatus_Handler, + }, + { + MethodName: "CreateRole", + Handler: _SystemService_CreateRole_Handler, + }, + { + MethodName: "UpdateRole", + Handler: _SystemService_UpdateRole_Handler, + }, + { + MethodName: "DeleteRole", + Handler: _SystemService_DeleteRole_Handler, + }, + { + MethodName: "GetRole", + Handler: _SystemService_GetRole_Handler, + }, + { + MethodName: "ListRole", + Handler: _SystemService_ListRole_Handler, + }, + { + MethodName: "SetRolePermissions", + Handler: _SystemService_SetRolePermissions_Handler, + }, + { + MethodName: "CreateMenu", + Handler: _SystemService_CreateMenu_Handler, + }, + { + MethodName: "UpdateMenu", + Handler: _SystemService_UpdateMenu_Handler, + }, + { + MethodName: "DeleteMenu", + Handler: _SystemService_DeleteMenu_Handler, + }, + { + MethodName: "GetMenu", + Handler: _SystemService_GetMenu_Handler, + }, + { + MethodName: "ListMenu", + Handler: _SystemService_ListMenu_Handler, + }, + { + MethodName: "CreateOperationLog", + Handler: _SystemService_CreateOperationLog_Handler, + }, + { + MethodName: "ListOperationLog", + Handler: _SystemService_ListOperationLog_Handler, + }, + { + MethodName: "ListConfig", + Handler: _SystemService_ListConfig_Handler, + }, + { + MethodName: "UpdateConfig", + Handler: _SystemService_UpdateConfig_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/system/system.proto", +} diff --git a/shared/pkg/grpc/system_client.go b/shared/pkg/grpc/system_client.go new file mode 100644 index 0000000..ce57e16 --- /dev/null +++ b/shared/pkg/grpc/system_client.go @@ -0,0 +1,101 @@ +package grpc + +import ( + "context" + "fmt" + + "google.golang.org/grpc" + + pb "github.com/muyuqingfeng/iloom/shared/pkg/grpc/pb/system" +) + +// SystemClient wraps the generated SystemServiceClient with convenience methods. +type SystemClient struct { + conn *grpc.ClientConn + Raw pb.SystemServiceClient +} + +// NewSystemClient creates a SystemClient connected to the given address. +func NewSystemClient(addr string) (*SystemClient, error) { + conn, err := Dial(addr) + if err != nil { + return nil, err + } + return &SystemClient{ + conn: conn, + Raw: pb.NewSystemServiceClient(conn), + }, nil +} + +// Close closes the underlying gRPC connection. +func (c *SystemClient) Close() error { + return c.conn.Close() +} + +// LoginResult holds the response from a Login call. +type LoginResult struct { + UserID string + Username string + RealName string + Avatar string + RoleKey string + RoleName string +} + +// Login authenticates a user and returns identity info. +func (c *SystemClient) Login(ctx context.Context, username, password, ip string) (*LoginResult, error) { + resp, err := c.Raw.Login(ctx, &pb.LoginReq{ + Username: username, + Password: password, + Ip: ip, + }) + if err != nil { + return nil, fmt.Errorf("system.Login: %w", err) + } + return &LoginResult{ + UserID: resp.UserId, + Username: resp.Username, + RealName: resp.RealName, + Avatar: resp.Avatar, + RoleKey: resp.RoleKey, + RoleName: resp.RoleName, + }, nil +} + +// GetUser returns full user info by user ID. +func (c *SystemClient) GetUser(ctx context.Context, userID string) (*pb.UserInfo, error) { + resp, err := c.Raw.GetUser(ctx, &pb.GetUserReq{UserId: userID}) + if err != nil { + return nil, fmt.Errorf("system.GetUser: %w", err) + } + return resp, nil +} + +// CreateUser creates a new user and returns the user ID. +func (c *SystemClient) CreateUser(ctx context.Context, username, password, realName, phone, roleID string) (string, error) { + resp, err := c.Raw.CreateUser(ctx, &pb.CreateUserReq{ + Username: username, + Password: password, + RealName: realName, + Phone: phone, + RoleId: roleID, + }) + if err != nil { + return "", fmt.Errorf("system.CreateUser: %w", err) + } + return resp.Id, nil +} + +// ListUser returns a paginated list of users with optional filters. +func (c *SystemClient) ListUser(ctx context.Context, page, pageSize int64, username string, status int64) ([]*pb.UserInfo, int64, error) { + resp, err := c.Raw.ListUser(ctx, &pb.ListUserReq{ + Page: page, + PageSize: pageSize, + Username: username, + Status: status, + }) + if err != nil { + return nil, 0, fmt.Errorf("system.ListUser: %w", err) + } + return resp.List, resp.Total, nil +} diff --git a/shared/pkg/middleware/cors.go b/shared/pkg/middleware/cors.go new file mode 100644 index 0000000..5feaa1c --- /dev/null +++ b/shared/pkg/middleware/cors.go @@ -0,0 +1,35 @@ +package middleware + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +func CORS(allowOrigins []string) gin.HandlerFunc { + originSet := make(map[string]bool, len(allowOrigins)) + for _, o := range allowOrigins { + originSet[o] = true + } + + return func(c *gin.Context) { + origin := c.GetHeader("Origin") + + if originSet["*"] || originSet[origin] { + c.Header("Access-Control-Allow-Origin", origin) + } + + c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") + c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Authorization, X-Request-ID") + c.Header("Access-Control-Expose-Headers", "X-Request-ID") + c.Header("Access-Control-Allow-Credentials", "true") + c.Header("Access-Control-Max-Age", "86400") + + if c.Request.Method == http.MethodOptions { + c.AbortWithStatus(http.StatusNoContent) + return + } + + c.Next() + } +} diff --git a/shared/pkg/middleware/logger.go b/shared/pkg/middleware/logger.go new file mode 100644 index 0000000..25162d8 --- /dev/null +++ b/shared/pkg/middleware/logger.go @@ -0,0 +1,27 @@ +package middleware + +import ( + "log" + "time" + + "github.com/gin-gonic/gin" +) + +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + + c.Next() + + latency := time.Since(start) + requestID, _ := c.Get("request_id") + + log.Printf("[HTTP] %s %s | %d | %v | req_id=%v", + c.Request.Method, + c.Request.URL.Path, + c.Writer.Status(), + latency, + requestID, + ) + } +} diff --git a/shared/pkg/middleware/recovery.go b/shared/pkg/middleware/recovery.go new file mode 100644 index 0000000..bf5407e --- /dev/null +++ b/shared/pkg/middleware/recovery.go @@ -0,0 +1,24 @@ +package middleware + +import ( + "log" + "net/http" + "runtime/debug" + + "github.com/gin-gonic/gin" +) + +func Recovery() gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if r := recover(); r != nil { + log.Printf("[PANIC] %v\n%s", r, debug.Stack()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ + "code": 50000, + "message": "internal server error", + }) + } + }() + c.Next() + } +} diff --git a/shared/pkg/middleware/requestid.go b/shared/pkg/middleware/requestid.go new file mode 100644 index 0000000..ee5887b --- /dev/null +++ b/shared/pkg/middleware/requestid.go @@ -0,0 +1,18 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +func RequestID() gin.HandlerFunc { + return func(c *gin.Context) { + id := c.GetHeader("X-Request-ID") + if id == "" { + id = uuid.New().String() + } + c.Set("request_id", id) + c.Header("X-Request-ID", id) + c.Next() + } +} diff --git a/shared/pkg/response/errors.go b/shared/pkg/response/errors.go new file mode 100644 index 0000000..525bc98 --- /dev/null +++ b/shared/pkg/response/errors.go @@ -0,0 +1,14 @@ +package response + +const ( + ErrCodeSuccess = 0 + ErrCodeBadRequest = 40000 + ErrCodeUnauthorized = 40100 + ErrCodeForbidden = 40300 + ErrCodeNotFound = 40400 + ErrCodeInternal = 50000 + ErrCodeDBError = 50001 + ErrCodeTokenExpired = 40101 + ErrCodeTokenInvalid = 40102 + ErrCodeRoleMismatch = 40301 +) diff --git a/shared/pkg/response/response.go b/shared/pkg/response/response.go new file mode 100644 index 0000000..d09666c --- /dev/null +++ b/shared/pkg/response/response.go @@ -0,0 +1,65 @@ +package response + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +type Response struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` + Meta *Meta `json:"meta,omitempty"` +} + +type Meta struct { + Page int `json:"page"` + PageSize int `json:"page_size"` + Total int `json:"total"` + HasMore bool `json:"has_more"` +} + +func OK(c *gin.Context, data interface{}) { + c.JSON(http.StatusOK, Response{ + Code: ErrCodeSuccess, + Message: "success", + Data: data, + }) +} + +func OKWithMeta(c *gin.Context, data interface{}, meta *Meta) { + c.JSON(http.StatusOK, Response{ + Code: ErrCodeSuccess, + Message: "success", + Data: data, + Meta: meta, + }) +} + +func Error(c *gin.Context, httpCode int, code int, message string) { + c.JSON(httpCode, Response{ + Code: code, + Message: message, + }) +} + +func BadRequest(c *gin.Context, message string) { + Error(c, http.StatusBadRequest, ErrCodeBadRequest, message) +} + +func Unauthorized(c *gin.Context, message string) { + Error(c, http.StatusUnauthorized, ErrCodeUnauthorized, message) +} + +func Forbidden(c *gin.Context, message string) { + Error(c, http.StatusForbidden, ErrCodeForbidden, message) +} + +func NotFound(c *gin.Context, message string) { + Error(c, http.StatusNotFound, ErrCodeNotFound, message) +} + +func InternalError(c *gin.Context, message string) { + Error(c, http.StatusInternalServerError, ErrCodeInternal, message) +} diff --git a/shared/pkg/types/enums.go b/shared/pkg/types/enums.go new file mode 100644 index 0000000..2c0fcfb --- /dev/null +++ b/shared/pkg/types/enums.go @@ -0,0 +1,59 @@ +package types + +type AppRole string + +const ( + RolePurchaser AppRole = "purchaser" + RoleTextile AppRole = "textile" + RoleWashing AppRole = "washing" +) + +type FactoryType string + +const ( + FactoryTextile FactoryType = "textile" + FactoryWashing FactoryType = "washing" +) + +type PlanStatus string + +const ( + PlanPending PlanStatus = "pending" + PlanProducing PlanStatus = "producing" + PlanCompleted PlanStatus = "completed" +) + +type StepStatus string + +const ( + StepPending StepStatus = "pending" + StepActive StepStatus = "active" + StepCompleted StepStatus = "completed" + StepRejected StepStatus = "rejected" +) + +type StepType string + +const ( + StepConfirm StepType = "confirm" + StepYarnPurchase StepType = "yarn_purchase" + StepDyeing StepType = "dyeing" + StepMachineStart StepType = "machine_start" + StepFabricWarehouse StepType = "fabric_warehouse" +) + +type WarehouseType string + +const ( + WarehouseRawFabric WarehouseType = "raw_fabric" + WarehouseFabric WarehouseType = "fabric" + WarehouseFinished WarehouseType = "finished" + WarehouseYarn WarehouseType = "yarn" +) + +type PaymentStatus string + +const ( + PaymentPending PaymentStatus = "pending" + PaymentCompleted PaymentStatus = "completed" +) diff --git a/shared/pkg/types/pagination.go b/shared/pkg/types/pagination.go new file mode 100644 index 0000000..00822b8 --- /dev/null +++ b/shared/pkg/types/pagination.go @@ -0,0 +1,22 @@ +package types + +type Pagination struct { + Page int `form:"page" json:"page"` + PageSize int `form:"size" json:"page_size"` +} + +func (p *Pagination) Normalize() { + if p.Page < 1 { + p.Page = 1 + } + if p.PageSize < 1 { + p.PageSize = 20 + } + if p.PageSize > 100 { + p.PageSize = 100 + } +} + +func (p *Pagination) Offset() int { + return (p.Page - 1) * p.PageSize +} diff --git a/shared/pkg/websocket/client.go b/shared/pkg/websocket/client.go new file mode 100644 index 0000000..491a605 --- /dev/null +++ b/shared/pkg/websocket/client.go @@ -0,0 +1,96 @@ +package websocket + +import ( + "log" + "time" + + "github.com/gorilla/websocket" +) + +const ( + writeWait = 10 * time.Second + pongWait = 60 * time.Second + pingPeriod = (pongWait * 9) / 10 + maxMessageSize = 4096 +) + +type Client struct { + hub *Hub + conn *websocket.Conn + send chan []byte + companyID string + userID string +} + +func NewClient(hub *Hub, conn *websocket.Conn, companyID, userID string) *Client { + return &Client{ + hub: hub, + conn: conn, + send: make(chan []byte, 256), + companyID: companyID, + userID: userID, + } +} + +func (c *Client) ReadPump() { + defer func() { + c.hub.unregister <- c + c.conn.Close() + }() + + c.conn.SetReadLimit(maxMessageSize) + c.conn.SetReadDeadline(time.Now().Add(pongWait)) + c.conn.SetPongHandler(func(string) error { + c.conn.SetReadDeadline(time.Now().Add(pongWait)) + return nil + }) + + for { + _, _, err := c.conn.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) { + log.Printf("websocket read error: %v", err) + } + break + } + } +} + +func (c *Client) WritePump() { + ticker := time.NewTicker(pingPeriod) + defer func() { + ticker.Stop() + c.conn.Close() + }() + + for { + select { + case message, ok := <-c.send: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if !ok { + c.conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + w, err := c.conn.NextWriter(websocket.TextMessage) + if err != nil { + return + } + w.Write(message) + + if err := w.Close(); err != nil { + return + } + + case <-ticker.C: + c.conn.SetWriteDeadline(time.Now().Add(writeWait)) + if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { + return + } + } + } +} + +func (c *Client) Register() { + c.hub.register <- c +} diff --git a/shared/pkg/websocket/event.go b/shared/pkg/websocket/event.go new file mode 100644 index 0000000..5526362 --- /dev/null +++ b/shared/pkg/websocket/event.go @@ -0,0 +1,13 @@ +package websocket + +import "encoding/json" + +type Event struct { + Table string `json:"table"` + Action string `json:"action"` // INSERT, UPDATE, DELETE + CompanyID string `json:"company_id"` +} + +func (e *Event) Marshal() ([]byte, error) { + return json.Marshal(e) +} diff --git a/shared/pkg/websocket/hub.go b/shared/pkg/websocket/hub.go new file mode 100644 index 0000000..7430b0c --- /dev/null +++ b/shared/pkg/websocket/hub.go @@ -0,0 +1,82 @@ +package websocket + +import "sync" + +type Hub struct { + clients map[string]map[*Client]bool // companyID -> clients + broadcast chan *Event + register chan *Client + unregister chan *Client + mu sync.RWMutex +} + +func NewHub() *Hub { + return &Hub{ + clients: make(map[string]map[*Client]bool), + broadcast: make(chan *Event, 256), + register: make(chan *Client), + unregister: make(chan *Client), + } +} + +func (h *Hub) Run() { + for { + select { + case client := <-h.register: + h.mu.Lock() + if h.clients[client.companyID] == nil { + h.clients[client.companyID] = make(map[*Client]bool) + } + h.clients[client.companyID][client] = true + h.mu.Unlock() + + case client := <-h.unregister: + h.mu.Lock() + if clients, ok := h.clients[client.companyID]; ok { + if _, exists := clients[client]; exists { + delete(clients, client) + close(client.send) + if len(clients) == 0 { + delete(h.clients, client.companyID) + } + } + } + h.mu.Unlock() + + case event := <-h.broadcast: + h.mu.RLock() + if event.CompanyID != "" { + h.broadcastToCompanyLocked(event.CompanyID, event) + } else { + for companyID := range h.clients { + h.broadcastToCompanyLocked(companyID, event) + } + } + h.mu.RUnlock() + } + } +} + +func (h *Hub) broadcastToCompanyLocked(companyID string, event *Event) { + data, err := event.Marshal() + if err != nil { + return + } + for client := range h.clients[companyID] { + select { + case client.send <- data: + default: + close(client.send) + delete(h.clients[companyID], client) + } + } +} + +func (h *Hub) Broadcast(event *Event) { + h.broadcast <- event +} + +func (h *Hub) BroadcastToCompany(companyID string, event *Event) { + event.CompanyID = companyID + h.broadcast <- event +} diff --git a/shared/proto/inventory/inventory.proto b/shared/proto/inventory/inventory.proto new file mode 100644 index 0000000..2ddb462 --- /dev/null +++ b/shared/proto/inventory/inventory.proto @@ -0,0 +1,314 @@ +syntax = "proto3"; + +package inventory; + +option go_package = "github.com/muyuqingfeng/iloom/shared/pkg/grpc/pb/inventory"; + +// ==================== Product ==================== + +message ProductInfo { + string product_id = 1; + string product_name = 2; + string image_url = 3; + string spec = 4; + string color = 5; + int64 unit_pieces = 6; + int64 unit_rolls = 7; + string stock_quantity = 8; + string location = 9; + string cost_price = 10; + string sales_price = 11; + string remark = 12; + int64 status = 13; + string created_at = 14; + string updated_at = 15; +} + +message CreateProductReq { + string product_name = 1; + string image_url = 2; + string spec = 3; + string color = 4; + int64 unit_pieces = 5; + int64 unit_rolls = 6; + string stock_quantity = 7; + string location = 8; + string cost_price = 9; + string sales_price = 10; + string remark = 11; +} + +message UpdateProductReq { + string product_id = 1; + string product_name = 2; + string image_url = 3; + string spec = 4; + string color = 5; + int64 unit_pieces = 6; + int64 unit_rolls = 7; + string stock_quantity = 8; + string location = 9; + string cost_price = 10; + string sales_price = 11; + string remark = 12; +} + +message DeleteProductReq { + string product_id = 1; +} + +message GetProductReq { + string product_id = 1; +} + +message ListProductReq { + int64 page = 1; + int64 page_size = 2; + string product_name = 3; + string spec = 4; + string color = 5; + string location = 6; + int64 status = 7; +} + +message ListProductResp { + int64 total = 1; + repeated ProductInfo list = 2; +} + +// ==================== Stock Import ==================== + +message ImportProductReq { + repeated CreateProductReq products = 1; + string file_name = 2; + string operator = 3; +} + +message ImportProductResp { + int64 total_count = 1; + int64 success_count = 2; + int64 fail_count = 3; + string error_msg = 4; + string import_id = 5; +} + +message ListImportLogReq { + int64 page = 1; + int64 page_size = 2; +} + +message ListImportLogResp { + int64 total = 1; + repeated ImportLogInfo list = 2; +} + +message ImportLogInfo { + string import_id = 1; + string file_name = 2; + int64 total_count = 3; + int64 success_count = 4; + int64 fail_count = 5; + string error_msg = 6; + string operator = 7; + string created_at = 8; +} + +// ==================== Stock Statistics ==================== + +message StockSummaryReq {} + +message StockSummaryResp { + int64 product_count = 1; + int64 total_pieces = 2; + int64 total_rolls = 3; + string total_cost_value = 4; + string total_sales_value = 5; +} + +message StockGroupReq { + string group_by = 1; +} + +message StockGroupItem { + string name = 1; + int64 count = 2; + string quantity = 3; +} + +message StockGroupResp { + repeated StockGroupItem list = 1; +} + +// ==================== Stock Check ==================== + +message StockCheckInfo { + string check_id = 1; + string check_no = 2; + string check_date = 3; + string checker = 4; + int64 status = 5; + string remark = 6; + string created_at = 7; + string updated_at = 8; + repeated StockCheckDetailInfo details = 9; +} + +message StockCheckDetailInfo { + string detail_id = 1; + string check_id = 2; + string product_id = 3; + string product_name = 4; + string system_quantity = 5; + string actual_quantity = 6; + string diff_quantity = 7; + string diff_amount = 8; + string remark = 9; +} + +message CreateStockCheckReq { + string check_date = 1; + string checker = 2; + string remark = 3; + repeated StockCheckDetailReq details = 4; +} + +message StockCheckDetailReq { + string product_id = 1; + string actual_quantity = 2; + string remark = 3; +} + +message UpdateStockCheckReq { + string check_id = 1; + string remark = 2; + repeated StockCheckDetailReq details = 3; +} + +message ConfirmStockCheckReq { + string check_id = 1; + string operator = 2; +} + +message GetStockCheckReq { + string check_id = 1; +} + +message ListStockCheckReq { + int64 page = 1; + int64 page_size = 2; + string check_no = 3; + int64 status = 4; + string start_date = 5; + string end_date = 6; +} + +message ListStockCheckResp { + int64 total = 1; + repeated StockCheckInfo list = 2; +} + +// ==================== Stock Adjust ==================== + +message StockAdjustInfo { + string adjust_id = 1; + string adjust_no = 2; + string adjust_date = 3; + string adjust_reason = 4; + string operator = 5; + string approver = 6; + int64 status = 7; + string remark = 8; + string created_at = 9; + string updated_at = 10; + repeated StockAdjustDetailInfo details = 11; +} + +message StockAdjustDetailInfo { + string detail_id = 1; + string adjust_id = 2; + string product_id = 3; + string product_name = 4; + string before_quantity = 5; + string adjust_quantity = 6; + string after_quantity = 7; + string remark = 8; +} + +message CreateStockAdjustReq { + string adjust_date = 1; + string adjust_reason = 2; + string operator = 3; + string remark = 4; + repeated StockAdjustDetailReq details = 5; +} + +message StockAdjustDetailReq { + string product_id = 1; + string adjust_quantity = 2; + string remark = 3; +} + +message ApproveStockAdjustReq { + string adjust_id = 1; + string approver = 2; + int64 action = 3; +} + +message GetStockAdjustReq { + string adjust_id = 1; +} + +message ListStockAdjustReq { + int64 page = 1; + int64 page_size = 2; + string adjust_no = 3; + string adjust_reason = 4; + int64 status = 5; + string start_date = 6; + string end_date = 7; +} + +message ListStockAdjustResp { + int64 total = 1; + repeated StockAdjustInfo list = 2; +} + +// ==================== Common ==================== + +message IdResp { + string id = 1; +} + +message Empty {} + +// ==================== Service ==================== + +service InventoryService { + // Product + rpc CreateProduct(CreateProductReq) returns (IdResp); + rpc UpdateProduct(UpdateProductReq) returns (Empty); + rpc DeleteProduct(DeleteProductReq) returns (Empty); + rpc GetProduct(GetProductReq) returns (ProductInfo); + rpc ListProduct(ListProductReq) returns (ListProductResp); + + // Import + rpc ImportProducts(ImportProductReq) returns (ImportProductResp); + rpc ListImportLog(ListImportLogReq) returns (ListImportLogResp); + + // Stock Statistics + rpc GetStockSummary(StockSummaryReq) returns (StockSummaryResp); + rpc GetStockGroup(StockGroupReq) returns (StockGroupResp); + + // Stock Check + rpc CreateStockCheck(CreateStockCheckReq) returns (IdResp); + rpc UpdateStockCheck(UpdateStockCheckReq) returns (Empty); + rpc ConfirmStockCheck(ConfirmStockCheckReq) returns (Empty); + rpc GetStockCheck(GetStockCheckReq) returns (StockCheckInfo); + rpc ListStockCheck(ListStockCheckReq) returns (ListStockCheckResp); + + // Stock Adjust + rpc CreateStockAdjust(CreateStockAdjustReq) returns (IdResp); + rpc ApproveStockAdjust(ApproveStockAdjustReq) returns (Empty); + rpc GetStockAdjust(GetStockAdjustReq) returns (StockAdjustInfo); + rpc ListStockAdjust(ListStockAdjustReq) returns (ListStockAdjustResp); +} diff --git a/shared/proto/system/system.proto b/shared/proto/system/system.proto new file mode 100644 index 0000000..5e7aca9 --- /dev/null +++ b/shared/proto/system/system.proto @@ -0,0 +1,319 @@ +syntax = "proto3"; + +package system; + +option go_package = "github.com/muyuqingfeng/iloom/shared/pkg/grpc/pb/system"; + +// ==================== Auth ==================== + +message LoginReq { + string username = 1; + string password = 2; + string ip = 3; +} + +message LoginResp { + string user_id = 1; + string username = 2; + string real_name = 3; + string avatar = 4; + string role_key = 5; + string role_name = 6; +} + +message ChangePasswordReq { + string user_id = 1; + string old_password = 2; + string new_password = 3; +} + +// ==================== User ==================== + +message UserInfo { + string user_id = 1; + string username = 2; + string real_name = 3; + string phone = 4; + string email = 5; + string avatar = 6; + int64 status = 7; + string role_id = 8; + string role_name = 9; + string last_login_time = 10; + string created_at = 11; + string updated_at = 12; +} + +message CreateUserReq { + string username = 1; + string password = 2; + string real_name = 3; + string phone = 4; + string email = 5; + string role_id = 6; +} + +message UpdateUserReq { + string user_id = 1; + string real_name = 2; + string phone = 3; + string email = 4; + string role_id = 5; + string avatar = 6; +} + +message DeleteUserReq { + string user_id = 1; +} + +message GetUserReq { + string user_id = 1; +} + +message ListUserReq { + int64 page = 1; + int64 page_size = 2; + string username = 3; + string real_name = 4; + string phone = 5; + int64 status = 6; +} + +message ListUserResp { + int64 total = 1; + repeated UserInfo list = 2; +} + +message UpdateUserStatusReq { + string user_id = 1; + int64 status = 2; +} + +// ==================== Role ==================== + +message RoleInfo { + string role_id = 1; + string role_name = 2; + string role_key = 3; + string role_desc = 4; + int64 sort_order = 5; + int64 status = 6; + repeated string menu_ids = 7; + string created_at = 8; + string updated_at = 9; +} + +message CreateRoleReq { + string role_name = 1; + string role_key = 2; + string role_desc = 3; + int64 sort_order = 4; +} + +message UpdateRoleReq { + string role_id = 1; + string role_name = 2; + string role_key = 3; + string role_desc = 4; + int64 sort_order = 5; +} + +message DeleteRoleReq { + string role_id = 1; +} + +message GetRoleReq { + string role_id = 1; +} + +message ListRoleReq { + int64 page = 1; + int64 page_size = 2; + string role_name = 3; + int64 status = 4; +} + +message ListRoleResp { + int64 total = 1; + repeated RoleInfo list = 2; +} + +message SetRolePermissionsReq { + string role_id = 1; + repeated string menu_ids = 2; +} + +// ==================== Menu ==================== + +message MenuInfo { + string menu_id = 1; + string parent_id = 2; + string menu_name = 3; + int64 menu_type = 4; + string path = 5; + string component = 6; + string permission = 7; + string icon = 8; + int64 sort_order = 9; + int64 visible = 10; + int64 status = 11; + repeated MenuInfo children = 12; + string created_at = 13; + string updated_at = 14; +} + +message CreateMenuReq { + string parent_id = 1; + string menu_name = 2; + int64 menu_type = 3; + string path = 4; + string component = 5; + string permission = 6; + string icon = 7; + int64 sort_order = 8; +} + +message UpdateMenuReq { + string menu_id = 1; + string parent_id = 2; + string menu_name = 3; + int64 menu_type = 4; + string path = 5; + string component = 6; + string permission = 7; + string icon = 8; + int64 sort_order = 9; + int64 visible = 10; +} + +message DeleteMenuReq { + string menu_id = 1; +} + +message GetMenuReq { + string menu_id = 1; +} + +message ListMenuReq {} + +message ListMenuResp { + repeated MenuInfo list = 1; +} + +// ==================== Operation Log ==================== + +message OperationLogInfo { + string log_id = 1; + string user_id = 2; + string username = 3; + string module = 4; + string operation = 5; + string method = 6; + string path = 7; + string ip = 8; + int64 duration = 9; + int64 status = 10; + string created_at = 11; +} + +message CreateLogReq { + string user_id = 1; + string username = 2; + string module = 3; + string operation = 4; + string method = 5; + string path = 6; + string request_body = 7; + string response_body = 8; + string ip = 9; + string user_agent = 10; + int64 duration = 11; + int64 status = 12; +} + +message ListLogReq { + int64 page = 1; + int64 page_size = 2; + string username = 3; + string module = 4; + string operation = 5; + string start_time = 6; + string end_time = 7; +} + +message ListLogResp { + int64 total = 1; + repeated OperationLogInfo list = 2; +} + +// ==================== Config ==================== + +message ConfigInfo { + string config_id = 1; + string config_key = 2; + string config_value = 3; + string config_name = 4; + string config_group = 5; + string remark = 6; + string updated_at = 7; +} + +message ListConfigReq { + string config_group = 1; +} + +message ListConfigResp { + repeated ConfigInfo list = 1; +} + +message UpdateConfigReq { + string config_key = 1; + string config_value = 2; +} + +// ==================== Common ==================== + +message IdResp { + string id = 1; +} + +message Empty {} + +// ==================== Service ==================== + +service SystemService { + // Auth + rpc Login(LoginReq) returns (LoginResp); + rpc ChangePassword(ChangePasswordReq) returns (Empty); + + // User + rpc CreateUser(CreateUserReq) returns (IdResp); + rpc UpdateUser(UpdateUserReq) returns (Empty); + rpc DeleteUser(DeleteUserReq) returns (Empty); + rpc GetUser(GetUserReq) returns (UserInfo); + rpc ListUser(ListUserReq) returns (ListUserResp); + rpc UpdateUserStatus(UpdateUserStatusReq) returns (Empty); + + // Role + rpc CreateRole(CreateRoleReq) returns (IdResp); + rpc UpdateRole(UpdateRoleReq) returns (Empty); + rpc DeleteRole(DeleteRoleReq) returns (Empty); + rpc GetRole(GetRoleReq) returns (RoleInfo); + rpc ListRole(ListRoleReq) returns (ListRoleResp); + rpc SetRolePermissions(SetRolePermissionsReq) returns (Empty); + + // Menu + rpc CreateMenu(CreateMenuReq) returns (IdResp); + rpc UpdateMenu(UpdateMenuReq) returns (Empty); + rpc DeleteMenu(DeleteMenuReq) returns (Empty); + rpc GetMenu(GetMenuReq) returns (MenuInfo); + rpc ListMenu(ListMenuReq) returns (ListMenuResp); + + // Operation Log + rpc CreateOperationLog(CreateLogReq) returns (Empty); + rpc ListOperationLog(ListLogReq) returns (ListLogResp); + + // Config + rpc ListConfig(ListConfigReq) returns (ListConfigResp); + rpc UpdateConfig(UpdateConfigReq) returns (Empty); +} diff --git a/textile-service/Dockerfile b/textile-service/Dockerfile new file mode 100644 index 0000000..125b981 --- /dev/null +++ b/textile-service/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.24-alpine AS builder +WORKDIR /app +COPY shared/ ./shared/ +COPY textile-service/ ./textile-service/ +WORKDIR /app/textile-service +RUN go build -o /textile-service ./cmd/ + +FROM alpine:3.20 +RUN apk add --no-cache wget +COPY --from=builder /textile-service /textile-service +EXPOSE 8083 +CMD ["/textile-service"] diff --git a/textile-service/cmd/main.go b/textile-service/cmd/main.go new file mode 100644 index 0000000..18bd398 --- /dev/null +++ b/textile-service/cmd/main.go @@ -0,0 +1,85 @@ +package main + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/database" + "github.com/muyuqingfeng/iloom/shared/pkg/middleware" + ws "github.com/muyuqingfeng/iloom/shared/pkg/websocket" + "github.com/muyuqingfeng/iloom/textile-service/internal/config" + "github.com/muyuqingfeng/iloom/textile-service/internal/handler" + "github.com/muyuqingfeng/iloom/textile-service/internal/repository" + "github.com/muyuqingfeng/iloom/textile-service/internal/service" +) + +func main() { + cfg := config.Load() + + db, err := database.NewDBFromDSN(cfg.DBDSN) + if err != nil { + log.Fatalf("failed to connect database: %v", err) + } + defer db.Close() + + hub := ws.NewHub() + go hub.Run() + + planRepo := repository.NewPlanRepo(db) + stepRepo := repository.NewStepRepo(db) + yarnRepo := repository.NewYarnRepo(db) + inventoryRepo := repository.NewInventoryRepo(db) + paymentRepo := repository.NewPaymentRepo(db) + + planSvc := service.NewPlanService(planRepo, stepRepo, inventoryRepo) + processSvc := service.NewProcessService(stepRepo, inventoryRepo) + yarnSvc := service.NewYarnService(yarnRepo, db) + inventorySvc := service.NewInventoryService(inventoryRepo) + paymentSvc := service.NewPaymentService(paymentRepo) + dashboardSvc := service.NewDashboardService(planRepo, yarnRepo, paymentRepo) + + planHandler := handler.NewPlanHandler(planSvc) + stepHandler := handler.NewProcessStepHandler(processSvc, hub) + yarnHandler := handler.NewYarnHandler(yarnSvc, hub) + inventoryHandler := handler.NewInventoryHandler(inventorySvc, hub) + paymentHandler := handler.NewPaymentHandler(paymentSvc) + dashboardHandler := handler.NewDashboardHandler(dashboardSvc) + + r := gin.New() + r.Use(middleware.Recovery()) + r.Use(middleware.Logger()) + r.Use(middleware.RequestID()) + r.Use(middleware.CORS([]string{"*"})) + + r.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "ok", "service": "textile-service"}) + }) + + r.GET("/ws/v1/textile", handler.HandleWebSocket(hub)) + + api := r.Group("/api/v1/textile") + { + api.GET("/dashboard/stats", dashboardHandler.Stats) + + api.GET("/plans", planHandler.List) + api.GET("/plans/:id", planHandler.Get) + api.POST("/plans/:id/steps/:stepId/complete", stepHandler.Complete) + api.POST("/plans/:id/steps/:stepId/reject", stepHandler.Reject) + api.POST("/plans/:id/inventory", inventoryHandler.Inbound) + api.GET("/plans/:id/inventory", inventoryHandler.List) + + api.GET("/yarn-stock", yarnHandler.List) + api.POST("/yarn-stock", yarnHandler.Create) + api.PUT("/yarn-stock/:id", yarnHandler.Update) + api.POST("/yarn-stock/:id/records", yarnHandler.AddRecord) + + api.GET("/payments", paymentHandler.List) + api.POST("/payments/:id/confirm", paymentHandler.Confirm) + } + + log.Printf("textile-service starting on :%s", cfg.Port) + if err := r.Run(":" + cfg.Port); err != nil { + log.Fatalf("failed to start server: %v", err) + } +} diff --git a/textile-service/go.mod b/textile-service/go.mod new file mode 100644 index 0000000..213d390 --- /dev/null +++ b/textile-service/go.mod @@ -0,0 +1,45 @@ +module github.com/muyuqingfeng/iloom/textile-service + +go 1.23 + +toolchain go1.24.4 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.3 + github.com/muyuqingfeng/iloom/shared v0.0.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + 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.2.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/muyuqingfeng/iloom/shared => ../shared diff --git a/textile-service/go.sum b/textile-service/go.sum new file mode 100644 index 0000000..64bfbd0 --- /dev/null +++ b/textile-service/go.sum @@ -0,0 +1,97 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +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= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +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= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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/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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/textile-service/internal/config/config.go b/textile-service/internal/config/config.go new file mode 100644 index 0000000..e17d0aa --- /dev/null +++ b/textile-service/internal/config/config.go @@ -0,0 +1,26 @@ +package config + +import "os" + +type Config struct { + Port string + DBDSN string + InternalServiceKey string + PurchaserServiceURL string +} + +func Load() *Config { + return &Config{ + Port: getEnv("PORT", "8083"), + DBDSN: getEnv("DB_DSN", "root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?parseTime=true&charset=utf8mb4"), + InternalServiceKey: getEnv("INTERNAL_SERVICE_KEY", "dev-internal-key"), + PurchaserServiceURL: getEnv("PURCHASER_SERVICE_URL", "http://localhost:8082"), + } +} + +func getEnv(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} diff --git a/textile-service/internal/handler/dashboard.go b/textile-service/internal/handler/dashboard.go new file mode 100644 index 0000000..145b05a --- /dev/null +++ b/textile-service/internal/handler/dashboard.go @@ -0,0 +1,31 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + "github.com/muyuqingfeng/iloom/textile-service/internal/service" +) + +type DashboardHandler struct { + svc *service.DashboardService +} + +func NewDashboardHandler(svc *service.DashboardService) *DashboardHandler { + return &DashboardHandler{svc: svc} +} + +func (h *DashboardHandler) Stats(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing X-Company-ID header") + return + } + + stats, err := h.svc.Stats(c.Request.Context(), companyID) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, stats) +} diff --git a/textile-service/internal/handler/inventory.go b/textile-service/internal/handler/inventory.go new file mode 100644 index 0000000..935a935 --- /dev/null +++ b/textile-service/internal/handler/inventory.go @@ -0,0 +1,81 @@ +package handler + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + ws "github.com/muyuqingfeng/iloom/shared/pkg/websocket" + "github.com/muyuqingfeng/iloom/textile-service/internal/service" +) + +type InventoryHandler struct { + svc *service.InventoryService + wsHub *ws.Hub +} + +type InboundRequest struct { + WarehouseID string `json:"warehouse_id" binding:"required"` + Quantity float64 `json:"quantity" binding:"required,gt=0"` + Rolls int `json:"rolls"` + PricePerMeter float64 `json:"price_per_meter"` + PriceNote string `json:"price_note"` + WarehouseLocation string `json:"warehouse_location"` +} + +func NewInventoryHandler(svc *service.InventoryService, wsHub *ws.Hub) *InventoryHandler { + return &InventoryHandler{svc: svc, wsHub: wsHub} +} + +func (h *InventoryHandler) Inbound(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + if companyID == "" || userID == "" { + response.BadRequest(c, "missing required headers") + return + } + + planID := c.Param("id") + + var req InboundRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + rec, err := h.svc.Inbound(c.Request.Context(), planID, companyID, userID, service.InboundRequest{ + WarehouseID: req.WarehouseID, + Quantity: req.Quantity, + Rolls: req.Rolls, + PricePerMeter: req.PricePerMeter, + PriceNote: req.PriceNote, + WarehouseLocation: req.WarehouseLocation, + }) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + h.wsHub.BroadcastToCompany(companyID, &ws.Event{ + Table: "inventory_records", + Action: "INSERT", + }) + + c.JSON(http.StatusCreated, response.Response{ + Code: 0, + Message: "success", + Data: rec, + }) +} + +func (h *InventoryHandler) List(c *gin.Context) { + planID := c.Param("id") + + records, err := h.svc.ListByPlan(c.Request.Context(), planID) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, records) +} diff --git a/textile-service/internal/handler/payment.go b/textile-service/internal/handler/payment.go new file mode 100644 index 0000000..5fb1dc0 --- /dev/null +++ b/textile-service/internal/handler/payment.go @@ -0,0 +1,48 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + "github.com/muyuqingfeng/iloom/textile-service/internal/service" +) + +type PaymentHandler struct { + svc *service.PaymentService +} + +func NewPaymentHandler(svc *service.PaymentService) *PaymentHandler { + return &PaymentHandler{svc: svc} +} + +func (h *PaymentHandler) List(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing X-Company-ID header") + return + } + + payments, err := h.svc.List(c.Request.Context(), companyID) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, payments) +} + +func (h *PaymentHandler) Confirm(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing X-Company-ID header") + return + } + + paymentID := c.Param("id") + + if err := h.svc.Confirm(c.Request.Context(), paymentID, companyID); err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, gin.H{"message": "payment confirmed"}) +} diff --git a/textile-service/internal/handler/plan.go b/textile-service/internal/handler/plan.go new file mode 100644 index 0000000..39c9ed3 --- /dev/null +++ b/textile-service/internal/handler/plan.go @@ -0,0 +1,50 @@ +package handler + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + "github.com/muyuqingfeng/iloom/textile-service/internal/service" +) + +type PlanHandler struct { + svc *service.PlanService +} + +func NewPlanHandler(svc *service.PlanService) *PlanHandler { + return &PlanHandler{svc: svc} +} + +func (h *PlanHandler) List(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing X-Company-ID header") + return + } + + plans, err := h.svc.List(c.Request.Context(), companyID) + if err != nil { + response.Error(c, http.StatusInternalServerError, 50000, err.Error()) + return + } + + response.OK(c, plans) +} + +func (h *PlanHandler) Get(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing X-Company-ID header") + return + } + + planID := c.Param("id") + plan, err := h.svc.GetByID(c.Request.Context(), planID, companyID) + if err != nil { + response.NotFound(c, "plan not found") + return + } + + response.OK(c, plan) +} diff --git a/textile-service/internal/handler/process_step.go b/textile-service/internal/handler/process_step.go new file mode 100644 index 0000000..a36b7bc --- /dev/null +++ b/textile-service/internal/handler/process_step.go @@ -0,0 +1,87 @@ +package handler + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + ws "github.com/muyuqingfeng/iloom/shared/pkg/websocket" + "github.com/muyuqingfeng/iloom/textile-service/internal/service" +) + +type ProcessStepHandler struct { + svc *service.ProcessService + wsHub *ws.Hub +} + +type CompleteStepRequest struct { + Notes string `json:"notes"` +} + +type RejectStepRequest struct { + Notes string `json:"notes" binding:"required"` +} + +func NewProcessStepHandler(svc *service.ProcessService, wsHub *ws.Hub) *ProcessStepHandler { + return &ProcessStepHandler{svc: svc, wsHub: wsHub} +} + +func (h *ProcessStepHandler) Complete(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + if companyID == "" || userID == "" { + response.BadRequest(c, "missing required headers") + return + } + + planID := c.Param("id") + stepID := c.Param("stepId") + + var req CompleteStepRequest + if err := c.ShouldBindJSON(&req); err != nil { + // Notes is optional, allow empty body + req = CompleteStepRequest{} + } + + if err := h.svc.CompleteStep(c.Request.Context(), planID, stepID, companyID, userID, req.Notes); err != nil { + response.Error(c, http.StatusBadRequest, 40000, err.Error()) + return + } + + h.wsHub.BroadcastToCompany(companyID, &ws.Event{ + Table: "plan_process_steps", + Action: "UPDATE", + }) + + response.OK(c, gin.H{"message": "step completed"}) +} + +func (h *ProcessStepHandler) Reject(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + if companyID == "" || userID == "" { + response.BadRequest(c, "missing required headers") + return + } + + planID := c.Param("id") + stepID := c.Param("stepId") + + var req RejectStepRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "notes is required for rejection") + return + } + + if err := h.svc.RejectStep(c.Request.Context(), planID, stepID, companyID, userID, req.Notes); err != nil { + response.Error(c, http.StatusBadRequest, 40000, err.Error()) + return + } + + h.wsHub.BroadcastToCompany(companyID, &ws.Event{ + Table: "plan_process_steps", + Action: "UPDATE", + }) + + response.OK(c, gin.H{"message": "step rejected"}) +} diff --git a/textile-service/internal/handler/ws.go b/textile-service/internal/handler/ws.go new file mode 100644 index 0000000..a3403be --- /dev/null +++ b/textile-service/internal/handler/ws.go @@ -0,0 +1,50 @@ +package handler + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + gorillaWs "github.com/gorilla/websocket" + ws "github.com/muyuqingfeng/iloom/shared/pkg/websocket" +) + +var upgrader = gorillaWs.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func HandleWebSocket(hub *ws.Hub) gin.HandlerFunc { + return func(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + + // Also try query params for WebSocket connections + if companyID == "" { + companyID = c.Query("company_id") + } + if userID == "" { + userID = c.Query("user_id") + } + + if companyID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "missing company_id"}) + return + } + + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Printf("websocket upgrade error: %v", err) + return + } + + client := ws.NewClient(hub, conn, companyID, userID) + client.Register() + + go client.WritePump() + go client.ReadPump() + } +} diff --git a/textile-service/internal/handler/yarn.go b/textile-service/internal/handler/yarn.go new file mode 100644 index 0000000..13d39f2 --- /dev/null +++ b/textile-service/internal/handler/yarn.go @@ -0,0 +1,165 @@ +package handler + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + ws "github.com/muyuqingfeng/iloom/shared/pkg/websocket" + "github.com/muyuqingfeng/iloom/textile-service/internal/service" +) + +type YarnHandler struct { + svc *service.YarnService + wsHub *ws.Hub +} + +type CreateYarnStockRequest struct { + Name string `json:"name" binding:"required"` + Spec string `json:"spec"` + Quantity float64 `json:"quantity"` + Unit string `json:"unit"` + MinStock float64 `json:"min_stock"` +} + +type UpdateYarnStockRequest struct { + Name string `json:"name"` + Spec string `json:"spec"` + Unit string `json:"unit"` + MinStock float64 `json:"min_stock"` +} + +type YarnRecordRequest struct { + RecordType string `json:"record_type" binding:"required,oneof=in out"` + Quantity float64 `json:"quantity" binding:"required,gt=0"` + Unit string `json:"unit"` + BatchNo string `json:"batch_no"` + PlanID string `json:"plan_id"` + Notes string `json:"notes"` +} + +func NewYarnHandler(svc *service.YarnService, wsHub *ws.Hub) *YarnHandler { + return &YarnHandler{svc: svc, wsHub: wsHub} +} + +func (h *YarnHandler) List(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing X-Company-ID header") + return + } + + stocks, err := h.svc.List(c.Request.Context(), companyID) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + response.OK(c, stocks) +} + +func (h *YarnHandler) Create(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing X-Company-ID header") + return + } + + var req CreateYarnStockRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + stock, err := h.svc.Create(c.Request.Context(), companyID, service.CreateYarnStockRequest{ + Name: req.Name, + Spec: req.Spec, + Quantity: req.Quantity, + Unit: req.Unit, + MinStock: req.MinStock, + }) + if err != nil { + response.InternalError(c, err.Error()) + return + } + + h.wsHub.BroadcastToCompany(companyID, &ws.Event{ + Table: "yarn_stock", + Action: "INSERT", + }) + + c.JSON(http.StatusCreated, response.Response{ + Code: 0, + Message: "success", + Data: stock, + }) +} + +func (h *YarnHandler) Update(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing X-Company-ID header") + return + } + + id := c.Param("id") + + var req UpdateYarnStockRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + if err := h.svc.Update(c.Request.Context(), id, companyID, service.UpdateYarnStockRequest{ + Name: req.Name, + Spec: req.Spec, + Unit: req.Unit, + MinStock: req.MinStock, + }); err != nil { + response.InternalError(c, err.Error()) + return + } + + h.wsHub.BroadcastToCompany(companyID, &ws.Event{ + Table: "yarn_stock", + Action: "UPDATE", + }) + + response.OK(c, gin.H{"message": "updated"}) +} + +func (h *YarnHandler) AddRecord(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + if companyID == "" || userID == "" { + response.BadRequest(c, "missing required headers") + return + } + + yarnStockID := c.Param("id") + + var req YarnRecordRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + if err := h.svc.AddRecord(c.Request.Context(), yarnStockID, companyID, userID, service.YarnRecordRequest{ + RecordType: req.RecordType, + Quantity: req.Quantity, + Unit: req.Unit, + BatchNo: req.BatchNo, + PlanID: req.PlanID, + Notes: req.Notes, + }); err != nil { + response.InternalError(c, err.Error()) + return + } + + h.wsHub.BroadcastToCompany(companyID, &ws.Event{ + Table: "yarn_stock_records", + Action: "INSERT", + }) + + response.OK(c, gin.H{"message": "record added"}) +} diff --git a/textile-service/internal/model/inventory.go b/textile-service/internal/model/inventory.go new file mode 100644 index 0000000..7f53cd5 --- /dev/null +++ b/textile-service/internal/model/inventory.go @@ -0,0 +1,43 @@ +package model + +import "time" + +type InventoryRecord struct { + ID string `json:"id" db:"id"` + PlanID string `json:"plan_id" db:"plan_id"` + WarehouseID string `json:"warehouse_id" db:"warehouse_id"` + Quantity float64 `json:"quantity" db:"quantity"` + Rolls *int `json:"rolls,omitempty" db:"rolls"` + PricePerMeter *float64 `json:"price_per_meter,omitempty" db:"price_per_meter"` + PriceNote *string `json:"price_note,omitempty" db:"price_note"` + WarehouseLocation *string `json:"warehouse_location,omitempty" db:"warehouse_location"` + OperatorID *string `json:"operator_id,omitempty" db:"operator_id"` + CompanyID *string `json:"company_id,omitempty" db:"company_id"` + CreatedAt time.Time `json:"created_at" db:"created_at"` +} + +type Warehouse struct { + ID string `json:"id" db:"id"` + CompanyID string `json:"company_id" db:"company_id"` + Name string `json:"name" db:"name"` + Type string `json:"type" db:"type"` + Location *string `json:"location,omitempty" db:"location"` + CreatedAt time.Time `json:"created_at" db:"created_at"` +} + +type Payment struct { + ID string `json:"id" db:"id"` + PlanID string `json:"plan_id" db:"plan_id"` + FromCompanyID string `json:"from_company_id" db:"from_company_id"` + ToCompanyID string `json:"to_company_id" db:"to_company_id"` + Amount float64 `json:"amount" db:"amount"` + Quantity float64 `json:"quantity" db:"quantity"` + PricePerMeter float64 `json:"price_per_meter" db:"price_per_meter"` + Status string `json:"status" db:"status"` + PaidAt *time.Time `json:"paid_at,omitempty" db:"paid_at"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + + // Aggregated + PlanCode string `json:"plan_code,omitempty"` + FromCompanyName string `json:"from_company_name,omitempty"` +} diff --git a/textile-service/internal/model/process_step.go b/textile-service/internal/model/process_step.go new file mode 100644 index 0000000..aebe356 --- /dev/null +++ b/textile-service/internal/model/process_step.go @@ -0,0 +1,27 @@ +package model + +import "time" + +type ProcessStep struct { + ID string `json:"id" db:"id"` + PlanID string `json:"plan_id" db:"plan_id"` + StepType string `json:"step_type" db:"step_type"` + Status string `json:"status" db:"status"` + Notes *string `json:"notes,omitempty" db:"notes"` + OperatorID *string `json:"operator_id,omitempty" db:"operator_id"` + CompanyID *string `json:"company_id,omitempty" db:"company_id"` + Timestamp *time.Time `json:"timestamp,omitempty" db:"timestamp"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + + // Aggregated + OperatorName string `json:"operator_name,omitempty"` +} + +// StepOrder defines the 5-step production process sequence. +var StepOrder = []string{ + "confirm", + "yarn_purchase", + "dyeing", + "machine_start", + "fabric_warehouse", +} diff --git a/textile-service/internal/model/yarn.go b/textile-service/internal/model/yarn.go new file mode 100644 index 0000000..8e8949c --- /dev/null +++ b/textile-service/internal/model/yarn.go @@ -0,0 +1,29 @@ +package model + +import "time" + +type YarnStock struct { + ID string `json:"id" db:"id"` + CompanyID string `json:"company_id" db:"company_id"` + Name string `json:"name" db:"name"` + Spec *string `json:"spec,omitempty" db:"spec"` + Quantity float64 `json:"quantity" db:"quantity"` + Unit string `json:"unit" db:"unit"` + MinStock float64 `json:"min_stock" db:"min_stock"` + WarehouseID *string `json:"warehouse_id,omitempty" db:"warehouse_id"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +type YarnStockRecord struct { + ID string `json:"id" db:"id"` + YarnStockID *string `json:"yarn_stock_id,omitempty" db:"yarn_stock_id"` + CompanyID string `json:"company_id" db:"company_id"` + RecordType string `json:"record_type" db:"record_type"` // in, out + Quantity float64 `json:"quantity" db:"quantity"` + Unit string `json:"unit" db:"unit"` + BatchNo *string `json:"batch_no,omitempty" db:"batch_no"` + PlanID *string `json:"plan_id,omitempty" db:"plan_id"` + Notes *string `json:"notes,omitempty" db:"notes"` + OperatorID *string `json:"operator_id,omitempty" db:"operator_id"` + CreatedAt time.Time `json:"created_at" db:"created_at"` +} diff --git a/textile-service/internal/repository/inventory_repo.go b/textile-service/internal/repository/inventory_repo.go new file mode 100644 index 0000000..6473d36 --- /dev/null +++ b/textile-service/internal/repository/inventory_repo.go @@ -0,0 +1,59 @@ +package repository + +import ( + "context" + "database/sql" + + "github.com/muyuqingfeng/iloom/textile-service/internal/model" +) + +type InventoryRepo struct { + db *sql.DB +} + +func NewInventoryRepo(db *sql.DB) *InventoryRepo { + return &InventoryRepo{db: db} +} + +func (r *InventoryRepo) Create(ctx context.Context, rec *model.InventoryRecord) error { + query := ` + INSERT INTO ilm_inventory_record + (id, plan_id, warehouse_id, quantity, rolls, price_per_meter, price_note, warehouse_location, operator_id, company_id, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + + _, err := r.db.ExecContext(ctx, query, + rec.ID, rec.PlanID, rec.WarehouseID, rec.Quantity, rec.Rolls, + rec.PricePerMeter, rec.PriceNote, rec.WarehouseLocation, + rec.OperatorID, rec.CompanyID, rec.CreatedAt, + ) + return err +} + +func (r *InventoryRepo) ListByPlan(ctx context.Context, planID string) ([]model.InventoryRecord, error) { + query := ` + SELECT id, plan_id, warehouse_id, quantity, rolls, price_per_meter, + price_note, warehouse_location, operator_id, company_id, created_at + FROM ilm_inventory_record + WHERE plan_id = ? + ORDER BY created_at DESC` + + rows, err := r.db.QueryContext(ctx, query, planID) + if err != nil { + return nil, err + } + defer rows.Close() + + var records []model.InventoryRecord + for rows.Next() { + var r model.InventoryRecord + if err := rows.Scan( + &r.ID, &r.PlanID, &r.WarehouseID, &r.Quantity, &r.Rolls, + &r.PricePerMeter, &r.PriceNote, &r.WarehouseLocation, + &r.OperatorID, &r.CompanyID, &r.CreatedAt, + ); err != nil { + return nil, err + } + records = append(records, r) + } + return records, rows.Err() +} diff --git a/textile-service/internal/repository/payment_repo.go b/textile-service/internal/repository/payment_repo.go new file mode 100644 index 0000000..4837c2e --- /dev/null +++ b/textile-service/internal/repository/payment_repo.go @@ -0,0 +1,70 @@ +package repository + +import ( + "context" + "database/sql" + "time" + + "github.com/muyuqingfeng/iloom/textile-service/internal/model" +) + +type PaymentRepo struct { + db *sql.DB +} + +func NewPaymentRepo(db *sql.DB) *PaymentRepo { + return &PaymentRepo{db: db} +} + +func (r *PaymentRepo) ListByCompany(ctx context.Context, companyID string) ([]model.Payment, error) { + query := ` + SELECT p.id, p.plan_id, p.from_company_id, p.to_company_id, + p.amount, p.quantity, p.price_per_meter, p.status, p.paid_at, p.created_at, + COALESCE(pp.plan_code, '') AS plan_code, + COALESCE(c.name, '') AS from_company_name + FROM ilm_payment p + LEFT JOIN ilm_production_plan pp ON p.plan_id = pp.id + LEFT JOIN ilm_company c ON p.from_company_id = c.id + WHERE p.to_company_id = ? + ORDER BY p.created_at DESC` + + rows, err := r.db.QueryContext(ctx, query, companyID) + if err != nil { + return nil, err + } + defer rows.Close() + + var payments []model.Payment + for rows.Next() { + var p model.Payment + if err := rows.Scan( + &p.ID, &p.PlanID, &p.FromCompanyID, &p.ToCompanyID, + &p.Amount, &p.Quantity, &p.PricePerMeter, &p.Status, &p.PaidAt, &p.CreatedAt, + &p.PlanCode, &p.FromCompanyName, + ); err != nil { + return nil, err + } + payments = append(payments, p) + } + return payments, rows.Err() +} + +func (r *PaymentRepo) Confirm(ctx context.Context, paymentID string) error { + now := time.Now() + query := ` + UPDATE ilm_payment + SET status = 'paid', paid_at = ? + WHERE id = ?` + + _, err := r.db.ExecContext(ctx, query, now, paymentID) + return err +} + +func (r *PaymentRepo) CountPending(ctx context.Context, companyID string) (int, error) { + var count int + err := r.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM ilm_payment WHERE to_company_id = ? AND status = 'pending'`, + companyID, + ).Scan(&count) + return count, err +} diff --git a/textile-service/internal/repository/plan_repo.go b/textile-service/internal/repository/plan_repo.go new file mode 100644 index 0000000..9851740 --- /dev/null +++ b/textile-service/internal/repository/plan_repo.go @@ -0,0 +1,102 @@ +package repository + +import ( + "context" + "database/sql" + "time" +) + +type PlanSummary struct { + ID string `json:"id"` + PlanCode string `json:"plan_code"` + ProductName string `json:"product_name"` + FabricCode string `json:"fabric_code"` + Color string `json:"color"` + ColorCode string `json:"color_code"` + TargetQuantity float64 `json:"target_quantity"` + CompletedQuantity float64 `json:"completed_quantity"` + Status string `json:"status"` + Remark *string `json:"remark,omitempty"` + StartTime *time.Time `json:"start_time,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + FactoryID string `json:"factory_id"` +} + +type PlanRepo struct { + db *sql.DB +} + +func NewPlanRepo(db *sql.DB) *PlanRepo { + return &PlanRepo{db: db} +} + +func (r *PlanRepo) ListByFactory(ctx context.Context, factoryCompanyID string) ([]PlanSummary, error) { + query := ` + SELECT pp.id, pp.plan_code, pp.product_name, pp.fabric_code, + pp.color, pp.color_code, pp.target_quantity, pp.completed_quantity, + pp.status, pp.remark, pp.start_time, pp.created_at, pp.updated_at, + pf.factory_id + FROM ilm_production_plan pp + JOIN ilm_plan_factory pf ON pp.id = pf.plan_id + WHERE pf.factory_id = ? AND pf.factory_type = 'textile' + ORDER BY pp.created_at DESC` + + rows, err := r.db.QueryContext(ctx, query, factoryCompanyID) + if err != nil { + return nil, err + } + defer rows.Close() + + var plans []PlanSummary + for rows.Next() { + var p PlanSummary + if err := rows.Scan( + &p.ID, &p.PlanCode, &p.ProductName, &p.FabricCode, + &p.Color, &p.ColorCode, &p.TargetQuantity, &p.CompletedQuantity, + &p.Status, &p.Remark, &p.StartTime, &p.CreatedAt, &p.UpdatedAt, + &p.FactoryID, + ); err != nil { + return nil, err + } + plans = append(plans, p) + } + return plans, rows.Err() +} + +func (r *PlanRepo) GetByID(ctx context.Context, planID string) (*PlanSummary, error) { + query := ` + SELECT pp.id, pp.plan_code, pp.product_name, pp.fabric_code, + pp.color, pp.color_code, pp.target_quantity, pp.completed_quantity, + pp.status, pp.remark, pp.start_time, pp.created_at, pp.updated_at, + COALESCE(pf.factory_id, '') + FROM ilm_production_plan pp + LEFT JOIN ilm_plan_factory pf ON pp.id = pf.plan_id AND pf.factory_type = 'textile' + WHERE pp.id = ?` + + var p PlanSummary + err := r.db.QueryRowContext(ctx, query, planID).Scan( + &p.ID, &p.PlanCode, &p.ProductName, &p.FabricCode, + &p.Color, &p.ColorCode, &p.TargetQuantity, &p.CompletedQuantity, + &p.Status, &p.Remark, &p.StartTime, &p.CreatedAt, &p.UpdatedAt, + &p.FactoryID, + ) + if err != nil { + return nil, err + } + return &p, nil +} + +func (r *PlanRepo) CountByFactory(ctx context.Context, factoryCompanyID string) (total, active, completed int, err error) { + query := ` + SELECT + COUNT(*), + SUM(CASE WHEN pp.status IN ('pending', 'in_progress') THEN 1 ELSE 0 END), + SUM(CASE WHEN pp.status = 'completed' THEN 1 ELSE 0 END) + FROM ilm_production_plan pp + JOIN ilm_plan_factory pf ON pp.id = pf.plan_id + WHERE pf.factory_id = ? AND pf.factory_type = 'textile'` + + err = r.db.QueryRowContext(ctx, query, factoryCompanyID).Scan(&total, &active, &completed) + return +} diff --git a/textile-service/internal/repository/step_repo.go b/textile-service/internal/repository/step_repo.go new file mode 100644 index 0000000..7615f11 --- /dev/null +++ b/textile-service/internal/repository/step_repo.go @@ -0,0 +1,88 @@ +package repository + +import ( + "context" + "database/sql" + "time" + + "github.com/google/uuid" + "github.com/muyuqingfeng/iloom/textile-service/internal/model" +) + +type StepRepo struct { + db *sql.DB +} + +func NewStepRepo(db *sql.DB) *StepRepo { + return &StepRepo{db: db} +} + +func (r *StepRepo) GetByPlan(ctx context.Context, planID string) ([]model.ProcessStep, error) { + query := ` + SELECT id, plan_id, step_type, status, notes, operator_id, company_id, timestamp, created_at + FROM ilm_process_step + WHERE plan_id = ? + ORDER BY created_at ASC` + + rows, err := r.db.QueryContext(ctx, query, planID) + if err != nil { + return nil, err + } + defer rows.Close() + + var steps []model.ProcessStep + for rows.Next() { + var s model.ProcessStep + if err := rows.Scan( + &s.ID, &s.PlanID, &s.StepType, &s.Status, &s.Notes, + &s.OperatorID, &s.CompanyID, &s.Timestamp, &s.CreatedAt, + ); err != nil { + return nil, err + } + steps = append(steps, s) + } + return steps, rows.Err() +} + +func (r *StepRepo) GetByID(ctx context.Context, id string) (*model.ProcessStep, error) { + query := ` + SELECT id, plan_id, step_type, status, notes, operator_id, company_id, timestamp, created_at + FROM ilm_process_step + WHERE id = ?` + + var s model.ProcessStep + err := r.db.QueryRowContext(ctx, query, id).Scan( + &s.ID, &s.PlanID, &s.StepType, &s.Status, &s.Notes, + &s.OperatorID, &s.CompanyID, &s.Timestamp, &s.CreatedAt, + ) + if err != nil { + return nil, err + } + return &s, nil +} + +func (r *StepRepo) UpdateStatus(ctx context.Context, id, status, operatorID string, notes *string) error { + now := time.Now() + query := ` + UPDATE ilm_process_step + SET status = ?, operator_id = ?, notes = ?, timestamp = ? + WHERE id = ?` + + _, err := r.db.ExecContext(ctx, query, status, operatorID, notes, now, id) + return err +} + +func (r *StepRepo) CreateSteps(ctx context.Context, tx *sql.Tx, planID, companyID string) error { + query := ` + INSERT INTO ilm_process_step (id, plan_id, step_type, status, company_id, created_at) + VALUES (?, ?, ?, ?, ?, ?)` + + now := time.Now() + for _, stepType := range model.StepOrder { + id := uuid.New().String() + if _, err := tx.ExecContext(ctx, query, id, planID, stepType, "pending", companyID, now); err != nil { + return err + } + } + return nil +} diff --git a/textile-service/internal/repository/yarn_repo.go b/textile-service/internal/repository/yarn_repo.go new file mode 100644 index 0000000..6e368f0 --- /dev/null +++ b/textile-service/internal/repository/yarn_repo.go @@ -0,0 +1,107 @@ +package repository + +import ( + "context" + "database/sql" + + "github.com/muyuqingfeng/iloom/textile-service/internal/model" +) + +type YarnRepo struct { + db *sql.DB +} + +func NewYarnRepo(db *sql.DB) *YarnRepo { + return &YarnRepo{db: db} +} + +func (r *YarnRepo) List(ctx context.Context, companyID string) ([]model.YarnStock, error) { + query := ` + SELECT id, company_id, name, spec, quantity, unit, min_stock, warehouse_id, updated_at + FROM ilm_yarn_stock + WHERE company_id = ? + ORDER BY updated_at DESC` + + rows, err := r.db.QueryContext(ctx, query, companyID) + if err != nil { + return nil, err + } + defer rows.Close() + + var stocks []model.YarnStock + for rows.Next() { + var s model.YarnStock + if err := rows.Scan( + &s.ID, &s.CompanyID, &s.Name, &s.Spec, &s.Quantity, + &s.Unit, &s.MinStock, &s.WarehouseID, &s.UpdatedAt, + ); err != nil { + return nil, err + } + stocks = append(stocks, s) + } + return stocks, rows.Err() +} + +func (r *YarnRepo) Create(ctx context.Context, y *model.YarnStock) error { + query := ` + INSERT INTO ilm_yarn_stock (id, company_id, name, spec, quantity, unit, min_stock, warehouse_id, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)` + + _, err := r.db.ExecContext(ctx, query, + y.ID, y.CompanyID, y.Name, y.Spec, y.Quantity, + y.Unit, y.MinStock, y.WarehouseID, y.UpdatedAt, + ) + return err +} + +func (r *YarnRepo) Update(ctx context.Context, y *model.YarnStock) error { + query := ` + UPDATE ilm_yarn_stock + SET name = ?, spec = ?, unit = ?, min_stock = ?, warehouse_id = ?, updated_at = ? + WHERE id = ? AND company_id = ?` + + _, err := r.db.ExecContext(ctx, query, + y.Name, y.Spec, y.Unit, y.MinStock, y.WarehouseID, y.UpdatedAt, + y.ID, y.CompanyID, + ) + return err +} + +func (r *YarnRepo) UpdateQuantity(ctx context.Context, tx *sql.Tx, id string, delta float64) error { + query := ` + UPDATE ilm_yarn_stock + SET quantity = quantity + ?, updated_at = NOW() + WHERE id = ?` + + _, err := tx.ExecContext(ctx, query, delta, id) + return err +} + +func (r *YarnRepo) CreateRecord(ctx context.Context, tx *sql.Tx, rec *model.YarnStockRecord) error { + query := ` + INSERT INTO ilm_yarn_stock_record + (id, yarn_stock_id, company_id, record_type, quantity, unit, batch_no, plan_id, notes, operator_id, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + + _, err := tx.ExecContext(ctx, query, + rec.ID, rec.YarnStockID, rec.CompanyID, rec.RecordType, rec.Quantity, + rec.Unit, rec.BatchNo, rec.PlanID, rec.Notes, rec.OperatorID, rec.CreatedAt, + ) + return err +} + +func (r *YarnRepo) Count(ctx context.Context, companyID string) (int, error) { + var count int + err := r.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM ilm_yarn_stock WHERE company_id = ?`, companyID, + ).Scan(&count) + return count, err +} + +func (r *YarnRepo) CountLowStock(ctx context.Context, companyID string) (int, error) { + var count int + err := r.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM ilm_yarn_stock WHERE company_id = ? AND quantity <= min_stock`, companyID, + ).Scan(&count) + return count, err +} diff --git a/textile-service/internal/service/dashboard_service.go b/textile-service/internal/service/dashboard_service.go new file mode 100644 index 0000000..0477dab --- /dev/null +++ b/textile-service/internal/service/dashboard_service.go @@ -0,0 +1,61 @@ +package service + +import ( + "context" + + "github.com/muyuqingfeng/iloom/textile-service/internal/repository" +) + +type DashboardStats struct { + TotalPlans int `json:"total_plans"` + ActivePlans int `json:"active_plans"` + CompletedPlans int `json:"completed_plans"` + YarnStockCount int `json:"yarn_stock_count"` + LowStockCount int `json:"low_stock_count"` + PendingPayments int `json:"pending_payments"` +} + +type DashboardService struct { + planRepo *repository.PlanRepo + yarnRepo *repository.YarnRepo + paymentRepo *repository.PaymentRepo +} + +func NewDashboardService(planRepo *repository.PlanRepo, yarnRepo *repository.YarnRepo, paymentRepo *repository.PaymentRepo) *DashboardService { + return &DashboardService{ + planRepo: planRepo, + yarnRepo: yarnRepo, + paymentRepo: paymentRepo, + } +} + +func (s *DashboardService) Stats(ctx context.Context, companyID string) (*DashboardStats, error) { + total, active, completed, err := s.planRepo.CountByFactory(ctx, companyID) + if err != nil { + return nil, err + } + + yarnCount, err := s.yarnRepo.Count(ctx, companyID) + if err != nil { + return nil, err + } + + lowStockCount, err := s.yarnRepo.CountLowStock(ctx, companyID) + if err != nil { + return nil, err + } + + pendingPayments, err := s.paymentRepo.CountPending(ctx, companyID) + if err != nil { + return nil, err + } + + return &DashboardStats{ + TotalPlans: total, + ActivePlans: active, + CompletedPlans: completed, + YarnStockCount: yarnCount, + LowStockCount: lowStockCount, + PendingPayments: pendingPayments, + }, nil +} diff --git a/textile-service/internal/service/inventory_service.go b/textile-service/internal/service/inventory_service.go new file mode 100644 index 0000000..49b9800 --- /dev/null +++ b/textile-service/internal/service/inventory_service.go @@ -0,0 +1,77 @@ +package service + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/muyuqingfeng/iloom/textile-service/internal/model" + "github.com/muyuqingfeng/iloom/textile-service/internal/repository" +) + +type InboundRequest struct { + WarehouseID string `json:"warehouse_id"` + Quantity float64 `json:"quantity"` + Rolls int `json:"rolls"` + PricePerMeter float64 `json:"price_per_meter"` + PriceNote string `json:"price_note"` + WarehouseLocation string `json:"warehouse_location"` +} + +type InventoryService struct { + repo *repository.InventoryRepo +} + +func NewInventoryService(repo *repository.InventoryRepo) *InventoryService { + return &InventoryService{repo: repo} +} + +func (s *InventoryService) Inbound(ctx context.Context, planID, companyID, userID string, req InboundRequest) (*model.InventoryRecord, error) { + var rolls *int + if req.Rolls > 0 { + rolls = &req.Rolls + } + var pricePerMeter *float64 + if req.PricePerMeter > 0 { + pricePerMeter = &req.PricePerMeter + } + var priceNote *string + if req.PriceNote != "" { + priceNote = &req.PriceNote + } + var warehouseLocation *string + if req.WarehouseLocation != "" { + warehouseLocation = &req.WarehouseLocation + } + var operatorID *string + if userID != "" { + operatorID = &userID + } + var companyIDPtr *string + if companyID != "" { + companyIDPtr = &companyID + } + + rec := &model.InventoryRecord{ + ID: uuid.New().String(), + PlanID: planID, + WarehouseID: req.WarehouseID, + Quantity: req.Quantity, + Rolls: rolls, + PricePerMeter: pricePerMeter, + PriceNote: priceNote, + WarehouseLocation: warehouseLocation, + OperatorID: operatorID, + CompanyID: companyIDPtr, + CreatedAt: time.Now(), + } + + if err := s.repo.Create(ctx, rec); err != nil { + return nil, err + } + return rec, nil +} + +func (s *InventoryService) ListByPlan(ctx context.Context, planID string) ([]model.InventoryRecord, error) { + return s.repo.ListByPlan(ctx, planID) +} diff --git a/textile-service/internal/service/payment_service.go b/textile-service/internal/service/payment_service.go new file mode 100644 index 0000000..2c3bfb9 --- /dev/null +++ b/textile-service/internal/service/payment_service.go @@ -0,0 +1,24 @@ +package service + +import ( + "context" + + "github.com/muyuqingfeng/iloom/textile-service/internal/model" + "github.com/muyuqingfeng/iloom/textile-service/internal/repository" +) + +type PaymentService struct { + repo *repository.PaymentRepo +} + +func NewPaymentService(repo *repository.PaymentRepo) *PaymentService { + return &PaymentService{repo: repo} +} + +func (s *PaymentService) List(ctx context.Context, companyID string) ([]model.Payment, error) { + return s.repo.ListByCompany(ctx, companyID) +} + +func (s *PaymentService) Confirm(ctx context.Context, paymentID, companyID string) error { + return s.repo.Confirm(ctx, paymentID) +} diff --git a/textile-service/internal/service/plan_service.go b/textile-service/internal/service/plan_service.go new file mode 100644 index 0000000..b08e38f --- /dev/null +++ b/textile-service/internal/service/plan_service.go @@ -0,0 +1,67 @@ +package service + +import ( + "context" + + "github.com/muyuqingfeng/iloom/textile-service/internal/model" + "github.com/muyuqingfeng/iloom/textile-service/internal/repository" +) + +type PlanWithSteps struct { + repository.PlanSummary + Steps []model.ProcessStep `json:"steps,omitempty"` + InventoryRecords []model.InventoryRecord `json:"inventory_records,omitempty"` +} + +type PlanService struct { + repo *repository.PlanRepo + stepRepo *repository.StepRepo + invRepo *repository.InventoryRepo +} + +func NewPlanService(repo *repository.PlanRepo, stepRepo *repository.StepRepo, invRepo *repository.InventoryRepo) *PlanService { + return &PlanService{repo: repo, stepRepo: stepRepo, invRepo: invRepo} +} + +func (s *PlanService) List(ctx context.Context, companyID string) ([]PlanWithSteps, error) { + plans, err := s.repo.ListByFactory(ctx, companyID) + if err != nil { + return nil, err + } + + result := make([]PlanWithSteps, 0, len(plans)) + for _, p := range plans { + steps, err := s.stepRepo.GetByPlan(ctx, p.ID) + if err != nil { + return nil, err + } + result = append(result, PlanWithSteps{ + PlanSummary: p, + Steps: steps, + }) + } + return result, nil +} + +func (s *PlanService) GetByID(ctx context.Context, planID, companyID string) (*PlanWithSteps, error) { + plan, err := s.repo.GetByID(ctx, planID) + if err != nil { + return nil, err + } + + steps, err := s.stepRepo.GetByPlan(ctx, planID) + if err != nil { + return nil, err + } + + records, err := s.invRepo.ListByPlan(ctx, planID) + if err != nil { + return nil, err + } + + return &PlanWithSteps{ + PlanSummary: *plan, + Steps: steps, + InventoryRecords: records, + }, nil +} diff --git a/textile-service/internal/service/process_engine.go b/textile-service/internal/service/process_engine.go new file mode 100644 index 0000000..1413080 --- /dev/null +++ b/textile-service/internal/service/process_engine.go @@ -0,0 +1,117 @@ +package service + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/muyuqingfeng/iloom/textile-service/internal/model" + "github.com/muyuqingfeng/iloom/textile-service/internal/repository" +) + +type ProcessService struct { + stepRepo *repository.StepRepo + inventoryRepo *repository.InventoryRepo +} + +func NewProcessService(stepRepo *repository.StepRepo, inventoryRepo *repository.InventoryRepo) *ProcessService { + return &ProcessService{ + stepRepo: stepRepo, + inventoryRepo: inventoryRepo, + } +} + +func (s *ProcessService) CompleteStep(ctx context.Context, planID, stepID, companyID, userID, notes string) error { + step, err := s.stepRepo.GetByID(ctx, stepID) + if err != nil { + return fmt.Errorf("step not found: %w", err) + } + + if step.PlanID != planID { + return errors.New("step does not belong to this plan") + } + + if step.Status != "pending" && step.Status != "active" { + return fmt.Errorf("step status is %s, cannot complete", step.Status) + } + + // Validate prerequisite steps are completed + allSteps, err := s.stepRepo.GetByPlan(ctx, planID) + if err != nil { + return fmt.Errorf("failed to get plan steps: %w", err) + } + + if err := s.validatePrerequisites(step.StepType, allSteps); err != nil { + return err + } + + var notePtr *string + if notes != "" { + notePtr = ¬es + } + + if err := s.stepRepo.UpdateStatus(ctx, stepID, "completed", userID, notePtr); err != nil { + return fmt.Errorf("failed to update step: %w", err) + } + + // Check if this is the last step (fabric_warehouse) + if step.StepType == "fabric_warehouse" { + // All steps completed - plan production is done at textile side + // Purchaser service can be notified via event/HTTP if needed + _ = companyID // available for future cross-service notification + } + + return nil +} + +func (s *ProcessService) RejectStep(ctx context.Context, planID, stepID, companyID, userID, reason string) error { + step, err := s.stepRepo.GetByID(ctx, stepID) + if err != nil { + return fmt.Errorf("step not found: %w", err) + } + + if step.PlanID != planID { + return errors.New("step does not belong to this plan") + } + + if step.Status != "pending" && step.Status != "active" { + return fmt.Errorf("step status is %s, cannot reject", step.Status) + } + + return s.stepRepo.UpdateStatus(ctx, stepID, "rejected", userID, &reason) +} + +func (s *ProcessService) validatePrerequisites(currentType string, allSteps []model.ProcessStep) error { + currentIdx := -1 + for i, st := range model.StepOrder { + if st == currentType { + currentIdx = i + break + } + } + if currentIdx < 0 { + return fmt.Errorf("unknown step type: %s", currentType) + } + + // Build a map of step type -> status + statusMap := make(map[string]string, len(allSteps)) + for _, step := range allSteps { + statusMap[step.StepType] = step.Status + } + + // All preceding steps must be completed + for i := 0; i < currentIdx; i++ { + prevType := model.StepOrder[i] + if statusMap[prevType] != "completed" { + return fmt.Errorf("prerequisite step %s is not completed (status: %s)", prevType, statusMap[prevType]) + } + } + + return nil +} + +// StepTimestamp returns the completion timestamp for a step, if available. +func StepTimestamp(step *model.ProcessStep) *time.Time { + return step.Timestamp +} diff --git a/textile-service/internal/service/yarn_service.go b/textile-service/internal/service/yarn_service.go new file mode 100644 index 0000000..23bff30 --- /dev/null +++ b/textile-service/internal/service/yarn_service.go @@ -0,0 +1,142 @@ +package service + +import ( + "context" + "database/sql" + "time" + + "github.com/google/uuid" + "github.com/muyuqingfeng/iloom/shared/pkg/database" + "github.com/muyuqingfeng/iloom/textile-service/internal/model" + "github.com/muyuqingfeng/iloom/textile-service/internal/repository" +) + +type CreateYarnStockRequest struct { + Name string `json:"name"` + Spec string `json:"spec"` + Quantity float64 `json:"quantity"` + Unit string `json:"unit"` + MinStock float64 `json:"min_stock"` +} + +type UpdateYarnStockRequest struct { + Name string `json:"name"` + Spec string `json:"spec"` + Unit string `json:"unit"` + MinStock float64 `json:"min_stock"` +} + +type YarnRecordRequest struct { + RecordType string `json:"record_type"` + Quantity float64 `json:"quantity"` + Unit string `json:"unit"` + BatchNo string `json:"batch_no"` + PlanID string `json:"plan_id"` + Notes string `json:"notes"` +} + +type YarnService struct { + repo *repository.YarnRepo + db *sql.DB +} + +func NewYarnService(repo *repository.YarnRepo, db *sql.DB) *YarnService { + return &YarnService{repo: repo, db: db} +} + +func (s *YarnService) List(ctx context.Context, companyID string) ([]model.YarnStock, error) { + return s.repo.List(ctx, companyID) +} + +func (s *YarnService) Create(ctx context.Context, companyID string, req CreateYarnStockRequest) (*model.YarnStock, error) { + var spec *string + if req.Spec != "" { + spec = &req.Spec + } + + unit := req.Unit + if unit == "" { + unit = "kg" + } + + stock := &model.YarnStock{ + ID: uuid.New().String(), + CompanyID: companyID, + Name: req.Name, + Spec: spec, + Quantity: req.Quantity, + Unit: unit, + MinStock: req.MinStock, + UpdatedAt: time.Now(), + } + + if err := s.repo.Create(ctx, stock); err != nil { + return nil, err + } + return stock, nil +} + +func (s *YarnService) Update(ctx context.Context, id, companyID string, req UpdateYarnStockRequest) error { + var spec *string + if req.Spec != "" { + spec = &req.Spec + } + + stock := &model.YarnStock{ + ID: id, + CompanyID: companyID, + Name: req.Name, + Spec: spec, + Unit: req.Unit, + MinStock: req.MinStock, + UpdatedAt: time.Now(), + } + return s.repo.Update(ctx, stock) +} + +func (s *YarnService) AddRecord(ctx context.Context, yarnStockID, companyID, userID string, req YarnRecordRequest) error { + return database.WithTx(ctx, s.db, func(tx *sql.Tx) error { + var batchNo, planID, notes, operatorID *string + if req.BatchNo != "" { + batchNo = &req.BatchNo + } + if req.PlanID != "" { + planID = &req.PlanID + } + if req.Notes != "" { + notes = &req.Notes + } + if userID != "" { + operatorID = &userID + } + + unit := req.Unit + if unit == "" { + unit = "kg" + } + + rec := &model.YarnStockRecord{ + ID: uuid.New().String(), + YarnStockID: &yarnStockID, + CompanyID: companyID, + RecordType: req.RecordType, + Quantity: req.Quantity, + Unit: unit, + BatchNo: batchNo, + PlanID: planID, + Notes: notes, + OperatorID: operatorID, + CreatedAt: time.Now(), + } + + if err := s.repo.CreateRecord(ctx, tx, rec); err != nil { + return err + } + + delta := req.Quantity + if req.RecordType == "out" { + delta = -delta + } + return s.repo.UpdateQuantity(ctx, tx, yarnStockID, delta) + }) +} diff --git a/washing-service/Dockerfile b/washing-service/Dockerfile new file mode 100644 index 0000000..bb3525c --- /dev/null +++ b/washing-service/Dockerfile @@ -0,0 +1,12 @@ +FROM golang:1.24-alpine AS builder +WORKDIR /app +COPY shared/ ./shared/ +COPY washing-service/ ./washing-service/ +WORKDIR /app/washing-service +RUN go build -o /washing-service ./cmd/ + +FROM alpine:3.20 +RUN apk add --no-cache wget +COPY --from=builder /washing-service /washing-service +EXPOSE 8084 +CMD ["/washing-service"] diff --git a/washing-service/cmd/main.go b/washing-service/cmd/main.go new file mode 100644 index 0000000..0cffaa7 --- /dev/null +++ b/washing-service/cmd/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "log" + "os" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/database" + "github.com/muyuqingfeng/iloom/shared/pkg/middleware" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + ws "github.com/muyuqingfeng/iloom/shared/pkg/websocket" + "github.com/muyuqingfeng/iloom/washing-service/internal/config" + "github.com/muyuqingfeng/iloom/washing-service/internal/handler" + "github.com/muyuqingfeng/iloom/washing-service/internal/repository" + "github.com/muyuqingfeng/iloom/washing-service/internal/service" +) + +func main() { + cfg := config.Load() + + db, err := database.NewDBFromDSN(cfg.DBDSN) + if err != nil { + log.Fatalf("failed to connect database: %v", err) + } + defer db.Close() + + wsHub := ws.NewHub() + go wsHub.Run() + + planRepo := repository.NewPlanRepo(db) + completionRepo := repository.NewCompletionRepo(db) + fpRepo := repository.NewFinishedProductRepo(db) + paymentRepo := repository.NewPaymentRepo(db) + + planSvc := service.NewPlanService(planRepo) + completionSvc := service.NewCompletionService(completionRepo, planRepo, fpRepo, db) + fpSvc := service.NewFinishedProductService(fpRepo, db) + paymentSvc := service.NewPaymentService(paymentRepo) + dashboardSvc := service.NewDashboardService(planRepo, fpRepo, paymentRepo) + + planHandler := handler.NewPlanHandler(planSvc) + completionHandler := handler.NewCompletionHandler(completionSvc, wsHub) + fpHandler := handler.NewFinishedProductHandler(fpSvc, wsHub) + paymentHandler := handler.NewPaymentHandler(paymentSvc) + dashboardHandler := handler.NewDashboardHandler(dashboardSvc) + + if os.Getenv("GIN_MODE") == "" { + gin.SetMode(gin.ReleaseMode) + } + r := gin.New() + r.Use(middleware.Recovery(), middleware.Logger(), middleware.RequestID()) + + r.GET("/health", func(c *gin.Context) { + response.OK(c, gin.H{"status": "ok", "service": "washing-service"}) + }) + + api := r.Group("/api/v1/washing") + { + api.GET("/dashboard/stats", dashboardHandler.Stats) + api.GET("/plans", planHandler.List) + api.GET("/plans/pending", planHandler.Pending) + api.GET("/plans/completed", planHandler.Completed) + api.GET("/plans/:id", planHandler.Get) + api.PATCH("/plans/:id/status", planHandler.UpdateStatus) + api.POST("/plans/:id/complete", completionHandler.Complete) + api.GET("/finished-products", fpHandler.List) + api.POST("/finished-products", fpHandler.Create) + api.POST("/finished-products/:id/outbound", fpHandler.Outbound) + api.GET("/payments", paymentHandler.List) + api.POST("/payments/:id/confirm", paymentHandler.Confirm) + } + + r.GET("/ws/v1/washing", handler.HandleWebSocket(wsHub)) + + port := cfg.Port + if port == "" { + port = "8084" + } + log.Printf("washing-service listening on :%s", port) + if err := r.Run(":" + port); err != nil { + log.Fatalf("failed to start server: %v", err) + } +} diff --git a/washing-service/go.mod b/washing-service/go.mod new file mode 100644 index 0000000..9ef9a0a --- /dev/null +++ b/washing-service/go.mod @@ -0,0 +1,45 @@ +module github.com/muyuqingfeng/iloom/washing-service + +go 1.23 + +toolchain go1.24.4 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.3 + github.com/muyuqingfeng/iloom/shared v0.0.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + 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.2.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/muyuqingfeng/iloom/shared => ../shared diff --git a/washing-service/go.sum b/washing-service/go.sum new file mode 100644 index 0000000..64bfbd0 --- /dev/null +++ b/washing-service/go.sum @@ -0,0 +1,97 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +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= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +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= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +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/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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/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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/washing-service/internal/config/config.go b/washing-service/internal/config/config.go new file mode 100644 index 0000000..50bf3ab --- /dev/null +++ b/washing-service/internal/config/config.go @@ -0,0 +1,26 @@ +package config + +import "os" + +type Config struct { + Port string + DBDSN string + InternalServiceKey string + PurchaserServiceURL string +} + +func Load() *Config { + return &Config{ + Port: getEnv("PORT", "8084"), + DBDSN: getEnv("DB_DSN", "root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?parseTime=true&charset=utf8mb4"), + InternalServiceKey: getEnv("INTERNAL_SERVICE_KEY", "dev-internal-key"), + PurchaserServiceURL: getEnv("PURCHASER_SERVICE_URL", "http://localhost:8082"), + } +} + +func getEnv(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} diff --git a/washing-service/internal/handler/completion.go b/washing-service/internal/handler/completion.go new file mode 100644 index 0000000..44440b7 --- /dev/null +++ b/washing-service/internal/handler/completion.go @@ -0,0 +1,55 @@ +package handler + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + "github.com/muyuqingfeng/iloom/shared/pkg/websocket" + "github.com/muyuqingfeng/iloom/washing-service/internal/service" +) + +type CompletionHandler struct { + svc *service.CompletionService + wsHub *websocket.Hub +} + +func NewCompletionHandler(svc *service.CompletionService, wsHub *websocket.Hub) *CompletionHandler { + return &CompletionHandler{svc: svc, wsHub: wsHub} +} + +func (h *CompletionHandler) Complete(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + washingPlanID := c.Param("id") + + var req service.CompleteRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + completion, err := h.svc.Complete(c.Request.Context(), washingPlanID, companyID, userID, req) + if err != nil { + log.Printf("complete washing plan error: %v", err) + response.InternalError(c, "failed to complete washing plan") + return + } + + h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ + Table: "washing_plan_completions", + Action: "INSERT", + }) + + c.JSON(http.StatusCreated, response.Response{ + Code: 0, + Message: "success", + Data: completion, + }) +} diff --git a/washing-service/internal/handler/dashboard.go b/washing-service/internal/handler/dashboard.go new file mode 100644 index 0000000..fbc4596 --- /dev/null +++ b/washing-service/internal/handler/dashboard.go @@ -0,0 +1,34 @@ +package handler + +import ( + "log" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + "github.com/muyuqingfeng/iloom/washing-service/internal/service" +) + +type DashboardHandler struct { + svc *service.DashboardService +} + +func NewDashboardHandler(svc *service.DashboardService) *DashboardHandler { + return &DashboardHandler{svc: svc} +} + +func (h *DashboardHandler) Stats(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + stats, err := h.svc.Stats(c.Request.Context(), companyID) + if err != nil { + log.Printf("get dashboard stats error: %v", err) + response.InternalError(c, "failed to get stats") + return + } + + response.OK(c, stats) +} diff --git a/washing-service/internal/handler/finished_product.go b/washing-service/internal/handler/finished_product.go new file mode 100644 index 0000000..311cc31 --- /dev/null +++ b/washing-service/internal/handler/finished_product.go @@ -0,0 +1,106 @@ +package handler + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + "github.com/muyuqingfeng/iloom/shared/pkg/websocket" + "github.com/muyuqingfeng/iloom/washing-service/internal/model" + "github.com/muyuqingfeng/iloom/washing-service/internal/service" +) + +type FinishedProductHandler struct { + svc *service.FinishedProductService + wsHub *websocket.Hub +} + +func NewFinishedProductHandler(svc *service.FinishedProductService, wsHub *websocket.Hub) *FinishedProductHandler { + return &FinishedProductHandler{svc: svc, wsHub: wsHub} +} + +func (h *FinishedProductHandler) List(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + products, err := h.svc.List(c.Request.Context(), companyID) + if err != nil { + log.Printf("list finished products error: %v", err) + response.InternalError(c, "failed to list finished products") + return + } + + response.OK(c, products) +} + +func (h *FinishedProductHandler) Create(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + var fp model.FinishedProduct + if err := c.ShouldBindJSON(&fp); err != nil { + response.BadRequest(c, err.Error()) + return + } + + if err := h.svc.Create(c.Request.Context(), companyID, userID, &fp); err != nil { + log.Printf("create finished product error: %v", err) + response.InternalError(c, "failed to create finished product") + return + } + + h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ + Table: "finished_products", + Action: "INSERT", + }) + + c.JSON(http.StatusCreated, response.Response{ + Code: 0, + Message: "success", + Data: fp, + }) +} + +type OutboundReq struct { + Rolls int `json:"rolls" binding:"required,gt=0"` + Meters float64 `json:"meters" binding:"required,gt=0"` + Notes string `json:"notes"` +} + +func (h *FinishedProductHandler) Outbound(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + userID := c.GetHeader("X-User-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + fpID := c.Param("id") + + var req OutboundReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + if err := h.svc.Outbound(c.Request.Context(), fpID, companyID, userID, req.Rolls, req.Meters, req.Notes); err != nil { + log.Printf("outbound finished product error: %v", err) + response.InternalError(c, "failed to outbound finished product") + return + } + + h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ + Table: "finished_products", + Action: "UPDATE", + }) + + response.OK(c, nil) +} diff --git a/washing-service/internal/handler/payment.go b/washing-service/internal/handler/payment.go new file mode 100644 index 0000000..661d795 --- /dev/null +++ b/washing-service/internal/handler/payment.go @@ -0,0 +1,52 @@ +package handler + +import ( + "log" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + "github.com/muyuqingfeng/iloom/washing-service/internal/service" +) + +type PaymentHandler struct { + svc *service.PaymentService +} + +func NewPaymentHandler(svc *service.PaymentService) *PaymentHandler { + return &PaymentHandler{svc: svc} +} + +func (h *PaymentHandler) List(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + payments, err := h.svc.List(c.Request.Context(), companyID) + if err != nil { + log.Printf("list payments error: %v", err) + response.InternalError(c, "failed to list payments") + return + } + + response.OK(c, payments) +} + +func (h *PaymentHandler) Confirm(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + paymentID := c.Param("id") + + if err := h.svc.Confirm(c.Request.Context(), paymentID, companyID); err != nil { + log.Printf("confirm payment error: %v", err) + response.InternalError(c, "failed to confirm payment") + return + } + + response.OK(c, nil) +} diff --git a/washing-service/internal/handler/plan.go b/washing-service/internal/handler/plan.go new file mode 100644 index 0000000..bfe6b15 --- /dev/null +++ b/washing-service/internal/handler/plan.go @@ -0,0 +1,114 @@ +package handler + +import ( + "log" + + "github.com/gin-gonic/gin" + "github.com/muyuqingfeng/iloom/shared/pkg/response" + "github.com/muyuqingfeng/iloom/washing-service/internal/service" +) + +type PlanHandler struct { + svc *service.PlanService +} + +func NewPlanHandler(svc *service.PlanService) *PlanHandler { + return &PlanHandler{svc: svc} +} + +func (h *PlanHandler) List(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + plans, err := h.svc.List(c.Request.Context(), companyID) + if err != nil { + log.Printf("list washing plans error: %v", err) + response.InternalError(c, "failed to list washing plans") + return + } + + response.OK(c, plans) +} + +func (h *PlanHandler) Get(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + id := c.Param("id") + plan, err := h.svc.GetByID(c.Request.Context(), id, companyID) + if err != nil { + log.Printf("get washing plan error: %v", err) + response.NotFound(c, "washing plan not found") + return + } + + response.OK(c, plan) +} + +type UpdateStatusReq struct { + Status string `json:"status" binding:"required"` +} + +func (h *PlanHandler) UpdateStatus(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + id := c.Param("id") + + var req UpdateStatusReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, err.Error()) + return + } + + if err := h.svc.UpdateStatus(c.Request.Context(), id, companyID, req.Status); err != nil { + log.Printf("update washing plan status error: %v", err) + response.InternalError(c, "failed to update status") + return + } + + response.OK(c, nil) +} + +func (h *PlanHandler) Pending(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + plans, err := h.svc.Pending(c.Request.Context(), companyID) + if err != nil { + log.Printf("list pending plans error: %v", err) + response.InternalError(c, "failed to list pending plans") + return + } + + response.OK(c, plans) +} + +func (h *PlanHandler) Completed(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + response.BadRequest(c, "missing company id") + return + } + + plans, err := h.svc.Completed(c.Request.Context(), companyID) + if err != nil { + log.Printf("list completed plans error: %v", err) + response.InternalError(c, "failed to list completed plans") + return + } + + response.OK(c, plans) +} diff --git a/washing-service/internal/handler/ws.go b/washing-service/internal/handler/ws.go new file mode 100644 index 0000000..03a3b25 --- /dev/null +++ b/washing-service/internal/handler/ws.go @@ -0,0 +1,48 @@ +package handler + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + gorillaWS "github.com/gorilla/websocket" + "github.com/muyuqingfeng/iloom/shared/pkg/websocket" +) + +var upgrader = gorillaWS.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func HandleWebSocket(hub *websocket.Hub) gin.HandlerFunc { + return func(c *gin.Context) { + companyID := c.GetHeader("X-Company-ID") + if companyID == "" { + companyID = c.Query("company_id") + } + userID := c.GetHeader("X-User-ID") + if userID == "" { + userID = c.Query("user_id") + } + + if companyID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "missing company_id"}) + return + } + + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Printf("websocket upgrade error: %v", err) + return + } + + client := websocket.NewClient(hub, conn, companyID, userID) + client.Register() + + go client.WritePump() + go client.ReadPump() + } +} diff --git a/washing-service/internal/model/completion.go b/washing-service/internal/model/completion.go new file mode 100644 index 0000000..522d12e --- /dev/null +++ b/washing-service/internal/model/completion.go @@ -0,0 +1,36 @@ +package model + +import "time" + +type WashingPlanCompletion struct { + ID string `json:"id" db:"id"` + WashingPlanID string `json:"washing_plan_id" db:"washing_plan_id"` + CompanyID string `json:"company_id" db:"company_id"` + ActualShrinkageRate float64 `json:"actual_shrinkage_rate" db:"actual_shrinkage_rate"` + ActualWashedMeters float64 `json:"actual_washed_meters" db:"actual_washed_meters"` + ActualWashingCost *float64 `json:"actual_washing_cost,omitempty" db:"actual_washing_cost"` + UnitCost *float64 `json:"unit_cost,omitempty" db:"unit_cost"` + Rolls int `json:"rolls" db:"rolls"` + WashingDate time.Time `json:"washing_date" db:"washing_date"` + Notes *string `json:"notes,omitempty" db:"notes"` + CompletedBy *string `json:"completed_by,omitempty" db:"completed_by"` + CompletedAt time.Time `json:"completed_at" db:"completed_at"` + FinishedProductID *string `json:"finished_product_id,omitempty" db:"finished_product_id"` + AutoImported bool `json:"auto_imported" db:"auto_imported"` + WarehouseID *string `json:"warehouse_id,omitempty" db:"warehouse_id"` + WarehouseLocation *string `json:"warehouse_location,omitempty" db:"warehouse_location"` + CreatedAt time.Time `json:"created_at" db:"created_at"` +} + +type WashingProcessStep struct { + ID string `json:"id" db:"id"` + WashingPlanID string `json:"washing_plan_id" db:"washing_plan_id"` + CompanyID string `json:"company_id" db:"company_id"` + StepType string `json:"step_type" db:"step_type"` + Status string `json:"status" db:"status"` + Notes *string `json:"notes,omitempty" db:"notes"` + OperatorID *string `json:"operator_id,omitempty" db:"operator_id"` + Timestamp *time.Time `json:"timestamp,omitempty" db:"timestamp"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} diff --git a/washing-service/internal/model/finished_product.go b/washing-service/internal/model/finished_product.go new file mode 100644 index 0000000..ce1851d --- /dev/null +++ b/washing-service/internal/model/finished_product.go @@ -0,0 +1,47 @@ +package model + +import "time" + +type FinishedProduct struct { + ID string `json:"id" db:"id"` + CompanyID string `json:"company_id" db:"company_id"` + ProductName string `json:"product_name" db:"product_name"` + FabricCode string `json:"fabric_code" db:"fabric_code"` + Color string `json:"color" db:"color"` + ColorCode *string `json:"color_code,omitempty" db:"color_code"` + Weight *float64 `json:"weight,omitempty" db:"weight"` + ShrinkageRate float64 `json:"shrinkage_rate" db:"shrinkage_rate"` + WashedMeters float64 `json:"washed_meters" db:"washed_meters"` + StockMeters float64 `json:"stock_meters" db:"stock_meters"` + StockRolls int `json:"stock_rolls" db:"stock_rolls"` + Rolls *int `json:"rolls,omitempty" db:"rolls"` + WashingCost *float64 `json:"washing_cost,omitempty" db:"washing_cost"` + UnitCost *float64 `json:"unit_cost,omitempty" db:"unit_cost"` + Status string `json:"status" db:"status"` + Notes *string `json:"notes,omitempty" db:"notes"` + ProductID *string `json:"product_id,omitempty" db:"product_id"` + WashingPlanID *string `json:"washing_plan_id,omitempty" db:"washing_plan_id"` + WarehouseID *string `json:"warehouse_id,omitempty" db:"warehouse_id"` + WarehouseLocation *string `json:"warehouse_location,omitempty" db:"warehouse_location"` + CreatedBy *string `json:"created_by,omitempty" db:"created_by"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +type FinishedProductInventoryRecord struct { + ID string `json:"id" db:"id"` + FinishedProductID string `json:"finished_product_id" db:"finished_product_id"` + CompanyID string `json:"company_id" db:"company_id"` + RecordType string `json:"record_type" db:"record_type"` + Rolls int `json:"rolls" db:"rolls"` + Meters float64 `json:"meters" db:"meters"` + BatchNo *string `json:"batch_no,omitempty" db:"batch_no"` + Notes *string `json:"notes,omitempty" db:"notes"` + OperatorID *string `json:"operator_id,omitempty" db:"operator_id"` + WarehouseID *string `json:"warehouse_id,omitempty" db:"warehouse_id"` + WarehouseLocation *string `json:"warehouse_location,omitempty" db:"warehouse_location"` + WashingCompletionID *string `json:"washing_completion_id,omitempty" db:"washing_completion_id"` + RelatedOrderID *string `json:"related_order_id,omitempty" db:"related_order_id"` + RelatedOrderType *string `json:"related_order_type,omitempty" db:"related_order_type"` + CreatedAt time.Time `json:"created_at" db:"created_at"` +} diff --git a/washing-service/internal/model/washing_plan.go b/washing-service/internal/model/washing_plan.go new file mode 100644 index 0000000..58b2f3b --- /dev/null +++ b/washing-service/internal/model/washing_plan.go @@ -0,0 +1,28 @@ +package model + +import "time" + +type WashingPlan struct { + ID string `json:"id" db:"id"` + PlanCode string `json:"plan_code" db:"plan_code"` + CompanyID string `json:"company_id" db:"company_id"` + ProductID string `json:"product_id" db:"product_id"` + WashingFactoryID *string `json:"washing_factory_id,omitempty" db:"washing_factory_id"` + PlannedMeters float64 `json:"planned_meters" db:"planned_meters"` + WashingPrice float64 `json:"washing_price" db:"washing_price"` + EstimatedShrinkageRate float64 `json:"estimated_shrinkage_rate" db:"estimated_shrinkage_rate"` + EstimatedWashedMeters *float64 `json:"estimated_washed_meters,omitempty" db:"estimated_washed_meters"` + EstimatedTotalCost *float64 `json:"estimated_total_cost,omitempty" db:"estimated_total_cost"` + ActualShrinkageRate *float64 `json:"actual_shrinkage_rate,omitempty" db:"actual_shrinkage_rate"` + ActualWashedMeters *float64 `json:"actual_washed_meters,omitempty" db:"actual_washed_meters"` + ActualTotalCost *float64 `json:"actual_total_cost,omitempty" db:"actual_total_cost"` + WashingDate *time.Time `json:"washing_date,omitempty" db:"washing_date"` + Status string `json:"status" db:"status"` + Notes *string `json:"notes,omitempty" db:"notes"` + CreatedBy *string `json:"created_by,omitempty" db:"created_by"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` + // Aggregated fields + ProductName string `json:"product_name,omitempty"` + PurchaserName string `json:"purchaser_name,omitempty"` +} diff --git a/washing-service/internal/repository/completion_repo.go b/washing-service/internal/repository/completion_repo.go new file mode 100644 index 0000000..6c03979 --- /dev/null +++ b/washing-service/internal/repository/completion_repo.go @@ -0,0 +1,35 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + + "github.com/muyuqingfeng/iloom/washing-service/internal/model" +) + +type CompletionRepo struct { + db *sql.DB +} + +func NewCompletionRepo(db *sql.DB) *CompletionRepo { + return &CompletionRepo{db: db} +} + +func (r *CompletionRepo) Create(ctx context.Context, tx *sql.Tx, c *model.WashingPlanCompletion) error { + query := `INSERT INTO ilm_washing_completion + (id, washing_plan_id, company_id, actual_shrinkage_rate, actual_washed_meters, + actual_washing_cost, unit_cost, rolls, washing_date, notes, completed_by, + completed_at, finished_product_id, auto_imported, warehouse_id, warehouse_location, created_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)` + _, err := tx.ExecContext(ctx, query, + c.ID, c.WashingPlanID, c.CompanyID, c.ActualShrinkageRate, c.ActualWashedMeters, + c.ActualWashingCost, c.UnitCost, c.Rolls, c.WashingDate, c.Notes, c.CompletedBy, + c.CompletedAt, c.FinishedProductID, c.AutoImported, c.WarehouseID, c.WarehouseLocation, + c.CreatedAt, + ) + if err != nil { + return fmt.Errorf("insert completion: %w", err) + } + return nil +} diff --git a/washing-service/internal/repository/finished_product_repo.go b/washing-service/internal/repository/finished_product_repo.go new file mode 100644 index 0000000..1f5d2f5 --- /dev/null +++ b/washing-service/internal/repository/finished_product_repo.go @@ -0,0 +1,117 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + + "github.com/muyuqingfeng/iloom/washing-service/internal/model" +) + +type FinishedProductRepo struct { + db *sql.DB +} + +func NewFinishedProductRepo(db *sql.DB) *FinishedProductRepo { + return &FinishedProductRepo{db: db} +} + +func (r *FinishedProductRepo) List(ctx context.Context, companyID string) ([]model.FinishedProduct, error) { + query := `SELECT id, company_id, product_name, fabric_code, color, color_code, + weight, shrinkage_rate, washed_meters, stock_meters, stock_rolls, rolls, + washing_cost, unit_cost, status, notes, product_id, washing_plan_id, + warehouse_id, warehouse_location, created_by, created_at, updated_at + FROM ilm_finished_product + WHERE company_id = ? + ORDER BY created_at DESC` + + rows, err := r.db.QueryContext(ctx, query, companyID) + if err != nil { + return nil, fmt.Errorf("list finished products: %w", err) + } + defer rows.Close() + + var products []model.FinishedProduct + for rows.Next() { + var fp model.FinishedProduct + if err := rows.Scan( + &fp.ID, &fp.CompanyID, &fp.ProductName, &fp.FabricCode, &fp.Color, &fp.ColorCode, + &fp.Weight, &fp.ShrinkageRate, &fp.WashedMeters, &fp.StockMeters, &fp.StockRolls, &fp.Rolls, + &fp.WashingCost, &fp.UnitCost, &fp.Status, &fp.Notes, &fp.ProductID, &fp.WashingPlanID, + &fp.WarehouseID, &fp.WarehouseLocation, &fp.CreatedBy, &fp.CreatedAt, &fp.UpdatedAt, + ); err != nil { + return nil, fmt.Errorf("scan finished product: %w", err) + } + products = append(products, fp) + } + return products, nil +} + +func (r *FinishedProductRepo) Create(ctx context.Context, tx *sql.Tx, fp *model.FinishedProduct) error { + query := `INSERT INTO ilm_finished_product + (id, company_id, product_name, fabric_code, color, color_code, + weight, shrinkage_rate, washed_meters, stock_meters, stock_rolls, rolls, + washing_cost, unit_cost, status, notes, product_id, washing_plan_id, + warehouse_id, warehouse_location, created_by, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)` + _, err := tx.ExecContext(ctx, query, + fp.ID, fp.CompanyID, fp.ProductName, fp.FabricCode, fp.Color, fp.ColorCode, + fp.Weight, fp.ShrinkageRate, fp.WashedMeters, fp.StockMeters, fp.StockRolls, fp.Rolls, + fp.WashingCost, fp.UnitCost, fp.Status, fp.Notes, fp.ProductID, fp.WashingPlanID, + fp.WarehouseID, fp.WarehouseLocation, fp.CreatedBy, fp.CreatedAt, fp.UpdatedAt, + ) + if err != nil { + return fmt.Errorf("insert finished product: %w", err) + } + return nil +} + +func (r *FinishedProductRepo) UpdateStock(ctx context.Context, tx *sql.Tx, id string, deltaMeters float64, deltaRolls int) error { + query := `UPDATE ilm_finished_product + SET stock_meters = stock_meters + ?, stock_rolls = stock_rolls + ?, updated_at = NOW() + WHERE id = ?` + _, err := tx.ExecContext(ctx, query, deltaMeters, deltaRolls, id) + if err != nil { + return fmt.Errorf("update finished product stock: %w", err) + } + return nil +} + +func (r *FinishedProductRepo) CreateInventoryRecord(ctx context.Context, tx *sql.Tx, rec *model.FinishedProductInventoryRecord) error { + query := `INSERT INTO ilm_finished_product_inventory + (id, finished_product_id, company_id, record_type, rolls, meters, + batch_no, notes, operator_id, warehouse_id, warehouse_location, + washing_completion_id, related_order_id, related_order_type, created_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)` + _, err := tx.ExecContext(ctx, query, + rec.ID, rec.FinishedProductID, rec.CompanyID, rec.RecordType, rec.Rolls, rec.Meters, + rec.BatchNo, rec.Notes, rec.OperatorID, rec.WarehouseID, rec.WarehouseLocation, + rec.WashingCompletionID, rec.RelatedOrderID, rec.RelatedOrderType, rec.CreatedAt, + ) + if err != nil { + return fmt.Errorf("insert inventory record: %w", err) + } + return nil +} + +func (r *FinishedProductRepo) Count(ctx context.Context, companyID string) (int, error) { + var count int + err := r.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM ilm_finished_product WHERE company_id = ?`, companyID, + ).Scan(&count) + if err != nil { + return 0, fmt.Errorf("count finished products: %w", err) + } + return count, nil +} + +func (r *FinishedProductRepo) TotalStock(ctx context.Context, companyID string) (float64, error) { + var total float64 + err := r.db.QueryRowContext(ctx, + `SELECT COALESCE(SUM(stock_meters), 0) FROM ilm_finished_product WHERE company_id = ?`, companyID, + ).Scan(&total) + if err != nil { + return 0, fmt.Errorf("total stock: %w", err) + } + return total, nil +} diff --git a/washing-service/internal/repository/payment_repo.go b/washing-service/internal/repository/payment_repo.go new file mode 100644 index 0000000..c2a2587 --- /dev/null +++ b/washing-service/internal/repository/payment_repo.go @@ -0,0 +1,92 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + "time" +) + +type PaymentWithDetails struct { + ID string `json:"id"` + WashingPlanID string `json:"washing_plan_id"` + CompanyID string `json:"company_id"` + Amount float64 `json:"amount"` + Status string `json:"status"` + Notes *string `json:"notes,omitempty"` + PaidAt *time.Time `json:"paid_at,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PlanCode string `json:"plan_code"` + FromCompanyName string `json:"from_company_name"` +} + +type PaymentRepo struct { + db *sql.DB +} + +func NewPaymentRepo(db *sql.DB) *PaymentRepo { + return &PaymentRepo{db: db} +} + +func (r *PaymentRepo) ListByCompany(ctx context.Context, companyID string) ([]PaymentWithDetails, error) { + query := `SELECT wpy.id, wpy.washing_plan_id, wpy.company_id, wpy.amount, + wpy.status, wpy.notes, wpy.paid_at, wpy.created_at, wpy.updated_at, + COALESCE(wp.plan_code, '') AS plan_code, + COALESCE(c.name, '') AS from_company_name + FROM ilm_washing_payment wpy + LEFT JOIN ilm_washing_plan wp ON wpy.washing_plan_id = wp.id + LEFT JOIN ilm_company c ON wp.company_id = c.id + WHERE wpy.company_id = ? + ORDER BY wpy.created_at DESC` + + rows, err := r.db.QueryContext(ctx, query, companyID) + if err != nil { + return nil, fmt.Errorf("list payments: %w", err) + } + defer rows.Close() + + var payments []PaymentWithDetails + for rows.Next() { + var p PaymentWithDetails + if err := rows.Scan( + &p.ID, &p.WashingPlanID, &p.CompanyID, &p.Amount, + &p.Status, &p.Notes, &p.PaidAt, &p.CreatedAt, &p.UpdatedAt, + &p.PlanCode, &p.FromCompanyName, + ); err != nil { + return nil, fmt.Errorf("scan payment: %w", err) + } + payments = append(payments, p) + } + return payments, nil +} + +func (r *PaymentRepo) Confirm(ctx context.Context, paymentID string) error { + query := `UPDATE ilm_washing_payment + SET status = 'confirmed', paid_at = NOW(), updated_at = NOW() + WHERE id = ?` + result, err := r.db.ExecContext(ctx, query, paymentID) + if err != nil { + return fmt.Errorf("confirm payment: %w", err) + } + affected, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("rows affected: %w", err) + } + if affected == 0 { + return fmt.Errorf("payment not found") + } + return nil +} + +func (r *PaymentRepo) CountPending(ctx context.Context, companyID string) (int, error) { + var count int + err := r.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM ilm_washing_payment WHERE company_id = ? AND status = 'pending'`, + companyID, + ).Scan(&count) + if err != nil { + return 0, fmt.Errorf("count pending payments: %w", err) + } + return count, nil +} diff --git a/washing-service/internal/repository/plan_repo.go b/washing-service/internal/repository/plan_repo.go new file mode 100644 index 0000000..e5a45ff --- /dev/null +++ b/washing-service/internal/repository/plan_repo.go @@ -0,0 +1,171 @@ +package repository + +import ( + "context" + "database/sql" + "fmt" + + "github.com/muyuqingfeng/iloom/washing-service/internal/model" +) + +type PlanRepo struct { + db *sql.DB +} + +func NewPlanRepo(db *sql.DB) *PlanRepo { + return &PlanRepo{db: db} +} + +func (r *PlanRepo) List(ctx context.Context, companyID string) ([]model.WashingPlan, error) { + query := `SELECT wp.id, wp.plan_code, wp.company_id, wp.product_id, + wp.washing_factory_id, wp.planned_meters, wp.washing_price, + wp.estimated_shrinkage_rate, wp.estimated_washed_meters, wp.estimated_total_cost, + wp.actual_shrinkage_rate, wp.actual_washed_meters, wp.actual_total_cost, + wp.washing_date, wp.status, wp.notes, wp.created_by, wp.created_at, wp.updated_at, + COALESCE(p.product_name, '') AS product_name + FROM ilm_washing_plan wp + LEFT JOIN ilm_product p ON wp.product_id = p.id + WHERE wp.washing_factory_id = ? + ORDER BY wp.created_at DESC` + + rows, err := r.db.QueryContext(ctx, query, companyID) + if err != nil { + return nil, fmt.Errorf("list washing plans: %w", err) + } + defer rows.Close() + + return scanPlans(rows) +} + +func (r *PlanRepo) GetByID(ctx context.Context, id string) (*model.WashingPlan, error) { + query := `SELECT wp.id, wp.plan_code, wp.company_id, wp.product_id, + wp.washing_factory_id, wp.planned_meters, wp.washing_price, + wp.estimated_shrinkage_rate, wp.estimated_washed_meters, wp.estimated_total_cost, + wp.actual_shrinkage_rate, wp.actual_washed_meters, wp.actual_total_cost, + wp.washing_date, wp.status, wp.notes, wp.created_by, wp.created_at, wp.updated_at, + COALESCE(p.product_name, '') AS product_name + FROM ilm_washing_plan wp + LEFT JOIN ilm_product p ON wp.product_id = p.id + WHERE wp.id = ?` + + plan := &model.WashingPlan{} + err := r.db.QueryRowContext(ctx, query, id).Scan( + &plan.ID, &plan.PlanCode, &plan.CompanyID, &plan.ProductID, + &plan.WashingFactoryID, &plan.PlannedMeters, &plan.WashingPrice, + &plan.EstimatedShrinkageRate, &plan.EstimatedWashedMeters, &plan.EstimatedTotalCost, + &plan.ActualShrinkageRate, &plan.ActualWashedMeters, &plan.ActualTotalCost, + &plan.WashingDate, &plan.Status, &plan.Notes, &plan.CreatedBy, + &plan.CreatedAt, &plan.UpdatedAt, + &plan.ProductName, + ) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, fmt.Errorf("get washing plan by id: %w", err) + } + return plan, nil +} + +func (r *PlanRepo) UpdateStatus(ctx context.Context, id, status string) error { + query := `UPDATE ilm_washing_plan SET status = ?, updated_at = NOW() WHERE id = ?` + result, err := r.db.ExecContext(ctx, query, status, id) + if err != nil { + return fmt.Errorf("update plan status: %w", err) + } + affected, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("rows affected: %w", err) + } + if affected == 0 { + return fmt.Errorf("washing plan not found") + } + return nil +} + +func (r *PlanRepo) UpdateActuals(ctx context.Context, tx *sql.Tx, id string, shrinkage, meters, cost float64) error { + query := `UPDATE ilm_washing_plan + SET actual_shrinkage_rate = ?, actual_washed_meters = ?, actual_total_cost = ?, + status = 'completed', washing_date = NOW(), updated_at = NOW() + WHERE id = ?` + _, err := tx.ExecContext(ctx, query, shrinkage, meters, cost, id) + if err != nil { + return fmt.Errorf("update plan actuals: %w", err) + } + return nil +} + +func (r *PlanRepo) Pending(ctx context.Context, companyID string) ([]model.WashingPlan, error) { + query := `SELECT wp.id, wp.plan_code, wp.company_id, wp.product_id, + wp.washing_factory_id, wp.planned_meters, wp.washing_price, + wp.estimated_shrinkage_rate, wp.estimated_washed_meters, wp.estimated_total_cost, + wp.actual_shrinkage_rate, wp.actual_washed_meters, wp.actual_total_cost, + wp.washing_date, wp.status, wp.notes, wp.created_by, wp.created_at, wp.updated_at, + COALESCE(p.product_name, '') AS product_name + FROM ilm_washing_plan wp + LEFT JOIN ilm_product p ON wp.product_id = p.id + WHERE wp.washing_factory_id = ? AND wp.status IN ('pending', 'washing') + ORDER BY wp.created_at DESC` + + rows, err := r.db.QueryContext(ctx, query, companyID) + if err != nil { + return nil, fmt.Errorf("list pending plans: %w", err) + } + defer rows.Close() + + return scanPlans(rows) +} + +func (r *PlanRepo) Completed(ctx context.Context, companyID string) ([]model.WashingPlan, error) { + query := `SELECT wp.id, wp.plan_code, wp.company_id, wp.product_id, + wp.washing_factory_id, wp.planned_meters, wp.washing_price, + wp.estimated_shrinkage_rate, wp.estimated_washed_meters, wp.estimated_total_cost, + wp.actual_shrinkage_rate, wp.actual_washed_meters, wp.actual_total_cost, + wp.washing_date, wp.status, wp.notes, wp.created_by, wp.created_at, wp.updated_at, + COALESCE(p.product_name, '') AS product_name + FROM ilm_washing_plan wp + LEFT JOIN ilm_product p ON wp.product_id = p.id + WHERE wp.washing_factory_id = ? AND wp.status = 'completed' + ORDER BY wp.washing_date DESC` + + rows, err := r.db.QueryContext(ctx, query, companyID) + if err != nil { + return nil, fmt.Errorf("list completed plans: %w", err) + } + defer rows.Close() + + return scanPlans(rows) +} + +func (r *PlanRepo) CountByStatus(ctx context.Context, companyID string) (total, pending, completed int, err error) { + query := `SELECT + COUNT(*), + SUM(CASE WHEN status IN ('pending', 'washing') THEN 1 ELSE 0 END), + SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) + FROM ilm_washing_plan WHERE washing_factory_id = ?` + err = r.db.QueryRowContext(ctx, query, companyID).Scan(&total, &pending, &completed) + if err != nil { + return 0, 0, 0, fmt.Errorf("count by status: %w", err) + } + return +} + +func scanPlans(rows *sql.Rows) ([]model.WashingPlan, error) { + var plans []model.WashingPlan + for rows.Next() { + var plan model.WashingPlan + if err := rows.Scan( + &plan.ID, &plan.PlanCode, &plan.CompanyID, &plan.ProductID, + &plan.WashingFactoryID, &plan.PlannedMeters, &plan.WashingPrice, + &plan.EstimatedShrinkageRate, &plan.EstimatedWashedMeters, &plan.EstimatedTotalCost, + &plan.ActualShrinkageRate, &plan.ActualWashedMeters, &plan.ActualTotalCost, + &plan.WashingDate, &plan.Status, &plan.Notes, &plan.CreatedBy, + &plan.CreatedAt, &plan.UpdatedAt, + &plan.ProductName, + ); err != nil { + return nil, fmt.Errorf("scan washing plan: %w", err) + } + plans = append(plans, plan) + } + return plans, nil +} diff --git a/washing-service/internal/service/completion_service.go b/washing-service/internal/service/completion_service.go new file mode 100644 index 0000000..21dfafc --- /dev/null +++ b/washing-service/internal/service/completion_service.go @@ -0,0 +1,175 @@ +package service + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/muyuqingfeng/iloom/shared/pkg/database" + "github.com/muyuqingfeng/iloom/washing-service/internal/model" + "github.com/muyuqingfeng/iloom/washing-service/internal/repository" +) + +type CompleteRequest struct { + ActualShrinkageRate float64 `json:"actual_shrinkage_rate" binding:"required"` + ActualWashedMeters float64 `json:"actual_washed_meters" binding:"required,gt=0"` + Rolls int `json:"rolls" binding:"required,gt=0"` + WashingDate *time.Time `json:"washing_date"` + Notes string `json:"notes"` + WarehouseID string `json:"warehouse_id"` + WarehouseLocation string `json:"warehouse_location"` +} + +type CompletionService struct { + completionRepo *repository.CompletionRepo + planRepo *repository.PlanRepo + fpRepo *repository.FinishedProductRepo + db *sql.DB +} + +func NewCompletionService( + cr *repository.CompletionRepo, + pr *repository.PlanRepo, + fpr *repository.FinishedProductRepo, + db *sql.DB, +) *CompletionService { + return &CompletionService{ + completionRepo: cr, + planRepo: pr, + fpRepo: fpr, + db: db, + } +} + +func (s *CompletionService) Complete( + ctx context.Context, + washingPlanID, companyID, userID string, + req CompleteRequest, +) (*model.WashingPlanCompletion, error) { + plan, err := s.planRepo.GetByID(ctx, washingPlanID) + if err != nil { + return nil, fmt.Errorf("get plan: %w", err) + } + if plan == nil { + return nil, fmt.Errorf("washing plan not found") + } + if plan.WashingFactoryID == nil || *plan.WashingFactoryID != companyID { + return nil, fmt.Errorf("washing plan not found") + } + if plan.Status == "completed" { + return nil, fmt.Errorf("washing plan already completed") + } + + now := time.Now() + washingDate := now + if req.WashingDate != nil { + washingDate = *req.WashingDate + } + + totalCost := req.ActualWashedMeters * plan.WashingPrice + var unitCost float64 + if req.ActualWashedMeters > 0 { + unitCost = totalCost / req.ActualWashedMeters + } + + completionID := uuid.New().String() + fpID := uuid.New().String() + + var notes *string + if req.Notes != "" { + notes = &req.Notes + } + var warehouseID *string + if req.WarehouseID != "" { + warehouseID = &req.WarehouseID + } + var warehouseLocation *string + if req.WarehouseLocation != "" { + warehouseLocation = &req.WarehouseLocation + } + + completion := &model.WashingPlanCompletion{ + ID: completionID, + WashingPlanID: washingPlanID, + CompanyID: companyID, + ActualShrinkageRate: req.ActualShrinkageRate, + ActualWashedMeters: req.ActualWashedMeters, + ActualWashingCost: &totalCost, + UnitCost: &unitCost, + Rolls: req.Rolls, + WashingDate: washingDate, + Notes: notes, + CompletedBy: &userID, + CompletedAt: now, + FinishedProductID: &fpID, + AutoImported: true, + WarehouseID: warehouseID, + WarehouseLocation: warehouseLocation, + CreatedAt: now, + } + + err = database.WithTx(ctx, s.db, func(tx *sql.Tx) error { + if err := s.completionRepo.Create(ctx, tx, completion); err != nil { + return fmt.Errorf("create completion: %w", err) + } + + if err := s.planRepo.UpdateActuals(ctx, tx, washingPlanID, + req.ActualShrinkageRate, req.ActualWashedMeters, totalCost); err != nil { + return fmt.Errorf("update plan actuals: %w", err) + } + + fp := &model.FinishedProduct{ + ID: fpID, + CompanyID: companyID, + ProductName: plan.ProductName, + FabricCode: "", + Color: "", + ShrinkageRate: req.ActualShrinkageRate, + WashedMeters: req.ActualWashedMeters, + StockMeters: req.ActualWashedMeters, + StockRolls: req.Rolls, + Rolls: &req.Rolls, + WashingCost: &totalCost, + UnitCost: &unitCost, + Status: "in_stock", + Notes: notes, + ProductID: &plan.ProductID, + WashingPlanID: &washingPlanID, + WarehouseID: warehouseID, + WarehouseLocation: warehouseLocation, + CreatedBy: &userID, + CreatedAt: now, + UpdatedAt: now, + } + if err := s.fpRepo.Create(ctx, tx, fp); err != nil { + return fmt.Errorf("create finished product: %w", err) + } + + invRecord := &model.FinishedProductInventoryRecord{ + ID: uuid.New().String(), + FinishedProductID: fpID, + CompanyID: companyID, + RecordType: "inbound", + Rolls: req.Rolls, + Meters: req.ActualWashedMeters, + Notes: notes, + OperatorID: &userID, + WarehouseID: warehouseID, + WarehouseLocation: warehouseLocation, + WashingCompletionID: &completionID, + CreatedAt: now, + } + if err := s.fpRepo.CreateInventoryRecord(ctx, tx, invRecord); err != nil { + return fmt.Errorf("create inventory record: %w", err) + } + + return nil + }) + if err != nil { + return nil, err + } + + return completion, nil +} diff --git a/washing-service/internal/service/dashboard_service.go b/washing-service/internal/service/dashboard_service.go new file mode 100644 index 0000000..f488d39 --- /dev/null +++ b/washing-service/internal/service/dashboard_service.go @@ -0,0 +1,66 @@ +package service + +import ( + "context" + "fmt" + + "github.com/muyuqingfeng/iloom/washing-service/internal/repository" +) + +type DashboardStats struct { + TotalPlans int `json:"total_plans"` + PendingPlans int `json:"pending_plans"` + CompletedPlans int `json:"completed_plans"` + FinishedProducts int `json:"finished_products"` + TotalStockMeters float64 `json:"total_stock_meters"` + PendingPayments int `json:"pending_payments"` +} + +type DashboardService struct { + planRepo *repository.PlanRepo + fpRepo *repository.FinishedProductRepo + payRepo *repository.PaymentRepo +} + +func NewDashboardService( + pr *repository.PlanRepo, + fpr *repository.FinishedProductRepo, + payr *repository.PaymentRepo, +) *DashboardService { + return &DashboardService{ + planRepo: pr, + fpRepo: fpr, + payRepo: payr, + } +} + +func (s *DashboardService) Stats(ctx context.Context, companyID string) (*DashboardStats, error) { + total, pending, completed, err := s.planRepo.CountByStatus(ctx, companyID) + if err != nil { + return nil, fmt.Errorf("count plans: %w", err) + } + + fpCount, err := s.fpRepo.Count(ctx, companyID) + if err != nil { + return nil, fmt.Errorf("count finished products: %w", err) + } + + stockMeters, err := s.fpRepo.TotalStock(ctx, companyID) + if err != nil { + return nil, fmt.Errorf("total stock: %w", err) + } + + pendingPay, err := s.payRepo.CountPending(ctx, companyID) + if err != nil { + return nil, fmt.Errorf("count pending payments: %w", err) + } + + return &DashboardStats{ + TotalPlans: total, + PendingPlans: pending, + CompletedPlans: completed, + FinishedProducts: fpCount, + TotalStockMeters: stockMeters, + PendingPayments: pendingPay, + }, nil +} diff --git a/washing-service/internal/service/finished_product_service.go b/washing-service/internal/service/finished_product_service.go new file mode 100644 index 0000000..4453cc7 --- /dev/null +++ b/washing-service/internal/service/finished_product_service.go @@ -0,0 +1,73 @@ +package service + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/muyuqingfeng/iloom/shared/pkg/database" + "github.com/muyuqingfeng/iloom/washing-service/internal/model" + "github.com/muyuqingfeng/iloom/washing-service/internal/repository" +) + +type FinishedProductService struct { + repo *repository.FinishedProductRepo + db *sql.DB +} + +func NewFinishedProductService(repo *repository.FinishedProductRepo, db *sql.DB) *FinishedProductService { + return &FinishedProductService{repo: repo, db: db} +} + +func (s *FinishedProductService) List(ctx context.Context, companyID string) ([]model.FinishedProduct, error) { + return s.repo.List(ctx, companyID) +} + +func (s *FinishedProductService) Create(ctx context.Context, companyID, userID string, fp *model.FinishedProduct) error { + now := time.Now() + fp.ID = uuid.New().String() + fp.CompanyID = companyID + fp.CreatedBy = &userID + fp.CreatedAt = now + fp.UpdatedAt = now + if fp.Status == "" { + fp.Status = "in_stock" + } + fp.StockMeters = fp.WashedMeters + fp.StockRolls = 0 + if fp.Rolls != nil { + fp.StockRolls = *fp.Rolls + } + + return database.WithTx(ctx, s.db, func(tx *sql.Tx) error { + return s.repo.Create(ctx, tx, fp) + }) +} + +func (s *FinishedProductService) Outbound(ctx context.Context, fpID, companyID, userID string, rolls int, meters float64, notes string) error { + return database.WithTx(ctx, s.db, func(tx *sql.Tx) error { + if err := s.repo.UpdateStock(ctx, tx, fpID, -meters, -rolls); err != nil { + return fmt.Errorf("update stock: %w", err) + } + + var notePtr *string + if notes != "" { + notePtr = ¬es + } + + rec := &model.FinishedProductInventoryRecord{ + ID: uuid.New().String(), + FinishedProductID: fpID, + CompanyID: companyID, + RecordType: "outbound", + Rolls: rolls, + Meters: meters, + Notes: notePtr, + OperatorID: &userID, + CreatedAt: time.Now(), + } + return s.repo.CreateInventoryRecord(ctx, tx, rec) + }) +} diff --git a/washing-service/internal/service/payment_service.go b/washing-service/internal/service/payment_service.go new file mode 100644 index 0000000..baa8cf8 --- /dev/null +++ b/washing-service/internal/service/payment_service.go @@ -0,0 +1,23 @@ +package service + +import ( + "context" + + "github.com/muyuqingfeng/iloom/washing-service/internal/repository" +) + +type PaymentService struct { + repo *repository.PaymentRepo +} + +func NewPaymentService(repo *repository.PaymentRepo) *PaymentService { + return &PaymentService{repo: repo} +} + +func (s *PaymentService) List(ctx context.Context, companyID string) ([]repository.PaymentWithDetails, error) { + return s.repo.ListByCompany(ctx, companyID) +} + +func (s *PaymentService) Confirm(ctx context.Context, paymentID, companyID string) error { + return s.repo.Confirm(ctx, paymentID) +} diff --git a/washing-service/internal/service/plan_service.go b/washing-service/internal/service/plan_service.go new file mode 100644 index 0000000..e10a8b1 --- /dev/null +++ b/washing-service/internal/service/plan_service.go @@ -0,0 +1,54 @@ +package service + +import ( + "context" + "fmt" + + "github.com/muyuqingfeng/iloom/washing-service/internal/model" + "github.com/muyuqingfeng/iloom/washing-service/internal/repository" +) + +type PlanService struct { + repo *repository.PlanRepo +} + +func NewPlanService(repo *repository.PlanRepo) *PlanService { + return &PlanService{repo: repo} +} + +func (s *PlanService) List(ctx context.Context, companyID string) ([]model.WashingPlan, error) { + return s.repo.List(ctx, companyID) +} + +func (s *PlanService) GetByID(ctx context.Context, id, companyID string) (*model.WashingPlan, error) { + plan, err := s.repo.GetByID(ctx, id) + if err != nil { + return nil, err + } + if plan == nil { + return nil, fmt.Errorf("washing plan not found") + } + if plan.WashingFactoryID == nil || *plan.WashingFactoryID != companyID { + return nil, fmt.Errorf("washing plan not found") + } + return plan, nil +} + +func (s *PlanService) UpdateStatus(ctx context.Context, id, companyID, status string) error { + plan, err := s.repo.GetByID(ctx, id) + if err != nil { + return err + } + if plan == nil || plan.WashingFactoryID == nil || *plan.WashingFactoryID != companyID { + return fmt.Errorf("washing plan not found") + } + return s.repo.UpdateStatus(ctx, id, status) +} + +func (s *PlanService) Pending(ctx context.Context, companyID string) ([]model.WashingPlan, error) { + return s.repo.Pending(ctx, companyID) +} + +func (s *PlanService) Completed(ctx context.Context, companyID string) ([]model.WashingPlan, error) { + return s.repo.Completed(ctx, companyID) +}